#include "FractoriumPch.h"
#include "GLWidget.h"
#include "Fractorium.h"

#ifdef USE_GLSL

	static const char* vertexShaderSource =
	"attribute vec4 posattr;\n"
	"uniform mat4 matrix;\n"
	"uniform float ps;\n"
	"void main() {\n"
	"   gl_Position = matrix * posattr;\n"
	"	gl_PointSize = ps;\n"
	"}\n";

	static const char* fragmentShaderSource =
	"uniform vec4 mycolor;\n"
	"void main() {\n"
	"   gl_FragColor = mycolor;\n"
	"}\n";

	static const char* quadVertexShaderSource =
	"attribute vec4 posattr;\n"
	"uniform mat4 matrix;\n"
	"varying vec4 texcoord;\n"
	"void main() {\n"
	"	gl_Position = matrix * posattr;\n"
	"	texcoord = posattr;\n"
	"}\n";

	static const char* quadFragmentShaderSource =
	"uniform sampler2D quadtex;\n"
	"varying vec4 texcoord;\n"
	"void main() {\n"
	"	gl_FragColor = texture2D(quadtex, texcoord.st);\n"
	"}\n";
#endif

/// <summary>
/// Constructor which passes parent widget to the base and initializes OpenGL profile.
/// This will need to change in the future to implement all drawing as shader programs.
/// </summary>
/// <param name="p">The parent widget</param>
GLWidget::GLWidget(QWidget* p)
	: QOpenGLWidget(p)
{
	/*
		auto qsf = this->format();
		qDebug() << "Version: " << qsf.majorVersion() << ',' << qsf.minorVersion();
		qDebug() << "Profile: " << qsf.profile();
		qDebug() << "Depth buffer size: " << qsf.depthBufferSize();
		qDebug() << "Swap behavior: " << qsf.swapBehavior();
		qDebug() << "Swap interval: " << qsf.swapInterval();
		//QSurfaceFormat qsf;
		//QSurfaceFormat::FormatOptions fo;
		//fo.
		//qsf.setDepthBufferSize(24);
		//qsf.setSwapInterval(1);//Vsync.
		//qsf.setSwapBehavior(QSurfaceFormat::DoubleBuffer);
		#ifndef USE_GLSL
		qsf.setVersion(2, 0);
		qsf.setProfile(QSurfaceFormat::CompatibilityProfile);
		#else
		qsf.setVersion(3, 3);
		//qsf.setProfile(QSurfaceFormat::CoreProfile);
		#endif
		setFormat(qsf);
	*/
	/*
		QSurfaceFormat fmt;
		fmt.setDepthBufferSize(24);

		// Request OpenGL 3.3 compatibility or OpenGL ES 3.0.
		if (QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGL)
		{
			qDebug("Requesting 3.3 compatibility context");
			fmt.setVersion(3, 3);
			fmt.setProfile(QSurfaceFormat::CoreProfile);
		}
		else
		{
			qDebug("Requesting 3.0 context");
			fmt.setVersion(3, 0);
		}

		setFormat(fmt);
	*/
	//auto qsf = this->format();
	//qDebug() << "Constructor*****************\nVersion: " << qsf.majorVersion() << ',' << qsf.minorVersion();
	//qDebug() << "Profile: " << qsf.profile();
	//qDebug() << "Depth buffer size: " << qsf.depthBufferSize();
	//qDebug() << "Swap behavior: " << qsf.swapBehavior();
	//qDebug() << "Swap interval: " << qsf.swapInterval();
}

/// <summary>
/// Empty destructor.
/// </summary>
GLWidget::~GLWidget()
{
}

/// <summary>
/// A manual initialization that must be called immediately after the main window is shown
/// and the virtual initializeGL() is called.
/// </summary>
void GLWidget::InitGL()
{
	if (!m_Init)
	{
		//auto qsf = this->format();
		//qDebug() << "InitGL*****************\nVersion: " << qsf.majorVersion() << ',' << qsf.minorVersion();
		//qDebug() << "Profile: " << qsf.profile();
		//qDebug() << "Depth buffer size: " << qsf.depthBufferSize();
		//qDebug() << "Swap behavior: " << qsf.swapBehavior();
		//qDebug() << "Swap interval: " << qsf.swapInterval();
		const auto w = std::ceil(m_Fractorium->ui.GLParentScrollArea->width() * devicePixelRatioF());
		const auto h = std::ceil(m_Fractorium->ui.GLParentScrollArea->height() * devicePixelRatioF());
		SetDimensions(w, h);
		//Start with either a flock of random embers, or the last flame from the previous run.
		//Can't do this until now because the window wasn't maximized yet, so the sizes would have been off.
		bool b = m_Fractorium->m_Settings->LoadLast();

		if (b)
		{
			auto path = GetDefaultUserPath();
			const QDir dir(path);
			const QString filename = path + "/lastonshutdown.flame";

			if (dir.exists(filename))
			{
				QStringList ql;
				ql << filename;
				m_Fractorium->m_Controller->OpenAndPrepFiles(ql, false);
			}
			else
				b = false;
		}

		if (!b)
		{
			m_Fractorium->m_WidthSpin->setValue(w);
			m_Fractorium->m_HeightSpin->setValue(h);
			m_Fractorium->OnActionNewFlock(false);//This must come after the previous two lines because it uses the values of the spinners.
		}

		m_Fractorium->m_Controller->DelayedStartRenderTimer();
		m_Init = true;
		/*
			auto clinfo = OpenCLInfo::DefInstance();
			auto& platforms = clinfo->Platforms();
			auto& alldevices = clinfo->Devices();
			std::vector<std::string> strs;
			auto cdc = wglGetCurrentDC();
			auto cc = wglGetCurrentContext();
			ostringstream os;
			strs.push_back(os.str()); os.str(""); os << "GLWidget::InitGL():";
			strs.push_back(os.str()); os.str(""); os << "\nCurrent DC: " << cdc;
			strs.push_back(os.str()); os.str(""); os << "\nCurrent Context: " << cc;

			for (int platform = 0; platform < platforms.size(); platform++)
			{
			cl_context_properties props[] =
			{
				CL_GL_CONTEXT_KHR, (cl_context_properties)wglGetCurrentContext(),
				CL_WGL_HDC_KHR, (cl_context_properties)wglGetCurrentDC(),
				CL_CONTEXT_PLATFORM, reinterpret_cast<cl_context_properties>((platforms[platform])()),
				0
			};
			// Find CL capable devices in the current GL context
			//wglMakeCurrent(wglGetCurrentDC(), wglGetCurrentContext());
			::wglMakeCurrent(wglGetCurrentDC(), wglGetCurrentContext());
			size_t sizedev;
			cl_device_id devices[32];
			clGetGLContextInfoKHR_fn clGetGLContextInfo = (clGetGLContextInfoKHR_fn)clGetExtensionFunctionAddressForPlatform(platforms[platform](), "clGetGLContextInfoKHR");
			clGetGLContextInfo(props, CL_DEVICES_FOR_GL_CONTEXT_KHR, 32 * sizeof(cl_device_id), devices, &sizedev);
			sizedev = (cl_uint)(sizedev / sizeof(cl_device_id));

			for (int i = 0; i < sizedev; i++)
			{
				std::string s;
				size_t pi, di;
				auto dd = clinfo->DeviceFromId(devices[i], pi, di);

				if (dd)
				{
					auto& dev = *dd;
					auto& plat = platforms[pi];
					strs.push_back(os.str()); os.str(""); os << "\nPlatform[" << pi << "], device[" << di << "] is GL capable.";
					strs.push_back(os.str()); os.str(""); os << "\nPlatform profile: " << plat.getInfo<CL_PLATFORM_PROFILE>(nullptr).c_str() << endl;
					strs.push_back(os.str()); os.str(""); os << "\nPlatform version: " << plat.getInfo<CL_PLATFORM_VERSION>(nullptr).c_str() << endl;
					strs.push_back(os.str()); os.str(""); os << "\nPlatform name: " << plat.getInfo<CL_PLATFORM_NAME>(nullptr).c_str() << endl;
					strs.push_back(os.str()); os.str(""); os << "\nPlatform vendor: " << plat.getInfo<CL_PLATFORM_VENDOR>(nullptr).c_str() << endl;
					strs.push_back(os.str()); os.str(""); os << "\nPlatform extensions: " << plat.getInfo<CL_PLATFORM_EXTENSIONS>(nullptr).c_str() << endl;
					strs.push_back(os.str()); os.str(""); os << "\nVendor: " << dev.getInfo<CL_DEVICE_VENDOR>(nullptr).c_str() << endl;
					strs.push_back(os.str()); os.str(""); os << "\nDevice: " << dev.getInfo<CL_DEVICE_NAME>(nullptr).c_str() << endl;
					strs.push_back(os.str()); os.str(""); os << "\nDriver version: " << dev.getInfo<CL_DRIVER_VERSION>(nullptr).c_str() << endl;
					strs.push_back(os.str()); os.str(""); os << "\nDevice profile: " << dev.getInfo<CL_DEVICE_PROFILE>(nullptr).c_str() << endl;
					strs.push_back(os.str()); os.str(""); os << "\nDevice version: " << dev.getInfo<CL_DEVICE_VERSION>(nullptr).c_str() << endl;
					strs.push_back(os.str()); os.str(""); os << "\nDevice extensions: " << dev.getInfo<CL_DEVICE_EXTENSIONS>(nullptr).c_str() << endl;
					strs.push_back(os.str()); os.str(""); os << "\nDevice OpenCL C version: " << dev.getInfo<CL_DEVICE_OPENCL_C_VERSION>(nullptr).c_str() << endl;
				}
			}
			}

			m_Fractorium->ErrorReportToQTextEdit(strs, m_Fractorium->ui.InfoRenderingTextEdit);
		*/
	}
}

/// <summary>
/// Draw the final rendered image as a texture on a quad that is the same size as the window.
/// Different action is taken based on whether a CPU or OpenCL renderer is used.
/// For CPU, the output image buffer must be copied to OpenGL every time it's drawn.
/// For OpenCL, the output image and the texture are the same thing, so no copying is necessary
/// and all image memory remains on the card.
/// </summary>
void GLWidget::DrawQuad()
{
#ifndef USE_GLSL
	glEnable(GL_TEXTURE_2D);
	auto renderer = m_Fractorium->m_Controller->Renderer();
	auto finalImage = m_Fractorium->m_Controller->FinalImage();

	//Ensure all allocation has taken place first.
	if (m_OutputTexID != 0 && finalImage && !finalImage->empty())
	{
		glBindTexture(GL_TEXTURE_2D, m_OutputTexID);//The texture to draw to.
		auto scaledW = std::ceil(width() * devicePixelRatioF());
		auto scaledH = std::ceil(height() * devicePixelRatioF());

		//Only draw if the dimensions match exactly.
		if (m_TexWidth == m_Fractorium->m_Controller->FinalRasW() &&
				m_TexHeight == m_Fractorium->m_Controller->FinalRasH() &&
				((m_TexWidth * m_TexHeight) == static_cast<GLint>(finalImage->size())))
		{
			glMatrixMode(GL_PROJECTION);
			glPushMatrix();
			glLoadIdentity();
			glOrtho(0, 1, 1, 0, -1, 1);
			glMatrixMode(GL_MODELVIEW);
			glPushMatrix();
			glLoadIdentity();

			//Copy data from CPU to OpenGL if using a CPU renderer. This is not needed when using OpenCL.
			if (renderer->RendererType() == eRendererType::CPU_RENDERER)
				glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_TexWidth, m_TexHeight, GL_RGBA, GL_FLOAT, finalImage->data());

			glBegin(GL_QUADS);//This will need to be converted to a shader at some point in the future.
			glTexCoord2f(0.0, 0.0); glVertex2f(0.0, 0.0);
			glTexCoord2f(0.0, 1.0); glVertex2f(0.0, 1.0);
			glTexCoord2f(1.0, 1.0); glVertex2f(1.0, 1.0);
			glTexCoord2f(1.0, 0.0); glVertex2f(1.0, 0.0);
			glEnd();
			glMatrixMode(GL_PROJECTION);
			glPopMatrix();
			glMatrixMode(GL_MODELVIEW);
			glPopMatrix();
		}

		glBindTexture(GL_TEXTURE_2D, 0);//Stop using this texture.
	}

	glDisable(GL_TEXTURE_2D);
