mirror of
				https://bitbucket.org/mfeemster/fractorium.git
				synced 2025-10-31 01:10:24 -04:00 
			
		
		
		
	 66f8f1e50c
			
		
	
	66f8f1e50c
	
	
	
		
			
			-Draw selection circles around all selected xforms plus the current one. --Bug fixes -Add control key as a modifier to increase the amount a spinbox is changed when using right mouse drag to edit. Previously only observed shift key to decrease the value. -Change the copy/paste selected xforms shortcuts to be Ctrl+X+C and Ctrl+X+V. --Code changes -Add function Fractorium::IsXformSelected() to determine if an xform has been selected with the checkboxes.
		
			
				
	
	
		
			1431 lines
		
	
	
		
			48 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1431 lines
		
	
	
		
			48 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include "FractoriumPch.h"
 | |
| #include "GLWidget.h"
 | |
| #include "Fractorium.h"
 | |
| 
 | |
| /// <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)
 | |
| {
 | |
| 	QSurfaceFormat qsf;
 | |
| 	m_Init = false;
 | |
| 	m_Drawing = false;
 | |
| 	m_TexWidth = 0;
 | |
| 	m_TexHeight = 0;
 | |
| 	m_OutputTexID = 0;
 | |
| 	m_Fractorium = nullptr;
 | |
| 	qsf.setSwapInterval(1);//Vsync.
 | |
| 	qsf.setSwapBehavior(QSurfaceFormat::DoubleBuffer);
 | |
| 	qsf.setVersion(2, 0);
 | |
| 	setFormat(qsf);
 | |
| }
 | |
| 
 | |
| /// <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)
 | |
