mirror of
https://bitbucket.org/mfeemster/fractorium.git
synced 2025-01-21 21:20:07 -05:00
3b261124b2
-Optimization and correction for hexaplay3D and hexnix3D. -Major optimization on the GPU for flames which only have one xform, by skipping all random xform selection code. -Changes to how xaos is "preserved" when adding new xforms, copying xforms and duplicating xforms. --Duplicating xforms when no xaos is present in the flame now maintains not using xaos, and keeps all values as one. --Duplicating xforms when xaos is present, will result in xaos rows and columns that are the same as the xforms being duplicated, with the new row and column area having values of 1. --Duplicating xforms when xaos is present, while Control is pressed, will result in xaos rows and columns that have values of 0, with the new row and column area having values of 1. ---Copying xforms has the same behavior as duplicating with Control pressed. --Bug fixes -hexaplay3D, hexnix3D and post_smartcrop were wrong on the GPU because they are the rare variations which preserve state between iterations. -Changing the sub batch size would improperly wrong the wrong number of iterations. --Code changes -Some functions in Affine2D made const. -Change in the index at which points and variation state are preserved between kernel calls. -Some arguments in some member functions of GLEmberController made const.
2427 lines
81 KiB
C++
2427 lines
81 KiB
C++
#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();
|
|
int w = std::ceil(m_Fractorium->ui.GLParentScrollArea->width() * devicePixelRatioF());
|
|
int 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();
|
|
QDir dir(path);
|
|
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) == 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);
|
|
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.
|
|
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())
|
|
{
|
|
//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())
|
|
{
|
|
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) == 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 & 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->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();
|
|
*/
|
|
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.
|
|
{
|
|
auto renderer = 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;
|
|
|
|
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.
|
|
bool pre = m_Fractorium->ui.PreAffineGroupBox->isChecked();
|
|
bool post = m_Fractorium->ui.PostAffineGroupBox->isChecked();
|
|
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()
|
|
{
|
|
auto renderer = m_FractoriumEmberController->Renderer();
|
|
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;
|
|
|
|
auto ember = m_FractoriumEmberController->CurrentEmber();
|
|
bool dragging = m_DragState == eDragState::DragDragging;
|
|
bool forceFinal = m_Fractorium->HaveFinal();
|
|
|
|
if (m_DragState == eDragState::DragRotateScale)
|
|
{
|
|
auto dprf = m_GL->devicePixelRatioF();
|
|
auto world = ScrolledCenter(true);
|
|
m_GL->glLineWidth(1.0f * dprf);
|
|
GLfloat vertices[] =
|
|
{
|
|
GLfloat(m_MouseWorldPos.x), GLfloat(m_MouseWorldPos.y),//Mouse position while dragging with right button down...
|
|
GLfloat(world.x), GLfloat(world.y)//...to center.
|
|
};
|
|
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 (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 (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 (auto xform = ember->GetTotalXform(i, forceFinal))
|
|
if (m_Fractorium->IsXformSelected(i++))
|
|
{
|
|
any = true;
|
|
break;
|
|
}
|
|
|
|
i = 0;
|
|
|
|
while (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 (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
|
|
GLfloat vertices[] =//Should these be of type T?//TODO
|
|
{
|
|
GLfloat(m_DragHandlePos.x), GLfloat(m_DragHandlePos.y)
|
|
};
|
|
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
|
|
GLfloat vertices[] =//Should these be of type T?//TODO
|
|
{
|
|
GLfloat(m_MouseDownWorldPos.x), GLfloat(m_MouseDownWorldPos.y),//UL->UR
|
|
GLfloat(m_MouseWorldPos.x ), GLfloat(m_MouseDownWorldPos.y),
|
|
GLfloat(m_MouseDownWorldPos.x), GLfloat(m_MouseWorldPos.y),//LL->LR
|
|
GLfloat(m_MouseWorldPos.x ), GLfloat(m_MouseWorldPos.y),
|
|
GLfloat(m_MouseDownWorldPos.x), GLfloat(m_MouseDownWorldPos.y),//UL->LL
|
|
GLfloat(m_MouseDownWorldPos.x), GLfloat(m_MouseWorldPos.y),
|
|
GLfloat(m_MouseWorldPos.x ), GLfloat(m_MouseDownWorldPos.y),//UR->LR
|
|
GLfloat(m_MouseWorldPos.x ), GLfloat(m_MouseWorldPos.y)
|
|
};
|
|
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
|
|
GLfloat vertices[] =//Should these be of type T?//TODO
|
|
{
|
|
GLfloat(m_HoverHandlePos.x), GLfloat(m_HoverHandlePos.y)
|
|
};
|
|
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.
|
|
auto ember = m_FractoriumEmberController->CurrentEmber();
|
|
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);
|
|
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)
|
|
{
|
|
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_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)//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.
|
|
{
|
|
if (m_Fractorium->DrawImage())
|
|
{
|
|
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->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 (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->devicePixelRatioF(), e->y() * m_GL->devicePixelRatioF());
|
|
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.
|
|
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.
|
|
{
|
|
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.
|
|
QPointF tl(m_MouseDownWorldPos.x, m_MouseDownWorldPos.y);
|
|
QPointF br(m_MouseWorldPos.x, m_MouseWorldPos.y);
|
|
QRectF qrf(tl, br);
|
|
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.
|
|
{
|
|
QPointF cd(xform->m_Affine.C() * scale, xform->m_Affine.F() * scale);
|
|
bool b = qrf.contains(cd);
|
|
m_FractoriumEmberController->XformCheckboxAt(int(xfindex), [&](QCheckBox * cb)
|
|
{
|
|
cb->setChecked(b);
|
|
});
|
|
}
|
|
|
|
if (m_Fractorium->DrawAllPost() || xform == m_SelectedXform)
|
|
{
|
|
QPointF cd(xform->m_Post.C() * scale, xform->m_Post.F() * scale);
|
|
bool b = qrf.contains(cd);
|
|
m_FractoriumEmberController->XformCheckboxAt(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.
|
|
{
|
|
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(std::max(T(10), 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)
|
|
{
|
|
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 (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)
|
|
{
|
|
auto downscaledW = std::ceil(w / devicePixelRatioF());
|
|
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 = GLint(w);
|
|
m_TexHeight = 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 = GLint(w);
|
|
m_TexHeight = 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, GLint(m_TexWidth), 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());
|
|
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);
|
|
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);
|
|
GLfloat vertices3[] =//Should these be of type T?//TODO
|
|
{
|
|
0, -1,
|
|
0, 1
|
|
};
|
|
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()
|
|
{
|
|
auto renderer = m_Fractorium->m_Controller->Renderer();
|
|
double scale = m_FractoriumEmberController->AffineScaleCurrentToLocked();
|
|
//qDebug() << renderer->UpperRightX(false) << " " << renderer->LowerLeftX(false) << " " << renderer->UpperRightY(false) << " " << renderer->LowerLeftY(false);
|
|
float unitX = (std::abs(renderer->UpperRightX(false) - renderer->LowerLeftX(false)) / 2.0f) / scale;
|
|
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;
|
|
}
|
|
|
|
float xLow = std::floor(-unitX);
|
|
float xHigh = std::ceil(unitX);
|
|
float yLow = std::floor(-unitY);
|
|
float yHigh = std::ceil(unitY);
|
|
float alpha = 0.25f;
|
|
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.
|
|
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
|
|
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)
|
|
{
|
|
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;
|
|
#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
|
|
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;
|
|
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.
|
|
{
|
|
float theta = float(M_PI) * 2.0f * float(i % 64) / 64.0f;
|
|
float fx = std::cos(theta);
|
|
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)
|
|
{
|
|
bool pre = m_Fractorium->ui.PreAffineGroupBox->isChecked();
|
|
bool post = m_Fractorium->ui.PostAffineGroupBox->isChecked();
|
|
int i = 0, bestIndex = -1;
|
|
T bestDist = 10;
|
|
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.
|
|
{
|
|
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.
|
|
bool isSel = m_Fractorium->IsXformSelected(ember->GetTotalXformIndex(m_SelectedXform));
|
|
bool checkPre = pre && (m_Fractorium->DrawAllPre() || (m_Fractorium->DrawSelectedPre() && isSel));
|
|
bool checkPost = post && (m_Fractorium->DrawAllPost() || (m_Fractorium->DrawSelectedPost() && isSel));
|
|
|
|
if (CheckXformHover(m_SelectedXform, glCoords, bestDist, checkPre, checkPost))
|
|
{
|
|
m_HoverXform = m_SelectedXform;
|
|
bestIndex = int(ember->GetTotalXformIndex(m_SelectedXform, forceFinal));
|
|
}
|
|
}
|
|
|
|
//Check all xforms.
|
|
while (auto xform = ember->GetTotalXform(i, forceFinal))
|
|
{
|
|
bool isSel = m_Fractorium->IsXformSelected(i);
|
|
|
|
if (pre)
|
|
{
|
|
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)
|
|
{
|
|
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, 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 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()
|
|
{
|
|
T affineToWorldScale = T(m_FractoriumEmberController->AffineScaleLockedToCurrent());
|
|
T worldToAffineScale = T(m_FractoriumEmberController->AffineScaleCurrentToLocked());
|
|
bool pre = m_AffineType == eAffineType::AffinePre;
|
|
bool worldPivotShiftAlt = !m_Fractorium->LocalPivot() && GetShift() && GetAlt();
|
|
auto worldRelAxisStartScaled = (v2T(m_HoverHandlePos) * affineToWorldScale) - m_DragSrcTransform.O();//World axis start position, relative, scaled by the zoom.
|
|
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.
|
|
}
|
|
|
|
T endAngle = std::atan2(relScaled.y, relScaled.x);
|
|
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;
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
auto newmag = glm::length(relScaled);
|
|
auto newprc = newmag / origmag;
|
|
T endAngle = std::atan2(relScaled.y, relScaled.x);
|
|
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;
|
|
}
|
|
}
|
|
|
|
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()
|
|
{
|
|
T affineToWorldScale = T(m_FractoriumEmberController->AffineScaleLockedToCurrent());
|
|
T worldToAffineScale = T(m_FractoriumEmberController->AffineScaleCurrentToLocked());
|
|
bool pre = m_AffineType == eAffineType::AffinePre;
|
|
bool worldPivotShiftAlt = !m_Fractorium->LocalPivot() && GetShift() && GetAlt();
|
|
auto worldRelAxisStartScaled = (v2T(m_HoverHandlePos) * affineToWorldScale) - m_DragSrcTransform.O();//World axis start position, relative, scaled by the zoom.
|
|
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.
|
|
}
|
|
|
|
T endAngle = std::atan2(relScaled.y, relScaled.x);
|
|
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;
|
|
}
|
|
}
|
|
|
|
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
|
|
{
|
|
auto origmag = Zeps(glm::length(m_DragSrcTransform.Y()));//Magnitude of original dragged axis before it was dragged.
|
|
|
|
if (GetControl())
|
|
{
|
|
relScaled = SnapToGrid(relScaled);
|
|
}
|
|
|
|
auto newmag = glm::length(relScaled);
|
|
auto newprc = newmag / origmag;
|
|
T endAngle = std::atan2(relScaled.y, relScaled.x);
|
|
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;
|
|
}
|
|
}
|
|
|
|
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()
|
|
{
|
|
T affineToWorldScale = T(m_FractoriumEmberController->AffineScaleLockedToCurrent());
|
|
T worldToAffineScale = T(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, 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());
|
|
}
|
|
|
|
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
|
|
{
|
|
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())
|
|
{
|
|
auto& src = it->second;
|
|
auto& affine = xform->m_Affine;
|
|
auto offset = src.O() + (affineToWorldScale * v2T(diff));
|
|
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
|