#else
	this->glEnable(GL_TEXTURE_2D);
	this->glActiveTexture(GL_TEXTURE0);
	const auto renderer = m_Fractorium->m_Controller->Renderer();

	//Ensure all allocation has taken place first.
	if (m_OutputTexID != 0)
	{
		glBindTexture(GL_TEXTURE_2D, m_OutputTexID);//The texture to draw to.
		const auto scaledW = std::ceil(width() * devicePixelRatioF());
		const auto scaledH = std::ceil(height() * devicePixelRatioF());

		//Only draw if the dimensions match exactly.
		if (m_TexWidth == m_Fractorium->m_Controller->FinalRasW() && m_TexHeight == m_Fractorium->m_Controller->FinalRasH())
		{
			//Copy data from CPU to OpenGL if using a CPU renderer. This is not needed when using OpenCL.
			if (renderer->RendererType() == eRendererType::CPU_RENDERER || !renderer->Shared())
			{
				const auto finalImage = m_Fractorium->m_Controller->FinalImage();

				if (finalImage &&//Make absolutely sure all image dimensions match when copying host side buffer to GL texture.
						!finalImage->empty() &&
						((m_TexWidth * m_TexHeight) == static_cast<GLint>(finalImage->size())) &&
						(finalImage->size() == renderer->FinalDimensions()))
					this->glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_TexWidth, m_TexHeight, GL_RGBA, GL_FLOAT, finalImage->data());
			}

			m_QuadProgram->bind();
			this->glVertexAttribPointer(m_TexturePosAttr, 2, GL_FLOAT, GL_FALSE, 0, m_TexVerts.data());
			this->glEnableVertexAttribArray(0);
			this->glDrawArrays(GL_TRIANGLE_STRIP, 0, 5);
			this->glDisableVertexAttribArray(0);
			m_QuadProgram->release();
		}
	}

	this->glBindTexture(GL_TEXTURE_2D, 0);//Stop using this texture.
	this->glDisable(GL_TEXTURE_2D);
#endif
}

/// <summary>
/// Set drag and drag modifier states to nothing.
/// </summary>
void GLEmberControllerBase::ClearDrag()
{
	m_DragModifier = 0;
	m_DragState = eDragState::DragNone;
}

/// <summary>
/// Wrapper around Allocate() call on the GL widget.
/// </summary>
bool GLEmberControllerBase::Allocate(bool force) { return m_GL->Allocate(force); }

/// <summary>
/// Helpers to set/get/clear which keys are pressed while dragging.
/// </summary>
/// <returns>bool</returns>
bool GLEmberControllerBase::GetAlt()       { return (m_DragModifier & static_cast<et>(eDragModifier::DragModAlt)) == static_cast<et>(eDragModifier::DragModAlt); }
bool GLEmberControllerBase::GetShift()     { return (m_DragModifier & static_cast<et>(eDragModifier::DragModShift)) == static_cast<et>(eDragModifier::DragModShift); }
bool GLEmberControllerBase::GetControl()   { return (m_DragModifier & static_cast<et>(eDragModifier::DragModControl)) == static_cast<et>(eDragModifier::DragModControl); }
void GLEmberControllerBase::SetAlt()       { m_DragModifier |= static_cast<et>(eDragModifier::DragModAlt); }
void GLEmberControllerBase::SetShift()     { m_DragModifier |= static_cast<et>(eDragModifier::DragModShift); }
void GLEmberControllerBase::SetControl()   { m_DragModifier |= static_cast<et>(eDragModifier::DragModControl); }
void GLEmberControllerBase::ClearAlt()     { m_DragModifier &= ~static_cast<et>(eDragModifier::DragModAlt); }
void GLEmberControllerBase::ClearShift()   { m_DragModifier &= ~static_cast<et>(eDragModifier::DragModShift); }
void GLEmberControllerBase::ClearControl() { m_DragModifier &= ~static_cast<et>(eDragModifier::DragModControl); }

/// <summary>
/// Clear the OpenGL output window to be the background color of the current ember.
/// Both buffers must be cleared, else artifacts will show up.
/// </summary>
template <typename T>
void GLEmberController<T>::ClearWindow()
{
	const auto ember = m_FractoriumEmberController->CurrentEmber();
	m_GL->makeCurrent();
	m_GL->glClearColor(ember->m_Background.r, ember->m_Background.g, ember->m_Background.b, 1.0);
	m_GL->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}

/// <summary>
/// Set the currently selected xform.
/// The currently selected xform is drawn with a circle around it, with all others only showing their axes.
/// </summary>
/// <param name="xform">The xform.</param>
template <typename T>
void GLEmberController<T>::SetSelectedXform(Xform<T>* xform)
{
	//By doing this check, it prevents triggering unnecessary events when selecting an xform on this window with the mouse,
	//which will set the combo box on the main window, which will trigger this call. However, if the xform has been selected
	//here with the mouse, the window has already been repainted, so there's no need to do it again.
	if (m_SelectedXform != xform)
	{
		m_SelectedXform = xform;

		if (m_GL->m_Init)
			//m_GL->update();
			m_GL->repaint();//Force immediate redraw with repaint() instead of update().
	}
}

/// <summary>
/// Setters for main window pointers.
/// </summary>

void GLWidget::SetMainWindow(Fractorium* f) { m_Fractorium = f; }

/// <summary>
/// Getters for OpenGL state.
/// </summary>

bool GLWidget::Init() { return m_Init; }
bool GLWidget::Drawing() { return m_Drawing; }
GLint GLWidget::MaxTexSize() { return m_MaxTexSize; }
GLuint GLWidget::OutputTexID() { return m_OutputTexID; }
GLint GLWidget::TexWidth() { return m_TexWidth; }
GLint GLWidget::TexHeight() { return m_TexHeight; }

/// <summary>
/// Initialize OpenGL, called once at startup after the main window constructor finishes.
/// Although it seems an awkward place to put some of this setup code, the dimensions of the
/// main window and its widgets are not fully initialized before this is called.
/// Once this is done, the render timer is started after a short delay.
/// Rendering is then clear to begin.
/// </summary>
void GLWidget::initializeGL()
{
#ifdef USE_GLSL
	//auto  qsf = this->format();
	//qDebug() << "initializeGL*****************\nVersion: " << qsf.majorVersion() << ',' << qsf.minorVersion();
	//qDebug() << "Profile: " << qsf.profile();
	//qDebug() << "Depth buffer size: " << qsf.depthBufferSize();
	//qDebug() << "Swap behavior: " << qsf.swapBehavior();
	//qDebug() << "Swap interval: " << qsf.swapInterval();

	if (!m_Init && m_Fractorium)
	{
		this->initializeOpenGLFunctions();

		if (!m_Program)
		{
			m_Program = new QOpenGLShaderProgram(this);

			if (!m_Program->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource))
			{
				QMessageBox::critical(m_Fractorium, "Shader Error", "Error compiling affine vertex source: " + m_Program->log());
				QApplication::exit(1);
			}

			if (!m_Program->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource))
			{
				QMessageBox::critical(m_Fractorium, "Shader Error", "Error compiling affine fragment source: " + m_Program->log());
				QApplication::exit(1);
			}

			if (!m_Program->link())
			{
				QMessageBox::critical(m_Fractorium, "Shader Error", "Error linking affine source: " + m_Program->log());
				QApplication::exit(1);
			}

			m_PosAttr = m_Program->attributeLocation("posattr");
			m_ColAttr = m_Program->uniformLocation("mycolor");
			m_PointSizeUniform = m_Program->uniformLocation("ps");
			m_MatrixUniform = m_Program->uniformLocation("matrix");
		}

		if (!m_QuadProgram)
		{
			m_QuadProgram = new QOpenGLShaderProgram(this);

			if (!m_QuadProgram->addShaderFromSourceCode(QOpenGLShader::Vertex, quadVertexShaderSource))
			{
				QMessageBox::critical(m_Fractorium, "Shader Error", "Error compiling image texture vertex source: " + m_QuadProgram->log());
				QApplication::exit(1);
			}

			if (!m_QuadProgram->addShaderFromSourceCode(QOpenGLShader::Fragment, quadFragmentShaderSource))
			{
				QMessageBox::critical(m_Fractorium, "Shader Error", "Error compiling image texture fragment source: " + m_QuadProgram->log());
				QApplication::exit(1);
			}

			if (!m_QuadProgram->link())
			{
				QMessageBox::critical(m_Fractorium, "Shader Error", "Error linking image texture source: " + m_QuadProgram->log());
				QApplication::exit(1);
			}

			m_TexturePosAttr = m_QuadProgram->attributeLocation("posattr");
			m_TextureUniform = m_QuadProgram->uniformLocation("quadtex");
			m_TextureMatrixUniform = m_QuadProgram->uniformLocation("matrix");
			m_TextureProjMatrix.ortho(0, 1, 1, 0, -1, 1);
			m_QuadProgram->bind();
			m_QuadProgram->setUniformValue(m_TextureUniform, 0);
			m_QuadProgram->setUniformValue(m_TextureMatrixUniform, m_TextureProjMatrix);
			m_QuadProgram->release();
		}

#else

	if (!m_Init && initializeOpenGLFunctions() && m_Fractorium)
	{
#endif
		//cout << "GL Version: " << (char *) glGetString(GL_VERSION) << endl;
		//cout << "GLSL version: " << (char *) glGetString(GL_SHADING_LANGUAGE_VERSION) << endl;
		this->glClearColor(0.0, 0.0, 0.0, 1.0);
		this->glDisable(GL_DEPTH_TEST);//This will remain disabled for the duration of the program.
		this->glEnable(GL_TEXTURE_2D);
		this->glEnable(GL_PROGRAM_POINT_SIZE);
		this->glGetIntegerv(GL_MAX_TEXTURE_SIZE, &m_MaxTexSize);
		this->glDisable(GL_TEXTURE_2D);
		m_Fractorium->m_WidthSpin->setMaximum(m_MaxTexSize);
		m_Fractorium->m_HeightSpin->setMaximum(m_MaxTexSize);
	}
}

/// <summary>
/// The main drawing/update function.
/// First the quad will be drawn, then the remaining affine circles.
/// </summary>
void GLWidget::paintGL()
{
	/*
		auto  qsf = this->format();
		qDebug() << "paintGL*****************\nVersion: " << qsf.majorVersion() << ',' << qsf.minorVersion();
		qDebug() << "Profile: " << qsf.profile();
		qDebug() << "Depth buffer size: " << qsf.depthBufferSize();
		qDebug() << "Swap behavior: " << qsf.swapBehavior();
		qDebug() << "Swap interval: " << qsf.swapInterval();
	*/
	const auto controller = m_Fractorium->m_Controller.get();

	//Ensure there is a renderer and that it's supposed to be drawing, signified by the running timer.
	if (controller && controller->Renderer()/* && controller->ProcessState() != eProcessState::NONE*/)//Need a way to determine if at leat one successful render has happened.
	{
		const auto renderer = controller->Renderer();
		const auto unitX = std::abs(renderer->UpperRightX(false) - renderer->LowerLeftX(false)) / 2.0f;
		const auto unitY = std::abs(renderer->UpperRightY(false) - renderer->LowerLeftY(false)) / 2.0f;

		if (unitX > 100000 || unitY > 100000)//Need a better way to do this.//TODO
		{
			qDebug() << unitX << " " << unitY;
			//return;
		}

		m_Drawing = true;

		if (m_Fractorium->DrawImage())
		{
			GLController()->DrawImage();
		}
		else
		{
			glClearColor(0.0, 0.0, 0.0, 1.0);
			glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		}

		//Affine drawing.
		const auto pre = m_Fractorium->DrawPreAffines();
		const auto post = m_Fractorium->DrawPostAffines();
		this->glEnable(GL_BLEND);
		this->glEnable(GL_LINE_SMOOTH);
		this->glEnable(GL_POINT_SMOOTH);
#if defined (__APPLE__) || defined(MACOSX)
		this->glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_COLOR);
#else
		this->glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);
#endif
#ifndef USE_GLSL
		this->glMatrixMode(GL_PROJECTION);
		this->glPushMatrix();
		this->glLoadIdentity();
		this->glOrtho(-unitX, unitX, -unitY, unitY, -1, 1);//Projection matrix: OpenGL camera is always centered, just move the ember internally inside the renderer.
		this->glMatrixMode(GL_MODELVIEW);
		this->glPushMatrix();
		this->glLoadIdentity();
		controller->GLController()->DrawAffines(pre, post);
		this->glMatrixMode(GL_PROJECTION);
		this->glPopMatrix();
		this->glMatrixMode(GL_MODELVIEW);
		this->glPopMatrix();
#else
		m_Program->bind();
		m_ProjMatrix.setToIdentity();
		m_ProjMatrix.ortho(-unitX, unitX, -unitY, unitY, -1, 1);//Projection matrix: OpenGL camera is always centered, just move the ember internally inside the renderer.
		m_ModelViewMatrix.setToIdentity();
		//this->DrawUnitSquare();
		controller->GLController()->DrawAffines(pre, post);
		m_Program->release();
#endif
		this->glDisable(GL_BLEND);
		this->glDisable(GL_LINE_SMOOTH);
		this->glDisable(GL_POINT_SMOOTH);
		m_Drawing = false;
	}
}