| 	{
 | |
| 		int w = m_Fractorium->ui.GLParentScrollArea->width();
 | |
| 		int h = m_Fractorium->ui.GLParentScrollArea->height();
 | |
| 		SetDimensions(w, h);
 | |
| 		m_Fractorium->m_WidthSpin->setValue(w);
 | |
| 		m_Fractorium->m_HeightSpin->setValue(h);
 | |
| 		//Start with a flock of 10 random embers. Can't do this until now because the window wasn't maximized yet, so the sizes would have been off.
 | |
| 		m_Fractorium->OnActionNewFlock(false);
 | |
| 		m_Fractorium->m_Controller->DelayedStartRenderTimer();
 | |
| 		m_Init = true;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /// <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()
 | |
| {
 | |
| 	GLint texWidth = 0, texHeight = 0;
 | |
| 	glEnable(GL_TEXTURE_2D);
 | |
| 	glEnable(GL_BLEND);
 | |
| 	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
 | |
| 	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.
 | |
| 
 | |
| 		//Only draw if the dimensions match exactly.
 | |
| 		if (m_TexWidth == width() && m_TexHeight == height() && ((m_TexWidth * m_TexHeight * 4) == 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_UNSIGNED_BYTE, 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_BLEND);
 | |
| 	glDisable(GL_TEXTURE_2D);
 | |
| }
 | |
| 
 | |
| /// <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 & et(eDragModifier::DragModAlt)) == et(eDragModifier::DragModAlt); }
 | |
| bool GLEmberControllerBase::GetShift()     { return (m_DragModifier & et(eDragModifier::DragModShift)) == et(eDragModifier::DragModShift); }
 | |
| bool GLEmberControllerBase::GetControl()   { return (m_DragModifier & et(eDragModifier::DragModControl)) == et(eDragModifier::DragModControl); }
 | |
| void GLEmberControllerBase::SetAlt()       { m_DragModifier |= et(eDragModifier::DragModAlt); }
 | |
| void GLEmberControllerBase::SetShift()     { m_DragModifier |= et(eDragModifier::DragModShift); }
 | |
| void GLEmberControllerBase::SetControl()   { m_DragModifier |= et(eDragModifier::DragModControl); }
 | |
| void GLEmberControllerBase::ClearAlt()     { m_DragModifier &= ~et(eDragModifier::DragModAlt); }
 | |
| void GLEmberControllerBase::ClearShift()   { m_DragModifier &= ~et(eDragModifier::DragModShift); }
 | |
| void GLEmberControllerBase::ClearControl() { m_DragModifier &= ~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()
 | |
| {
 | |
| 	auto ember = m_FractoriumEmberController->CurrentEmber();
 | |
| 	m_GL->makeCurrent();
 | |
| 	m_GL->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 | |
| 	m_GL->glClearColor(ember->m_Background.r, ember->m_Background.g, ember->m_Background.b, 1.0);
 | |
| }
 | |
| 
 | |
| /// <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 repainted, so there's no need to do it again.
 | |
| 	if (m_SelectedXform != xform || m_HoverXform != xform)
 | |
| 	{
 | |
| 		m_HoverXform = 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; }
 | |
| 
 | |
| /// <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()
 | |
| {
 | |
| 	if (!m_Init && initializeOpenGLFunctions() && m_Fractorium)
 | |
| 	{
 | |
| 		glClearColor(0.0, 0.0, 0.0, 1.0);
 | |
| 		glEnable(GL_TEXTURE_2D);
 | |
| 		glGetIntegerv(GL_MAX_TEXTURE_SIZE, &m_MaxTexSize);
 | |
| 		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 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())
 | |
| 	{
 | |
| 		auto renderer = controller->Renderer();
 | |
| 		m_Drawing = true;
 | |
| 		GLController()->DrawImage();
 | |
| 		//Affine drawing.
 | |
| 		bool pre = m_Fractorium->ui.PreAffineGroupBox->isChecked();
 | |
| 		bool post = m_Fractorium->ui.PostAffineGroupBox->isChecked();
 | |
| 		float unitX = std::abs(renderer->UpperRightX(false) - renderer->LowerLeftX(false)) / 2.0f;
 | |
| 		float unitY = std::abs(renderer->UpperRightY(false) - renderer->LowerLeftY(false)) / 2.0f;
 | |
| 		glEnable(GL_DEPTH_TEST);
 | |
| 		glEnable(GL_BLEND);
 | |
| 		glEnable(GL_LINE_SMOOTH);
 | |
| 		glEnable(GL_POINT_SMOOTH);
 | |
| 		glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);
 | |
| 		glMatrixMode(GL_PROJECTION);
 | |
| 		glPushMatrix();
 | |
| 		glLoadIdentity();
 | |
| 		glOrtho(-unitX, unitX, -unitY, unitY, -1, 1);//Projection matrix: OpenGL camera is always centered, just move the ember internally inside the renderer.
 | |
| 		glMatrixMode(GL_MODELVIEW);
 | |
| 		glPushMatrix();
 | |
| 		glLoadIdentity();
 | |
| 		glDisable(GL_DEPTH_TEST);
 | |
| 		controller->GLController()->DrawAffines(pre, post);
 | |
| 		glMatrixMode(GL_PROJECTION);
 | |
| 		glPopMatrix();
 | |
| 		glMatrixMode(GL_MODELVIEW);
 | |
| 		glPopMatrix();
 | |
| 		glDisable(GL_DEPTH_TEST);
 | |
| 		glDisable(GL_BLEND);
 | |
| 		glDisable(GL_LINE_SMOOTH);
 | |
| 		glDisable(GL_POINT_SMOOTH);
 | |
| 		m_Drawing = false;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /// <summary>
 | |
| /// Draw the image on the quad.
 | |
| /// </summary>
 | |
| template <typename T>
 | |
| void GLEmberController<T>::DrawImage()
 | |
| {
 | |
| 	auto renderer = m_FractoriumEmberController->Renderer();
 | |
| 	auto ember = m_FractoriumEmberController->CurrentEmber();
 | |
| 	m_GL->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 | |
| 	m_GL->glClearColor(ember->m_Background.r, ember->m_Background.g, ember->m_Background.b, 1.0);
 | |
| 	m_GL->glDisable(GL_DEPTH_TEST);
 | |
| 	renderer->EnterFinalAccum();//Lock, may not be necessary, but just in case.
 | |
| 	renderer->EnterResize();
 | |
| 
 | |
| 	if (SizesMatch())//Ensure all sizes are correct. If not, do nothing.
 | |
| 	{
 | |
| 		vector<byte>* finalImage = m_FractoriumEmberController->FinalImage();
 | |
| 
 | |
| 		if ((renderer->RendererType() == eRendererType::OPENCL_RENDERER) || finalImage)//Final image only matters for CPU renderer.
 | |
| 			if ((renderer->RendererType() == eRendererType::OPENCL_RENDERER) || finalImage->size() == renderer->FinalBufferSize())
 | |
| 				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.
 | |
| 	auto ember = m_FractoriumEmberController->CurrentEmber();
 | |
| 	bool dragging = m_DragState == eDragState::DragDragging;
 | |
| 
 | |
| 	//Draw grid if control key is pressed.
 | |
| 	if (m_GL->hasFocus() && GetControl())
 | |
| 	{
 | |
| 		m_GL->glLineWidth(1.0f);
 | |
| 		m_GL->DrawGrid(m_FractoriumEmberController->AffineScaleLockedToCurrent());
 | |
| 	}
 | |
| 
 | |
| 	//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);
 | |
| 	}
 | |
| 	else//Show all while dragging, or not dragging just hovering/mouse move.
 | |
| 	{
 | |
| 		if (pre && m_Fractorium->DrawAllPre())//Draw all pre affine if specified.
 | |
| 		{
 | |
| 			for (size_t i = 0; i < ember->TotalXformCount(); i++)
 | |
| 			{
 | |
| 				auto xform = ember->GetTotalXform(i);
 | |
| 				bool selected = m_Fractorium->IsXformSelected(i) || (dragging ? (m_SelectedXform == xform) : (m_HoverXform == xform));
 | |
| 				DrawAffine(xform, true, selected);
 | |
| 			}
 | |
| 		}
 | |
| 		else if (pre && m_HoverXform)//Only draw current pre affine.
 | |
| 		{
 | |
| 			DrawAffine(m_HoverXform, true, true);
 | |
| 		}
 | |
| 
 | |
| 		if (post && m_Fractorium->DrawAllPost())//Draw all post affine if specified.
 | |
| 		{
 | |
| 			for (size_t i = 0; i < ember->TotalXformCount(); i++)
 | |
| 			{
 | |
| 				auto xform = ember->GetTotalXform(i);
 | |
| 				bool selected = m_Fractorium->IsXformSelected(i) || (dragging ? (m_SelectedXform == xform) : (m_HoverXform == xform));
 | |
| 				DrawAffine(xform, false, selected);
 | |
| 			}
 | |
| 		}
 | |
| 		else if (post && m_HoverXform)//Only draw current post affine.
 | |
| 		{
 | |
| 			DrawAffine(m_HoverXform, false, true);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (dragging)//Draw large yellow dot on select or drag.
 | |
| 	{
 | |
| 		m_GL->glPointSize(6.0f);
 | |
| 		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();
 | |
| 		m_GL->glPointSize(1.0f);//Restore point size.
 | |
| 	}
 | |
| 	else if (m_HoverType != eHoverType::HoverNone && m_HoverXform == m_SelectedXform)//Draw large turquoise dot on hover if they are hovering over the selected xform.
 | |
| 	{
 | |
| 		m_GL->glPointSize(6.0f);
 | |
| 		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();
 | |
| 		m_GL->glPointSize(1.0f);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /// <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->devicePixelRatio(), m_Viewport[3] - e->y() * m_GL->devicePixelRatio(), 0);//Must flip y because in OpenGL, 0,0 is bottom left, but in windows, it's top left.
 | |
| 	auto ember = m_FractoriumEmberController->CurrentEmber();
 | |
| 	auto xforms = ember->TotalXformCount();
 | |
| 	auto renderer = m_FractoriumEmberController->Renderer();
 | |
| 	size_t i = 0;
 | |
| 
 | |
| 	//Ensure everything has been initialized.
 | |
| 	if (!renderer)
 | |
| 		return;
 | |
| 
 | |
| 	m_MouseDownPos = glm::ivec2(e->x() * m_GL->devicePixelRatio(), e->y() * m_GL->devicePixelRatio());//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);
 | |
| 	auto mod = e->modifiers();
 | |
| 
 | |
| 	if (mod.testFlag(Qt::ShiftModifier))
 | |
| 		SetShift();
 | |
| 
 | |
| 	//if (mod.testFlag(Qt::ControlModifier))// || mod.testFlag(Qt::Key_C))
 | |
| 	//	m_DragModifier |= DragModControl;
 | |
| 	if (mod.testFlag(Qt::AltModifier))// || mod.testFlag(Qt::Key_A))
 | |
| 		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)
 | |
| 		{
 | |
| 			int 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_DragSrcTransforms.clear();
 | |
| 				m_FractoriumEmberController->UpdateXform([&](Xform<T>* xform)
 | |
| 				{
 | |
| 					m_DragSrcTransforms.push_back(m_AffineType == eAffineType::AffinePre ? xform->m_Affine : xform->m_Post);
 | |
| 				}, eXformUpdate::UPDATE_CURRENT_AND_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_DragHandleOffset = m_DragHandlePos - m_MouseWorldPos;//The distance in world coordinates from the point selected to the center of the spinner.
 | |
| 				m_DragState = eDragState::DragDragging;
 | |
| 				//Draw large yellow dot on select or drag.
 | |
| 				m_GL->glPointSize(6.0f);
 | |
| 				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();
 | |
| 				m_GL->glPointSize(1.0f);//Restore point size.
 | |
| 				m_GL->repaint();
 | |
| 			}
 | |
| 			else//Nothing was selected.
 | |
| 			{
 | |
| 				//m_SelectedXform = nullptr;
 | |
| 				m_DragState = eDragState::DragNone;
 | |
| 			}
 | |
| 		}
 | |
| 		else if (e->button() == Qt::MiddleButton)//Middle button does 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.
 | |
| 		{
 | |
| 			UpdateHover(mouseFlipped);
 | |
| 			m_SelectedXform = m_HoverXform;
 | |
| 			m_CenterDownX = ember->m_CenterX;//Capture these because they will change when rotating and scaling.
 | |
| 			m_CenterDownY = ember->m_CenterY;
 | |
| 			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 (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->devicePixelRatio(), m_Viewport[3] - e->y() * m_GL->devicePixelRatio(), 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);
 | |
| 
 | |
| 	m_DragState = eDragState::DragNone;
 | |
| 	m_DragModifier = 0;
 | |
| 	m_GL->repaint();//Force immediate redraw.
 | |
| }
 | |
| 
 | |
| /// <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 (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;
 | |
| 	glm::ivec2 mouse(e->x() * m_GL->devicePixelRatio(), e->y() * m_GL->devicePixelRatio());
 | |
| 	v3T mouseFlipped(e->x() * m_GL->devicePixelRatio(), m_Viewport[3] - e->y() * m_GL->devicePixelRatio(), 0);//Must flip y because in OpenGL, 0,0 is bottom left, but in windows, it's top left.
 | |
| 	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->devicePixelRatio(), e->y() * m_GL->devicePixelRatio(), m_MouseWorldPos.x, m_MouseWorldPos.y);
 | |
| 
 | |
| 	if (m_SelectedXform && m_DragState == eDragState::DragDragging)//Dragging and affine.
 | |
| 	{
 | |
| 		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::DragPanning)//Translating the whole image.
 | |
| 	{
 | |
| 		T x = -(m_MouseWorldPos.x - m_MouseDownWorldPos.x);
 | |
| 		T 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);
 | |
| 		v2T v1(x, y);
 | |
| 		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.
 | |
| 	{
 | |
| 		T rot = CalcRotation();
 | |
| 		T scale = CalcScale();
 | |
| 		ember->m_Rotate = NormalizeDeg180<T>(m_RotationDown + rot);
 | |
| 		m_Fractorium->SetRotation(ember->m_Rotate, true);
 | |
| 		m_Fractorium->SetScale(m_ScaleDown + scale);//Will restart the rendering process.
 | |
| 	}
 | |
| 	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)
 | |
| 			draw = false;
 | |
| 	}
 | |
| 
 | |
| 	//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 (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.
 | |
| /// </summary>
 | |
| /// <param name="e">The event</param>
 | |
| template <typename T>
 | |
| void GLEmberController<T>::Wheel(QWheelEvent* e)
 | |
| {
 | |
| 	auto ember = m_FractoriumEmberController->CurrentEmber();
 | |
| 
 | |
| 	if (m_Fractorium && !(e->buttons() & Qt::MiddleButton))//Middle button does whole image translation, so ignore the mouse wheel while panning to avoid inadvertent zooming.
 | |
| 		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);
 | |
| 
 | |
| 	//Do not call QOpenGLWidget::wheelEvent(e) because this should only affect the scale and not the position of the scroll bars.
 | |
| }
 | |
| 
 | |
| /// <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)
 | |
| {
 | |
| 	setFixedSize(w, h);
 | |
| }
 | |
| 
 | |
| /// <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;
 | |
| 	bool doResize = force || m_TexWidth != width() || m_TexHeight != height();
 | |
| 	bool doIt = doResize || m_OutputTexID == 0;
 | |
| 
 | |
| 	if (doIt)
 | |
| 	{
 | |
| 		m_TexWidth = width();
 | |
| 		m_TexHeight = height();
 | |
| 		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);
 | |
| 		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_TexWidth, m_TexHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
 | |
| 		alloc = true;
 | |
| 	}
 | |
| 
 | |
| 	if (alloc)
 | |
| 	{
 | |
| 		glBindTexture(GL_TEXTURE_2D, 0);
 | |
| 		glDisable(GL_TEXTURE_2D);
 | |
| 	}
 | |
| 
 | |
| 	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)
 | |
| 	{
 | |
| 		glBindTexture(GL_TEXTURE_2D, m_OutputTexID);
 | |
| 		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))
 | |
