mirror of
https://bitbucket.org/mfeemster/fractorium.git
synced 2025-01-21 21:20:07 -05:00
745f06d29d
-Remove the Type field from the variations tree and instead just put the type indicator icon next to the variation name. -Double clicking to toggle variation parameter spinners now resets the value to the default if there is one, else it uses zero. If it is already using the default, it is toggled to 0. -Add a new button to toggle xaos on and off. -When duplicating a flame, insert it immediately after the one being duplicated instead of at the end of the file. -When switching between flames in a file, keep the same xform index selected rather than resetting it to the first xform each time. -Create a threaded writer for the final render and EmberAnimate so the rendering process does not get delayed by file saving which may take a long time. -Remove warning which said "Frames per rot cannot be greater than one while Rotations is zero" when generating a sequence. -Add the Circle_Rand variation from Chaotica. -Add tool tips to clarify the following items: --Auto Unique Filenames checkbox in the options dialog. --Xaos table headers. --Bug fixes -Generating sequences using the following variations would be done incorrectly: circletrans1, collideoscope, crob, curlsp, glynnsim1, glynnsim2, hypercrop, julian, julian, mobiusn, nblur, waves2, wavesn. -Adding/removing nodes from the color curve had accidentally been disabled. -The applied xaos weight table was not showing normalized weight values. -Changing the size of a flame was not observing the Apply To All checkbox. -Do not clamp the Rotate field to +/-180, because this causes the rotation to switch from CW to CCW during sequence generation. Instead, leave it exactly as the user entered it so the rotations proceed in the same direction.
2479 lines
86 KiB
C++
Executable File
2479 lines
86 KiB
C++
Executable File
#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
|