/// <summary>
/// Draw the image on the quad.
/// </summary>
template <typename T>
void GLEmberController<T>::DrawImage()
{
	const auto renderer = m_FractoriumEmberController->Renderer();
	const auto ember = m_FractoriumEmberController->CurrentEmber();
	m_GL->glClearColor(ember->m_Background.r, ember->m_Background.g, ember->m_Background.b, 1.0);
	m_GL->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	renderer->EnterFinalAccum();//Lock, may not be necessary, but just in case.
	renderer->EnterResize();

	if (SizesMatch())//Ensure all sizes are correct. If not, do nothing.
	{
		m_GL->DrawQuad();//Output image is drawn here.
	}

	renderer->LeaveResize();//Unlock, may not be necessary.
	renderer->LeaveFinalAccum();
}

/// <summary>
/// Draw the affine circles.
/// </summary>
/// <param name="pre">True to draw pre affines, else don't.</param>
/// <param name="post">True to draw post affines, else don't.</param>
template <typename T>
void GLEmberController<T>::DrawAffines(bool pre, bool post)
{
	QueryVMP();//Resolves to float or double specialization function depending on T.

	if (!m_Fractorium->DrawXforms())
		return;

	const auto ember = m_FractoriumEmberController->CurrentEmber();
	const auto dragging = m_DragState == eDragState::DragDragging;
	const auto forceFinal = m_Fractorium->HaveFinal();

	if (m_DragState == eDragState::DragRotateScale)
	{
		const auto dprf = m_GL->devicePixelRatioF();
		const auto world = ScrolledCenter(true);
		m_GL->glLineWidth(1.0f * dprf);
		const GLfloat vertices[] =
		{
			static_cast<GLfloat>(m_MouseWorldPos.x), static_cast<GLfloat>(m_MouseWorldPos.y),//Mouse position while dragging with right button down...
			static_cast<GLfloat>(world.x), static_cast<GLfloat>(world.y)//...to center.
		};
		const QVector4D col(0.0f, 1.0f, 1.0f, 1.0f);
		m_GL->DrawPointOrLine(col, vertices, 2, GL_LINES);
	}

	//Draw grid if control key is pressed.
	if ((m_GL->hasFocus() && GetControl()) || m_Fractorium->DrawGrid())
		DrawGrid();

	//When dragging, only draw the selected xform's affine and hide all others.
	if (!m_Fractorium->m_Settings->ShowAllXforms() && dragging)
	{
		if (m_SelectedXform)
			DrawAffine(m_SelectedXform, m_AffineType == eAffineType::AffinePre, true, false);
	}
	else//Show all while dragging, or not dragging just hovering/mouse move.
	{
		if (pre && m_Fractorium->DrawAllPre())//Draw all pre affine if specified.
		{
			size_t i = 0;
			bool any = false;

			while (auto xform = ember->GetTotalXform(i, forceFinal))
				if (m_Fractorium->IsXformSelected(i++))
				{
					any = true;
					break;
				}

			i = 0;

			while (const auto xform = ember->GetTotalXform(i, forceFinal))
			{
				bool selected = m_Fractorium->IsXformSelected(i++) || (!any && m_SelectedXform == xform);
				DrawAffine(xform, true, selected, !dragging && (m_HoverXform == xform));
			}
		}
		else if (pre && m_Fractorium->DrawSelectedPre())//Only draw selected pre affine, and if none are selected, draw current. All are considered "selected", so circles are drawn around them.
		{
			size_t i = 0;
			bool any = false;

			while (const auto xform = ember->GetTotalXform(i, forceFinal))
			{
				if (m_Fractorium->IsXformSelected(i++))
				{
					DrawAffine(xform, true, true, !dragging && (m_HoverXform == xform));
					any = true;
				}
			}

			if (!any)
				DrawAffine(m_FractoriumEmberController->CurrentXform(), true, true, !dragging && (m_HoverXform == m_FractoriumEmberController->CurrentXform()));
		}

		if (post && m_Fractorium->DrawAllPost())//Draw all post affine if specified.
		{
			size_t i = 0;
			bool any = false;

			while (const auto xform = ember->GetTotalXform(i, forceFinal))
				if (m_Fractorium->IsXformSelected(i++))
				{
					any = true;
					break;
				}

			i = 0;

			while (const auto xform = ember->GetTotalXform(i, forceFinal))
			{
				bool selected = m_Fractorium->IsXformSelected(i++) || (!any && m_SelectedXform == xform);
				DrawAffine(xform, false, selected, !dragging && (m_HoverXform == xform));
			}
		}
		else if (post && m_Fractorium->DrawSelectedPost())//Only draw selected post, and if none are selected, draw current. All are considered "selected", so circles are drawn around them.
		{
			size_t i = 0;
			bool any = false;

			while (const auto xform = ember->GetTotalXform(i, forceFinal))
			{
				if (m_Fractorium->IsXformSelected(i++))
				{
					DrawAffine(xform, false, true, !dragging && (m_HoverXform == xform));
					any = true;
				}
			}

			if (!any)
				DrawAffine(m_FractoriumEmberController->CurrentXform(), false, true, !dragging && (m_HoverXform == m_FractoriumEmberController->CurrentXform()));
		}
	}

	if (dragging)//Draw large yellow dot on select or drag.
	{
#ifndef USE_GLSL
		m_GL->glBegin(GL_POINTS);
		m_GL->glColor4f(1.0f, 1.0f, 0.5f, 1.0f);
		m_GL->glVertex2f(m_DragHandlePos.x, m_DragHandlePos.y);
		m_GL->glEnd();
#else
		const GLfloat vertices[] =//Should these be of type T?//TODO
		{
			static_cast<GLfloat>(m_DragHandlePos.x), static_cast<GLfloat>(m_DragHandlePos.y)
		};
		const QVector4D col(1.0f, 1.0f, 0.5f, 1.0f);
		m_GL->DrawPointOrLine(col, vertices, 1, GL_POINTS, false, 6.0f);
#endif
	}
	else if (m_DragState == eDragState::DragSelect)
	{
		m_GL->glLineWidth(2.0f * m_GL->devicePixelRatioF());
#ifndef USE_GLSL
		m_GL->glBegin(GL_LINES);
		m_GL->glColor4f(0.0f, 0.0f, 1.0f, 1.0f);
		m_GL->glVertex2f(m_MouseDownWorldPos.x, m_MouseDownWorldPos.y);//UL->UR
		m_GL->glVertex2f(m_MouseWorldPos.x, m_MouseDownWorldPos.y);
		m_GL->glVertex2f(m_MouseDownWorldPos.x, m_MouseWorldPos.y);//LL->LR
		m_GL->glVertex2f(m_MouseWorldPos.x, m_MouseWorldPos.y);
		m_GL->glVertex2f(m_MouseDownWorldPos.x, m_MouseDownWorldPos.y);//UL->LL
		m_GL->glVertex2f(m_MouseDownWorldPos.x, m_MouseWorldPos.y);
		m_GL->glVertex2f(m_MouseWorldPos.x, m_MouseDownWorldPos.y);//UR->LR
		m_GL->glVertex2f(m_MouseWorldPos.x, m_MouseWorldPos.y);
		m_GL->glEnd();
#else
		const GLfloat vertices[] =//Should these be of type T?//TODO
		{
			static_cast<GLfloat>(m_MouseDownWorldPos.x), static_cast<GLfloat>(m_MouseDownWorldPos.y),//UL->UR
			static_cast<GLfloat>(m_MouseWorldPos.x    ), static_cast<GLfloat>(m_MouseDownWorldPos.y),
			static_cast<GLfloat>(m_MouseDownWorldPos.x), static_cast<GLfloat>(m_MouseWorldPos.y),//LL->LR
			static_cast<GLfloat>(m_MouseWorldPos.x    ), static_cast<GLfloat>(m_MouseWorldPos.y),
			static_cast<GLfloat>(m_MouseDownWorldPos.x), static_cast<GLfloat>(m_MouseDownWorldPos.y),//UL->LL
			static_cast<GLfloat>(m_MouseDownWorldPos.x), static_cast<GLfloat>(m_MouseWorldPos.y),
			static_cast<GLfloat>(m_MouseWorldPos.x    ), static_cast<GLfloat>(m_MouseDownWorldPos.y),//UR->LR
			static_cast<GLfloat>(m_MouseWorldPos.x    ), static_cast<GLfloat>(m_MouseWorldPos.y)
		};
		const QVector4D col(0.0f, 0.0f, 1.0f, 1.0f);
		m_GL->DrawPointOrLine(col, vertices, 8, GL_LINES);
#endif
		m_GL->glLineWidth(1.0f * m_GL->devicePixelRatioF());
	}
	else if (m_HoverType != eHoverType::HoverNone && m_HoverXform == m_SelectedXform)//Draw large turquoise dot on hover if they are hovering over the selected xform.
	{
#ifndef USE_GLSL
		m_GL->glBegin(GL_POINTS);
		m_GL->glColor4f(0.5f, 1.0f, 1.0f, 1.0f);
		m_GL->glVertex2f(m_HoverHandlePos.x, m_HoverHandlePos.y);
		m_GL->glEnd();
#else
		const GLfloat vertices[] =//Should these be of type T?//TODO
		{
			static_cast<GLfloat>(m_HoverHandlePos.x), static_cast<GLfloat>(m_HoverHandlePos.y)
		};
		const QVector4D col(0.5f, 1.0f, 1.0f, 1.0f);
		m_GL->DrawPointOrLine(col, vertices, 1, GL_POINTS, false, 6.0f);
#endif
	}
}

/// <summary>
/// Set drag modifiers based on key press.
/// </summary>
/// <param name="e">The event</param>
bool GLEmberControllerBase::KeyPress_(QKeyEvent* e)
{
	if (e->key() == Qt::Key_Control)
	{
		SetControl();
		return true;
	}

	return false;
}

/// <summary>
/// Call controller KeyPress().
/// </summary>
/// <param name="e">The event</param>
void GLWidget::keyPressEvent(QKeyEvent* e)
{
	if (!GLController() || !GLController()->KeyPress_(e))
		QOpenGLWidget::keyPressEvent(e);

	update();
}

/// <summary>
/// Set drag modifiers based on key release.
/// </summary>
/// <param name="e">The event</param>
bool GLEmberControllerBase::KeyRelease_(QKeyEvent* e)
{
	if (e->key() == Qt::Key_Control)
	{
		ClearControl();
		return true;
	}

	return false;
}

/// <summary>
/// Call controller KeyRelease_().
/// </summary>
/// <param name="e">The event</param>
void GLWidget::keyReleaseEvent(QKeyEvent* e)
{
	if (!GLController() || !GLController()->KeyRelease_(e))
		QOpenGLWidget::keyReleaseEvent(e);

	update();
}