| 	{
 | |
| 		glViewport(0, 0, GLint(m_TexWidth), GLint(m_TexHeight));
 | |
| 		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 ember = m_FractoriumEmberController->CurrentEmber();
 | |
| 	return (ember &&
 | |
| 			ember->m_FinalRasW == m_GL->width() &&
 | |
| 			ember->m_FinalRasH == m_GL->height() &&
 | |
| 			m_GL->width() == m_GL->m_TexWidth &&
 | |
| 			m_GL->height() == m_GL->m_TexHeight &&
 | |
| 			m_GL->m_TexWidth == m_GL->m_ViewWidth &&
 | |
| 			m_GL->m_TexHeight == m_GL->m_ViewHeight);
 | |
| }
 | |
| 
 | |
| /// <summary>
 | |
| /// Draw the grid in response to the control key being pressed.
 | |
| /// The frequency of the grid lines will change depending on the zoom.
 | |
| /// Calculated with the frame always centered, the renderer just moves the camera.
 | |
| /// </summary>
 | |
| /// <param name="scale">A value to scale by, used when locking the affine scale</param>
 | |
| void GLWidget::DrawGrid(double scale)
 | |
| {
 | |
| 	auto renderer = m_Fractorium->m_Controller->Renderer();
 | |
| 	float unitX = std::abs(renderer->UpperRightX(false) - renderer->LowerLeftX(false)) / 2.0f;
 | |
| 	float unitY = std::abs(renderer->UpperRightY(false) - renderer->LowerLeftY(false)) / 2.0f;
 | |
| 	float rad = std::max(unitX * scale, unitY * scale);
 | |
| 	float xLow =  floor(-unitX);
 | |
| 	float xHigh = ceil(unitX);
 | |
| 	float yLow =  floor(-unitY);
 | |
| 	float yHigh = ceil(unitY);
 | |
| 	glBegin(GL_LINES);
 | |
| 
 | |
| 	if (rad <= 8.0f)
 | |
| 	{
 | |
| 		glColor4f(0.5f, 0.5f, 0.5f, 0.5f);
 | |
| 
 | |
| 		for (float fx = xLow; fx <= xHigh; fx += GridStep)
 | |
| 		{
 | |
| 			glVertex2f(fx, yLow);
 | |
| 			glVertex2f(fx, yHigh);
 | |
| 		}
 | |
| 
 | |
| 		for (float fy = yLow; fy < yHigh; fy += GridStep)
 | |
| 		{
 | |
| 			glVertex2f(xLow,  fy);
 | |
| 			glVertex2f(xHigh, fy);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	unitX *= scale;
 | |
| 	unitY *= scale;
 | |
| 
 | |
| 	if (unitX <= 64.0f)
 | |
| 	{
 | |
| 		glColor4f(0.5f, 0.5f, 0.5f, 1.0f);
 | |
| 
 | |
| 		for (float fx = xLow; fx <= xHigh; fx += 1.0f)
 | |
| 		{
 | |
| 			glVertex2f(fx, yLow);
 | |
| 			glVertex2f(fx, yHigh);
 | |
| 		}
 | |
| 
 | |
| 		for (float fy = yLow; fy < yHigh; fy += 1.0f)
 | |
| 		{
 | |
| 			glVertex2f(xLow,  fy);
 | |
| 			glVertex2f(xHigh, fy);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	glColor4f(1.0f,   0.0f, 0.0f, 1.0f);
 | |
| 	glVertex2f(0.0f,  0.0f);
 | |
| 	glVertex2f(xHigh, 0.0f);
 | |
| 	glColor4f(0.5f,   0.0f, 0.0f, 1.0f);
 | |
| 	glVertex2f(0.0f,  0.0f);
 | |
| 	glVertex2f(xLow,  0.0f);
 | |
| 	glColor4f(0.0f,   1.0f, 0.0f, 1.0f);
 | |
| 	glVertex2f(0.0f,  0.0f);
 | |
| 	glVertex2f(0.0f,  yHigh);
 | |
| 	glColor4f(0.0f,   0.5f, 0.0f, 1.0f);
 | |
| 	glVertex2f(0.0f,  0.0f);
 | |
| 	glVertex2f(0.0f,  yLow);
 | |
| 	glEnd();
 | |
| }
 | |
| 
 | |
| /// <summary>
 | |
| /// Draw the unit square.
 | |
| /// </summary>
 | |
| void GLWidget::DrawUnitSquare()
 | |
| {
 | |
| 	glLineWidth(1.0f);
 | |
| 	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();
 | |
| }
 | |
| 
 | |
| /// <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>
 | |
| template <typename T>
 | |
| void GLEmberController<T>::DrawAffine(Xform<T>* xform, bool pre, bool selected)
 | |
| {
 | |
| 	auto ember = m_FractoriumEmberController->CurrentEmber();
 | |
| 	auto final = ember->IsFinalXform(xform);
 | |
| 	auto index = ember->GetXformIndex(xform);
 | |
| 	auto size = ember->m_Palette.m_Entries.size();
 | |
| 	auto color = ember->m_Palette.m_Entries[Clamp<T>(xform->m_ColorX * size, 0, size - 1)];
 | |
| 	auto affine = pre ? &xform->m_Affine : &xform->m_Post;
 | |
| 	//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);
 | |
| 	m_GL->glLineWidth(3.0f);//One 3px wide, colored black, except green on x axis for post affine.
 | |
| 	m_GL->DrawAffineHelper(index, selected, pre, final, true);
 | |
| 	m_GL->glLineWidth(1.0f);//Again 1px wide, colored white, to give a white middle with black outline effect.
 | |
| 	m_GL->DrawAffineHelper(index, selected, pre, final, false);
 | |
| 	m_GL->glPointSize(5.0f);//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);//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);//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->glEnd();
 | |
| 	m_GL->glPopMatrix();
 | |
| }
 | |
| 
 | |
| /// <summary>
 | |
| /// Draw the axes, and optionally the surrounding circle
 | |
| /// of an affine transform.
 | |
| /// </summary>
 | |
| /// <param name="index"></param>
 | |
| /// <param name="selected">True if selected (draw enclosing circle), else false (only draw axes).</param>
 | |
| /// <param name="pre"></param>
 | |
| /// <param name="final"></param>
 | |
| /// <param name="background"></param>
 | |
| void GLWidget::DrawAffineHelper(int index, bool selected, bool pre, bool final, bool background)
 | |
| {
 | |
| 	float px = 1.0f;
 | |
| 	float py = 0.0f;
 | |
| 	auto col = final ? m_Fractorium->m_FinalXformComboColor : m_Fractorium->m_XformComboColors[index % XFORM_COLOR_COUNT];
 | |
| 	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();
 | |
| }
 | |
| 
 | |
| /// <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(v3T& glCoords)
 | |
| {
 | |
| 	bool pre = m_Fractorium->ui.PreAffineGroupBox->isChecked();
 | |
| 	bool post = m_Fractorium->ui.PostAffineGroupBox->isChecked();
 | |
| 	bool preAll = pre && m_Fractorium->DrawAllPre();
 | |
| 	bool postAll = post && m_Fractorium->DrawAllPost();
 | |
| 	int bestIndex = -1;
 | |
| 	T bestDist = 10;
 | |
| 	auto ember = m_FractoriumEmberController->CurrentEmber();
 | |
| 	m_HoverType = eHoverType::HoverNone;
 | |
| 
 | |
| 	//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.
 | |
| 		bool checkSelPre = preAll || (pre && m_HoverXform == m_SelectedXform);
 | |
| 		bool checkSelPost = postAll || (post && m_HoverXform == m_SelectedXform);
 | |
| 
 | |
| 		if (CheckXformHover(m_SelectedXform, glCoords, bestDist, checkSelPre, checkSelPost))
 | |
| 		{
 | |
| 			m_HoverXform = m_SelectedXform;
 | |
| 			bestIndex = int(ember->GetTotalXformIndex(m_SelectedXform));
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	//Check all xforms.
 | |
| 	for (int i = 0; i < int(ember->TotalXformCount()); i++)
 | |
| 	{
 | |
| 		auto xform = ember->GetTotalXform(i);
 | |
| 
 | |
| 		if (preAll || (pre && m_HoverXform == xform))//Only check pre affine if they are shown.
 | |
| 		{
 | |
| 			if (CheckXformHover(xform, glCoords, bestDist, true, false))
 | |
| 			{
 | |
| 				m_HoverXform = xform;
 | |
| 				bestIndex = i;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if (postAll || (post && m_HoverXform == xform))//Only check post affine if they are shown.
 | |
| 		{
 | |
| 			if (CheckXformHover(xform, glCoords, bestDist, false, true))
 | |
| 			{
 | |
| 				m_HoverXform = xform;
 | |
| 				bestIndex = 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(Xform<T>* xform, v3T& glCoords, T& bestDist, bool pre, bool post)
 | |
| {
 | |
| 	bool preFound = false, postFound = false;
 | |
| 	T dist = 0, scale = m_FractoriumEmberController->AffineScaleCurrentToLocked();
 | |
| 	v3T pos;
 | |
| 
 | |
| 	if (pre)
 | |
| 	{
 | |
| 		auto affineScaled = xform->m_Affine * scale;
 | |
| 		v3T translation(affineScaled.C(), affineScaled.F(), 0);
 | |
| 		v3T transScreen = glm::project(translation, m_Modelview, m_Projection, m_Viewport);
 | |
| 		v3T xAxis(affineScaled.A(), affineScaled.D(), 0);
 | |
| 		v3T xAxisScreen = glm::project(translation + xAxis, m_Modelview, m_Projection, m_Viewport);
 | |
| 		v3T yAxis(affineScaled.B(), affineScaled.E(), 0);
 | |
| 		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)
 | |
| 	{
 | |
| 		auto affineScaled = xform->m_Post * scale;
 | |
| 		v3T translation(affineScaled.C(), affineScaled.F(), 0);
 | |
| 		v3T transScreen = glm::project(translation, m_Modelview, m_Projection, m_Viewport);
 | |
| 		v3T xAxis(affineScaled.A(), affineScaled.D(), 0);
 | |
| 		v3T xAxisScreen = glm::project(translation + xAxis, m_Modelview, m_Projection, m_Viewport);
 | |
| 		v3T yAxis(affineScaled.B(), affineScaled.E(), 0);
 | |
| 		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 and scale only.
 | |
| /// Local Pivot:
 | |
| ///		Shift: Rotate only about affine center.
 | |
| ///		Alt: Free transform.
 | |
| ///		Shift + Alt: Rotate single axis about affine center.
 | |
| ///		Control: Rotate and scale, snapping to grid.
 | |
| ///		Control + Shift: Rotate only, snapping to grid.
 | |
| ///		Control + Alt: Free transform, snapping to grid.
 | |
| ///		Control + Shift + Alt: Rotate single axis about affine center, 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()
 | |
| {
 | |
| 	size_t index = 0;
 | |
| 	auto scale = m_FractoriumEmberController->AffineScaleLockedToCurrent();
 | |
| 	auto scaleBack = m_FractoriumEmberController->AffineScaleCurrentToLocked();
 | |
| 	bool pre = m_AffineType == eAffineType::AffinePre;
 | |
| 	bool worldPivotShiftAlt = !m_Fractorium->LocalPivot() && GetShift() && GetAlt();
 | |
| 
 | |
| 	if (GetShift())
 | |
| 	{
 | |
| 		auto posOffset = m_MouseWorldPos + m_DragHandleOffset;
 | |
| 		v3T snapped = GetControl() ? SnapToNormalizedAngle(posOffset, 24u) : posOffset;
 | |
| 		auto startDiff = (v2T(m_MouseDownWorldPos) * scale) - m_DragSrcTransform.O();
 | |
| 		auto endDiff = (v2T(snapped) * scale) - m_DragSrcTransform.O();
 | |
| 		T startAngle = std::atan2(startDiff.y, startDiff.x);
 | |
| 		T endAngle = std::atan2(endDiff.y, endDiff.x);
 | |
| 		T angle = startAngle - endAngle;
 | |
| 		m_FractoriumEmberController->UpdateXform([&](Xform<T>* xform)
 | |
| 		{
 | |
| 			auto affine = pre ? &xform->m_Affine : &xform->m_Post;
 | |
| 			auto srcRotated = m_DragSrcTransforms[index++];
 | |
| 
 | |
| 			if (worldPivotShiftAlt)
 | |
| 			{
 | |
| 				srcRotated.X(srcRotated.O() + srcRotated.X());
 | |
| 				srcRotated.O(v2T(0));
 | |
| 				srcRotated.Rotate(angle);
 | |
| 				affine->X(srcRotated.X() - affine->O());
 | |
| 			}
 | |
| 			else if (GetAlt())
 | |
| 			{
 | |
| 				srcRotated.Rotate(angle);
 | |
| 				affine->X(srcRotated.X());
 | |
| 			}
 | |
| 			else
 | |
| 			{
 | |
| 				srcRotated.Rotate(angle);
 | |
| 				*affine = srcRotated;
 | |
| 			}
 | |
| 
 | |
| 			if (xform == m_FractoriumEmberController->CurrentXform())
 | |
| 				m_DragHandlePos = v3T((affine->O() + affine->X()) * scaleBack, 0);
 | |
| 		}, eXformUpdate::UPDATE_CURRENT_AND_SELECTED, false);//Calling code will update renderer.
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		v3T diff;
 | |
| 		auto posOffset = m_MouseWorldPos + m_DragHandleOffset;
 | |
| 
 | |
| 		if (GetControl())
 | |
| 			diff = SnapToGrid(posOffset) - m_MouseDownWorldPos;
 | |
| 		else
 | |
| 			diff = posOffset - m_MouseDownWorldPos;
 | |
| 
 | |
| 		auto origXPlusOff = v3T(m_DragSrcTransform.X(), 0) + (diff * scale);
 | |
| 		m_FractoriumEmberController->UpdateXform([&](Xform<T>* xform)
 | |
| 		{
 | |
| 			auto affine = pre ? &xform->m_Affine : &xform->m_Post;
 | |
| 			auto axis = v3T(m_DragSrcTransforms[index++].X(), 0) + (diff * scale);
 | |
| 
 | |
| 			if (GetAlt())
 | |
| 				affine->X(v2T(origXPlusOff));//Absolute, not ratio.
 | |
| 			else
 | |
| 				affine->RotateScaleXTo(v2T(axis));
 | |
| 
 | |
| 			if (xform == m_FractoriumEmberController->CurrentXform())
 | |
| 				m_DragHandlePos = v3T((affine->O() + affine->X()) * scaleBack, 0);
 | |
| 		}, eXformUpdate::UPDATE_CURRENT_AND_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 and scale only.
 | |
| /// Local Pivot:
 | |
| ///		Shift: Rotate only about affine center.
 | |
| ///		Alt: Free transform.
 | |
| ///		Shift + Alt: Rotate single axis about affine center.
 | |
| ///		Control: Rotate and scale, snapping to grid.
 | |
| ///		Control + Shift: Rotate only, snapping to grid.
 | |
| ///		Control + Alt: Free transform, snapping to grid.
 | |
| ///		Control + Shift + Alt: Rotate single axis about affine center, 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()
 | |
| {
 | |
| 	size_t index = 0;
 | |
| 	auto scale = m_FractoriumEmberController->AffineScaleLockedToCurrent();
 | |
| 	auto scaleBack = m_FractoriumEmberController->AffineScaleCurrentToLocked();
 | |
| 	bool pre = m_AffineType == eAffineType::AffinePre;
 | |
| 	bool worldPivotShiftAlt = !m_Fractorium->LocalPivot() && GetShift() && GetAlt();
 | |
| 
 | |
| 	if (GetShift())
 | |
| 	{
 | |
| 		auto posOffset = m_MouseWorldPos + m_DragHandleOffset;
 | |
| 		v3T snapped = GetControl() ? SnapToNormalizedAngle(posOffset, 24u) : posOffset;
 | |
| 		auto startDiff = (v2T(m_MouseDownWorldPos) * scale) - m_DragSrcTransform.O();
 | |
| 		auto endDiff = (v2T(snapped) * scale) - m_DragSrcTransform.O();
 | |
| 		T startAngle = std::atan2(startDiff.y, startDiff.x);
 | |
| 		T endAngle = std::atan2(endDiff.y, endDiff.x);
 | |
| 		T angle = startAngle - endAngle;
 | |
| 		m_FractoriumEmberController->UpdateXform([&](Xform<T>* xform)
 | |
| 		{
 | |
| 			auto affine = pre ? &xform->m_Affine : &xform->m_Post;
 | |
| 			auto srcRotated = m_DragSrcTransforms[index++];
 | |
| 
 | |
| 			if (worldPivotShiftAlt)
 | |
| 			{
 | |
| 				srcRotated.Y(srcRotated.O() + srcRotated.Y());
 | |
| 				srcRotated.O(v2T(0));
 | |
| 				srcRotated.Rotate(angle);
 | |
| 				affine->Y(srcRotated.Y() - affine->O());
 | |
| 			}
 | |
| 			else if (GetAlt())
 | |
| 			{
 | |
| 				srcRotated.Rotate(angle);
 | |
| 				affine->Y(srcRotated.Y());
 | |
| 			}
 | |
| 			else
 | |
| 			{
 | |
| 				srcRotated.Rotate(angle);
 | |
| 				*affine = srcRotated;
 | |
| 			}
 | |
| 
 | |
| 			if (xform == m_FractoriumEmberController->CurrentXform())
 | |
| 				m_DragHandlePos = v3T((affine->O() + affine->Y()) * scaleBack, 0);
 | |
| 		}, eXformUpdate::UPDATE_CURRENT_AND_SELECTED, false);//Calling code will update renderer.
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		v3T diff;
 | |
| 		auto posOffset = m_MouseWorldPos + m_DragHandleOffset;
 | |
| 
 | |
| 		if (GetControl())
 | |
| 			diff = SnapToGrid(posOffset) - m_MouseDownWorldPos;
 | |
| 		else
 | |
| 			diff = posOffset - m_MouseDownWorldPos;
 | |
| 
 | |
| 		auto origXPlusOff = v3T(m_DragSrcTransform.Y(), 0) + (diff * scale);
 | |
| 		m_FractoriumEmberController->UpdateXform([&](Xform<T>* xform)
 | |
| 		{
 | |
| 			auto affine = pre ? &xform->m_Affine : &xform->m_Post;
 | |
| 			auto axis = v3T(m_DragSrcTransforms[index++].Y(), 0) + (diff * scale);
 | |
| 
 | |
| 			if (GetAlt())
 | |
| 				affine->Y(v2T(origXPlusOff));//Absolute, not ratio.
 | |
| 			else
 | |
| 				affine->RotateScaleYTo(v2T(axis));
 | |
| 
 | |
| 			if (xform == m_FractoriumEmberController->CurrentXform())
 | |
| 				m_DragHandlePos = v3T((affine->O() + affine->Y()) * scaleBack, 0);
 | |
| 		}, eXformUpdate::UPDATE_CURRENT_AND_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()
 | |
| {
 | |
| 	size_t index = 0;
 | |
| 	auto scale = m_FractoriumEmberController->AffineScaleLockedToCurrent();
 | |
| 	auto scaleBack = m_FractoriumEmberController->AffineScaleCurrentToLocked();
 | |
| 	bool worldPivotShift = !m_Fractorium->LocalPivot() && GetShift();
 | |
| 	bool pre = m_AffineType == eAffineType::AffinePre;
 | |
| 
 | |
| 	if (GetShift())
 | |
| 	{
 | |
| 		v3T snapped = GetControl() ? SnapToNormalizedAngle(m_MouseWorldPos, 24) : m_MouseWorldPos;
 | |
| 		T startAngle = std::atan2(m_DragSrcTransform.O().y, m_DragSrcTransform.O().x);
 | |
| 		T endAngle = std::atan2(snapped.y, snapped.x);
 | |
| 		T angle = startAngle - endAngle;
 | |
| 		m_FractoriumEmberController->UpdateXform([&](Xform<T>* xform)
 | |
| 		{
 | |
| 			auto affine = pre ? &xform->m_Affine : &xform->m_Post;
 | |
| 			auto srcRotated = m_DragSrcTransforms[index++];
 | |
| 			srcRotated.RotateTrans(angle);
 | |
| 
 | |
| 			if (worldPivotShift)
 | |
| 			{
 | |
| 				srcRotated.Rotate(angle);
 | |
| 				affine->X(srcRotated.X());
 | |
| 				affine->Y(srcRotated.Y());
 | |
| 			}
 | |
| 
 | |
| 			affine->O(srcRotated.O());
 | |
| 
 | |
| 			if (xform == m_FractoriumEmberController->CurrentXform())
 | |
| 				m_DragHandlePos = v3T(srcRotated.O(), 0) * scaleBack;
 | |
| 		}, eXformUpdate::UPDATE_CURRENT_AND_SELECTED, false);//Calling code will update renderer.
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		auto diff = m_MouseWorldPos - m_MouseDownWorldPos;
 | |
| 
 | |
| 		if (GetControl())
 | |
| 		{
 | |
| 			m_FractoriumEmberController->UpdateXform([&](Xform<T>* xform)
 | |
| 			{
 | |
| 				auto affine = pre ? &xform->m_Affine : &xform->m_Post;
 | |
| 				auto offset = m_DragSrcTransforms[index++].O() + (scale * v2T(diff));
 | |
| 				auto snapped = SnapToGrid(offset);
 | |
| 				affine->O(v2T(snapped.x, snapped.y));
 | |
| 			}, eXformUpdate::UPDATE_CURRENT_AND_SELECTED, false);
 | |
| 			m_DragHandlePos = SnapToGrid(m_MouseWorldPos);
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			m_FractoriumEmberController->UpdateXform([&](Xform<T>* xform)
 | |
| 			{
 | |
| 				auto affine = pre ? &xform->m_Affine : &xform->m_Post;
 | |
| 				affine->O(m_DragSrcTransforms[index++].O() + (scale * v2T(diff)));
 | |
| 			}, eXformUpdate::UPDATE_CURRENT_AND_SELECTED, false);
 | |
| 			m_DragHandlePos = m_MouseWorldPos;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /// <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
 |