mirror of
https://bitbucket.org/mfeemster/fractorium.git
synced 2025-01-21 05:00:06 -05:00
2479 lines
86 KiB
C++
2479 lines
86 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();
|
|
const auto w = std::ceil(m_Fractorium->ui.GLParentScrollArea->width() * devicePixelRatioF());
|
|
const auto h = std::ceil(m_Fractorium->ui.GLParentScrollArea->height() * devicePixelRatioF());
|
|
SetDimensions(w, h);
|
|
//Start with either a flock of random embers, or the last flame from the previous run.
|
|
//Can't do this until now because the window wasn't maximized yet, so the sizes would have been off.
|
|
bool b = m_Fractorium->m_Settings->LoadLast();
|
|
|
|
if (b)
|
|
{
|
|
auto path = GetDefaultUserPath();
|
|
const QDir dir(path);
|
|
const QString filename = path + "/lastonshutdown.flame";
|
|
|
|
if (dir.exists(filename))
|
|
{
|
|
QStringList ql;
|
|
ql << filename;
|
|
m_Fractorium->m_Controller->OpenAndPrepFiles(ql, false);
|
|
}
|
|
else
|
|
b = false;
|
|
}
|
|
|
|
if (!b)
|
|
{
|
|
m_Fractorium->m_WidthSpin->setValue(w);
|
|
m_Fractorium->m_HeightSpin->setValue(h);
|
|
m_Fractorium->OnActionNewFlock(false);//This must come after the previous two lines because it uses the values of the spinners.
|
|
}
|
|
|
|
m_Fractorium->m_Controller->DelayedStartRenderTimer();
|
|
m_Init = true;
|
|
/*
|
|
auto clinfo = OpenCLInfo::DefInstance();
|
|
auto& platforms = clinfo->Platforms();
|
|
auto& alldevices = clinfo->Devices();
|
|
std::vector<std::string> strs;
|
|
auto cdc = wglGetCurrentDC();
|
|
auto cc = wglGetCurrentContext();
|
|
ostringstream os;
|
|
strs.push_back(os.str()); os.str(""); os << "GLWidget::InitGL():";
|
|
strs.push_back(os.str()); os.str(""); os << "\nCurrent DC: " << cdc;
|
|
strs.push_back(os.str()); os.str(""); os << "\nCurrent Context: " << cc;
|
|
|
|
for (int platform = 0; platform < platforms.size(); platform++)
|
|
{
|
|
cl_context_properties props[] =
|
|
{
|
|
CL_GL_CONTEXT_KHR, (cl_context_properties)wglGetCurrentContext(),
|
|
CL_WGL_HDC_KHR, (cl_context_properties)wglGetCurrentDC(),
|
|
CL_CONTEXT_PLATFORM, reinterpret_cast<cl_context_properties>((platforms[platform])()),
|
|
0
|
|
};
|
|
// Find CL capable devices in the current GL context
|
|
//wglMakeCurrent(wglGetCurrentDC(), wglGetCurrentContext());
|
|
::wglMakeCurrent(wglGetCurrentDC(), wglGetCurrentContext());
|
|
size_t sizedev;
|
|
cl_device_id devices[32];
|
|
clGetGLContextInfoKHR_fn clGetGLContextInfo = (clGetGLContextInfoKHR_fn)clGetExtensionFunctionAddressForPlatform(platforms[platform](), "clGetGLContextInfoKHR");
|
|
clGetGLContextInfo(props, CL_DEVICES_FOR_GL_CONTEXT_KHR, 32 * sizeof(cl_device_id), devices, &sizedev);
|
|
sizedev = (cl_uint)(sizedev / sizeof(cl_device_id));
|
|
|
|
for (int i = 0; i < sizedev; i++)
|
|
{
|
|
std::string s;
|
|
size_t pi, di;
|
|
auto dd = clinfo->DeviceFromId(devices[i], pi, di);
|
|
|
|
if (dd)
|
|
{
|
|
auto& dev = *dd;
|
|
auto& plat = platforms[pi];
|
|
strs.push_back(os.str()); os.str(""); os << "\nPlatform[" << pi << "], device[" << di << "] is GL capable.";
|
|
strs.push_back(os.str()); os.str(""); os << "\nPlatform profile: " << plat.getInfo<CL_PLATFORM_PROFILE>(nullptr).c_str() << endl;
|
|
strs.push_back(os.str()); os.str(""); os << "\nPlatform version: " << plat.getInfo<CL_PLATFORM_VERSION>(nullptr).c_str() << endl;
|
|
strs.push_back(os.str()); os.str(""); os << "\nPlatform name: " << plat.getInfo<CL_PLATFORM_NAME>(nullptr).c_str() << endl;
|
|
strs.push_back(os.str()); os.str(""); os << "\nPlatform vendor: " << plat.getInfo<CL_PLATFORM_VENDOR>(nullptr).c_str() << endl;
|
|
strs.push_back(os.str()); os.str(""); os << "\nPlatform extensions: " << plat.getInfo<CL_PLATFORM_EXTENSIONS>(nullptr).c_str() << endl;
|
|
strs.push_back(os.str()); os.str(""); os << "\nVendor: " << dev.getInfo<CL_DEVICE_VENDOR>(nullptr).c_str() << endl;
|
|
strs.push_back(os.str()); os.str(""); os << "\nDevice: " << dev.getInfo<CL_DEVICE_NAME>(nullptr).c_str() << endl;
|
|
strs.push_back(os.str()); os.str(""); os << "\nDriver version: " << dev.getInfo<CL_DRIVER_VERSION>(nullptr).c_str() << endl;
|
|
strs.push_back(os.str()); os.str(""); os << "\nDevice profile: " << dev.getInfo<CL_DEVICE_PROFILE>(nullptr).c_str() << endl;
|
|
strs.push_back(os.str()); os.str(""); os << "\nDevice version: " << dev.getInfo<CL_DEVICE_VERSION>(nullptr).c_str() << endl;
|
|
strs.push_back(os.str()); os.str(""); os << "\nDevice extensions: " << dev.getInfo<CL_DEVICE_EXTENSIONS>(nullptr).c_str() << endl;
|
|
strs.push_back(os.str()); os.str(""); os << "\nDevice OpenCL C version: " << dev.getInfo<CL_DEVICE_OPENCL_C_VERSION>(nullptr).c_str() << endl;
|
|
}
|
|
}
|
|
}
|
|
|
|
m_Fractorium->ErrorReportToQTextEdit(strs, m_Fractorium->ui.InfoRenderingTextEdit);
|
|
*/
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draw the final rendered image as a texture on a quad that is the same size as the window.
|
|
/// Different action is taken based on whether a CPU or OpenCL renderer is used.
|
|
/// For CPU, the output image buffer must be copied to OpenGL every time it's drawn.
|
|
/// For OpenCL, the output image and the texture are the same thing, so no copying is necessary
|
|
/// and all image memory remains on the card.
|
|
/// </summary>
|
|
void GLWidget::DrawQuad()
|
|
{
|
|
#ifndef USE_GLSL
|
|
glEnable(GL_TEXTURE_2D);
|
|
auto renderer = m_Fractorium->m_Controller->Renderer();
|
|
auto finalImage = m_Fractorium->m_Controller->FinalImage();
|
|
|
|
//Ensure all allocation has taken place first.
|
|
if (m_OutputTexID != 0 && finalImage && !finalImage->empty())
|
|
{
|
|
glBindTexture(GL_TEXTURE_2D, m_OutputTexID);//The texture to draw to.
|
|
auto scaledW = std::ceil(width() * devicePixelRatioF());
|
|
auto scaledH = std::ceil(height() * devicePixelRatioF());
|
|
|
|
//Only draw if the dimensions match exactly.
|
|
if (m_TexWidth == m_Fractorium->m_Controller->FinalRasW() &&
|
|
m_TexHeight == m_Fractorium->m_Controller->FinalRasH() &&
|
|
((m_TexWidth * m_TexHeight) == static_cast<GLint>(finalImage->size())))
|
|
{
|
|
glMatrixMode(GL_PROJECTION);
|
|
glPushMatrix();
|
|
glLoadIdentity();
|
|
glOrtho(0, 1, 1, 0, -1, 1);
|
|
glMatrixMode(GL_MODELVIEW);
|
|
glPushMatrix();
|
|
glLoadIdentity();
|
|
|
|
//Copy data from CPU to OpenGL if using a CPU renderer. This is not needed when using OpenCL.
|
|
if (renderer->RendererType() == eRendererType::CPU_RENDERER)
|
|
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_TexWidth, m_TexHeight, GL_RGBA, GL_FLOAT, finalImage->data());
|
|
|
|
glBegin(GL_QUADS);//This will need to be converted to a shader at some point in the future.
|
|
glTexCoord2f(0.0, 0.0); glVertex2f(0.0, 0.0);
|
|
glTexCoord2f(0.0, 1.0); glVertex2f(0.0, 1.0);
|
|
glTexCoord2f(1.0, 1.0); glVertex2f(1.0, 1.0);
|
|
glTexCoord2f(1.0, 0.0); glVertex2f(1.0, 0.0);
|
|
glEnd();
|
|
glMatrixMode(GL_PROJECTION);
|
|
glPopMatrix();
|
|
glMatrixMode(GL_MODELVIEW);
|
|
glPopMatrix();
|
|
}
|
|
|
|
glBindTexture(GL_TEXTURE_2D, 0);//Stop using this texture.
|
|
}
|
|
|
|
glDisable(GL_TEXTURE_2D);
|
|
#else
|
|
this->glEnable(GL_TEXTURE_2D);
|
|
this->glActiveTexture(GL_TEXTURE0);
|
|
const auto renderer = m_Fractorium->m_Controller->Renderer();
|
|
|
|
//Ensure all allocation has taken place first.
|
|
if (m_OutputTexID != 0)
|
|
{
|
|
glBindTexture(GL_TEXTURE_2D, m_OutputTexID);//The texture to draw to.
|
|
const auto scaledW = std::ceil(width() * devicePixelRatioF());
|
|
const auto scaledH = std::ceil(height() * devicePixelRatioF());
|
|
|
|
//Only draw if the dimensions match exactly.
|
|
if (m_TexWidth == m_Fractorium->m_Controller->FinalRasW() && m_TexHeight == m_Fractorium->m_Controller->FinalRasH())
|
|
{
|
|
//Copy data from CPU to OpenGL if using a CPU renderer. This is not needed when using OpenCL.
|
|
if (renderer->RendererType() == eRendererType::CPU_RENDERER || !renderer->Shared())
|
|
{
|
|
const auto finalImage = m_Fractorium->m_Controller->FinalImage();
|
|
|
|
if (finalImage &&//Make absolutely sure all image dimensions match when copying host side buffer to GL texture.
|
|
!finalImage->empty() &&
|
|
((m_TexWidth * m_TexHeight) == static_cast<GLint>(finalImage->size())) &&
|
|
(finalImage->size() == renderer->FinalDimensions()))
|
|
this->glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_TexWidth, m_TexHeight, GL_RGBA, GL_FLOAT, finalImage->data());
|
|
}
|
|
|
|
m_QuadProgram->bind();
|
|
this->glVertexAttribPointer(m_TexturePosAttr, 2, GL_FLOAT, GL_FALSE, 0, m_TexVerts.data());
|
|
this->glEnableVertexAttribArray(0);
|
|
this->glDrawArrays(GL_TRIANGLE_STRIP, 0, 5);
|
|
this->glDisableVertexAttribArray(0);
|
|
m_QuadProgram->release();
|
|
}
|
|
}
|
|
|
|
this->glBindTexture(GL_TEXTURE_2D, 0);//Stop using this texture.
|
|
this->glDisable(GL_TEXTURE_2D);
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set drag and drag modifier states to nothing.
|
|
/// </summary>
|
|
void GLEmberControllerBase::ClearDrag()
|
|
{
|
|
m_DragModifier = 0;
|
|
m_DragState = eDragState::DragNone;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Wrapper around Allocate() call on the GL widget.
|
|
/// </summary>
|
|
bool GLEmberControllerBase::Allocate(bool force) { return m_GL->Allocate(force); }
|
|
|
|
/// <summary>
|
|
/// Helpers to set/get/clear which keys are pressed while dragging.
|
|
/// </summary>
|
|
/// <returns>bool</returns>
|
|
bool GLEmberControllerBase::GetAlt() { return (m_DragModifier & static_cast<et>(eDragModifier::DragModAlt)) == static_cast<et>(eDragModifier::DragModAlt); }
|
|
bool GLEmberControllerBase::GetShift() { return (m_DragModifier & static_cast<et>(eDragModifier::DragModShift)) == static_cast<et>(eDragModifier::DragModShift); }
|
|
bool GLEmberControllerBase::GetControl() { return (m_DragModifier & static_cast<et>(eDragModifier::DragModControl)) == static_cast<et>(eDragModifier::DragModControl); }
|
|
void GLEmberControllerBase::SetAlt() { m_DragModifier |= static_cast<et>(eDragModifier::DragModAlt); }
|
|
void GLEmberControllerBase::SetShift() { m_DragModifier |= static_cast<et>(eDragModifier::DragModShift); }
|
|
void GLEmberControllerBase::SetControl() { m_DragModifier |= static_cast<et>(eDragModifier::DragModControl); }
|
|
void GLEmberControllerBase::ClearAlt() { m_DragModifier &= ~static_cast<et>(eDragModifier::DragModAlt); }
|
|
void GLEmberControllerBase::ClearShift() { m_DragModifier &= ~static_cast<et>(eDragModifier::DragModShift); }
|
|
void GLEmberControllerBase::ClearControl() { m_DragModifier &= ~static_cast<et>(eDragModifier::DragModControl); }
|
|
|
|
/// <summary>
|
|
/// Clear the OpenGL output window to be the background color of the current ember.
|
|
/// Both buffers must be cleared, else artifacts will show up.
|
|
/// </summary>
|
|
template <typename T>
|
|
void GLEmberController<T>::ClearWindow()
|
|
{
|
|
const auto ember = m_FractoriumEmberController->CurrentEmber();
|
|
m_GL->makeCurrent();
|
|
m_GL->glClearColor(ember->m_Background.r, ember->m_Background.g, ember->m_Background.b, 1.0);
|
|
m_GL->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the currently selected xform.
|
|
/// The currently selected xform is drawn with a circle around it, with all others only showing their axes.
|
|
/// </summary>
|
|
/// <param name="xform">The xform.</param>
|
|
template <typename T>
|
|
void GLEmberController<T>::SetSelectedXform(Xform<T>* xform)
|
|
{
|
|
//By doing this check, it prevents triggering unnecessary events when selecting an xform on this window with the mouse,
|
|
//which will set the combo box on the main window, which will trigger this call. However, if the xform has been selected
|
|
//here with the mouse, the window has already been repainted, so there's no need to do it again.
|
|
if (m_SelectedXform != xform)
|
|
{
|
|
m_SelectedXform = xform;
|
|
|
|
if (m_GL->m_Init)
|
|
//m_GL->update();
|
|
m_GL->repaint();//Force immediate redraw with repaint() instead of update().
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Setters for main window pointers.
|
|
/// </summary>
|
|
|
|
void GLWidget::SetMainWindow(Fractorium* f) { m_Fractorium = f; }
|
|
|
|
/// <summary>
|
|
/// Getters for OpenGL state.
|
|
/// </summary>
|
|
|
|
bool GLWidget::Init() const { return m_Init; }
|
|
bool GLWidget::Drawing() const { return m_Drawing; }
|
|
GLint GLWidget::MaxTexSize() const { return m_MaxTexSize; }
|
|
GLuint GLWidget::OutputTexID() const { return m_OutputTexID; }
|
|
GLint GLWidget::TexWidth() const { return m_TexWidth; }
|
|
GLint GLWidget::TexHeight() const { return m_TexHeight; }
|
|
|
|
/// <summary>
|
|
/// Initialize OpenGL, called once at startup after the main window constructor finishes.
|
|
/// Although it seems an awkward place to put some of this setup code, the dimensions of the
|
|
/// main window and its widgets are not fully initialized before this is called.
|
|
/// Once this is done, the render timer is started after a short delay.
|
|
/// Rendering is then clear to begin.
|
|
/// </summary>
|
|
void GLWidget::initializeGL()
|
|
{
|
|
#ifdef USE_GLSL
|
|
//auto qsf = this->format();
|
|
//qDebug() << "initializeGL*****************\nVersion: " << qsf.majorVersion() << ',' << qsf.minorVersion();
|
|
//qDebug() << "Profile: " << qsf.profile();
|
|
//qDebug() << "Depth buffer size: " << qsf.depthBufferSize();
|
|
//qDebug() << "Swap behavior: " << qsf.swapBehavior();
|
|
//qDebug() << "Swap interval: " << qsf.swapInterval();
|
|
|
|
if (!m_Init && m_Fractorium)
|
|
{
|
|
this->initializeOpenGLFunctions();
|
|
|
|
if (!m_Program)
|
|
{
|
|
m_Program = new QOpenGLShaderProgram(this);
|
|
|
|
if (!m_Program->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource))
|
|
{
|
|
QMessageBox::critical(m_Fractorium, "Shader Error", "Error compiling affine vertex source: " + m_Program->log());
|
|
QApplication::exit(1);
|
|
}
|
|
|
|
if (!m_Program->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource))
|
|
{
|
|
QMessageBox::critical(m_Fractorium, "Shader Error", "Error compiling affine fragment source: " + m_Program->log());
|
|
QApplication::exit(1);
|
|
}
|
|
|
|
if (!m_Program->link())
|
|
{
|
|
QMessageBox::critical(m_Fractorium, "Shader Error", "Error linking affine source: " + m_Program->log());
|
|
QApplication::exit(1);
|
|
}
|
|
|
|
m_PosAttr = m_Program->attributeLocation("posattr");
|
|
m_ColAttr = m_Program->uniformLocation("mycolor");
|
|
m_PointSizeUniform = m_Program->uniformLocation("ps");
|
|
m_MatrixUniform = m_Program->uniformLocation("matrix");
|
|
}
|
|
|
|
if (!m_QuadProgram)
|
|
{
|
|
m_QuadProgram = new QOpenGLShaderProgram(this);
|
|
|
|
if (!m_QuadProgram->addShaderFromSourceCode(QOpenGLShader::Vertex, quadVertexShaderSource))
|
|
{
|
|
QMessageBox::critical(m_Fractorium, "Shader Error", "Error compiling image texture vertex source: " + m_QuadProgram->log());
|
|
QApplication::exit(1);
|
|
}
|
|
|
|
if (!m_QuadProgram->addShaderFromSourceCode(QOpenGLShader::Fragment, quadFragmentShaderSource))
|
|
{
|
|
QMessageBox::critical(m_Fractorium, "Shader Error", "Error compiling image texture fragment source: " + m_QuadProgram->log());
|
|
QApplication::exit(1);
|
|
}
|
|
|
|
if (!m_QuadProgram->link())
|
|
{
|
|
QMessageBox::critical(m_Fractorium, "Shader Error", "Error linking image texture source: " + m_QuadProgram->log());
|
|
QApplication::exit(1);
|
|
}
|
|
|
|
m_TexturePosAttr = m_QuadProgram->attributeLocation("posattr");
|
|
m_TextureUniform = m_QuadProgram->uniformLocation("quadtex");
|
|
m_TextureMatrixUniform = m_QuadProgram->uniformLocation("matrix");
|
|
m_TextureProjMatrix.ortho(0, 1, 1, 0, -1, 1);
|
|
m_QuadProgram->bind();
|
|
m_QuadProgram->setUniformValue(m_TextureUniform, 0);
|
|
m_QuadProgram->setUniformValue(m_TextureMatrixUniform, m_TextureProjMatrix);
|
|
m_QuadProgram->release();
|
|
}
|
|
|
|
#else
|
|
|
|
if (!m_Init && initializeOpenGLFunctions() && m_Fractorium)
|
|
{
|
|
#endif
|
|
//cout << "GL Version: " << (char *) glGetString(GL_VERSION) << endl;
|
|
//cout << "GLSL version: " << (char *) glGetString(GL_SHADING_LANGUAGE_VERSION) << endl;
|
|
this->glClearColor(0.0, 0.0, 0.0, 1.0);
|
|
this->glDisable(GL_DEPTH_TEST);//This will remain disabled for the duration of the program.
|
|
this->glEnable(GL_TEXTURE_2D);
|
|
this->glEnable(GL_PROGRAM_POINT_SIZE);
|
|
this->glGetIntegerv(GL_MAX_TEXTURE_SIZE, &m_MaxTexSize);
|
|
this->glDisable(GL_TEXTURE_2D);
|
|
m_Fractorium->m_WidthSpin->setMaximum(m_MaxTexSize);
|
|
m_Fractorium->m_HeightSpin->setMaximum(m_MaxTexSize);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The main drawing/update function.
|
|
/// First the quad will be drawn, then the remaining affine circles.
|
|
/// </summary>
|
|
void GLWidget::paintGL()
|
|
{
|
|
/*
|
|
auto qsf = this->format();
|
|
qDebug() << "paintGL*****************\nVersion: " << qsf.majorVersion() << ',' << qsf.minorVersion();
|
|
qDebug() << "Profile: " << qsf.profile();
|
|
qDebug() << "Depth buffer size: " << qsf.depthBufferSize();
|
|
qDebug() << "Swap behavior: " << qsf.swapBehavior();
|
|
qDebug() << "Swap interval: " << qsf.swapInterval();
|
|
*/
|
|
const auto controller = m_Fractorium->m_Controller.get();
|
|
|
|
//Ensure there is a renderer and that it's supposed to be drawing, signified by the running timer.
|
|
if (controller && controller->Renderer()/* && controller->ProcessState() != eProcessState::NONE*/)//Need a way to determine if at leat one successful render has happened.
|
|
{
|
|
const auto renderer = controller->Renderer();
|
|
const auto unitX = std::abs(renderer->UpperRightX(false) - renderer->LowerLeftX(false)) / 2.0f;
|
|
const auto unitY = std::abs(renderer->UpperRightY(false) - renderer->LowerLeftY(false)) / 2.0f;
|
|
|
|
if (unitX > 100000 || unitY > 100000)//Need a better way to do this.//TODO
|
|
{
|
|
qDebug() << unitX << " " << unitY;
|
|
//return;
|
|
}
|
|
|
|
m_Drawing = true;
|
|
|
|
if (m_Fractorium->DrawImage())
|
|
{
|
|
GLController()->DrawImage();
|
|
}
|
|
else
|
|
{
|
|
glClearColor(0.0, 0.0, 0.0, 1.0);
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
}
|
|
|
|
//Affine drawing.
|
|
const auto pre = m_Fractorium->DrawPreAffines();
|
|
const auto post = m_Fractorium->DrawPostAffines();
|
|
this->glEnable(GL_BLEND);
|
|
this->glEnable(GL_LINE_SMOOTH);
|
|
this->glEnable(GL_POINT_SMOOTH);
|
|
#if defined (__APPLE__) || defined(MACOSX)
|
|
this->glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_COLOR);
|
|
#else
|
|
this->glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);
|
|
#endif
|
|
#ifndef USE_GLSL
|
|
this->glMatrixMode(GL_PROJECTION);
|
|
this->glPushMatrix();
|
|
this->glLoadIdentity();
|
|
this->glOrtho(-unitX, unitX, -unitY, unitY, -1, 1);//Projection matrix: OpenGL camera is always centered, just move the ember internally inside the renderer.
|
|
this->glMatrixMode(GL_MODELVIEW);
|
|
this->glPushMatrix();
|
|
this->glLoadIdentity();
|
|
controller->GLController()->DrawAffines(pre, post);
|
|
this->glMatrixMode(GL_PROJECTION);
|
|
this->glPopMatrix();
|
|
this->glMatrixMode(GL_MODELVIEW);
|
|
this->glPopMatrix();
|
|
#else
|
|
m_Program->bind();
|
|
m_ProjMatrix.setToIdentity();
|
|
m_ProjMatrix.ortho(-unitX, unitX, -unitY, unitY, -1, 1);//Projection matrix: OpenGL camera is always centered, just move the ember internally inside the renderer.
|
|
m_ModelViewMatrix.setToIdentity();
|
|
//this->DrawUnitSquare();
|
|
controller->GLController()->DrawAffines(pre, post);
|
|
m_Program->release();
|
|
#endif
|
|
this->glDisable(GL_BLEND);
|
|
this->glDisable(GL_LINE_SMOOTH);
|
|
this->glDisable(GL_POINT_SMOOTH);
|
|
m_Drawing = false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draw the image on the quad.
|
|
/// </summary>
|
|
template <typename T>
|
|
void GLEmberController<T>::DrawImage()
|
|
{
|
|
const auto renderer = m_FractoriumEmberController->Renderer();
|
|
const auto ember = m_FractoriumEmberController->CurrentEmber();
|
|
m_GL->glClearColor(ember->m_Background.r, ember->m_Background.g, ember->m_Background.b, 1.0);
|
|
m_GL->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
renderer->EnterFinalAccum();//Lock, may not be necessary, but just in case.
|
|
renderer->EnterResize();
|
|
|
|
if (SizesMatch())//Ensure all sizes are correct. If not, do nothing.
|
|
{
|
|
m_GL->DrawQuad();//Output image is drawn here.
|
|
}
|
|
|
|
renderer->LeaveResize();//Unlock, may not be necessary.
|
|
renderer->LeaveFinalAccum();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draw the affine circles.
|
|
/// </summary>
|
|
/// <param name="pre">True to draw pre affines, else don't.</param>
|
|
/// <param name="post">True to draw post affines, else don't.</param>
|
|
template <typename T>
|
|
void GLEmberController<T>::DrawAffines(bool pre, bool post)
|
|
{
|
|
QueryVMP();//Resolves to float or double specialization function depending on T.
|
|
|
|
if (!m_Fractorium->DrawXforms())
|
|
return;
|
|
|
|
const auto ember = m_FractoriumEmberController->CurrentEmber();
|
|
const auto dragging = m_DragState == eDragState::DragDragging;
|
|
const auto forceFinal = m_Fractorium->HaveFinal();
|
|
|
|
if (m_DragState == eDragState::DragRotateScale)
|
|
{
|
|
const auto dprf = m_GL->devicePixelRatioF();
|
|
const auto world = ScrolledCenter(true);
|
|
m_GL->glLineWidth(1.0f * dprf);
|
|
const GLfloat vertices[] =
|
|
{
|
|
static_cast<GLfloat>(m_MouseWorldPos.x), static_cast<GLfloat>(m_MouseWorldPos.y),//Mouse position while dragging with right button down...
|
|
static_cast<GLfloat>(world.x), static_cast<GLfloat>(world.y)//...to center.
|
|
};
|
|
const QVector4D col(0.0f, 1.0f, 1.0f, 1.0f);
|
|
m_GL->DrawPointOrLine(col, vertices, 2, GL_LINES);
|
|
}
|
|
|
|
//Draw grid if control key is pressed.
|
|
if ((m_GL->hasFocus() && GetControl()) || m_Fractorium->DrawGrid())
|
|
DrawGrid();
|
|
|
|
//When dragging, only draw the selected xform's affine and hide all others.
|
|
if (!m_Fractorium->m_Settings->ShowAllXforms() && dragging)
|
|
{
|
|
if (m_SelectedXform)
|
|
DrawAffine(m_SelectedXform, m_AffineType == eAffineType::AffinePre, true, false);
|
|
}
|
|
else//Show all while dragging, or not dragging just hovering/mouse move.
|
|
{
|
|
if (pre && m_Fractorium->DrawAllPre())//Draw all pre affine if specified.
|
|
{
|
|
size_t i = 0;
|
|
bool any = false;
|
|
|
|
while (auto xform = ember->GetTotalXform(i, forceFinal))
|
|
if (m_Fractorium->IsXformSelected(i++))
|
|
{
|
|
any = true;
|
|
break;
|
|
}
|
|
|
|
i = 0;
|
|
|
|
while (const auto xform = ember->GetTotalXform(i, forceFinal))
|
|
{
|
|
bool selected = m_Fractorium->IsXformSelected(i++) || (!any && m_SelectedXform == xform);
|
|
DrawAffine(xform, true, selected, !dragging && (m_HoverXform == xform));
|
|
}
|
|
}
|
|
else if (pre && m_Fractorium->DrawSelectedPre())//Only draw selected pre affine, and if none are selected, draw current. All are considered "selected", so circles are drawn around them.
|
|
{
|
|
size_t i = 0;
|
|
bool any = false;
|
|
|
|
while (const auto xform = ember->GetTotalXform(i, forceFinal))
|
|
{
|
|
if (m_Fractorium->IsXformSelected(i++))
|
|
{
|
|
DrawAffine(xform, true, true, !dragging && (m_HoverXform == xform));
|
|
any = true;
|
|
}
|
|
}
|
|
|
|
if (!any)
|
|
DrawAffine(m_FractoriumEmberController->CurrentXform(), true, true, !dragging && (m_HoverXform == m_FractoriumEmberController->CurrentXform()));
|
|
}
|
|
|
|
if (post && m_Fractorium->DrawAllPost())//Draw all post affine if specified.
|
|
{
|
|
size_t i = 0;
|
|
bool any = false;
|
|
|
|
while (const auto xform = ember->GetTotalXform(i, forceFinal))
|
|
if (m_Fractorium->IsXformSelected(i++))
|
|
{
|
|
any = true;
|
|
break;
|
|
}
|
|
|
|
i = 0;
|
|
|
|
while (const auto xform = ember->GetTotalXform(i, forceFinal))
|
|
{
|
|
bool selected = m_Fractorium->IsXformSelected(i++) || (!any && m_SelectedXform == xform);
|
|
DrawAffine(xform, false, selected, !dragging && (m_HoverXform == xform));
|
|
}
|
|
}
|
|
else if (post && m_Fractorium->DrawSelectedPost())//Only draw selected post, and if none are selected, draw current. All are considered "selected", so circles are drawn around them.
|
|
{
|
|
size_t i = 0;
|
|
bool any = false;
|
|
|
|
while (const auto xform = ember->GetTotalXform(i, forceFinal))
|
|
{
|
|
if (m_Fractorium->IsXformSelected(i++))
|
|
{
|
|
DrawAffine(xform, false, true, !dragging && (m_HoverXform == xform));
|
|
any = true;
|
|
}
|
|
}
|
|
|
|
if (!any)
|
|
DrawAffine(m_FractoriumEmberController->CurrentXform(), false, true, !dragging && (m_HoverXform == m_FractoriumEmberController->CurrentXform()));
|
|
}
|
|
}
|
|
|
|
if (dragging)//Draw large yellow dot on select or drag.
|
|
{
|
|
#ifndef USE_GLSL
|
|
m_GL->glBegin(GL_POINTS);
|
|
m_GL->glColor4f(1.0f, 1.0f, 0.5f, 1.0f);
|
|
m_GL->glVertex2f(m_DragHandlePos.x, m_DragHandlePos.y);
|
|
m_GL->glEnd();
|
|
#else
|
|
const GLfloat vertices[] =//Should these be of type T?//TODO
|
|
{
|
|
static_cast<GLfloat>(m_DragHandlePos.x), static_cast<GLfloat>(m_DragHandlePos.y)
|
|
};
|
|
const QVector4D col(1.0f, 1.0f, 0.5f, 1.0f);
|
|
m_GL->DrawPointOrLine(col, vertices, 1, GL_POINTS, false, 6.0f);
|
|
#endif
|
|
}
|
|
else if (m_DragState == eDragState::DragSelect)
|
|
{
|
|
m_GL->glLineWidth(2.0f * m_GL->devicePixelRatioF());
|
|
#ifndef USE_GLSL
|
|
m_GL->glBegin(GL_LINES);
|
|
m_GL->glColor4f(0.0f, 0.0f, 1.0f, 1.0f);
|
|
m_GL->glVertex2f(m_MouseDownWorldPos.x, m_MouseDownWorldPos.y);//UL->UR
|
|
m_GL->glVertex2f(m_MouseWorldPos.x, m_MouseDownWorldPos.y);
|
|
m_GL->glVertex2f(m_MouseDownWorldPos.x, m_MouseWorldPos.y);//LL->LR
|
|
m_GL->glVertex2f(m_MouseWorldPos.x, m_MouseWorldPos.y);
|
|
m_GL->glVertex2f(m_MouseDownWorldPos.x, m_MouseDownWorldPos.y);//UL->LL
|
|
m_GL->glVertex2f(m_MouseDownWorldPos.x, m_MouseWorldPos.y);
|
|
m_GL->glVertex2f(m_MouseWorldPos.x, m_MouseDownWorldPos.y);//UR->LR
|
|
m_GL->glVertex2f(m_MouseWorldPos.x, m_MouseWorldPos.y);
|
|
m_GL->glEnd();
|
|
#else
|
|
const GLfloat vertices[] =//Should these be of type T?//TODO
|
|
{
|
|
static_cast<GLfloat>(m_MouseDownWorldPos.x), static_cast<GLfloat>(m_MouseDownWorldPos.y),//UL->UR
|
|
static_cast<GLfloat>(m_MouseWorldPos.x ), static_cast<GLfloat>(m_MouseDownWorldPos.y),
|
|
static_cast<GLfloat>(m_MouseDownWorldPos.x), static_cast<GLfloat>(m_MouseWorldPos.y),//LL->LR
|
|
static_cast<GLfloat>(m_MouseWorldPos.x ), static_cast<GLfloat>(m_MouseWorldPos.y),
|
|
static_cast<GLfloat>(m_MouseDownWorldPos.x), static_cast<GLfloat>(m_MouseDownWorldPos.y),//UL->LL
|
|
static_cast<GLfloat>(m_MouseDownWorldPos.x), static_cast<GLfloat>(m_MouseWorldPos.y),
|
|
static_cast<GLfloat>(m_MouseWorldPos.x ), static_cast<GLfloat>(m_MouseDownWorldPos.y),//UR->LR
|
|
static_cast<GLfloat>(m_MouseWorldPos.x ), static_cast<GLfloat>(m_MouseWorldPos.y)
|
|
};
|
|
const QVector4D col(0.0f, 0.0f, 1.0f, 1.0f);
|
|
m_GL->DrawPointOrLine(col, vertices, 8, GL_LINES);
|
|
#endif
|
|
m_GL->glLineWidth(1.0f * m_GL->devicePixelRatioF());
|
|
}
|
|
else if (m_HoverType != eHoverType::HoverNone && m_HoverXform == m_SelectedXform)//Draw large turquoise dot on hover if they are hovering over the selected xform.
|
|
{
|
|
#ifndef USE_GLSL
|
|
m_GL->glBegin(GL_POINTS);
|
|
m_GL->glColor4f(0.5f, 1.0f, 1.0f, 1.0f);
|
|
m_GL->glVertex2f(m_HoverHandlePos.x, m_HoverHandlePos.y);
|
|
m_GL->glEnd();
|
|
#else
|
|
const GLfloat vertices[] =//Should these be of type T?//TODO
|
|
{
|
|
static_cast<GLfloat>(m_HoverHandlePos.x), static_cast<GLfloat>(m_HoverHandlePos.y)
|
|
};
|
|
const QVector4D col(0.5f, 1.0f, 1.0f, 1.0f);
|
|
m_GL->DrawPointOrLine(col, vertices, 1, GL_POINTS, false, 6.0f);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set drag modifiers based on key press.
|
|
/// </summary>
|
|
/// <param name="e">The event</param>
|
|
bool GLEmberControllerBase::KeyPress_(QKeyEvent* e)
|
|
{
|
|
if (e->key() == Qt::Key_Control)
|
|
{
|
|
SetControl();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Call controller KeyPress().
|
|
/// </summary>
|
|
/// <param name="e">The event</param>
|
|
void GLWidget::keyPressEvent(QKeyEvent* e)
|
|
{
|
|
if (!GLController() || !GLController()->KeyPress_(e))
|
|
QOpenGLWidget::keyPressEvent(e);
|
|
|
|
update();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set drag modifiers based on key release.
|
|
/// </summary>
|
|
/// <param name="e">The event</param>
|
|
bool GLEmberControllerBase::KeyRelease_(QKeyEvent* e)
|
|
{
|
|
if (e != nullptr && 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 (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)
|
|
{
|
|
if (!e)
|
|
return;
|
|
|
|
const auto x = e->position().x();
|
|
const auto y = e->position().y();
|
|
v3T const mouseFlipped(x * m_GL->devicePixelRatioF(), m_Viewport[3] - y * m_GL->devicePixelRatioF(), 0);//Must flip y because in OpenGL, 0,0 is bottom left, but in windows, it's top left.
|
|
const auto ember = m_FractoriumEmberController->CurrentEmber();
|
|
const auto renderer = m_FractoriumEmberController->Renderer();
|
|
|
|
//Ensure everything has been initialized.
|
|
if (!renderer)
|
|
return;
|
|
|
|
m_MouseDownPos = glm::ivec2(x * m_GL->devicePixelRatioF(), y * m_GL->devicePixelRatioF());//Capture the raster coordinates of where the mouse was clicked.
|
|
m_MouseWorldPos = WindowToWorld(mouseFlipped, false);//Capture the world cartesian coordinates of where the mouse is.
|
|
m_BoundsDown.w = renderer->LowerLeftX(false);//Need to capture these because they'll be changing if scaling.
|
|
m_BoundsDown.x = renderer->LowerLeftY(false);
|
|
m_BoundsDown.y = renderer->UpperRightX(false);
|
|
m_BoundsDown.z = renderer->UpperRightY(false);
|
|
const auto mod = e->modifiers();
|
|
|
|
if (mod.testFlag(Qt::ShiftModifier))
|
|
SetShift();
|
|
|
|
if (mod.testFlag(Qt::AltModifier))
|
|
SetAlt();
|
|
|
|
if (m_DragState == eDragState::DragNone)//Only take action if the user wasn't already dragging.
|
|
{
|
|
m_MouseDownWorldPos = m_MouseWorldPos;//Set the mouse down position to the current position.
|
|
|
|
if (e->button() & Qt::LeftButton)
|
|
{
|
|
const auto xformIndex = UpdateHover(mouseFlipped);//Determine if an affine circle was clicked.
|
|
|
|
if (m_HoverXform && xformIndex != -1)
|
|
{
|
|
m_SelectedXform = m_HoverXform;
|
|
m_DragSrcTransform = Affine2D<T>(m_AffineType == eAffineType::AffinePre ? m_SelectedXform->m_Affine : m_SelectedXform->m_Post);//Copy the affine of the xform that was selected.
|
|
//The user has selected an xform by clicking on it, so update the main GUI by selecting this xform in the combo box.
|
|
m_Fractorium->CurrentXform(xformIndex);//Must do this first so UpdateXform() below properly grabs the current plus any selected.
|
|
m_DragSrcPreTransforms.clear();
|
|
m_DragSrcPostTransforms.clear();
|
|
m_FractoriumEmberController->UpdateXform([&](Xform<T>* xform, size_t xfindex, size_t selIndex)
|
|
{
|
|
if (m_AffineType == eAffineType::AffinePre)
|
|
m_DragSrcPreTransforms[xfindex] = xform->m_Affine;
|
|
else
|
|
m_DragSrcPostTransforms[xfindex] = xform->m_Post;
|
|
}, eXformUpdate::UPDATE_SELECTED, false);//Don't update renderer here.
|
|
m_DragHandlePos = m_HoverHandlePos;//The location in local coordinates of the point selected on the spinner, x, y or center.
|
|
m_DragState = eDragState::DragDragging;
|
|
m_GL->repaint();
|
|
}
|
|
else//Nothing was selected.
|
|
{
|
|
//m_SelectedXform = nullptr;
|
|
m_DragSrcPreTransforms.clear();
|
|
m_DragSrcPostTransforms.clear();
|
|
m_DragState = eDragState::DragNone;
|
|
}
|
|
}
|
|
else if (e->button() == Qt::MiddleButton || (e->button() == Qt::RightButton && e->modifiers() & Qt::ShiftModifier && !(e->modifiers() & Qt::AltModifier)))//Middle button or right button with shift key, do whole image translation.
|
|
{
|
|
m_CenterDownX = ember->m_CenterX;//Capture where the center of the image is because this value will change when panning.
|
|
m_CenterDownY = ember->m_CenterY;
|
|
m_DragState = eDragState::DragPanning;
|
|
}
|
|
else if (e->button() == Qt::RightButton)//Right button does whole image rotation and scaling.
|
|
{
|
|
if (m_Fractorium->DrawImage())
|
|
{
|
|
m_CenterDownX = ember->m_CenterX;//Capture these because they will change when rotating and scaling.
|
|
m_CenterDownY = ember->m_CenterY;
|
|
|
|
if (GetAlt() && GetShift())
|
|
{
|
|
m_PitchDown = ember->m_CamPitch * RAD_2_DEG_T;
|
|
m_YawDown = ember->m_CamYaw * RAD_2_DEG_T;
|
|
m_DragState = eDragState::DragPitchYaw;
|
|
}
|
|
else
|
|
{
|
|
m_RotationDown = ember->m_Rotate;
|
|
m_ScaleDown = ember->m_PixelsPerUnit;
|
|
m_DragState = eDragState::DragRotateScale;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Call controller MousePress().
|
|
/// </summary>
|
|
/// <param name="e">The event</param>
|
|
void GLWidget::mousePressEvent(QMouseEvent* e)
|
|
{
|
|
if (e)
|
|
{
|
|
setFocus();//Must do this so that this window gets keyboard events.
|
|
|
|
if (const auto controller = GLController())
|
|
controller->MousePress(e);
|
|
|
|
QOpenGLWidget::mousePressEvent(e);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reset the selection and dragging state, but re-calculate the
|
|
/// hovering state because the mouse might still be over an affine circle.
|
|
/// </summary>
|
|
/// <param name="e">The event</param>
|
|
template <typename T>
|
|
void GLEmberController<T>::MouseRelease(QMouseEvent* e)
|
|
{
|
|
if (e)
|
|
{
|
|
const auto x = e->position().x();
|
|
const auto y = e->position().y();
|
|
v3T const mouseFlipped(x * m_GL->devicePixelRatioF(), m_Viewport[3] - y * m_GL->devicePixelRatioF(), 0);//Must flip y because in OpenGL, 0,0 is bottom left, but in windows, it's top left.
|
|
m_MouseWorldPos = WindowToWorld(mouseFlipped, false);
|
|
|
|
if (m_DragState == eDragState::DragDragging && (e->button() & Qt::LeftButton))
|
|
UpdateHover(mouseFlipped);
|
|
|
|
if (m_DragState == eDragState::DragNone)
|
|
m_Fractorium->OnXformsSelectNoneButtonClicked(false);
|
|
|
|
m_DragState = eDragState::DragNone;
|
|
m_DragModifier = 0;
|
|
m_GL->update();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Call controller MouseRelease().
|
|
/// </summary>
|
|
/// <param name="e">The event</param>
|
|
void GLWidget::mouseReleaseEvent(QMouseEvent* e)
|
|
{
|
|
setFocus();//Must do this so that this window gets keyboard events.
|
|
|
|
if (const auto controller = GLController())
|
|
controller->MouseRelease(e);
|
|
|
|
QOpenGLWidget::mouseReleaseEvent(e);
|
|
}
|
|
|
|
/// <summary>
|
|
/// If dragging, update relevant values and reset entire rendering process.
|
|
/// If hovering, update display.
|
|
/// </summary>
|
|
/// <param name="e">The event</param>
|
|
template <typename T>
|
|
void GLEmberController<T>::MouseMove(QMouseEvent* e)
|
|
{
|
|
const auto draw = true;
|
|
const auto x = e->position().x();
|
|
const auto y = e->position().y();
|
|
const glm::ivec2 mouse(x * m_GL->devicePixelRatioF(), y * m_GL->devicePixelRatioF());
|
|
const v3T mouseFlipped(x * m_GL->devicePixelRatioF(), m_Viewport[3] - y * m_GL->devicePixelRatioF(), 0);//Must flip y because in OpenGL, 0,0 is bottom left, but in windows, it's top left.
|
|
const auto ember = m_FractoriumEmberController->CurrentEmber();
|
|
|
|
//First check to see if the mouse actually moved.
|
|
if (mouse == m_MousePos)
|
|
return;
|
|
|
|
m_MousePos = mouse;
|
|
m_MouseWorldPos = WindowToWorld(mouseFlipped, false);
|
|
|
|
//Update status bar on main window, regardless of whether anything is being dragged.
|
|
if (m_Fractorium->m_Controller->RenderTimerRunning())
|
|
m_Fractorium->SetCoordinateStatus(x * m_GL->devicePixelRatioF(), y * m_GL->devicePixelRatioF(), m_MouseWorldPos.x, m_MouseWorldPos.y);
|
|
|
|
if (m_SelectedXform && m_DragState == eDragState::DragDragging)//Dragging and affine.
|
|
{
|
|
const bool pre = m_AffineType == eAffineType::AffinePre;
|
|
|
|
if (m_HoverType == eHoverType::HoverTranslation)
|
|
CalcDragTranslation();
|
|
else if (m_HoverType == eHoverType::HoverXAxis)
|
|
CalcDragXAxis();
|
|
else if (m_HoverType == eHoverType::HoverYAxis)
|
|
CalcDragYAxis();
|
|
|
|
m_FractoriumEmberController->FillAffineWithXform(m_SelectedXform, pre);//Update the spinners in the affine tab of the main window.
|
|
m_FractoriumEmberController->UpdateRender();//Restart the rendering process.
|
|
}
|
|
else if ((m_DragState == eDragState::DragNone || m_DragState == eDragState::DragSelect) && (e->buttons() & Qt::LeftButton))
|
|
{
|
|
m_DragState = eDragState::DragSelect;//Only set drag state once the user starts moving the mouse with the left button down.
|
|
//Iterate over each xform, seeing if it's in the bounding box.
|
|
const QPointF tl(m_MouseDownWorldPos.x, m_MouseDownWorldPos.y);
|
|
const QPointF br(m_MouseWorldPos.x, m_MouseWorldPos.y);
|
|
const QRectF qrf(tl, br);
|
|
const T scale = m_FractoriumEmberController->AffineScaleCurrentToLocked();
|
|
const auto i = 0;
|
|
m_FractoriumEmberController->UpdateXform([&](Xform<T>* xform, size_t xfindex, size_t selIndex)
|
|
{
|
|
if (m_Fractorium->DrawAllPre() || xform == m_SelectedXform)//Draw all pre affine if specified.
|
|
{
|
|
const QPointF cd(xform->m_Affine.C() * scale, xform->m_Affine.F() * scale);
|
|
bool b = qrf.contains(cd);
|
|
m_FractoriumEmberController->XformCheckboxAt(static_cast<int>(xfindex), [&](QCheckBox * cb)
|
|
{
|
|
cb->setChecked(b);
|
|
});
|
|
}
|
|
|
|
if (m_Fractorium->DrawAllPost() || xform == m_SelectedXform)
|
|
{
|
|
const QPointF cd(xform->m_Post.C() * scale, xform->m_Post.F() * scale);
|
|
bool b = qrf.contains(cd);
|
|
m_FractoriumEmberController->XformCheckboxAt(static_cast<int>(xfindex), [&](QCheckBox * cb)
|
|
{
|
|
if (!cb->isChecked() && b)
|
|
cb->setChecked(b);
|
|
});
|
|
}
|
|
}, eXformUpdate::UPDATE_ALL, false);
|
|
}
|
|
else if (m_DragState == eDragState::DragPanning)//Translating the whole image.
|
|
{
|
|
const auto x = -(m_MouseWorldPos.x - m_MouseDownWorldPos.x);
|
|
const auto y = (m_MouseWorldPos.y - m_MouseDownWorldPos.y);
|
|
Affine2D<T> rotMat;
|
|
rotMat.C(m_CenterDownX);
|
|
rotMat.F(m_CenterDownY);
|
|
rotMat.Rotate(ember->m_Rotate * DEG_2_RAD_T);
|
|
const v2T v1(x, y);
|
|
const v2T v2 = rotMat.TransformVector(v1);
|
|
ember->m_CenterX = v2.x;
|
|
ember->m_CenterY = ember->m_RotCenterY = v2.y;
|
|
m_FractoriumEmberController->SetCenter(ember->m_CenterX, ember->m_CenterY);//Will restart the rendering process.
|
|
}
|
|
else if (m_DragState == eDragState::DragRotateScale)//Rotating and scaling the whole image.
|
|
{
|
|
const T rot = CalcRotation();
|
|
const T scale = CalcScale();
|
|
ember->m_Rotate = NormalizeDeg360<T>(m_RotationDown + rot);
|
|
m_Fractorium->SetRotation(ember->m_Rotate, true);
|
|
m_Fractorium->SetScale(std::max(static_cast<T>(10), m_ScaleDown + scale));//Will restart the rendering process.
|
|
}
|
|
else if (m_DragState == eDragState::DragPitchYaw)//Pitching and yawing the whole image.
|
|
{
|
|
T pitch;
|
|
T yaw;
|
|
auto rotate = ember->m_Rotate;
|
|
|
|
if ((rotate <= 45 && rotate >= -45) || (rotate >= 135) || (rotate <= -135))
|
|
{
|
|
pitch = m_PitchDown + (m_MouseWorldPos.y - m_MouseDownWorldPos.y) * 100;
|
|
yaw = m_YawDown + (m_MouseWorldPos.x - m_MouseDownWorldPos.x) * 100;
|
|
}
|
|
else
|
|
{
|
|
pitch = m_PitchDown + (m_MouseWorldPos.x - m_MouseDownWorldPos.x) * 100;
|
|
yaw = m_YawDown + (m_MouseWorldPos.y - m_MouseDownWorldPos.y) * 100;
|
|
}
|
|
|
|
m_Fractorium->SetPitch(pitch);
|
|
m_Fractorium->SetYaw(yaw);
|
|
}
|
|
else
|
|
{
|
|
//If the user doesn't already have a key down, and they aren't dragging, clear the keys to be safe.
|
|
//This is done because if they do an alt+tab between windows, it thinks the alt key is down.
|
|
if (e->modifiers() == Qt::NoModifier)
|
|
ClearDrag();
|
|
|
|
//Check if they weren't dragging and weren't hovering over any affine.
|
|
//In that case, nothing needs to be done.
|
|
if (UpdateHover(mouseFlipped) == -1)
|
|
{
|
|
m_HoverXform = nullptr;
|
|
}
|
|
}
|
|
|
|
//Only update if the user was dragging or hovered over a point.
|
|
//Use repaint() to update immediately for a more responsive feel.
|
|
if ((m_DragState != eDragState::DragNone) || draw)
|
|
m_GL->update();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Call controller MouseMove().
|
|
/// </summary>
|
|
/// <param name="e">The event</param>
|
|
void GLWidget::mouseMoveEvent(QMouseEvent* e)
|
|
{
|
|
setFocus();//Must do this so that this window gets keyboard events.
|
|
|
|
if (const auto controller = GLController())
|
|
controller->MouseMove(e);
|
|
|
|
QOpenGLWidget::mouseMoveEvent(e);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Mouse wheel changes the scale (pixels per unit) which
|
|
/// will zoom in the image in our out, while sacrificing quality.
|
|
/// If the user needs to preserve quality, they can use the zoom spinner
|
|
/// on the main window.
|
|
/// If Alt is pressed, only the scale of the affines is changed, the scale of the image remains untouched.
|
|
/// </summary>
|
|
/// <param name="e">The event</param>
|
|
template <typename T>
|
|
void GLEmberController<T>::Wheel(QWheelEvent* e)
|
|
{
|
|
if ((e->modifiers() & Qt::AltModifier) && m_Fractorium->DrawXforms())
|
|
{
|
|
#ifdef __APPLE__
|
|
m_FractoriumEmberController->ChangeLockedScale(e->angleDelta().y() >= 0 ? 1.0981 : 0.9);
|
|
#else
|
|
m_FractoriumEmberController->ChangeLockedScale(e->angleDelta().x() >= 0 ? 1.0981 : 0.9);
|
|
#endif
|
|
m_GL->update();
|
|
}
|
|
else
|
|
{
|
|
if (m_Fractorium->DrawImage() && !(e->buttons() & Qt::MiddleButton) && !(e->modifiers() & Qt::ShiftModifier))//Middle button does whole image translation, so ignore the mouse wheel while panning to avoid inadvertent zooming. ShiftModifier for sensitive mouse.
|
|
{
|
|
auto ember = m_FractoriumEmberController->CurrentEmber();
|
|
m_Fractorium->SetScale(ember->m_PixelsPerUnit + (e->angleDelta().y() >= 0 ? 50 : -50));
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Call controller Wheel().
|
|
/// </summary>
|
|
/// <param name="e">The event</param>
|
|
void GLWidget::wheelEvent(QWheelEvent* e)
|
|
{
|
|
if (e)
|
|
{
|
|
if (auto controller = GLController())
|
|
{
|
|
controller->Wheel(e);
|
|
e->accept();//Prevents it from being sent to the main scroll bars. Scrolling should only affect the scale parameter and affine display zooming.
|
|
}
|
|
}
|
|
|
|
//Do not call QOpenGLWidget::wheelEvent(e) because this should only affect the scale and not the position of the scroll bars.
|
|
}
|
|
|
|
/// <summary>
|
|
/// Wrapper around drawing a simple primitive, like a point or line, using a GLSL program.
|
|
/// </summary>
|
|
/// <param name="col">The color to draw with</param>
|
|
/// <param name="vertices">The vertices to use</param>
|
|
/// <param name="drawType">The type of primitive to draw, such as GL_POINT or GL_LINES</param>
|
|
/// <param name="dashed">True to draw dashed lines, else solid</param>
|
|
/// <param name="pointSize">The size in pixels of points, which is internally scaled by the device pixel ratio.</param>
|
|
void GLWidget::DrawPointOrLine(const QVector4D& col, const std::vector<float>& vertices, int drawType, bool dashed, GLfloat pointSize)
|
|
{
|
|
DrawPointOrLine(col, vertices.data(), int(vertices.size() / 2), drawType, dashed, pointSize);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Wrapper around drawing a simple primitive, like a point or line, using a GLSL program.
|
|
/// </summary>
|
|
/// <param name="col">The color to draw with</param>
|
|
/// <param name="vertices">The vertices to use</param>
|
|
/// <param name="size">The number of verticies. This is usually the size of vertices / 2.</param>
|
|
/// <param name="drawType">The type of primitive to draw, such as GL_POINT or GL_LINES</param>
|
|
/// <param name="dashed">True to draw dashed lines, else solid</param>
|
|
/// <param name="pointSize">The size in pixels of points, which is internally scaled by the device pixel ratio.</param>
|
|
void GLWidget::DrawPointOrLine(const QVector4D& col, const GLfloat* vertices, int size, int drawType, bool dashed, GLfloat pointSize)
|
|
{
|
|
#ifdef USE_GLSL
|
|
|
|
if (dashed && (drawType == GL_LINES || drawType == GL_LINE_LOOP))
|
|
{
|
|
glLineStipple(1, 0XFF00);
|
|
glEnable(GL_LINE_STIPPLE);
|
|
}
|
|
|
|
m_ModelViewProjectionMatrix = m_ProjMatrix * m_ModelViewMatrix;
|
|
m_Program->setUniformValue(m_ColAttr, col);
|
|
m_Program->setUniformValue(m_PointSizeUniform, pointSize * GLfloat(devicePixelRatioF()));
|
|
m_Program->setUniformValue(m_MatrixUniform, m_ModelViewProjectionMatrix);
|
|
this->glVertexAttribPointer(m_PosAttr, 2, GL_FLOAT, GL_FALSE, 0, vertices);
|
|
this->glEnableVertexAttribArray(0);
|
|
this->glDrawArrays(drawType, 0, size);
|
|
this->glDisableVertexAttribArray(0);
|
|
|
|
if (dashed && (drawType == GL_LINES || drawType == GL_LINE_LOOP))
|
|
glDisable(GL_LINE_STIPPLE);
|
|
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the dimensions of the drawing area.
|
|
/// This will be called from the main window's SyncSizes() function.
|
|
/// </summary>
|
|
/// <param name="w">Width in pixels</param>
|
|
/// <param name="h">Height in pixels</param>
|
|
void GLWidget::SetDimensions(int w, int h)
|
|
{
|
|
const auto downscaledW = std::ceil(w / devicePixelRatioF());
|
|
const auto downscaledH = std::ceil(h / devicePixelRatioF());
|
|
setFixedSize(downscaledW, downscaledH);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set up texture memory to match the size of the window.
|
|
/// If first allocation, generate, bind and set parameters.
|
|
/// If subsequent call, only take action if dimensions don't match the window. In such case,
|
|
/// first deallocate, then reallocate.
|
|
/// </summary>
|
|
/// <returns>True if success, else false.</returns>
|
|
bool GLWidget::Allocate(bool force)
|
|
{
|
|
bool alloc = false;
|
|
//auto scaledW = std::ceil(width() * devicePixelRatioF());
|
|
const auto w = m_Fractorium->m_Controller->FinalRasW();
|
|
const auto h = m_Fractorium->m_Controller->FinalRasH();
|
|
bool const doResize = force || m_TexWidth != w || m_TexHeight != h;
|
|
bool const doIt = doResize || m_OutputTexID == 0;
|
|
#ifndef USE_GLSL
|
|
|
|
if (doIt)
|
|
{
|
|
m_TexWidth = static_cast<GLint>(w);
|
|
m_TexHeight = static_cast<GLint>(h);
|
|
glEnable(GL_TEXTURE_2D);
|
|
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
|
|
|
|
if (doResize)
|
|
Deallocate();
|
|
|
|
glGenTextures(1, &m_OutputTexID);
|
|
glBindTexture(GL_TEXTURE_2D, m_OutputTexID);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);//Fractron had this as GL_LINEAR_MIPMAP_LINEAR for OpenCL and Cuda.
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
|
|
#if defined (__APPLE__) || defined(MACOSX)
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, m_TexWidth, m_TexHeight, 0, GL_RGB, GL_FLOAT, nullptr);
|
|
#else
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, m_TexWidth, m_TexHeight, 0, GL_RGBA, GL_FLOAT, nullptr);
|
|
#endif
|
|
alloc = true;
|
|
}
|
|
|
|
if (alloc)
|
|
{
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
glDisable(GL_TEXTURE_2D);
|
|
}
|
|
|
|
#else
|
|
|
|
if (doIt)
|
|
{
|
|
m_TexWidth = static_cast<GLint>(w);
|
|
m_TexHeight = static_cast<GLint>(h);
|
|
this->glEnable(GL_TEXTURE_2D);
|
|
|
|
if (doResize)
|
|
Deallocate();
|
|
|
|
this->glActiveTexture(GL_TEXTURE0);
|
|
this->glGenTextures(1, &m_OutputTexID);
|
|
this->glBindTexture(GL_TEXTURE_2D, m_OutputTexID);
|
|
this->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);//Fractron had this as GL_LINEAR_MIPMAP_LINEAR for OpenCL and Cuda.
|
|
this->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
this->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
|
|
this->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
|
|
#if defined (__APPLE__) || defined(MACOSX)
|
|
this->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, m_TexWidth, m_TexHeight, 0, GL_RGB, GL_FLOAT, nullptr);
|
|
#else
|
|
this->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, m_TexWidth, m_TexHeight, 0, GL_RGBA, GL_FLOAT, nullptr);
|
|
#endif
|
|
alloc = true;
|
|
}
|
|
|
|
if (alloc)
|
|
{
|
|
this->glBindTexture(GL_TEXTURE_2D, 0);
|
|
this->glDisable(GL_TEXTURE_2D);
|
|
}
|
|
|
|
#endif
|
|
this->glFinish();
|
|
return m_OutputTexID != 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deallocate texture memory.
|
|
/// </summary>
|
|
/// <returns>True if anything deleted, else false.</returns>
|
|
bool GLWidget::Deallocate()
|
|
{
|
|
bool deleted = false;
|
|
|
|
if (m_OutputTexID != 0)
|
|
{
|
|
this->glBindTexture(GL_TEXTURE_2D, m_OutputTexID);
|
|
this->glDeleteTextures(1, &m_OutputTexID);
|
|
m_OutputTexID = 0;
|
|
deleted = true;
|
|
}
|
|
|
|
return deleted;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the viewport to match the window dimensions.
|
|
/// If the dimensions already match, no action is taken.
|
|
/// </summary>
|
|
void GLWidget::SetViewport()
|
|
{
|
|
if (m_Init && (m_ViewWidth != m_TexWidth || m_ViewHeight != m_TexHeight))
|
|
{
|
|
this->glViewport(0, 0, 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());
|
|
const auto ember = m_FractoriumEmberController->CurrentEmber();
|
|
return (ember &&
|
|
ember->m_FinalRasW == m_GL->m_TexWidth &&
|
|
ember->m_FinalRasH == m_GL->m_TexHeight &&
|
|
m_GL->m_TexWidth == m_GL->m_ViewWidth &&
|
|
m_GL->m_TexHeight == m_GL->m_ViewHeight);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draw the unit square.
|
|
/// </summary>
|
|
void GLWidget::DrawUnitSquare()
|
|
{
|
|
glLineWidth(1.0f * devicePixelRatioF());
|
|
#ifndef USE_GLSL
|
|
glBegin(GL_LINES);
|
|
glColor4f(1.0f, 1.0f, 1.0f, 0.25f);
|
|
glVertex2f(-1, -1);
|
|
glVertex2f( 1, -1);
|
|
glVertex2f(-1, 1);
|
|
glVertex2f( 1, 1);
|
|
glVertex2f(-1, -1);
|
|
glVertex2f(-1, 1);
|
|
glVertex2f( 1, -1);
|
|
glVertex2f( 1, 1);
|
|
glColor4f(1.0f, 0.0f, 0.0f, 0.5f);
|
|
glVertex2f(-1, 0);
|
|
glVertex2f( 1, 0);
|
|
glColor4f(0.0f, 1.0f, 0.0f, 0.5f);
|
|
glVertex2f( 0, -1);
|
|
glVertex2f( 0, 1);
|
|
glEnd();
|
|
#else
|
|
GLfloat vertices[] =//Should these be of type T?//TODO
|
|
{
|
|
-1, -1,
|
|
1, -1,
|
|
-1, 1,
|
|
1, 1,
|
|
-1, -1,
|
|
-1, 1,
|
|
1, -1,
|
|
1, 1
|
|
};
|
|
QVector4D col(1.0f, 1.0f, 1.0f, 0.25f);
|
|
DrawPointOrLine(col, vertices, 8, GL_LINES);
|
|
const GLfloat vertices2[] =//Should these be of type T?//TODO
|
|
{
|
|
-1, 0,
|
|
1, 0
|
|
};
|
|
QVector4D col2(1.0f, 0.0f, 0.0f, 0.5f);
|
|
DrawPointOrLine(col2, vertices2, 2, GL_LINES);
|
|
const GLfloat vertices3[] =//Should these be of type T?//TODO
|
|
{
|
|
0, -1,
|
|
0, 1
|
|
};
|
|
const QVector4D col3(0.0f, 1.0f, 0.0f, 0.5f);
|
|
DrawPointOrLine(col3, vertices3, 2, GL_LINES);
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draw the grid
|
|
/// The frequency of the grid lines will change depending on the zoom (ALT+WHEEL).
|
|
/// Calculated with the frame always centered, the renderer just moves the camera.
|
|
/// </summary>
|
|
template <typename T>
|
|
void GLEmberController<T>::DrawGrid()
|
|
{
|
|
const auto renderer = m_Fractorium->m_Controller->Renderer();
|
|
const auto scale = m_FractoriumEmberController->AffineScaleCurrentToLocked();
|
|
//qDebug() << renderer->UpperRightX(false) << " " << renderer->LowerLeftX(false) << " " << renderer->UpperRightY(false) << " " << renderer->LowerLeftY(false);
|
|
const float unitX = (std::abs(renderer->UpperRightX(false) - renderer->LowerLeftX(false)) / 2.0f) / scale;
|
|
const float unitY = (std::abs(renderer->UpperRightY(false) - renderer->LowerLeftY(false)) / 2.0f) / scale;
|
|
|
|
if (unitX > 100000 || unitY > 100000)//Need a better way to do this.//TODO
|
|
{
|
|
qDebug() << unitX << " " << unitY;
|
|
//return;
|
|
}
|
|
|
|
const float xLow = std::floor(-unitX);
|
|
const float xHigh = std::ceil(unitX);
|
|
const float yLow = std::floor(-unitY);
|
|
const float yHigh = std::ceil(unitY);
|
|
const float alpha = 0.25f;
|
|
const int xsteps = std::ceil(std::abs(xHigh - xLow) / GridStep);//Need these because sometimes the float value never reaches the max and it gets stuck in an infinite loop.
|
|
const int ysteps = std::ceil(std::abs(yHigh - yLow) / GridStep);
|
|
Affine2D<T> temp;
|
|
m_GL->glLineWidth(1.0f * m_GL->devicePixelRatioF());
|
|
#ifndef USE_GLSL
|
|
m4T mat = (temp * scale).ToMat4RowMajor();
|
|
m_GL->glPushMatrix();
|
|
m_GL->glLoadIdentity();
|
|
MultMatrix(mat);
|
|
m_GL->glBegin(GL_LINES);
|
|
m_GL->glColor4f(0.5f, 0.5f, 0.5f, alpha);
|
|
|
|
for (float fx = xLow, i = 0; fx <= xHigh && i < xsteps; fx += GridStep, i++)
|
|
{
|
|
m_GL->glVertex2f(fx, yLow);
|
|
m_GL->glVertex2f(fx, yHigh);
|
|
}
|
|
|
|
for (float fy = yLow, i = 0; fy < yHigh && i < ysteps; fy += GridStep, i++)
|
|
{
|
|
m_GL->glVertex2f(xLow, fy);
|
|
m_GL->glVertex2f(xHigh, fy);
|
|
}
|
|
|
|
m_GL->glColor4f(1.0f, 0.0f, 0.0f, alpha);
|
|
m_GL->glVertex2f(0.0f, 0.0f);
|
|
m_GL->glVertex2f(xHigh, 0.0f);
|
|
m_GL->glColor4f(0.5f, 0.0f, 0.0f, alpha);
|
|
m_GL->glVertex2f(0.0f, 0.0f);
|
|
m_GL->glVertex2f(xLow, 0.0f);
|
|
m_GL->glColor4f(0.0f, 1.0f, 0.0f, alpha);
|
|
m_GL->glVertex2f(0.0f, 0.0f);
|
|
m_GL->glVertex2f(0.0f, yHigh);
|
|
m_GL->glColor4f(0.0f, 0.5f, 0.0f, alpha);
|
|
m_GL->glVertex2f(0.0f, 0.0f);
|
|
m_GL->glVertex2f(0.0f, yLow);
|
|
m_GL->glColor4f(0.0f, 0.0f, 0.0f, 1.0f);
|
|
m_GL->glEnd();
|
|
m_GL->glPopMatrix();
|
|
#else
|
|
const m4T mat = (temp * scale).ToMat4ColMajor();
|
|
glm::tmat4x4<float, glm::defaultp> tempmat4 = mat;
|
|
m_GL->m_ModelViewMatrix = QMatrix4x4(glm::value_ptr(tempmat4));
|
|
m_GL->glLineWidth(1.0f * m_GL->devicePixelRatioF());
|
|
m_Verts.clear();
|
|
|
|
for (float fx = xLow, i = 0; fx <= xHigh && i < xsteps; fx += GridStep, i++)
|
|
{
|
|
m_Verts.push_back(fx);
|
|
m_Verts.push_back(yLow);
|
|
m_Verts.push_back(fx);
|
|
m_Verts.push_back(yHigh);
|
|
}
|
|
|
|
for (float fy = yLow, i = 0; fy < yHigh && i < ysteps; fy += GridStep, i++)
|
|
{
|
|
m_Verts.push_back(xLow);
|
|
m_Verts.push_back(fy);
|
|
m_Verts.push_back(xHigh);
|
|
m_Verts.push_back(fy);
|
|
}
|
|
|
|
QVector4D col(0.5f, 0.5f, 0.5f, alpha);
|
|
m_GL->DrawPointOrLine(col, m_Verts, GL_LINES);
|
|
m_Verts.clear();
|
|
m_Verts.push_back(0.0f);
|
|
m_Verts.push_back(0.0f);
|
|
m_Verts.push_back(xHigh);
|
|
m_Verts.push_back(0.0f);
|
|
col = QVector4D(1.0f, 0.0f, 0.0f, alpha);
|
|
m_GL->DrawPointOrLine(col, m_Verts, GL_LINES);
|
|
m_Verts.clear();
|
|
m_Verts.push_back(0.0f);
|
|
m_Verts.push_back(0.0f);
|
|
m_Verts.push_back(xLow);
|
|
m_Verts.push_back(0.0f);
|
|
col = QVector4D(0.5f, 0.0f, 0.0f, alpha);
|
|
m_GL->DrawPointOrLine(col, m_Verts, GL_LINES);
|
|
m_Verts.clear();
|
|
m_Verts.push_back(0.0f);
|
|
m_Verts.push_back(0.0f);
|
|
m_Verts.push_back(0.0f);
|
|
m_Verts.push_back(yHigh);
|
|
col = QVector4D(0.0f, 1.0f, 0.0f, alpha);
|
|
m_GL->DrawPointOrLine(col, m_Verts, GL_LINES);
|
|
m_Verts.clear();
|
|
m_Verts.push_back(0.0f);
|
|
m_Verts.push_back(0.0f);
|
|
m_Verts.push_back(0.0f);
|
|
m_Verts.push_back(yLow);
|
|
col = QVector4D(0.0f, 0.5f, 0.0f, alpha);
|
|
m_GL->DrawPointOrLine(col, m_Verts, GL_LINES);
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draw the pre or post affine circle for the passed in xform.
|
|
/// For drawing affine transforms, multiply the identity model view matrix by the
|
|
/// affine for each xform, so that all points are considered to be "1".
|
|
/// </summary>
|
|
/// <param name="xform">A pointer to the xform whose affine will be drawn</param>
|
|
/// <param name="pre">True for pre affine, else false for post.</param>
|
|
/// <param name="selected">True if selected (draw enclosing circle), else false (only draw axes).</param>
|
|
/// <param name="hovered">True if the xform is being hovered over (draw tansparent disc), else false (no disc).</param>
|
|
template <typename T>
|
|
void GLEmberController<T>::DrawAffine(const Xform<T>* xform, bool pre, bool selected, bool hovered)
|
|
{
|
|
const auto ember = m_FractoriumEmberController->CurrentEmber();
|
|
const auto final = ember->IsFinalXform(xform);
|
|
const auto index = ember->GetXformIndex(xform);
|
|
const auto size = ember->m_Palette.m_Entries.size();
|
|
const auto color = ember->m_Palette.m_Entries[Clamp<T>(xform->m_ColorX * size, 0, size - 1)];
|
|
const auto& affine = pre ? xform->m_Affine : xform->m_Post;
|
|
#ifndef USE_GLSL
|
|
//For some incredibly strange reason, even though glm and OpenGL use matrices with a column-major
|
|
//data layout, nothing will work here unless they are flipped to row major order. This is how it was
|
|
//done in Fractron.
|
|
m4T mat = (affine * m_FractoriumEmberController->AffineScaleCurrentToLocked()).ToMat4RowMajor();
|
|
m_GL->glPushMatrix();
|
|
m_GL->glLoadIdentity();
|
|
MultMatrix(mat);
|
|
//QueryMatrices(true);
|
|
m_GL->glLineWidth(3.0f * m_GL->devicePixelRatioF());//One 3px wide, colored black, except green on x axis for post affine.
|
|
m_GL->DrawAffineHelper(index, selected, hovered, pre, final, true);
|
|
m_GL->glLineWidth(1.0f * m_GL->devicePixelRatioF());//Again 1px wide, colored white, to give a white middle with black outline effect.
|
|
m_GL->DrawAffineHelper(index, selected, hovered, pre, final, false);
|
|
m_GL->glPointSize(5.0f * m_GL->devicePixelRatioF());//Three black points, one in the center and two on the circle. Drawn big 5px first to give a black outline.
|
|
m_GL->glBegin(GL_POINTS);
|
|
m_GL->glColor4f(0.0f, 0.0f, 0.0f, selected ? 1.0f : 0.5f);
|
|
m_GL->glVertex2f(0.0f, 0.0f);
|
|
m_GL->glVertex2f(1.0f, 0.0f);
|
|
m_GL->glVertex2f(0.0f, 1.0f);
|
|
m_GL->glEnd();
|
|
m_GL->glLineWidth(2.0f * m_GL->devicePixelRatioF());//Draw lines again for y axis only, without drawing the circle, using the color of the selected xform.
|
|
m_GL->glBegin(GL_LINES);
|
|
m_GL->glColor4f(color.r, color.g, color.b, 1.0f);
|
|
m_GL->glVertex2f(0.0f, 0.0f);
|
|
m_GL->glVertex2f(0.0f, 1.0f);
|
|
m_GL->glEnd();
|
|
m_GL->glPointSize(3.0f * m_GL->devicePixelRatioF());//Draw smaller white points, to give a black outline effect.
|
|
m_GL->glBegin(GL_POINTS);
|
|
m_GL->glColor4f(1.0f, 1.0f, 1.0f, selected ? 1.0f : 0.5f);
|
|
m_GL->glVertex2f(0.0f, 0.0f);
|
|
m_GL->glVertex2f(1.0f, 0.0f);
|
|
m_GL->glVertex2f(0.0f, 1.0f);
|
|
m_GL->glColor4f(0.0f, 0.0f, 0.0f, 1.0f);
|
|
m_GL->glEnd();
|
|
m_GL->glPopMatrix();
|
|
#else
|
|
const m4T mat = (affine * m_FractoriumEmberController->AffineScaleCurrentToLocked()).ToMat4ColMajor();
|
|
glm::tmat4x4<float, glm::defaultp> tempmat4 = mat;
|
|
m_GL->m_ModelViewMatrix = QMatrix4x4(glm::value_ptr(tempmat4));
|
|
m_GL->DrawAffineHelper(index, 1.0, 3.0, selected, hovered, pre, final, true);//Circle line width is thinner than the line width for the axes, just to distinguish it.
|
|
m_GL->DrawAffineHelper(index, 0.5, 2.0, selected, hovered, pre, final, false);
|
|
QVector4D col(0.0f, 0.0f, 0.0f, selected ? 1.0f : 0.5f);
|
|
m_Verts.clear();
|
|
m_Verts.push_back(0.0f);
|
|
m_Verts.push_back(0.0f);
|
|
m_Verts.push_back(1.0f);
|
|
m_Verts.push_back(0.0f);
|
|
m_Verts.push_back(0.0f);
|
|
m_Verts.push_back(1.0f);
|
|
m_GL->DrawPointOrLine(col, m_Verts, GL_POINTS, !pre, 6.0f);//Three black points, one in the center and two on the circle. Drawn big 5px first to give a black outline.
|
|
//Somewhat of a hack, since it's drawing over the Y axis line it just drew.
|
|
m_GL->glLineWidth(2.0f * m_GL->devicePixelRatioF());//Draw lines again for y axis only, using the color of the selected xform.
|
|
m_Verts.clear();
|
|
m_Verts.push_back(0.0f);
|
|
m_Verts.push_back(0.0f);
|
|
m_Verts.push_back(0.0f);
|
|
m_Verts.push_back(1.0f);
|
|
col = QVector4D(color.r, color.g, color.b, 1.0f);
|
|
m_GL->DrawPointOrLine(col, m_Verts, GL_LINES, !pre);
|
|
//Line from x to y in the color of the xform combo, thinner and solid with no background to somewhat distinguish it.
|
|
m_GL->glLineWidth(0.5f * m_GL->devicePixelRatioF());
|
|
m_Verts.clear();
|
|
m_Verts.push_back(0);
|
|
m_Verts.push_back(1);
|
|
m_Verts.push_back(1);
|
|
m_Verts.push_back(0);
|
|
auto qcol = final ? m_Fractorium->m_FinalXformComboColor : m_Fractorium->m_XformComboColors[index % XFORM_COLOR_COUNT];
|
|
m_GL->DrawPointOrLine(QVector4D(qcol.redF(), qcol.greenF(), qcol.blueF(), 1.0f), m_Verts, GL_LINES, !pre);
|
|
//
|
|
m_GL->glLineWidth(2.0f * m_GL->devicePixelRatioF());
|
|
m_Verts.clear();
|
|
m_Verts.push_back(0.0f);//Center.
|
|
m_Verts.push_back(0.0f);
|
|
col = QVector4D(1.0f, 1.0f, 1.0f, selected ? 1.0f : 0.5f);
|
|
m_GL->DrawPointOrLine(col, m_Verts, GL_POINTS, false, 5.0f);//Draw smaller white point, to give a black outline effect.
|
|
m_Verts.clear();
|
|
m_Verts.push_back(1.0f);//X axis.
|
|
m_Verts.push_back(0.0f);
|
|
col = QVector4D(0.0f, 1.0f, 0.0f, selected ? 1.0f : 0.5f);
|
|
m_GL->DrawPointOrLine(col, m_Verts, GL_POINTS, false, 5.0f);//Draw smaller green point, to give a black outline effect.
|
|
m_Verts.clear();
|
|
m_Verts.push_back(0.0f);//Y axis.
|
|
m_Verts.push_back(1.0f);
|
|
col = QVector4D(1.0f, 0.0f, 1.0f, selected ? 1.0f : 0.5f);
|
|
m_GL->DrawPointOrLine(col, m_Verts, GL_POINTS, false, 5.0f);//Draw smaller purple point, to give a black outline effect.
|
|
m_GL->m_ModelViewMatrix.setToIdentity();
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draw the axes, and optionally the surrounding circle
|
|
/// of an affine transform.
|
|
/// </summary>
|
|
/// <param name="index"></param>
|
|
/// <param name="circleWidth"></param>
|
|
/// <param name="lineWidth"></param>
|
|
/// <param name="selected">True if selected (draw enclosing circle), else false (only draw axes).</param>
|
|
/// <param name="hovered">True if the xform is being hovered over (draw tansparent disc), else false (no disc).</param>
|
|
/// <param name="pre"></param>
|
|
/// <param name="final"></param>
|
|
/// <param name="background"></param>
|
|
void GLWidget::DrawAffineHelper(int index, float circleWidth, float lineWidth, bool selected, bool hovered, bool pre, bool final, bool background)
|
|
{
|
|
float px = 1.0f;
|
|
float py = 0.0f;
|
|
const auto col = final ? m_Fractorium->m_FinalXformComboColor : m_Fractorium->m_XformComboColors[index % XFORM_COLOR_COUNT];
|
|
#ifndef USE_GLSL
|
|
glBegin(GL_LINES);
|
|
|
|
//Circle part.
|
|
if (!background)
|
|
{
|
|
glColor4f(col.redF(), col.greenF(), col.blueF(), 1.0f);//Draw pre affine transform with white.
|
|
}
|
|
else
|
|
{
|
|
glColor4f(0.0f, 0.0f, 0.0f, 1.0f);//Draw pre affine transform outline with white.
|
|
}
|
|
|
|
if (selected)
|
|
{
|
|
for (size_t i = 1; i <= 64; i++)//The circle.
|
|
{
|
|
float theta = float(M_PI) * 2.0f * float(i % 64) / 64.0f;
|
|
float fx = std::cos(theta);
|
|
float fy = std::sin(theta);
|
|
glVertex2f(px, py);
|
|
glVertex2f(fx, fy);
|
|
px = fx;
|
|
py = fy;
|
|
}
|
|
}
|
|
|
|
//Lines from center to circle.
|
|
if (!background)
|
|
{
|
|
glColor4f(col.redF(), col.greenF(), col.blueF(), 1.0f);
|
|
}
|
|
else
|
|
{
|
|
if (pre)
|
|
glColor4f(0.0f, 0.0f, 0.0f, 1.0f);//Draw pre affine transform outline with white.
|
|
else
|
|
glColor4f(0.0f, 0.75f, 0.0f, 1.0f);//Draw post affine transform outline with green.
|
|
}
|
|
|
|
//The lines from the center to the circle.
|
|
glVertex2f(0.0f, 0.0f);//X axis.
|
|
glVertex2f(1.0f, 0.0f);
|
|
|
|
if (background)
|
|
glColor4f(0.0f, 0.0f, 0.0f, 1.0f);
|
|
|
|
glVertex2f(0.0f, 0.0f);//Y axis.
|
|
glVertex2f(0.0f, 1.0f);
|
|
glEnd();
|
|
#else
|
|
QVector4D color;
|
|
|
|
//Circle part.
|
|
if (!background)
|
|
{
|
|
color = QVector4D(col.redF(), col.greenF(), col.blueF(), hovered ? 0.25f : 1.0f);//Draw pre affine transform with white.
|
|
}
|
|
else
|
|
{
|
|
color = QVector4D(0.0f, 0.0f, 0.0f, hovered ? 0.25f : 1.0f);//Draw pre affine transform outline with white.
|
|
}
|
|
|
|
m_Verts.clear();
|
|
glLineWidth(circleWidth * devicePixelRatioF());//One thinner, colored black, except green on x axis for post affine.
|
|
|
|
if (selected || hovered)
|
|
{
|
|
for (size_t i = 1; i <= 64; i++)//The circle.
|
|
{
|
|
const float theta = static_cast<float>(M_PI) * 2.0f * static_cast<float>(i % 64) / 64.0f;
|
|
const float fx = std::cos(theta);
|
|
const float fy = std::sin(theta);
|
|
m_Verts.push_back(fx);
|
|
m_Verts.push_back(fy);
|
|
}
|
|
|
|
DrawPointOrLine(color, m_Verts, hovered ? GL_TRIANGLE_FAN : GL_LINE_LOOP, !pre);
|
|
}
|
|
|
|
glLineWidth(lineWidth * devicePixelRatioF());//One thicker, colored black, except green on x axis for post affine.
|
|
|
|
//Lines from center to circle.
|
|
if (!background)
|
|
{
|
|
color = QVector4D(col.redF(), col.greenF(), col.blueF(), 1.0f);
|
|
}
|
|
else
|
|
{
|
|
if (pre)
|
|
color = QVector4D(0.0f, 0.0f, 0.0f, 1.0f);//Draw pre affine transform outline with white.
|
|
else
|
|
color = QVector4D(0.0f, 0.75f, 0.0f, 1.0f);//Draw post affine transform outline with green.
|
|
}
|
|
|
|
//The lines from the center to the circle.
|
|
m_Verts.clear();
|
|
m_Verts.push_back(0);//X axis.
|
|
m_Verts.push_back(0);
|
|
m_Verts.push_back(1);
|
|
m_Verts.push_back(0);
|
|
DrawPointOrLine(color, m_Verts, GL_LINES, !pre);
|
|
|
|
if (background)
|
|
color = QVector4D(0.0f, 0.0f, 0.0f, 1.0f);
|
|
|
|
m_Verts.clear();
|
|
m_Verts.push_back(0);//Y axis.
|
|
m_Verts.push_back(0);
|
|
m_Verts.push_back(0);
|
|
m_Verts.push_back(1);
|
|
DrawPointOrLine(color, m_Verts, GL_LINES, !pre);
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determine the index of the xform being hovered over if any.
|
|
/// Give precedence to the currently selected xform, if any.
|
|
/// </summary>
|
|
/// <param name="glCoords">The mouse raster coordinates to check</param>
|
|
/// <returns>The index of the xform being hovered over, else -1 if no hover.</returns>
|
|
template <typename T>
|
|
int GLEmberController<T>::UpdateHover(const v3T& glCoords)
|
|
{
|
|
const bool pre = m_Fractorium->DrawPreAffines();
|
|
const bool post = m_Fractorium->DrawPostAffines();
|
|
int i = 0, bestIndex = -1;
|
|
T bestDist = 10;
|
|
const auto ember = m_FractoriumEmberController->CurrentEmber();
|
|
m_HoverType = eHoverType::HoverNone;
|
|
|
|
if (m_Fractorium->DrawXforms())//Don't bother checking anything if the user wants to see no xforms.
|
|
{
|
|
const bool forceFinal = m_Fractorium->HaveFinal();
|
|
|
|
//If there's a selected/current xform, check it first so it gets precedence over the others.
|
|
if (m_SelectedXform)
|
|
{
|
|
//These checks prevent highlighting the pre/post selected xform circle, when one is set to show all, and the other
|
|
//is set to show current, and the user hovers over another xform, but doesn't select it, then moves the mouse
|
|
//back over the hidden circle for the pre/post that was set to only show current.
|
|
const bool isSel = m_Fractorium->IsXformSelected(ember->GetTotalXformIndex(m_SelectedXform));
|
|
const bool checkPre = pre && (m_Fractorium->DrawAllPre() || (m_Fractorium->DrawSelectedPre() && isSel));
|
|
const bool checkPost = post && (m_Fractorium->DrawAllPost() || (m_Fractorium->DrawSelectedPost() && isSel));
|
|
|
|
if (CheckXformHover(m_SelectedXform, glCoords, bestDist, checkPre, checkPost))
|
|
{
|
|
m_HoverXform = m_SelectedXform;
|
|
bestIndex = static_cast<int>(ember->GetTotalXformIndex(m_SelectedXform, forceFinal));
|
|
}
|
|
}
|
|
|
|
//Check all xforms.
|
|
while (const auto xform = ember->GetTotalXform(i, forceFinal))
|
|
{
|
|
const bool isSel = m_Fractorium->IsXformSelected(i);
|
|
|
|
if (pre)
|
|
{
|
|
const bool checkPre = m_Fractorium->DrawAllPre() || (m_Fractorium->DrawSelectedPre() && isSel) || (m_SelectedXform == xform);
|
|
|
|
if (checkPre)//Only check pre affine if they are shown.
|
|
{
|
|
if (CheckXformHover(xform, glCoords, bestDist, true, false))
|
|
{
|
|
m_HoverXform = xform;
|
|
bestIndex = i;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (post)
|
|
{
|
|
const bool checkPost = m_Fractorium->DrawAllPost() || (m_Fractorium->DrawSelectedPost() && isSel) || (m_SelectedXform == xform);
|
|
|
|
if (checkPost)
|
|
{
|
|
if (CheckXformHover(xform, glCoords, bestDist, false, true))
|
|
{
|
|
m_HoverXform = xform;
|
|
bestIndex = i;
|
|
}
|
|
}
|
|
}
|
|
|
|
i++;
|
|
}
|
|
}
|
|
|
|
return bestIndex;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determine the passed in xform's pre/post affine transforms are being hovered over.
|
|
/// Meant to be called in succession when checking all xforms for hover, and the best
|
|
/// hover distance is recorded in the bestDist reference parameter.
|
|
/// Mouse coordinates will be converted internally to world cartesian coordinates for checking.
|
|
/// </summary>
|
|
/// <param name="xform">A pointer to the xform to check for hover</param>
|
|
/// <param name="glCoords">The mouse raster coordinates to check</param>
|
|
/// <param name="bestDist">Reference to hold the best distance found so far</param>
|
|
/// <param name="pre">True to check pre affine, else don't.</param>
|
|
/// <param name="post">True to check post affine, else don't.</param>
|
|
/// <returns>True if hovering and the distance is smaller than the bestDist parameter</returns>
|
|
template <typename T>
|
|
bool GLEmberController<T>::CheckXformHover(const Xform<T>* xform, const v3T& glCoords, T& bestDist, bool pre, bool post)
|
|
{
|
|
bool preFound = false, postFound = false;
|
|
T dist = 0;
|
|
const auto scale = m_FractoriumEmberController->AffineScaleCurrentToLocked();
|
|
v3T pos;
|
|
|
|
if (pre)
|
|
{
|
|
const auto affineScaled = xform->m_Affine * scale;
|
|
const v3T translation(affineScaled.C(), affineScaled.F(), 0);
|
|
const v3T transScreen = glm::project(translation, m_Modelview, m_Projection, m_Viewport);
|
|
const v3T xAxis(affineScaled.A(), affineScaled.D(), 0);
|
|
const v3T xAxisScreen = glm::project(translation + xAxis, m_Modelview, m_Projection, m_Viewport);
|
|
const v3T yAxis(affineScaled.B(), affineScaled.E(), 0);
|
|
const v3T yAxisScreen = glm::project(translation + yAxis, m_Modelview, m_Projection, m_Viewport);
|
|
pos = translation;
|
|
dist = glm::distance(glCoords, transScreen);
|
|
|
|
if (dist < bestDist)
|
|
{
|
|
bestDist = dist;
|
|
m_HoverType = eHoverType::HoverTranslation;
|
|
m_HoverHandlePos = pos;
|
|
preFound = true;
|
|
}
|
|
|
|
pos = translation + xAxis;
|
|
dist = glm::distance(glCoords, xAxisScreen);
|
|
|
|
if (dist < bestDist)
|
|
{
|
|
bestDist = dist;
|
|
m_HoverType = eHoverType::HoverXAxis;
|
|
m_HoverHandlePos = pos;
|
|
preFound = true;
|
|
}
|
|
|
|
pos = translation + yAxis;
|
|
dist = glm::distance(glCoords, yAxisScreen);
|
|
|
|
if (dist < bestDist)
|
|
{
|
|
bestDist = dist;
|
|
m_HoverType = eHoverType::HoverYAxis;
|
|
m_HoverHandlePos = pos;
|
|
preFound = true;
|
|
}
|
|
|
|
if (preFound)
|
|
m_AffineType = eAffineType::AffinePre;
|
|
}
|
|
|
|
if (post)
|
|
{
|
|
const auto affineScaled = xform->m_Post * scale;
|
|
const v3T translation(affineScaled.C(), affineScaled.F(), 0);
|
|
const v3T transScreen = glm::project(translation, m_Modelview, m_Projection, m_Viewport);
|
|
const v3T xAxis(affineScaled.A(), affineScaled.D(), 0);
|
|
const v3T xAxisScreen = glm::project(translation + xAxis, m_Modelview, m_Projection, m_Viewport);
|
|
const v3T yAxis(affineScaled.B(), affineScaled.E(), 0);
|
|
const v3T yAxisScreen = glm::project(translation + yAxis, m_Modelview, m_Projection, m_Viewport);
|
|
pos = translation;
|
|
dist = glm::distance(glCoords, transScreen);
|
|
|
|
if (dist < bestDist)
|
|
{
|
|
bestDist = dist;
|
|
m_HoverType = eHoverType::HoverTranslation;
|
|
m_HoverHandlePos = pos;
|
|
postFound = true;
|
|
}
|
|
|
|
pos = translation + xAxis;
|
|
dist = glm::distance(glCoords, xAxisScreen);
|
|
|
|
if (dist < bestDist)
|
|
{
|
|
bestDist = dist;
|
|
m_HoverType = eHoverType::HoverXAxis;
|
|
m_HoverHandlePos = pos;
|
|
postFound = true;
|
|
}
|
|
|
|
pos = translation + yAxis;
|
|
dist = glm::distance(glCoords, yAxisScreen);
|
|
|
|
if (dist < bestDist)
|
|
{
|
|
bestDist = dist;
|
|
m_HoverType = eHoverType::HoverYAxis;
|
|
m_HoverHandlePos = pos;
|
|
postFound = true;
|
|
}
|
|
|
|
if (postFound)
|
|
m_AffineType = eAffineType::AffinePost;
|
|
}
|
|
|
|
return preFound || postFound;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculate the new affine transform when dragging with the x axis with the left mouse button.
|
|
/// The value returned will depend on whether any modifier keys were held down.
|
|
/// None: Rotate only.
|
|
/// Local Pivot:
|
|
/// Shift: Scale and optionally Rotate about affine center.
|
|
/// Alt: Rotate single axis about affine center.
|
|
/// Shift + Alt: Free transform.
|
|
/// Control: Rotate, snapping to grid.
|
|
/// Control + Shift: Scale and optionally Rotate, snapping to grid.
|
|
/// Control + Alt: Rotate single axis about affine center, snapping to grid.
|
|
/// Control + Shift + Alt: Free transform, snapping to grid.
|
|
/// World Pivot:
|
|
/// Shift + Alt: Rotate single axis about world center.
|
|
/// Control + Shift + Alt: Rotate single axis about world center, snapping to grid.
|
|
/// All others are the same as local pivot.
|
|
/// </summary>
|
|
/// <returns>The new affine transform to be assigned to the selected xform</returns>
|
|
template <typename T>
|
|
void GLEmberController<T>::CalcDragXAxis()
|
|
{
|
|
const T affineToWorldScale = static_cast<T>(m_FractoriumEmberController->AffineScaleLockedToCurrent());
|
|
const T worldToAffineScale = static_cast<T>(m_FractoriumEmberController->AffineScaleCurrentToLocked());
|
|
const bool pre = m_AffineType == eAffineType::AffinePre;
|
|
const bool worldPivotShiftAlt = !m_Fractorium->LocalPivot() && GetShift() && GetAlt();
|
|
const auto worldRelAxisStartScaled = (v2T(m_HoverHandlePos) * affineToWorldScale) - m_DragSrcTransform.O();//World axis start position, relative, scaled by the zoom.
|
|
const T startAngle = std::atan2(worldRelAxisStartScaled.y, worldRelAxisStartScaled.x);
|
|
v3T relScaled = (m_MouseWorldPos * affineToWorldScale) - v3T(m_DragSrcTransform.O(), 0);
|
|
|
|
if (!GetShift())
|
|
{
|
|
if (GetControl())
|
|
{
|
|
relScaled = SnapToNormalizedAngle(relScaled, 24u);//relScaled is using the relative scaled position of the axis.
|
|
}
|
|
|
|
const T endAngle = std::atan2(relScaled.y, relScaled.x);
|
|
const T angle = startAngle - endAngle;
|
|
m_FractoriumEmberController->UpdateXform([&](Xform<T>* xform, size_t xfindex, size_t selIndex)
|
|
{
|
|
auto it = m_DragSrcPreTransforms.find(xfindex);
|
|
|
|
if (it != m_DragSrcPreTransforms.end())
|
|
{
|
|
auto src = it->second;
|
|
auto& affine = xform->m_Affine;
|
|
|
|
if (GetAlt())
|
|
{
|
|
src.Rotate(angle);
|
|
affine.X(src.X());
|
|
}
|
|
else
|
|
{
|
|
src.Rotate(angle);
|
|
affine = src;
|
|
}
|
|
}
|
|
|
|
it = m_DragSrcPostTransforms.find(xfindex);
|
|
|
|
if (it != m_DragSrcPostTransforms.end())
|
|
{
|
|
auto src = it->second;
|
|
auto& affine = xform->m_Post;
|
|
|
|
if (GetAlt())
|
|
{
|
|
src.Rotate(angle);
|
|
affine.X(src.X());
|
|
}
|
|
else
|
|
{
|
|
src.Rotate(angle);
|
|
affine = src;
|
|
}
|
|
}
|
|
|
|
const auto& affine = pre ? xform->m_Affine : xform->m_Post;
|
|
|
|
if (xform == m_FractoriumEmberController->CurrentXform())
|
|
m_DragHandlePos = v3T((affine.O() + affine.X()) * worldToAffineScale, 0);
|
|
}, eXformUpdate::UPDATE_SELECTED, false);//Calling code will update renderer.
|
|
}
|
|
else
|
|
{
|
|
auto origmag = Zeps(glm::length(m_DragSrcTransform.X()));//Magnitude of original dragged axis before it was dragged.
|
|
|
|
if (GetControl())
|
|
{
|
|
relScaled = SnapToGrid(relScaled);
|
|
}
|
|
|
|
const auto newmag = glm::length(relScaled);
|
|
const auto newprc = newmag / origmag;
|
|
const T endAngle = std::atan2(relScaled.y, relScaled.x);
|
|
const T angle = startAngle - endAngle;
|
|
m_FractoriumEmberController->UpdateXform([&](Xform<T>* xform, size_t xfindex, size_t selIndex)
|
|
{
|
|
auto it = m_DragSrcPreTransforms.find(xfindex);
|
|
|
|
if (it != m_DragSrcPreTransforms.end())
|
|
{
|
|
auto src = it->second;
|
|
auto& affine = xform->m_Affine;
|
|
|
|
if (worldPivotShiftAlt)
|
|
{
|
|
src.X(src.O() + src.X());
|
|
src.O(v2T(0));
|
|
src.Rotate(angle);
|
|
affine.X(src.X() - affine.O());
|
|
}
|
|
else if (GetAlt())
|
|
{
|
|
affine.X(v2T(relScaled));//Absolute, not ratio.
|
|
}
|
|
else
|
|
{
|
|
src.ScaleXY(newprc);
|
|
|
|
if (m_Fractorium->m_Settings->RotateAndScale())
|
|
src.Rotate(angle);
|
|
|
|
affine = src;
|
|
}
|
|
}
|
|
|
|
it = m_DragSrcPostTransforms.find(xfindex);
|
|
|
|
if (it != m_DragSrcPostTransforms.end())
|
|
{
|
|
auto src = it->second;
|
|
auto& affine = xform->m_Post;
|
|
|
|
if (worldPivotShiftAlt)
|
|
{
|
|
src.X(src.O() + src.X());
|
|
src.O(v2T(0));
|
|
src.Rotate(angle);
|
|
affine.X(src.X() - affine.O());
|
|
}
|
|
else if (GetAlt())
|
|
{
|
|
affine.X(v2T(relScaled));//Absolute, not ratio.
|
|
}
|
|
else
|
|
{
|
|
src.ScaleXY(newprc);
|
|
|
|
if (m_Fractorium->m_Settings->RotateAndScale())
|
|
src.Rotate(angle);
|
|
|
|
affine = src;
|
|
}
|
|
}
|
|
|
|
const auto& affine = pre ? xform->m_Affine : xform->m_Post;
|
|
|
|
if (xform == m_FractoriumEmberController->CurrentXform())
|
|
m_DragHandlePos = v3T((affine.O() + affine.X()) * worldToAffineScale, 0);
|
|
}, eXformUpdate::UPDATE_SELECTED, false);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculate the new affine transform when dragging with the y axis with the left mouse button.
|
|
/// The value returned will depend on whether any modifier keys were held down.
|
|
/// None: Rotate only.
|
|
/// Local Pivot:
|
|
/// Shift: Scale and optionally Rotate about affine center.
|
|
/// Alt: Rotate single axis about affine center.
|
|
/// Shift + Alt: Free transform.
|
|
/// Control: Rotate, snapping to grid.
|
|
/// Control + Shift: Scale and optionally Rotate, snapping to grid.
|
|
/// Control + Alt: Rotate single axis about affine center, snapping to grid.
|
|
/// Control + Shift + Alt: Free transform, snapping to grid.
|
|
/// World Pivot:
|
|
/// Shift + Alt: Rotate single axis about world center.
|
|
/// Control + Shift + Alt: Rotate single axis about world center, snapping to grid.
|
|
/// All others are the same as local pivot.
|
|
/// </summary>
|
|
/// <returns>The new affine transform to be assigned to the selected xform</returns>
|
|
template <typename T>
|
|
void GLEmberController<T>::CalcDragYAxis()
|
|
{
|
|
const T affineToWorldScale = static_cast<T>(m_FractoriumEmberController->AffineScaleLockedToCurrent());
|
|
const T worldToAffineScale = static_cast<T>(m_FractoriumEmberController->AffineScaleCurrentToLocked());
|
|
const bool pre = m_AffineType == eAffineType::AffinePre;
|
|
const bool worldPivotShiftAlt = !m_Fractorium->LocalPivot() && GetShift() && GetAlt();
|
|
const auto worldRelAxisStartScaled = (v2T(m_HoverHandlePos) * affineToWorldScale) - m_DragSrcTransform.O();//World axis start position, relative, scaled by the zoom.
|
|
const T startAngle = std::atan2(worldRelAxisStartScaled.y, worldRelAxisStartScaled.x);
|
|
v3T relScaled = (m_MouseWorldPos * affineToWorldScale) - v3T(m_DragSrcTransform.O(), 0);
|
|
|
|
if (!GetShift())
|
|
{
|
|
if (GetControl())
|
|
{
|
|
relScaled = SnapToNormalizedAngle(relScaled, 24u);//relScaled is using the relative scaled position of the axis.
|
|
}
|
|
|
|
const T endAngle = std::atan2(relScaled.y, relScaled.x);
|
|
const T angle = startAngle - endAngle;
|
|
m_FractoriumEmberController->UpdateXform([&](Xform<T>* xform, size_t xfindex, size_t selIndex)
|
|
{
|
|
auto it = m_DragSrcPreTransforms.find(xfindex);
|
|
|
|
if (it != m_DragSrcPreTransforms.end())
|
|
{
|
|
auto src = it->second;
|
|
auto& affine = xform->m_Affine;
|
|
|
|
if (GetAlt())
|
|
{
|
|
src.Rotate(angle);
|
|
affine.Y(src.Y());
|
|
}
|
|
else
|
|
{
|
|
src.Rotate(angle);
|
|
affine = src;
|
|
}
|
|
}
|
|
|
|
it = m_DragSrcPostTransforms.find(xfindex);
|
|
|
|
if (it != m_DragSrcPostTransforms.end())
|
|
{
|
|
auto src = it->second;
|
|
auto& affine = xform->m_Post;
|
|
|
|
if (GetAlt())
|
|
{
|
|
src.Rotate(angle);
|
|
affine.Y(src.Y());
|
|
}
|
|
else
|
|
{
|
|
src.Rotate(angle);
|
|
affine = src;
|
|
}
|
|
}
|
|
|
|
const auto& affine = pre ? xform->m_Affine : xform->m_Post;
|
|
|
|
if (xform == m_FractoriumEmberController->CurrentXform())
|
|
m_DragHandlePos = v3T((affine.O() + affine.Y()) * worldToAffineScale, 0);
|
|
}, eXformUpdate::UPDATE_SELECTED, false);//Calling code will update renderer.
|
|
}
|
|
else
|
|
{
|
|
const auto origmag = Zeps(glm::length(m_DragSrcTransform.Y()));//Magnitude of original dragged axis before it was dragged.
|
|
|
|
if (GetControl())
|
|
{
|
|
relScaled = SnapToGrid(relScaled);
|
|
}
|
|
|
|
const auto newmag = glm::length(relScaled);
|
|
const auto newprc = newmag / origmag;
|
|
const T endAngle = std::atan2(relScaled.y, relScaled.x);
|
|
const T angle = startAngle - endAngle;
|
|
m_FractoriumEmberController->UpdateXform([&](Xform<T>* xform, size_t xfindex, size_t selIndex)
|
|
{
|
|
auto it = m_DragSrcPreTransforms.find(xfindex);
|
|
|
|
if (it != m_DragSrcPreTransforms.end())
|
|
{
|
|
auto src = it->second;
|
|
auto& affine = xform->m_Affine;
|
|
|
|
if (worldPivotShiftAlt)
|
|
{
|
|
src.Y(src.O() + src.Y());
|
|
src.O(v2T(0));
|
|
src.Rotate(angle);
|
|
affine.Y(src.Y() - affine.O());
|
|
}
|
|
else if (GetAlt())
|
|
{
|
|
affine.Y(v2T(relScaled));//Absolute, not ratio.
|
|
}
|
|
else
|
|
{
|
|
src.ScaleXY(newprc);
|
|
|
|
if (m_Fractorium->m_Settings->RotateAndScale())
|
|
src.Rotate(angle);
|
|
|
|
affine = src;
|
|
}
|
|
}
|
|
|
|
it = m_DragSrcPostTransforms.find(xfindex);
|
|
|
|
if (it != m_DragSrcPostTransforms.end())
|
|
{
|
|
auto src = it->second;
|
|
auto& affine = xform->m_Post;
|
|
|
|
if (worldPivotShiftAlt)
|
|
{
|
|
src.Y(src.O() + src.Y());
|
|
src.O(v2T(0));
|
|
src.Rotate(angle);
|
|
affine.Y(src.Y() - affine.O());
|
|
}
|
|
else if (GetAlt())
|
|
{
|
|
affine.Y(v2T(relScaled));//Absolute, not ratio.
|
|
}
|
|
else
|
|
{
|
|
src.ScaleXY(newprc);
|
|
|
|
if (m_Fractorium->m_Settings->RotateAndScale())
|
|
src.Rotate(angle);
|
|
|
|
affine = src;
|
|
}
|
|
}
|
|
|
|
const auto& affine = pre ? xform->m_Affine : xform->m_Post;
|
|
|
|
if (xform == m_FractoriumEmberController->CurrentXform())
|
|
m_DragHandlePos = v3T((affine.O() + affine.Y()) * worldToAffineScale, 0);
|
|
}, eXformUpdate::UPDATE_SELECTED, false);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculate the new affine transform when dragging the center with the left mouse button.
|
|
/// The value returned will depend on whether any modifier keys were held down.
|
|
/// None: Free transform.
|
|
/// Local Pivot:
|
|
/// Shift: Rotate about world center, keeping orientation the same.
|
|
/// Control: Free transform, snapping to grid.
|
|
/// Control + Shift: Rotate about world center, keeping orientation the same, snapping to grid.
|
|
/// World Pivot:
|
|
/// Shift: Rotate about world center, rotating orientation.
|
|
/// Control + Shift: Rotate about world center, rotating orientation, snapping to grid.
|
|
/// All others are the same as local pivot.
|
|
/// </summary>
|
|
template <typename T>
|
|
void GLEmberController<T>::CalcDragTranslation()
|
|
{
|
|
const T affineToWorldScale = static_cast<T>(m_FractoriumEmberController->AffineScaleLockedToCurrent());
|
|
const T worldToAffineScale = static_cast<T>(m_FractoriumEmberController->AffineScaleCurrentToLocked());
|
|
const bool worldPivotShift = !m_Fractorium->LocalPivot() && GetShift();
|
|
const bool pre = m_AffineType == eAffineType::AffinePre;
|
|
|
|
if (GetShift())
|
|
{
|
|
const v3T snapped = GetControl() ? SnapToNormalizedAngle(m_MouseWorldPos, 24) : m_MouseWorldPos;
|
|
const T startAngle = std::atan2(m_DragSrcTransform.O().y, m_DragSrcTransform.O().x);
|
|
const T endAngle = std::atan2(snapped.y, snapped.x);
|
|
const T angle = startAngle - endAngle;
|
|
m_FractoriumEmberController->UpdateXform([&](Xform<T>* xform, size_t xfindex, size_t selIndex)
|
|
{
|
|
auto it = m_DragSrcPreTransforms.find(xfindex);
|
|
|
|
if (it != m_DragSrcPreTransforms.end())
|
|
{
|
|
auto src = it->second;
|
|
auto& affine = xform->m_Affine;
|
|
src.RotateTrans(angle);
|
|
|
|
if (worldPivotShift)
|
|
{
|
|
src.Rotate(angle);
|
|
affine.X(src.X());
|
|
affine.Y(src.Y());
|
|
}
|
|
|
|
affine.O(src.O());
|
|
}
|
|
|
|
it = m_DragSrcPostTransforms.find(xfindex);
|
|
|
|
if (it != m_DragSrcPostTransforms.end())
|
|
{
|
|
auto src = it->second;
|
|
auto& affine = xform->m_Post;
|
|
src.RotateTrans(angle);
|
|
|
|
if (worldPivotShift)
|
|
{
|
|
src.Rotate(angle);
|
|
affine.X(src.X());
|
|
affine.Y(src.Y());
|
|
}
|
|
|
|
affine.O(src.O());
|
|
}
|
|
|
|
const auto& affine = pre ? xform->m_Affine : xform->m_Post;
|
|
|
|
if (xform == m_FractoriumEmberController->CurrentXform())
|
|
m_DragHandlePos = v3T(affine.O(), 0) * worldToAffineScale;
|
|
}, eXformUpdate::UPDATE_SELECTED, false);//Calling code will update renderer.
|
|
}
|
|
else
|
|
{
|
|
const auto diff = m_MouseWorldPos - m_MouseDownWorldPos;
|
|
|
|
if (GetControl())
|
|
{
|
|
m_FractoriumEmberController->UpdateXform([&](Xform<T>* xform, size_t xfindex, size_t selIndex)
|
|
{
|
|
auto it = m_DragSrcPreTransforms.find(xfindex);
|
|
|
|
if (it != m_DragSrcPreTransforms.end())
|
|
{
|
|
const auto& src = it->second;
|
|
auto& affine = xform->m_Affine;
|
|
const auto offset = src.O() + (affineToWorldScale * v2T(diff));
|
|
const auto snapped = SnapToGrid(offset);
|
|
affine.O(v2T(snapped.x, snapped.y));
|
|
}
|
|
|
|
it = m_DragSrcPostTransforms.find(xfindex);
|
|
|
|
if (it != m_DragSrcPostTransforms.end())
|
|
{
|
|
auto& src = it->second;
|
|
auto& affine = xform->m_Post;
|
|
auto offset = src.O() + (affineToWorldScale * v2T(diff));
|
|
auto snapped = SnapToGrid(offset);
|
|
affine.O(v2T(snapped.x, snapped.y));
|
|
}
|
|
|
|
auto& affine = pre ? xform->m_Affine : xform->m_Post;
|
|
|
|
if (xform == m_FractoriumEmberController->CurrentXform())
|
|
m_DragHandlePos = v3T(affine.O(), 0) * worldToAffineScale;
|
|
}, eXformUpdate::UPDATE_CURRENT_AND_SELECTED, false);
|
|
}
|
|
else
|
|
{
|
|
m_FractoriumEmberController->UpdateXform([&](Xform<T>* xform, size_t xfindex, size_t selIndex)
|
|
{
|
|
auto it = m_DragSrcPreTransforms.find(xfindex);
|
|
|
|
if (it != m_DragSrcPreTransforms.end())
|
|
{
|
|
auto& src = it->second;
|
|
auto& affine = xform->m_Affine;
|
|
affine.O(src.O() + (affineToWorldScale * v2T(diff)));
|
|
}
|
|
|
|
it = m_DragSrcPostTransforms.find(xfindex);
|
|
|
|
if (it != m_DragSrcPostTransforms.end())
|
|
{
|
|
auto& src = it->second;
|
|
auto& affine = xform->m_Post;
|
|
affine.O(src.O() + (affineToWorldScale * v2T(diff)));
|
|
}
|
|
|
|
auto& affine = pre ? xform->m_Affine : xform->m_Post;
|
|
|
|
if (xform == m_FractoriumEmberController->CurrentXform())
|
|
m_DragHandlePos = v3T(affine.O(), 0) * worldToAffineScale;
|
|
}, eXformUpdate::UPDATE_SELECTED, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Thin wrapper to check if all controllers are ok and return a pointer to the GLController.
|
|
/// </summary>
|
|
/// <returns>A pointer to the GLController if everything is ok, else false.</returns>
|
|
GLEmberControllerBase* GLWidget::GLController()
|
|
{
|
|
if (m_Fractorium && m_Fractorium->ControllersOk())
|
|
return m_Fractorium->m_Controller->GLController();
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
template class GLEmberController<float>;
|
|
|
|
#ifdef DO_DOUBLE
|
|
template class GLEmberController<double>;
|
|
#endif
|