/// <summary>
/// Determine if the mouse click was over an affine circle
/// and set the appropriate selection information to be used
/// on subsequent mouse move events.
/// If nothing was selected, then reset the selection and drag states.
/// </summary>
/// <param name="e">The event</param>
template <typename T>
void GLEmberController<T>::MousePress(QMouseEvent* e)
{
	v3T mouseFlipped(e->x() * m_GL->devicePixelRatioF(), m_Viewport[3] - e->y() * m_GL->devicePixelRatioF(), 0);//Must flip y because in OpenGL, 0,0 is bottom left, but in windows, it's top left.
	const auto ember = m_FractoriumEmberController->CurrentEmber();
	const auto renderer = m_FractoriumEmberController->Renderer();

	//Ensure everything has been initialized.
	if (!renderer)
		return;

	m_MouseDownPos = glm::ivec2(e->x() * m_GL->devicePixelRatioF(), e->y() * m_GL->devicePixelRatioF());//Capture the raster coordinates of where the mouse was clicked.
	m_MouseWorldPos = WindowToWorld(mouseFlipped, false);//Capture the world cartesian coordinates of where the mouse is.
	m_BoundsDown.w = renderer->LowerLeftX(false);//Need to capture these because they'll be changing if scaling.
	m_BoundsDown.x = renderer->LowerLeftY(false);
	m_BoundsDown.y = renderer->UpperRightX(false);
	m_BoundsDown.z = renderer->UpperRightY(false);
	const auto mod = e->modifiers();

	if (mod.testFlag(Qt::ShiftModifier))
		SetShift();

	if (mod.testFlag(Qt::AltModifier))
		SetAlt();

	if (m_DragState == eDragState::DragNone)//Only take action if the user wasn't already dragging.
	{
		m_MouseDownWorldPos = m_MouseWorldPos;//Set the mouse down position to the current position.

		if (e->button() & Qt::LeftButton)
		{
			const auto xformIndex = UpdateHover(mouseFlipped);//Determine if an affine circle was clicked.

			if (m_HoverXform && xformIndex != -1)
			{
				m_SelectedXform = m_HoverXform;
				m_DragSrcTransform = Affine2D<T>(m_AffineType == eAffineType::AffinePre ? m_SelectedXform->m_Affine : m_SelectedXform->m_Post);//Copy the affine of the xform that was selected.
				//The user has selected an xform by clicking on it, so update the main GUI by selecting this xform in the combo box.
				m_Fractorium->CurrentXform(xformIndex);//Must do this first so UpdateXform() below properly grabs the current plus any selected.
				m_DragSrcPreTransforms.clear();
				m_DragSrcPostTransforms.clear();
				m_FractoriumEmberController->UpdateXform([&](Xform<T>* xform, size_t xfindex, size_t selIndex)
				{
					if (m_AffineType == eAffineType::AffinePre)
						m_DragSrcPreTransforms[xfindex] = xform->m_Affine;
					else
						m_DragSrcPostTransforms[xfindex] = xform->m_Post;
				}, eXformUpdate::UPDATE_SELECTED, false);//Don't update renderer here.
				m_DragHandlePos = m_HoverHandlePos;//The location in local coordinates of the point selected on the spinner, x, y or center.
				m_DragState = eDragState::DragDragging;
				m_GL->repaint();
			}
			else//Nothing was selected.
			{
				//m_SelectedXform = nullptr;
				m_DragSrcPreTransforms.clear();
				m_DragSrcPostTransforms.clear();
				m_DragState = eDragState::DragNone;
			}
		}
		else if (e->button() == Qt::MiddleButton || (e->button() == Qt::RightButton && e->modifiers() & Qt::ShiftModifier && !(e->modifiers() & Qt::AltModifier)))//Middle button or right button with shift key, do whole image translation.
		{
			m_CenterDownX = ember->m_CenterX;//Capture where the center of the image is because this value will change when panning.
			m_CenterDownY = ember->m_CenterY;
			m_DragState = eDragState::DragPanning;
		}
		else if (e->button() == Qt::RightButton)//Right button does whole image rotation and scaling.
		{
			if (m_Fractorium->DrawImage())
			{
				m_CenterDownX = ember->m_CenterX;//Capture these because they will change when rotating and scaling.
				m_CenterDownY = ember->m_CenterY;

				if (GetAlt() && GetShift())
				{
					m_PitchDown = ember->m_CamPitch * RAD_2_DEG_T;
					m_YawDown = ember->m_CamYaw * RAD_2_DEG_T;
					m_DragState = eDragState::DragPitchYaw;
				}
				else
				{
					m_RotationDown = ember->m_Rotate;
					m_ScaleDown = ember->m_PixelsPerUnit;
					m_DragState = eDragState::DragRotateScale;
				}
			}
		}
	}
}

/// <summary>
/// Call controller MousePress().
/// </summary>
/// <param name="e">The event</param>
void GLWidget::mousePressEvent(QMouseEvent* e)
{
	setFocus();//Must do this so that this window gets keyboard events.

	if (const auto controller = GLController())
		controller->MousePress(e);

	QOpenGLWidget::mousePressEvent(e);
}

/// <summary>
/// Reset the selection and dragging state, but re-calculate the
/// hovering state because the mouse might still be over an affine circle.
/// </summary>
/// <param name="e">The event</param>
template <typename T>
void GLEmberController<T>::MouseRelease(QMouseEvent* e)
{
	v3T mouseFlipped(e->x() * m_GL->devicePixelRatioF(), m_Viewport[3] - e->y() * m_GL->devicePixelRatioF(), 0);//Must flip y because in OpenGL, 0,0 is bottom left, but in windows, it's top left.
	m_MouseWorldPos = WindowToWorld(mouseFlipped, false);

	if (m_DragState == eDragState::DragDragging && (e->button() & Qt::LeftButton))
		UpdateHover(mouseFlipped);

	if (m_DragState == eDragState::DragNone)
		m_Fractorium->OnXformsSelectNoneButtonClicked(false);

	m_DragState = eDragState::DragNone;
	m_DragModifier = 0;
	m_GL->update();
}

/// <summary>
/// Call controller MouseRelease().
/// </summary>
/// <param name="e">The event</param>
void GLWidget::mouseReleaseEvent(QMouseEvent* e)
{
	setFocus();//Must do this so that this window gets keyboard events.

	if (const auto controller = GLController())
		controller->MouseRelease(e);

	QOpenGLWidget::mouseReleaseEvent(e);
}

/// <summary>
/// If dragging, update relevant values and reset entire rendering process.
/// If hovering, update display.
/// </summary>
/// <param name="e">The event</param>
template <typename T>
void GLEmberController<T>::MouseMove(QMouseEvent* e)
{
	bool draw = true;
	const glm::ivec2 mouse(e->x() * m_GL->devicePixelRatioF(), e->y() * m_GL->devicePixelRatioF());
	const v3T mouseFlipped(e->x() * m_GL->devicePixelRatioF(), m_Viewport[3] - e->y() * m_GL->devicePixelRatioF(), 0);//Must flip y because in OpenGL, 0,0 is bottom left, but in windows, it's top left.
	const auto ember = m_FractoriumEmberController->CurrentEmber();

	//First check to see if the mouse actually moved.
	if (mouse == m_MousePos)
		return;

	m_MousePos = mouse;
	m_MouseWorldPos = WindowToWorld(mouseFlipped, false);

	//Update status bar on main window, regardless of whether anything is being dragged.
	if (m_Fractorium->m_Controller->RenderTimerRunning())
		m_Fractorium->SetCoordinateStatus(e->x() * m_GL->devicePixelRatioF(), e->y() * m_GL->devicePixelRatioF(), m_MouseWorldPos.x, m_MouseWorldPos.y);

	if (m_SelectedXform && m_DragState == eDragState::DragDragging)//Dragging and affine.
	{
		const bool pre = m_AffineType == eAffineType::AffinePre;

		if (m_HoverType == eHoverType::HoverTranslation)
			CalcDragTranslation();
		else if (m_HoverType == eHoverType::HoverXAxis)
			CalcDragXAxis();
		else if (m_HoverType == eHoverType::HoverYAxis)
			CalcDragYAxis();

		m_FractoriumEmberController->FillAffineWithXform(m_SelectedXform, pre);//Update the spinners in the affine tab of the main window.
		m_FractoriumEmberController->UpdateRender();//Restart the rendering process.
	}
	else if ((m_DragState == eDragState::DragNone || m_DragState == eDragState::DragSelect) && (e->buttons() & Qt::LeftButton))
	{
		m_DragState = eDragState::DragSelect;//Only set drag state once the user starts moving the mouse with the left button down.
		//Iterate over each xform, seeing if it's in the bounding box.
		const QPointF tl(m_MouseDownWorldPos.x, m_MouseDownWorldPos.y);
		const QPointF br(m_MouseWorldPos.x, m_MouseWorldPos.y);
		const QRectF qrf(tl, br);
		const T scale = m_FractoriumEmberController->AffineScaleCurrentToLocked();
		int i = 0;
		m_FractoriumEmberController->UpdateXform([&](Xform<T>* xform, size_t xfindex, size_t selIndex)
		{
			if (m_Fractorium->DrawAllPre() || xform == m_SelectedXform)//Draw all pre affine if specified.
			{
				const QPointF cd(xform->m_Affine.C() * scale, xform->m_Affine.F() * scale);
				bool b = qrf.contains(cd);
				m_FractoriumEmberController->XformCheckboxAt(static_cast<int>(xfindex), [&](QCheckBox * cb)
				{
					cb->setChecked(b);
				});
			}

			if (m_Fractorium->DrawAllPost() || xform == m_SelectedXform)
			{
				const QPointF cd(xform->m_Post.C() * scale, xform->m_Post.F() * scale);
				bool b = qrf.contains(cd);
				m_FractoriumEmberController->XformCheckboxAt(static_cast<int>(xfindex), [&](QCheckBox * cb)
				{
					if (!cb->isChecked() && b)
						cb->setChecked(b);
				});
			}
		}, eXformUpdate::UPDATE_ALL, false);
	}
	else if (m_DragState == eDragState::DragPanning)//Translating the whole image.
	{
		const auto x = -(m_MouseWorldPos.x - m_MouseDownWorldPos.x);
		const auto y = (m_MouseWorldPos.y - m_MouseDownWorldPos.y);
		Affine2D<T> rotMat;
		rotMat.C(m_CenterDownX);
		rotMat.F(m_CenterDownY);
		rotMat.Rotate(ember->m_Rotate * DEG_2_RAD_T);
		const v2T v1(x, y);
		const v2T v2 = rotMat.TransformVector(v1);
		ember->m_CenterX = v2.x;
		ember->m_CenterY = ember->m_RotCenterY = v2.y;
		m_FractoriumEmberController->SetCenter(ember->m_CenterX, ember->m_CenterY);//Will restart the rendering process.
	}
	else if (m_DragState == eDragState::DragRotateScale)//Rotating and scaling the whole image.
	{
		const T rot = CalcRotation();
		const T scale = CalcScale();
		ember->m_Rotate = NormalizeDeg180<T>(m_RotationDown + rot);
		m_Fractorium->SetRotation(ember->m_Rotate, true);
		m_Fractorium->SetScale(std::max(static_cast<T>(10), m_ScaleDown + scale));//Will restart the rendering process.
	}
	else if (m_DragState == eDragState::DragPitchYaw)//Pitching and yawing the whole image.
	{
		T pitch;
		T yaw;
		auto rotate = ember->m_Rotate;

		if ((rotate <= 45 && rotate >= -45) || (rotate >= 135) || (rotate <= -135))
		{
			pitch = m_PitchDown + (m_MouseWorldPos.y - m_MouseDownWorldPos.y) * 100;
			yaw   = m_YawDown + (m_MouseWorldPos.x - m_MouseDownWorldPos.x) * 100;
		}
		else
		{
			pitch = m_PitchDown + (m_MouseWorldPos.x - m_MouseDownWorldPos.x) * 100;
			yaw   = m_YawDown + (m_MouseWorldPos.y - m_MouseDownWorldPos.y) * 100;
		}

		m_Fractorium->SetPitch(pitch);
		m_Fractorium->SetYaw(yaw);
	}
	else
	{
		//If the user doesn't already have a key down, and they aren't dragging, clear the keys to be safe.
		//This is done because if they do an alt+tab between windows, it thinks the alt key is down.
		if (e->modifiers() == Qt::NoModifier)
			ClearDrag();

		//Check if they weren't dragging and weren't hovering over any affine.
		//In that case, nothing needs to be done.
		if (UpdateHover(mouseFlipped) == -1)
		{
			m_HoverXform = nullptr;
		}
	}

	//Only update if the user was dragging or hovered over a point.
	//Use repaint() to update immediately for a more responsive feel.
	if ((m_DragState != eDragState::DragNone) || draw)
		m_GL->update();
}

/// <summary>
/// Call controller MouseMove().
/// </summary>
/// <param name="e">The event</param>
void GLWidget::mouseMoveEvent(QMouseEvent* e)
{
	setFocus();//Must do this so that this window gets keyboard events.

	if (const auto controller = GLController())
		controller->MouseMove(e);

	QOpenGLWidget::mouseMoveEvent(e);
}

/// <summary>
/// Mouse wheel changes the scale (pixels per unit) which
/// will zoom in the image in our out, while sacrificing quality.
/// If the user needs to preserve quality, they can use the zoom spinner
/// on the main window.
/// If Alt is pressed, only the scale of the affines is changed, the scale of the image remains untouched.
/// </summary>
/// <param name="e">The event</param>
template <typename T>
void GLEmberController<T>::Wheel(QWheelEvent* e)
{
	if ((e->modifiers() & Qt::AltModifier) && m_Fractorium->DrawXforms())
	{
#ifdef __APPLE__
		m_FractoriumEmberController->ChangeLockedScale(e->angleDelta().y() >= 0 ? 1.0981 : 0.9);
#else
		m_FractoriumEmberController->ChangeLockedScale(e->angleDelta().x() >= 0 ? 1.0981 : 0.9);
#endif
		m_GL->update();
	}
	else
	{
		if (m_Fractorium->DrawImage() && !(e->buttons() & Qt::MiddleButton) && !(e->modifiers() & Qt::ShiftModifier))//Middle button does whole image translation, so ignore the mouse wheel while panning to avoid inadvertent zooming. ShiftModifier for sensitive mouse.
		{
			auto ember = m_FractoriumEmberController->CurrentEmber();
			m_Fractorium->SetScale(ember->m_PixelsPerUnit + (e->angleDelta().y() >= 0 ? 50 : -50));
		}
	}
}

/// <summary>
/// Call controller Wheel().
/// </summary>
/// <param name="e">The event</param>
void GLWidget::wheelEvent(QWheelEvent* e)
{
	if (auto controller = GLController())
	{
		controller->Wheel(e);
		e->accept();//Prevents it from being sent to the main scroll bars. Scrolling should only affect the scale parameter and affine display zooming.
	}

	//Do not call QOpenGLWidget::wheelEvent(e) because this should only affect the scale and not the position of the scroll bars.
}

/// <summary>
/// Wrapper around drawing a simple primitive, like a point or line, using a GLSL program.
/// </summary>
/// <param name="col">The color to draw with</param>
/// <param name="vertices">The vertices to use</param>
/// <param name="drawType">The type of primitive to draw, such as GL_POINT or GL_LINES</param>
/// <param name="dashed">True to draw dashed lines, else solid</param>
/// <param name="pointSize">The size in pixels of points, which is internally scaled by the device pixel ratio.</param>
void GLWidget::DrawPointOrLine(const QVector4D& col, const std::vector<float>& vertices, int drawType, bool dashed, GLfloat pointSize)
{
	DrawPointOrLine(col, vertices.data(), int(vertices.size() / 2), drawType, dashed, pointSize);
}

/// <summary>
/// Wrapper around drawing a simple primitive, like a point or line, using a GLSL program.
/// </summary>
/// <param name="col">The color to draw with</param>
/// <param name="vertices">The vertices to use</param>
/// <param name="size">The number of verticies. This is usually the size of vertices / 2.</param>
/// <param name="drawType">The type of primitive to draw, such as GL_POINT or GL_LINES</param>
/// <param name="dashed">True to draw dashed lines, else solid</param>
/// <param name="pointSize">The size in pixels of points, which is internally scaled by the device pixel ratio.</param>
void GLWidget::DrawPointOrLine(const QVector4D& col, const GLfloat* vertices, int size, int drawType, bool dashed, GLfloat pointSize)
{
#ifdef USE_GLSL

	if (dashed && (drawType == GL_LINES || drawType == GL_LINE_LOOP))
	{
		glLineStipple(1, 0XFF00);
		glEnable(GL_LINE_STIPPLE);
	}

	m_ModelViewProjectionMatrix = m_ProjMatrix * m_ModelViewMatrix;
	m_Program->setUniformValue(m_ColAttr, col);
	m_Program->setUniformValue(m_PointSizeUniform, pointSize * GLfloat(devicePixelRatioF()));
	m_Program->setUniformValue(m_MatrixUniform, m_ModelViewProjectionMatrix);
	this->glVertexAttribPointer(m_PosAttr, 2, GL_FLOAT, GL_FALSE, 0, vertices);
	this->glEnableVertexAttribArray(0);
	this->glDrawArrays(drawType, 0, size);
	this->glDisableVertexAttribArray(0);

	if (dashed && (drawType == GL_LINES || drawType == GL_LINE_LOOP))
		glDisable(GL_LINE_STIPPLE);

#endif
}

/// <summary>
/// Set the dimensions of the drawing area.
/// This will be called from the main window's SyncSizes() function.
/// </summary>
/// <param name="w">Width in pixels</param>
/// <param name="h">Height in pixels</param>
void GLWidget::SetDimensions(int w, int h)
{
	const auto downscaledW = std::ceil(w / devicePixelRatioF());
	const auto downscaledH = std::ceil(h / devicePixelRatioF());
	setFixedSize(downscaledW, downscaledH);
}

/// <summary>
/// Set up texture memory to match the size of the window.
/// If first allocation, generate, bind and set parameters.
/// If subsequent call, only take action if dimensions don't match the window. In such case,
/// first deallocate, then reallocate.
/// </summary>
/// <returns>True if success, else false.</returns>
bool GLWidget::Allocate(bool force)
{
	bool alloc = false;
	//auto scaledW = std::ceil(width() * devicePixelRatioF());
	auto w = m_Fractorium->m_Controller->FinalRasW();
	auto h = m_Fractorium->m_Controller->FinalRasH();
	bool doResize = force || m_TexWidth != w || m_TexHeight != h;
	bool doIt = doResize || m_OutputTexID == 0;
#ifndef USE_GLSL

	if (doIt)
	{
		m_TexWidth = static_cast<GLint>(w);
		m_TexHeight = static_cast<GLint>(h);
		glEnable(GL_TEXTURE_2D);
		glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

		if (doResize)
			Deallocate();

		glGenTextures(1, &m_OutputTexID);
		glBindTexture(GL_TEXTURE_2D, m_OutputTexID);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);//Fractron had this as GL_LINEAR_MIPMAP_LINEAR for OpenCL and Cuda.
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
#if defined (__APPLE__) || defined(MACOSX)
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, m_TexWidth, m_TexHeight, 0, GL_RGB, GL_FLOAT, nullptr);
#else
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, m_TexWidth, m_TexHeight, 0, GL_RGBA, GL_FLOAT, nullptr);
#endif
		alloc = true;
	}

	if (alloc)
	{
		glBindTexture(GL_TEXTURE_2D, 0);
		glDisable(GL_TEXTURE_2D);
	}

#else

	if (doIt)
	{
		m_TexWidth = static_cast<GLint>(w);
		m_TexHeight = static_cast<GLint>(h);
		this->glEnable(GL_TEXTURE_2D);

		if (doResize)
			Deallocate();

		this->glActiveTexture(GL_TEXTURE0);
		this->glGenTextures(1, &m_OutputTexID);
		this->glBindTexture(GL_TEXTURE_2D, m_OutputTexID);
		this->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);//Fractron had this as GL_LINEAR_MIPMAP_LINEAR for OpenCL and Cuda.
		this->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		this->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
		this->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
#if defined (__APPLE__) || defined(MACOSX)
		this->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, m_TexWidth, m_TexHeight, 0, GL_RGB, GL_FLOAT, nullptr);
#else
		this->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, m_TexWidth, m_TexHeight, 0, GL_RGBA, GL_FLOAT, nullptr);
#endif
		alloc = true;
	}

	if (alloc)
	{
		this->glBindTexture(GL_TEXTURE_2D, 0);
		this->glDisable(GL_TEXTURE_2D);
	}

#endif
	this->glFinish();
	return m_OutputTexID != 0;
}

/// <summary>
/// Deallocate texture memory.
/// </summary>
/// <returns>True if anything deleted, else false.</returns>
bool GLWidget::Deallocate()
{
	bool deleted = false;

	if (m_OutputTexID != 0)
	{
		this->glBindTexture(GL_TEXTURE_2D, m_OutputTexID);
		this->glDeleteTextures(1, &m_OutputTexID);
		m_OutputTexID = 0;
		deleted = true;
	}

	return deleted;
}

/// <summary>
/// Set the viewport to match the window dimensions.
/// If the dimensions already match, no action is taken.
/// </summary>
void GLWidget::SetViewport()
{
	if (m_Init && (m_ViewWidth != m_TexWidth || m_ViewHeight != m_TexHeight))
	{
		this->glViewport(0, 0, static_cast<GLint>(m_TexWidth), static_cast<GLint>(m_TexHeight));
#ifdef USE_GLSL
		m_Viewport = glm::ivec4(0, 0, m_TexWidth, m_TexHeight);
#endif
		m_ViewWidth = m_TexWidth;
		m_ViewHeight = m_TexHeight;
	}
}

/// <summary>
/// Determine whether the dimensions of the renderer's current ember match
/// the dimensions of the widget, texture and viewport.
/// Since this uses the renderer's dimensions, this
/// must be called after the renderer has set the current ember.
/// </summary>
/// <returns>True if all sizes match, else false.</returns>
template <typename T>
bool GLEmberController<T>::SizesMatch()
{
	//auto scaledW = std::ceil(m_GL->width() * m_GL->devicePixelRatioF());
	//auto scaledH = std::ceil(m_GL->height() * m_GL->devicePixelRatioF());
	const auto ember = m_FractoriumEmberController->CurrentEmber();
	return (ember &&
			ember->m_FinalRasW == m_GL->m_TexWidth &&
			ember->m_FinalRasH == m_GL->m_TexHeight &&
			m_GL->m_TexWidth == m_GL->m_ViewWidth &&
			m_GL->m_TexHeight == m_GL->m_ViewHeight);
}

/// <summary>
/// Draw the unit square.
/// </summary>
void GLWidget::DrawUnitSquare()
{
	glLineWidth(1.0f * devicePixelRatioF());
#ifndef USE_GLSL
	glBegin(GL_LINES);
	glColor4f(1.0f, 1.0f, 1.0f, 0.25f);
	glVertex2f(-1, -1);
	glVertex2f( 1, -1);
	glVertex2f(-1, 1);
	glVertex2f( 1, 1);
	glVertex2f(-1, -1);
	glVertex2f(-1, 1);
	glVertex2f( 1, -1);
	glVertex2f( 1, 1);
	glColor4f(1.0f, 0.0f, 0.0f, 0.5f);
	glVertex2f(-1, 0);
	glVertex2f( 1, 0);
	glColor4f(0.0f, 1.0f, 0.0f, 0.5f);
	glVertex2f( 0, -1);
	glVertex2f( 0, 1);
	glEnd();
#else
	GLfloat vertices[] =//Should these be of type T?//TODO
	{
		-1, -1,
			1, -1,
			-1, 1,
			1, 1,
			-1, -1,
			-1, 1,
			1, -1,
			1, 1
		};
	QVector4D col(1.0f, 1.0f, 1.0f, 0.25f);
	DrawPointOrLine(col, vertices, 8, GL_LINES);
	const GLfloat vertices2[] =//Should these be of type T?//TODO
	{
		-1, 0,
			1, 0
		};
	QVector4D col2(1.0f, 0.0f, 0.0f, 0.5f);
	DrawPointOrLine(col2, vertices2, 2, GL_LINES);
	const GLfloat vertices3[] =//Should these be of type T?//TODO
	{
		0, -1,
		0, 1
	};
	const QVector4D col3(0.0f, 1.0f, 0.0f, 0.5f);
	DrawPointOrLine(col3, vertices3, 2, GL_LINES);
#endif
}

/// <summary>
/// Draw the grid
/// The frequency of the grid lines will change depending on the zoom (ALT+WHEEL).
/// Calculated with the frame always centered, the renderer just moves the camera.
/// </summary>
template <typename T>
void GLEmberController<T>::DrawGrid()
{
	const auto renderer = m_Fractorium->m_Controller->Renderer();
	const auto scale = m_FractoriumEmberController->AffineScaleCurrentToLocked();
	//qDebug() << renderer->UpperRightX(false) << " " << renderer->LowerLeftX(false) << " " << renderer->UpperRightY(false) << " " << renderer->LowerLeftY(false);
	const float unitX = (std::abs(renderer->UpperRightX(false) - renderer->LowerLeftX(false)) / 2.0f) / scale;
	const float unitY = (std::abs(renderer->UpperRightY(false) - renderer->LowerLeftY(false)) / 2.0f) / scale;

	if (unitX > 100000 || unitY > 100000)//Need a better way to do this.//TODO
	{
		qDebug() << unitX << " " << unitY;
		//return;
	}

	const float xLow = std::floor(-unitX);
	const float xHigh = std::ceil(unitX);
	const float yLow = std::floor(-unitY);
	const float yHigh = std::ceil(unitY);
	const float alpha = 0.25f;
	const int xsteps = std::ceil(std::abs(xHigh - xLow) / GridStep);//Need these because sometimes the float value never reaches the max and it gets stuck in an infinite loop.
	const int ysteps = std::ceil(std::abs(yHigh - yLow) / GridStep);
	Affine2D<T> temp;
	m_GL->glLineWidth(1.0f * m_GL->devicePixelRatioF());
#ifndef USE_GLSL
	m4T mat = (temp * scale).ToMat4RowMajor();
	m_GL->glPushMatrix();
	m_GL->glLoadIdentity();
	MultMatrix(mat);
	m_GL->glBegin(GL_LINES);
	m_GL->glColor4f(0.5f, 0.5f, 0.5f, alpha);

	for (float fx = xLow, i = 0; fx <= xHigh && i < xsteps; fx += GridStep, i++)
	{
		m_GL->glVertex2f(fx, yLow);
		m_GL->glVertex2f(fx, yHigh);
	}

	for (float fy = yLow, i = 0; fy < yHigh && i < ysteps; fy += GridStep, i++)
	{
		m_GL->glVertex2f(xLow,  fy);
		m_GL->glVertex2f(xHigh, fy);
	}

	m_GL->glColor4f(1.0f,   0.0f, 0.0f, alpha);
	m_GL->glVertex2f(0.0f,  0.0f);
	m_GL->glVertex2f(xHigh, 0.0f);
	m_GL->glColor4f(0.5f,   0.0f, 0.0f, alpha);
	m_GL->glVertex2f(0.0f,  0.0f);
	m_GL->glVertex2f(xLow,  0.0f);
	m_GL->glColor4f(0.0f,   1.0f, 0.0f, alpha);
	m_GL->glVertex2f(0.0f,  0.0f);
	m_GL->glVertex2f(0.0f,  yHigh);
	m_GL->glColor4f(0.0f,   0.5f, 0.0f, alpha);
	m_GL->glVertex2f(0.0f,  0.0f);
	m_GL->glVertex2f(0.0f,  yLow);
	m_GL->glColor4f(0.0f, 0.0f, 0.0f, 1.0f);
	m_GL->glEnd();
	m_GL->glPopMatrix();
#else
	const m4T mat = (temp * scale).ToMat4ColMajor();
	glm::tmat4x4<float, glm::defaultp> tempmat4 = mat;
	m_GL->m_ModelViewMatrix = QMatrix4x4(glm::value_ptr(tempmat4));
	m_GL->glLineWidth(1.0f * m_GL->devicePixelRatioF());
	m_Verts.clear();

	for (float fx = xLow, i = 0; fx <= xHigh && i < xsteps; fx += GridStep, i++)
	{
		m_Verts.push_back(fx);
		m_Verts.push_back(yLow);
		m_Verts.push_back(fx);
		m_Verts.push_back(yHigh);
	}

	for (float fy = yLow, i = 0; fy < yHigh && i < ysteps; fy += GridStep, i++)
	{
		m_Verts.push_back(xLow);
		m_Verts.push_back(fy);
		m_Verts.push_back(xHigh);
		m_Verts.push_back(fy);
	}

	QVector4D col(0.5f, 0.5f, 0.5f, alpha);
	m_GL->DrawPointOrLine(col, m_Verts, GL_LINES);
	m_Verts.clear();
	m_Verts.push_back(0.0f);
	m_Verts.push_back(0.0f);
	m_Verts.push_back(xHigh);
	m_Verts.push_back(0.0f);
	col = QVector4D(1.0f, 0.0f, 0.0f, alpha);
	m_GL->DrawPointOrLine(col, m_Verts, GL_LINES);
	m_Verts.clear();
	m_Verts.push_back(0.0f);
	m_Verts.push_back(0.0f);
	m_Verts.push_back(xLow);
	m_Verts.push_back(0.0f);
	col = QVector4D(0.5f, 0.0f, 0.0f, alpha);
	m_GL->DrawPointOrLine(col, m_Verts, GL_LINES);
	m_Verts.clear();
	m_Verts.push_back(0.0f);
	m_Verts.push_back(0.0f);
	m_Verts.push_back(0.0f);
	m_Verts.push_back(yHigh);
	col = QVector4D(0.0f, 1.0f, 0.0f, alpha);
	m_GL->DrawPointOrLine(col, m_Verts, GL_LINES);
	m_Verts.clear();
	m_Verts.push_back(0.0f);
	m_Verts.push_back(0.0f);
	m_Verts.push_back(0.0f);
	m_Verts.push_back(yLow);
	col = QVector4D(0.0f, 0.5f, 0.0f, alpha);
	m_GL->DrawPointOrLine(col, m_Verts, GL_LINES);
#endif
}

/// <summary>
/// Draw the pre or post affine circle for the passed in xform.
/// For drawing affine transforms, multiply the identity model view matrix by the
/// affine for each xform, so that all points are considered to be "1".
/// </summary>
/// <param name="xform">A pointer to the xform whose affine will be drawn</param>
/// <param name="pre">True for pre affine, else false for post.</param>
/// <param name="selected">True if selected (draw enclosing circle), else false (only draw axes).</param>
/// <param name="hovered">True if the xform is being hovered over (draw tansparent disc), else false (no disc).</param>
template <typename T>
void GLEmberController<T>::DrawAffine(const Xform<T>* xform, bool pre, bool selected, bool hovered)
{
	const auto ember = m_FractoriumEmberController->CurrentEmber();
	const auto final = ember->IsFinalXform(xform);
	const auto index = ember->GetXformIndex(xform);
	const auto size = ember->m_Palette.m_Entries.size();
	const auto color = ember->m_Palette.m_Entries[Clamp<T>(xform->m_ColorX * size, 0, size - 1)];
	const auto& affine = pre ? xform->m_Affine : xform->m_Post;
#ifndef USE_GLSL
	//For some incredibly strange reason, even though glm and OpenGL use matrices with a column-major
	//data layout, nothing will work here unless they are flipped to row major order. This is how it was
	//done in Fractron.
	m4T mat = (affine * m_FractoriumEmberController->AffineScaleCurrentToLocked()).ToMat4RowMajor();
	m_GL->glPushMatrix();
	m_GL->glLoadIdentity();
	MultMatrix(mat);
	//QueryMatrices(true);
	m_GL->glLineWidth(3.0f * m_GL->devicePixelRatioF());//One 3px wide, colored black, except green on x axis for post affine.
	m_GL->DrawAffineHelper(index, selected, hovered, pre, final, true);
	m_GL->glLineWidth(1.0f * m_GL->devicePixelRatioF());//Again 1px wide, colored white, to give a white middle with black outline effect.
	m_GL->DrawAffineHelper(index, selected, hovered, pre, final, false);
	m_GL->glPointSize(5.0f * m_GL->devicePixelRatioF());//Three black points, one in the center and two on the circle. Drawn big 5px first to give a black outline.
	m_GL->glBegin(GL_POINTS);
	m_GL->glColor4f(0.0f, 0.0f, 0.0f, selected ? 1.0f : 0.5f);
	m_GL->glVertex2f(0.0f, 0.0f);
	m_GL->glVertex2f(1.0f, 0.0f);
	m_GL->glVertex2f(0.0f, 1.0f);
	m_GL->glEnd();
	m_GL->glLineWidth(2.0f * m_GL->devicePixelRatioF());//Draw lines again for y axis only, without drawing the circle, using the color of the selected xform.
	m_GL->glBegin(GL_LINES);
	m_GL->glColor4f(color.r, color.g, color.b, 1.0f);
	m_GL->glVertex2f(0.0f, 0.0f);
	m_GL->glVertex2f(0.0f, 1.0f);
	m_GL->glEnd();
	m_GL->glPointSize(3.0f * m_GL->devicePixelRatioF());//Draw smaller white points, to give a black outline effect.
	m_GL->glBegin(GL_POINTS);
	m_GL->glColor4f(1.0f, 1.0f, 1.0f, selected ? 1.0f : 0.5f);
	m_GL->glVertex2f(0.0f, 0.0f);
	m_GL->glVertex2f(1.0f, 0.0f);
	m_GL->glVertex2f(0.0f, 1.0f);
	m_GL->glColor4f(0.0f, 0.0f, 0.0f, 1.0f);
	m_GL->glEnd();
	m_GL->glPopMatrix();
#else
	const m4T mat = (affine * m_FractoriumEmberController->AffineScaleCurrentToLocked()).ToMat4ColMajor();
	glm::tmat4x4<float, glm::defaultp> tempmat4 = mat;
	m_GL->m_ModelViewMatrix = QMatrix4x4(glm::value_ptr(tempmat4));
	m_GL->DrawAffineHelper(index, 1.0, 3.0, selected, hovered, pre, final, true);//Circle line width is thinner than the line width for the axes, just to distinguish it.
	m_GL->DrawAffineHelper(index, 0.5, 2.0, selected, hovered, pre, final, false);
	QVector4D col(0.0f, 0.0f, 0.0f, selected ? 1.0f : 0.5f);
	m_Verts.clear();
	m_Verts.push_back(0.0f);
	m_Verts.push_back(0.0f);
	m_Verts.push_back(1.0f);
	m_Verts.push_back(0.0f);
	m_Verts.push_back(0.0f);
	m_Verts.push_back(1.0f);
	m_GL->DrawPointOrLine(col, m_Verts, GL_POINTS, !pre, 6.0f);//Three black points, one in the center and two on the circle. Drawn big 5px first to give a black outline.
	//Somewhat of a hack, since it's drawing over the Y axis line it just drew.
	m_GL->glLineWidth(2.0f * m_GL->devicePixelRatioF());//Draw lines again for y axis only, using the color of the selected xform.
	m_Verts.clear();
	m_Verts.push_back(0.0f);
	m_Verts.push_back(0.0f);
	m_Verts.push_back(0.0f);
	m_Verts.push_back(1.0f);
	col = QVector4D(color.r, color.g, color.b, 1.0f);
	m_GL->DrawPointOrLine(col, m_Verts, GL_LINES, !pre);
	//Line from x to y in the color of the xform combo, thinner and solid with no background to somewhat distinguish it.
	m_GL->glLineWidth(0.5f * m_GL->devicePixelRatioF());
	m_Verts.clear();
	m_Verts.push_back(0);
	m_Verts.push_back(1);
	m_Verts.push_back(1);
	m_Verts.push_back(0);
	auto qcol = final ? m_Fractorium->m_FinalXformComboColor : m_Fractorium->m_XformComboColors[index % XFORM_COLOR_COUNT];
	m_GL->DrawPointOrLine(QVector4D(qcol.redF(), qcol.greenF(), qcol.blueF(), 1.0f), m_Verts, GL_LINES, !pre);
	//
	m_GL->glLineWidth(2.0f * m_GL->devicePixelRatioF());
	m_Verts.clear();
	m_Verts.push_back(0.0f);//Center.
	m_Verts.push_back(0.0f);
	col = QVector4D(1.0f, 1.0f, 1.0f, selected ? 1.0f : 0.5f);
	m_GL->DrawPointOrLine(col, m_Verts, GL_POINTS, false, 5.0f);//Draw smaller white point, to give a black outline effect.
	m_Verts.clear();
	m_Verts.push_back(1.0f);//X axis.
	m_Verts.push_back(0.0f);
	col = QVector4D(0.0f, 1.0f, 0.0f, selected ? 1.0f : 0.5f);
	m_GL->DrawPointOrLine(col, m_Verts, GL_POINTS, false, 5.0f);//Draw smaller green point, to give a black outline effect.
	m_Verts.clear();
	m_Verts.push_back(0.0f);//Y axis.
	m_Verts.push_back(1.0f);
	col = QVector4D(1.0f, 0.0f, 1.0f, selected ? 1.0f : 0.5f);
	m_GL->DrawPointOrLine(col, m_Verts, GL_POINTS, false, 5.0f);//Draw smaller purple point, to give a black outline effect.
	m_GL->m_ModelViewMatrix.setToIdentity();
#endif
}

/// <summary>
/// Draw the axes, and optionally the surrounding circle
/// of an affine transform.
/// </summary>
/// <param name="index"></param>
/// <param name="circleWidth"></param>
/// <param name="lineWidth"></param>
/// <param name="selected">True if selected (draw enclosing circle), else false (only draw axes).</param>
/// <param name="hovered">True if the xform is being hovered over (draw tansparent disc), else false (no disc).</param>
/// <param name="pre"></param>
/// <param name="final"></param>
/// <param name="background"></param>
void GLWidget::DrawAffineHelper(int index, float circleWidth, float lineWidth, bool selected, bool hovered, bool pre, bool final, bool background)
{
	float px = 1.0f;
	float py = 0.0f;
	const auto col = final ? m_Fractorium->m_FinalXformComboColor : m_Fractorium->m_XformComboColors[index % XFORM_COLOR_COUNT];
#ifndef USE_GLSL
	glBegin(GL_LINES);

	//Circle part.
	if (!background)
	{
		glColor4f(col.redF(), col.greenF(), col.blueF(), 1.0f);//Draw pre affine transform with white.
	}
	else
	{
		glColor4f(0.0f, 0.0f, 0.0f, 1.0f);//Draw pre affine transform outline with white.
	}

	if (selected)
	{
		for (size_t i = 1; i <= 64; i++)//The circle.
		{
			float theta = float(M_PI) * 2.0f * float(i % 64) / 64.0f;
			float fx = std::cos(theta);
			float fy = std::sin(theta);
			glVertex2f(px, py);
			glVertex2f(fx, fy);
			px = fx;
			py = fy;
		}
	}

	//Lines from center to circle.
	if (!background)
	{
		glColor4f(col.redF(), col.greenF(), col.blueF(), 1.0f);
	}
	else
	{
		if (pre)
			glColor4f(0.0f, 0.0f, 0.0f, 1.0f);//Draw pre affine transform outline with white.
		else
			glColor4f(0.0f, 0.75f, 0.0f, 1.0f);//Draw post affine transform outline with green.
	}

	//The lines from the center to the circle.
	glVertex2f(0.0f, 0.0f);//X axis.
	glVertex2f(1.0f, 0.0f);

	if (background)
		glColor4f(0.0f, 0.0f, 0.0f, 1.0f);

	glVertex2f(0.0f, 0.0f);//Y axis.
	glVertex2f(0.0f, 1.0f);
	glEnd();
#else
	QVector4D color;

	//Circle part.
	if (!background)
	{
		color = QVector4D(col.redF(), col.greenF(), col.blueF(), hovered ? 0.25f : 1.0f);//Draw pre affine transform with white.
	}
	else
	{
		color = QVector4D(0.0f, 0.0f, 0.0f, hovered ? 0.25f : 1.0f);//Draw pre affine transform outline with white.
	}

	m_Verts.clear();
	glLineWidth(circleWidth * devicePixelRatioF());//One thinner, colored black, except green on x axis for post affine.

	if (selected || hovered)
	{
		for (size_t i = 1; i <= 64; i++)//The circle.
		{
			const float theta = static_cast<float>(M_PI) * 2.0f * static_cast<float>(i % 64) / 64.0f;
			const float fx = std::cos(theta);
			const float fy = std::sin(theta);
			m_Verts.push_back(fx);
			m_Verts.push_back(fy);
		}

		DrawPointOrLine(color, m_Verts, hovered ? GL_TRIANGLE_FAN : GL_LINE_LOOP, !pre);
	}

	glLineWidth(lineWidth * devicePixelRatioF());//One thicker, colored black, except green on x axis for post affine.

	//Lines from center to circle.
	if (!background)
	{
		color = QVector4D(col.redF(), col.greenF(), col.blueF(), 1.0f);
	}
	else
	{
		if (pre)
			color = QVector4D(0.0f, 0.0f, 0.0f, 1.0f);//Draw pre affine transform outline with white.
		else
			color = QVector4D(0.0f, 0.75f, 0.0f, 1.0f);//Draw post affine transform outline with green.
	}

	//The lines from the center to the circle.
	m_Verts.clear();
	m_Verts.push_back(0);//X axis.
	m_Verts.push_back(0);
	m_Verts.push_back(1);
	m_Verts.push_back(0);
	DrawPointOrLine(color, m_Verts, GL_LINES, !pre);

	if (background)
		color = QVector4D(0.0f, 0.0f, 0.0f, 1.0f);

	m_Verts.clear();
	m_Verts.push_back(0);//Y axis.
	m_Verts.push_back(0);
	m_Verts.push_back(0);
	m_Verts.push_back(1);
	DrawPointOrLine(color, m_Verts, GL_LINES, !pre);
#endif
}

/// <summary>
/// Determine the index of the xform being hovered over if any.
/// Give precedence to the currently selected xform, if any.
/// </summary>
/// <param name="glCoords">The mouse raster coordinates to check</param>
/// <returns>The index of the xform being hovered over, else -1 if no hover.</returns>
template <typename T>
int GLEmberController<T>::UpdateHover(const v3T& glCoords)
{
	const bool pre = m_Fractorium->DrawPreAffines();
	const bool post = m_Fractorium->DrawPostAffines();
	int i = 0, bestIndex = -1;
	T bestDist = 10;
	const auto ember = m_FractoriumEmberController->CurrentEmber();
	m_HoverType = eHoverType::HoverNone;

	if (m_Fractorium->DrawXforms())//Don't bother checking anything if the user wants to see no xforms.
	{
		const bool forceFinal = m_Fractorium->HaveFinal();

		//If there's a selected/current xform, check it first so it gets precedence over the others.
		if (m_SelectedXform)
		{
			//These checks prevent highlighting the pre/post selected xform circle, when one is set to show all, and the other
			//is set to show current, and the user hovers over another xform, but doesn't select it, then moves the mouse
			//back over the hidden circle for the pre/post that was set to only show current.
			const bool isSel = m_Fractorium->IsXformSelected(ember->GetTotalXformIndex(m_SelectedXform));
			const bool checkPre = pre && (m_Fractorium->DrawAllPre() || (m_Fractorium->DrawSelectedPre() && isSel));
			const bool checkPost = post && (m_Fractorium->DrawAllPost() || (m_Fractorium->DrawSelectedPost() && isSel));

			if (CheckXformHover(m_SelectedXform, glCoords, bestDist, checkPre, checkPost))
			{
				m_HoverXform = m_SelectedXform;
				bestIndex = static_cast<int>(ember->GetTotalXformIndex(m_SelectedXform, forceFinal));
			}
		}

		//Check all xforms.
		while (const auto xform = ember->GetTotalXform(i, forceFinal))
		{
			const bool isSel = m_Fractorium->IsXformSelected(i);

			if (pre)
			{
				const bool checkPre = m_Fractorium->DrawAllPre() || (m_Fractorium->DrawSelectedPre() && isSel) || (m_SelectedXform == xform);

				if (checkPre)//Only check pre affine if they are shown.
				{
					if (CheckXformHover(xform, glCoords, bestDist, true, false))
					{
						m_HoverXform = xform;
						bestIndex = i;
					}
				}
			}

			if (post)
			{
				const bool checkPost = m_Fractorium->DrawAllPost() || (m_Fractorium->DrawSelectedPost() && isSel) || (m_SelectedXform == xform);

				if (checkPost)
				{
					if (CheckXformHover(xform, glCoords, bestDist, false, true))
					{
						m_HoverXform = xform;
						bestIndex = i;
					}
				}
			}

			i++;
		}
	}

	return bestIndex;
}

/// <summary>
/// Determine the passed in xform's pre/post affine transforms are being hovered over.
/// Meant to be called in succession when checking all xforms for hover, and the best
/// hover distance is recorded in the bestDist reference parameter.
/// Mouse coordinates will be converted internally to world cartesian coordinates for checking.
/// </summary>
/// <param name="xform">A pointer to the xform to check for hover</param>
/// <param name="glCoords">The mouse raster coordinates to check</param>
/// <param name="bestDist">Reference to hold the best distance found so far</param>
/// <param name="pre">True to check pre affine, else don't.</param>
/// <param name="post">True to check post affine, else don't.</param>
/// <returns>True if hovering and the distance is smaller than the bestDist parameter</returns>
template <typename T>
bool GLEmberController<T>::CheckXformHover(const Xform<T>* xform, const v3T& glCoords, T& bestDist, bool pre, bool post)
{
	bool preFound = false, postFound = false;
	T dist = 0;
	const auto scale = m_FractoriumEmberController->AffineScaleCurrentToLocked();
	v3T pos;

	if (pre)
	{
		const auto affineScaled = xform->m_Affine * scale;
		const v3T translation(affineScaled.C(), affineScaled.F(), 0);
		const v3T transScreen = glm::project(translation, m_Modelview, m_Projection, m_Viewport);
		const v3T xAxis(affineScaled.A(), affineScaled.D(), 0);
		const v3T xAxisScreen = glm::project(translation + xAxis, m_Modelview, m_Projection, m_Viewport);
		const v3T yAxis(affineScaled.B(), affineScaled.E(), 0);
		const v3T yAxisScreen = glm::project(translation + yAxis, m_Modelview, m_Projection, m_Viewport);
		pos = translation;
		dist = glm::distance(glCoords, transScreen);

		if (dist < bestDist)
		{
			bestDist = dist;
			m_HoverType = eHoverType::HoverTranslation;
			m_HoverHandlePos = pos;
			preFound = true;
		}

		pos = translation + xAxis;
		dist = glm::distance(glCoords, xAxisScreen);

		if (dist < bestDist)
		{
			bestDist = dist;
			m_HoverType = eHoverType::HoverXAxis;
			m_HoverHandlePos = pos;
			preFound = true;
		}

		pos = translation + yAxis;
		dist = glm::distance(glCoords, yAxisScreen);

		if (dist < bestDist)
		{
			bestDist = dist;
			m_HoverType = eHoverType::HoverYAxis;
			m_HoverHandlePos = pos;
			preFound = true;
		}

		if (preFound)
			m_AffineType = eAffineType::AffinePre;
	}

	if (post)
	{
		const auto affineScaled = xform->m_Post * scale;
		const v3T translation(affineScaled.C(), affineScaled.F(), 0);
		const v3T transScreen = glm::project(translation, m_Modelview, m_Projection, m_Viewport);
		const v3T xAxis(affineScaled.A(), affineScaled.D(), 0);
		const v3T xAxisScreen = glm::project(translation + xAxis, m_Modelview, m_Projection, m_Viewport);
		const v3T yAxis(affineScaled.B(), affineScaled.E(), 0);
		const v3T yAxisScreen = glm::project(translation + yAxis, m_Modelview, m_Projection, m_Viewport);
		pos = translation;
		dist = glm::distance(glCoords, transScreen);

		if (dist < bestDist)
		{
			bestDist = dist;
			m_HoverType = eHoverType::HoverTranslation;
			m_HoverHandlePos = pos;
			postFound = true;
		}

		pos = translation + xAxis;
		dist = glm::distance(glCoords, xAxisScreen);

		if (dist < bestDist)
		{
			bestDist = dist;
			m_HoverType = eHoverType::HoverXAxis;
			m_HoverHandlePos = pos;
			postFound = true;
		}

		pos = translation + yAxis;
		dist = glm::distance(glCoords, yAxisScreen);

		if (dist < bestDist)
		{
			bestDist = dist;
			m_HoverType = eHoverType::HoverYAxis;
			m_HoverHandlePos = pos;
			postFound = true;
		}

		if (postFound)
			m_AffineType = eAffineType::AffinePost;
	}

	return preFound || postFound;
}

/// <summary>
/// Calculate the new affine transform when dragging with the x axis with the left mouse button.
/// The value returned will depend on whether any modifier keys were held down.
/// None: Rotate only.
/// Local Pivot:
///		Shift: Scale and optionally Rotate about affine center.
///		Alt: Rotate single axis about affine center.
///		Shift + Alt: Free transform.
///		Control: Rotate, snapping to grid.
///		Control + Shift: Scale and optionally Rotate, snapping to grid.
///		Control + Alt: Rotate single axis about affine center, snapping to grid.
///		Control + Shift + Alt: Free transform, snapping to grid.
/// World Pivot:
///		Shift + Alt: Rotate single axis about world center.
///		Control + Shift + Alt: Rotate single axis about world center, snapping to grid.
///		All others are the same as local pivot.
/// </summary>
/// <returns>The new affine transform to be assigned to the selected xform</returns>
template <typename T>
void GLEmberController<T>::CalcDragXAxis()
{
	const T affineToWorldScale = static_cast<T>(m_FractoriumEmberController->AffineScaleLockedToCurrent());
	const T worldToAffineScale = static_cast<T>(m_FractoriumEmberController->AffineScaleCurrentToLocked());
	const bool pre = m_AffineType == eAffineType::AffinePre;
	const bool worldPivotShiftAlt = !m_Fractorium->LocalPivot() && GetShift() && GetAlt();
	const auto worldRelAxisStartScaled = (v2T(m_HoverHandlePos) * affineToWorldScale) - m_DragSrcTransform.O();//World axis start position, relative, scaled by the zoom.
	const T startAngle = std::atan2(worldRelAxisStartScaled.y, worldRelAxisStartScaled.x);
	v3T relScaled = (m_MouseWorldPos * affineToWorldScale) - v3T(m_DragSrcTransform.O(), 0);

	if (!GetShift())
	{
		if (GetControl())
		{
			relScaled = SnapToNormalizedAngle(relScaled, 24u);//relScaled is using the relative scaled position of the axis.
		}

		const T endAngle = std::atan2(relScaled.y, relScaled.x);
		const T angle = startAngle - endAngle;
		m_FractoriumEmberController->UpdateXform([&](Xform<T>* xform, size_t xfindex, size_t selIndex)
		{
			auto it = m_DragSrcPreTransforms.find(xfindex);

			if (it != m_DragSrcPreTransforms.end())
			{
				auto src = it->second;
				auto& affine = xform->m_Affine;

				if (GetAlt())
				{
					src.Rotate(angle);
					affine.X(src.X());
				}
				else
				{
					src.Rotate(angle);
					affine = src;
				}
			}

			it = m_DragSrcPostTransforms.find(xfindex);

			if (it != m_DragSrcPostTransforms.end())
			{
				auto src = it->second;
				auto& affine = xform->m_Post;

				if (GetAlt())
				{
					src.Rotate(angle);
					affine.X(src.X());
				}
				else
				{
					src.Rotate(angle);
					affine = src;
				}
			}

			const auto& affine = pre ? xform->m_Affine : xform->m_Post;

			if (xform == m_FractoriumEmberController->CurrentXform())
				m_DragHandlePos = v3T((affine.O() + affine.X()) * worldToAffineScale, 0);
		}, eXformUpdate::UPDATE_SELECTED, false);//Calling code will update renderer.
	}
	else
	{
		auto origmag = Zeps(glm::length(m_DragSrcTransform.X()));//Magnitude of original dragged axis before it was dragged.

		if (GetControl())
		{
			relScaled = SnapToGrid(relScaled);
		}

		const auto newmag = glm::length(relScaled);
		const auto newprc = newmag / origmag;
		const T endAngle = std::atan2(relScaled.y, relScaled.x);
		const T angle = startAngle - endAngle;
		m_FractoriumEmberController->UpdateXform([&](Xform<T>* xform, size_t xfindex, size_t selIndex)
		{
			auto it = m_DragSrcPreTransforms.find(xfindex);

			if (it != m_DragSrcPreTransforms.end())
			{
				auto src = it->second;
				auto& affine = xform->m_Affine;

				if (worldPivotShiftAlt)
				{
					src.X(src.O() + src.X());
					src.O(v2T(0));
					src.Rotate(angle);
					affine.X(src.X() - affine.O());
				}
				else if (GetAlt())
				{
					affine.X(v2T(relScaled));//Absolute, not ratio.
				}
				else
				{
					src.ScaleXY(newprc);

					if (m_Fractorium->m_Settings->RotateAndScale())
						src.Rotate(angle);

					affine = src;
				}
			}

			it = m_DragSrcPostTransforms.find(xfindex);

			if (it != m_DragSrcPostTransforms.end())
			{
				auto src = it->second;
				auto& affine = xform->m_Post;

				if (worldPivotShiftAlt)
				{
					src.X(src.O() + src.X());
					src.O(v2T(0));
					src.Rotate(angle);
					affine.X(src.X() - affine.O());
				}
				else if (GetAlt())
				{
					affine.X(v2T(relScaled));//Absolute, not ratio.
				}
				else
				{
					src.ScaleXY(newprc);

					if (m_Fractorium->m_Settings->RotateAndScale())
						src.Rotate(angle);

					affine = src;
				}
			}

			const auto& affine = pre ? xform->m_Affine : xform->m_Post;

			if (xform == m_FractoriumEmberController->CurrentXform())
				m_DragHandlePos = v3T((affine.O() + affine.X()) * worldToAffineScale, 0);
		}, eXformUpdate::UPDATE_SELECTED, false);
	}
}

/// <summary>
/// Calculate the new affine transform when dragging with the y axis with the left mouse button.
/// The value returned will depend on whether any modifier keys were held down.
/// None: Rotate only.
/// Local Pivot:
///		Shift: Scale and optionally Rotate about affine center.
///		Alt: Rotate single axis about affine center.
///		Shift + Alt: Free transform.
///		Control: Rotate, snapping to grid.
///		Control + Shift: Scale and optionally Rotate, snapping to grid.
///		Control + Alt: Rotate single axis about affine center, snapping to grid.
///		Control + Shift + Alt: Free transform, snapping to grid.
/// World Pivot:
///		Shift + Alt: Rotate single axis about world center.
///		Control + Shift + Alt: Rotate single axis about world center, snapping to grid.
///		All others are the same as local pivot.
/// </summary>
/// <returns>The new affine transform to be assigned to the selected xform</returns>
template <typename T>
void GLEmberController<T>::CalcDragYAxis()
{
	const T affineToWorldScale = static_cast<T>(m_FractoriumEmberController->AffineScaleLockedToCurrent());
	const T worldToAffineScale = static_cast<T>(m_FractoriumEmberController->AffineScaleCurrentToLocked());
	const bool pre = m_AffineType == eAffineType::AffinePre;
	const bool worldPivotShiftAlt = !m_Fractorium->LocalPivot() && GetShift() && GetAlt();
	const auto worldRelAxisStartScaled = (v2T(m_HoverHandlePos) * affineToWorldScale) - m_DragSrcTransform.O();//World axis start position, relative, scaled by the zoom.
	const T startAngle = std::atan2(worldRelAxisStartScaled.y, worldRelAxisStartScaled.x);
	v3T relScaled = (m_MouseWorldPos * affineToWorldScale) - v3T(m_DragSrcTransform.O(), 0);

	if (!GetShift())
	{
		if (GetControl())
		{
			relScaled = SnapToNormalizedAngle(relScaled, 24u);//relScaled is using the relative scaled position of the axis.
		}

		const T endAngle = std::atan2(relScaled.y, relScaled.x);
		const T angle = startAngle - endAngle;
		m_FractoriumEmberController->UpdateXform([&](Xform<T>* xform, size_t xfindex, size_t selIndex)
		{
			auto it = m_DragSrcPreTransforms.find(xfindex);

			if (it != m_DragSrcPreTransforms.end())
			{
				auto src = it->second;
				auto& affine = xform->m_Affine;

				if (GetAlt())
				{
					src.Rotate(angle);
					affine.Y(src.Y());
				}
				else
				{
					src.Rotate(angle);
					affine = src;
				}
			}

			it = m_DragSrcPostTransforms.find(xfindex);

			if (it != m_DragSrcPostTransforms.end())
			{
				auto src = it->second;
				auto& affine = xform->m_Post;

				if (GetAlt())
				{
					src.Rotate(angle);
					affine.Y(src.Y());
				}
				else
				{
					src.Rotate(angle);
					affine = src;
				}
			}

			const auto& affine = pre ? xform->m_Affine : xform->m_Post;

			if (xform == m_FractoriumEmberController->CurrentXform())
				m_DragHandlePos = v3T((affine.O() + affine.Y()) * worldToAffineScale, 0);
		}, eXformUpdate::UPDATE_SELECTED, false);//Calling code will update renderer.
	}
	else
	{
		const auto origmag = Zeps(glm::length(m_DragSrcTransform.Y()));//Magnitude of original dragged axis before it was dragged.

		if (GetControl())
		{
			relScaled = SnapToGrid(relScaled);
		}

		const auto newmag = glm::length(relScaled);
		const auto newprc = newmag / origmag;
		const T endAngle = std::atan2(relScaled.y, relScaled.x);
		const T angle = startAngle - endAngle;
		m_FractoriumEmberController->UpdateXform([&](Xform<T>* xform, size_t xfindex, size_t selIndex)
		{
			auto it = m_DragSrcPreTransforms.find(xfindex);

			if (it != m_DragSrcPreTransforms.end())
			{
				auto src = it->second;
				auto& affine = xform->m_Affine;

				if (worldPivotShiftAlt)
				{
					src.Y(src.O() + src.Y());
					src.O(v2T(0));
					src.Rotate(angle);
					affine.Y(src.Y() - affine.O());
				}
				else if (GetAlt())
				{
					affine.Y(v2T(relScaled));//Absolute, not ratio.
				}
				else
				{
					src.ScaleXY(newprc);

					if (m_Fractorium->m_Settings->RotateAndScale())
						src.Rotate(angle);

					affine = src;
				}
			}

			it = m_DragSrcPostTransforms.find(xfindex);

			if (it != m_DragSrcPostTransforms.end())
			{
				auto src = it->second;
				auto& affine = xform->m_Post;

				if (worldPivotShiftAlt)
				{
					src.Y(src.O() + src.Y());
					src.O(v2T(0));
					src.Rotate(angle);
					affine.Y(src.Y() - affine.O());
				}
				else if (GetAlt())
				{
					affine.Y(v2T(relScaled));//Absolute, not ratio.
				}
				else
				{
					src.ScaleXY(newprc);

					if (m_Fractorium->m_Settings->RotateAndScale())
						src.Rotate(angle);

					affine = src;
				}
			}

			const auto& affine = pre ? xform->m_Affine : xform->m_Post;

			if (xform == m_FractoriumEmberController->CurrentXform())
				m_DragHandlePos = v3T((affine.O() + affine.Y()) * worldToAffineScale, 0);
		}, eXformUpdate::UPDATE_SELECTED, false);
	}
}

/// <summary>
/// Calculate the new affine transform when dragging the center with the left mouse button.
/// The value returned will depend on whether any modifier keys were held down.
/// None: Free transform.
/// Local Pivot:
///		Shift: Rotate about world center, keeping orientation the same.
///		Control: Free transform, snapping to grid.
///		Control + Shift: Rotate about world center, keeping orientation the same, snapping to grid.
/// World Pivot:
///		Shift: Rotate about world center, rotating orientation.
///		Control + Shift: Rotate about world center, rotating orientation, snapping to grid.
///		All others are the same as local pivot.
/// </summary>
template <typename T>
void GLEmberController<T>::CalcDragTranslation()
{
	const T affineToWorldScale = static_cast<T>(m_FractoriumEmberController->AffineScaleLockedToCurrent());
	const T worldToAffineScale = static_cast<T>(m_FractoriumEmberController->AffineScaleCurrentToLocked());
	const bool worldPivotShift = !m_Fractorium->LocalPivot() && GetShift();
	const bool pre = m_AffineType == eAffineType::AffinePre;

	if (GetShift())
	{
		const v3T snapped = GetControl() ? SnapToNormalizedAngle(m_MouseWorldPos, 24) : m_MouseWorldPos;
		const T startAngle = std::atan2(m_DragSrcTransform.O().y, m_DragSrcTransform.O().x);
		const T endAngle = std::atan2(snapped.y, snapped.x);
		const T angle = startAngle - endAngle;
		m_FractoriumEmberController->UpdateXform([&](Xform<T>* xform, size_t xfindex, size_t selIndex)
		{
			auto it = m_DragSrcPreTransforms.find(xfindex);

			if (it != m_DragSrcPreTransforms.end())
			{
				auto src = it->second;
				auto& affine = xform->m_Affine;
				src.RotateTrans(angle);

				if (worldPivotShift)
				{
					src.Rotate(angle);
					affine.X(src.X());
					affine.Y(src.Y());
				}

				affine.O(src.O());
			}

			it = m_DragSrcPostTransforms.find(xfindex);

			if (it != m_DragSrcPostTransforms.end())
			{
				auto src = it->second;
				auto& affine = xform->m_Post;
				src.RotateTrans(angle);

				if (worldPivotShift)
				{
					src.Rotate(angle);
					affine.X(src.X());
					affine.Y(src.Y());
				}

				affine.O(src.O());
			}

			const auto& affine = pre ? xform->m_Affine : xform->m_Post;

			if (xform == m_FractoriumEmberController->CurrentXform())
				m_DragHandlePos = v3T(affine.O(), 0) * worldToAffineScale;
		}, eXformUpdate::UPDATE_SELECTED, false);//Calling code will update renderer.
	}
	else
	{
		const auto diff = m_MouseWorldPos - m_MouseDownWorldPos;

		if (GetControl())
		{
			m_FractoriumEmberController->UpdateXform([&](Xform<T>* xform, size_t xfindex, size_t selIndex)
			{
				auto it = m_DragSrcPreTransforms.find(xfindex);

				if (it != m_DragSrcPreTransforms.end())
				{
					const auto& src = it->second;
					auto& affine = xform->m_Affine;
					const auto offset = src.O() + (affineToWorldScale * v2T(diff));
					const auto snapped = SnapToGrid(offset);
					affine.O(v2T(snapped.x, snapped.y));
				}

				it = m_DragSrcPostTransforms.find(xfindex);

				if (it != m_DragSrcPostTransforms.end())
				{
					auto& src = it->second;
					auto& affine = xform->m_Post;
					auto offset = src.O() + (affineToWorldScale * v2T(diff));
					auto snapped = SnapToGrid(offset);
					affine.O(v2T(snapped.x, snapped.y));
				}

				auto& affine = pre ? xform->m_Affine : xform->m_Post;

				if (xform == m_FractoriumEmberController->CurrentXform())
					m_DragHandlePos = v3T(affine.O(), 0) * worldToAffineScale;
			}, eXformUpdate::UPDATE_CURRENT_AND_SELECTED, false);
		}
		else
		{
			m_FractoriumEmberController->UpdateXform([&](Xform<T>* xform, size_t xfindex, size_t selIndex)
			{
				auto it = m_DragSrcPreTransforms.find(xfindex);

				if (it != m_DragSrcPreTransforms.end())
				{
					auto& src = it->second;
					auto& affine = xform->m_Affine;
					affine.O(src.O() + (affineToWorldScale * v2T(diff)));
				}

				it = m_DragSrcPostTransforms.find(xfindex);

				if (it != m_DragSrcPostTransforms.end())
				{
					auto& src = it->second;
					auto& affine = xform->m_Post;
					affine.O(src.O() + (affineToWorldScale * v2T(diff)));
				}

				auto& affine = pre ? xform->m_Affine : xform->m_Post;

				if (xform == m_FractoriumEmberController->CurrentXform())
					m_DragHandlePos = v3T(affine.O(), 0) * worldToAffineScale;
			}, eXformUpdate::UPDATE_SELECTED, false);
		}
	}
}

/// <summary>
/// Thin wrapper to check if all controllers are ok and return a pointer to the GLController.
/// </summary>
/// <returns>A pointer to the GLController if everything is ok, else false.</returns>
GLEmberControllerBase* GLWidget::GLController()
{
	if (m_Fractorium && m_Fractorium->ControllersOk())
		return m_Fractorium->m_Controller->GLController();

	return nullptr;
}

template class GLEmberController<float>;

#ifdef DO_DOUBLE
	template class GLEmberController<double>;
#endif
