--User changes

-Add buttons to copy and paste affine transforms.
 -Show xform names on the column headers of the xaos table.
 -Add a color-coded third column to the variations tree which shows any properties of each variation which are non-standard.
 -Draw a transparent circle over hovered xforms.
 -Change how xforms respond to dragging. Rotate only is now the default, and scale will only happen with shift.
 --Optionally do scale and rotate when holding shift, via a setting in the options dialog.

--Bug fixes
 -Snapping when dragging was wrong sometimes.
 -The program would very rarely crash on startup due to some values being in an uninitialized state.

--Code changes
 -Change almost every variation to use fma() in OpenCL when doing computations of the form a * b + c. This provides a slight speedup, mostly in double precision mode.
 -Also apply fma() to affine calcs.
 -Cleanup of OpenGL affine drawing code.
 -Separate the concept of hovering and selecting xforms.
This commit is contained in:
Person
2018-09-15 03:11:12 -07:00
parent dee4304bf2
commit 15fdc860b8
34 changed files with 2149 additions and 1698 deletions

View File

@ -873,7 +873,9 @@ void Fractorium::SetTabOrders()
w = SetTabOrder(this, w, m_PreO1Spin);
w = SetTabOrder(this, w, m_PreO2Spin);
w = SetTabOrder(this, w, ui.PreFlipVerticalButton);
w = SetTabOrder(this, w, ui.PreCopyButton);
w = SetTabOrder(this, w, ui.PreResetButton);
w = SetTabOrder(this, w, ui.PrePasteButton);
w = SetTabOrder(this, w, ui.PreFlipHorizontalButton);
w = SetTabOrder(this, w, ui.PreRotate90CcButton);
w = SetTabOrder(this, w, ui.PreRotateCcButton);
@ -899,7 +901,9 @@ void Fractorium::SetTabOrders()
w = SetTabOrder(this, w, m_PostO1Spin);
w = SetTabOrder(this, w, m_PostO2Spin);
w = SetTabOrder(this, w, ui.PostFlipVerticalButton);
w = SetTabOrder(this, w, ui.PostCopyButton);
w = SetTabOrder(this, w, ui.PostResetButton);
w = SetTabOrder(this, w, ui.PostPasteButton);
w = SetTabOrder(this, w, ui.PostFlipHorizontalButton);
w = SetTabOrder(this, w, ui.PostRotate90CcButton);
w = SetTabOrder(this, w, ui.PostRotateCcButton);

View File

@ -114,7 +114,11 @@ public:
void CurrentXform(uint i);
//Xforms Affine.
bool DrawCurrentPre();
bool DrawSelectedPre();
bool DrawAllPre();
bool DrawCurrentPost();
bool DrawSelectedPost();
bool DrawAllPost();
bool LocalPivot();
@ -279,6 +283,8 @@ public slots:
void OnScaleDownButtonClicked(bool checked);
void OnScaleUpButtonClicked(bool checked);
void OnResetAffineButtonClicked(bool checked);
void OnCopyAffineButtonClicked(bool checked);
void OnPasteAffineButtonClicked(bool checked);
void OnRandomAffineButtonClicked(bool checked);
void OnAffineGroupBoxToggled(bool on);

View File

@ -3233,7 +3233,7 @@
<enum>QTabWidget::Triangular</enum>
</property>
<property name="currentIndex">
<number>3</number>
<number>2</number>
</property>
<widget class="QWidget" name="XformColorTab">
<property name="sizePolicy">
@ -4572,6 +4572,50 @@
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QPushButton" name="PreCopyButton">
<property name="minimumSize">
<size>
<width>0</width>
<height>24</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>41</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Copy the pre affine values, which can then be pasted into other pre/post affines&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Copy</string>
</property>
</widget>
</item>
<item row="0" column="4">
<widget class="QPushButton" name="PrePasteButton">
<property name="minimumSize">
<size>
<width>0</width>
<height>24</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>41</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Paste the pre/post affine values which were previously copied&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Paste</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
@ -4617,6 +4661,13 @@
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="ShowPreAffineSelectedRadio">
<property name="text">
<string>Selected</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="ShowPreAffineAllRadio">
<property name="text">
@ -5402,6 +5453,50 @@
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QPushButton" name="PostCopyButton">
<property name="minimumSize">
<size>
<width>0</width>
<height>24</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>41</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Copy the post affine values, which can then be pasted into other pre/post affines&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Copy</string>
</property>
</widget>
</item>
<item row="0" column="4">
<widget class="QPushButton" name="PostPasteButton">
<property name="minimumSize">
<size>
<width>0</width>
<height>24</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>41</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Paste the pre/post affine values which were previously copied&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Paste</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
@ -5444,6 +5539,13 @@
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="ShowPostAffineSelectedRadio">
<property name="text">
<string>Selected</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="ShowPostAffineAllRadio">
<property name="enabled">
@ -5675,7 +5777,7 @@
<bool>false</bool>
</property>
<property name="columnCount">
<number>2</number>
<number>3</number>
</property>
<attribute name="headerDefaultSectionSize">
<number>70</number>
@ -5702,6 +5804,14 @@
<string>Weight</string>
</property>
</column>
<column>
<property name="text">
<string>Type</string>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Red: Uses non-standard assignment which means direct assignment for regular variations, sum for pre/post.&lt;/p&gt;&lt;p&gt;Green: Uses direct color.&lt;/p&gt;&lt;p&gt;Blue: Uses an internal variation state.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</column>
<item>
<property name="text">
<string>Spherical</string>

View File

@ -194,6 +194,8 @@ public:
virtual void MoveXforms(double x, double y, bool pre) { }
virtual void ScaleXforms(double scale, bool pre) { }
virtual void ResetXformsAffine(bool pre) { }
virtual void CopyXformsAffine(bool pre) { }
virtual void PasteXformsAffine(bool pre) { }
virtual void RandomXformsAffine(bool pre) { }
virtual void FillBothAffines() { }
double LockedScale() { return m_LockedScale; }
@ -223,6 +225,7 @@ public:
virtual void FillVariationTreeWithCurrentXform() { }
//Xforms Selection.
virtual QString MakeXformCaption(size_t i) { return ""; }
//Xaos.
virtual void FillXaos() { }
@ -265,12 +268,12 @@ public:
vector<v4F>* FinalImage() { return &(m_FinalImage); }
vector<v4F>* PreviewFinalImage() { return &m_PreviewFinalImage; }
EmberStats Stats() { return m_Stats; }
eProcessState ProcessState() { return m_Renderer.get() ? m_Renderer->ProcessState() : eProcessState::NONE; }
protected:
//Rendering/progress.
void AddProcessAction(eProcessAction action);
eProcessAction CondenseAndClearProcessActions();
eProcessState ProcessState() { return m_Renderer.get() ? m_Renderer->ProcessState() : eProcessState::NONE; }
//Non-templated members.
bool m_Rendering = false;
@ -468,6 +471,8 @@ public:
virtual void MoveXforms(double x, double y, bool pre) override;
virtual void ScaleXforms(double scale, bool pre) override;
virtual void ResetXformsAffine(bool pre) override;
virtual void CopyXformsAffine(bool pre) override;
virtual void PasteXformsAffine(bool pre) override;
virtual void RandomXformsAffine(bool pre) override;
virtual void FillBothAffines() override;
virtual void InitLockedScale() override;
@ -506,6 +511,7 @@ public:
virtual void AddLayer(int xforms) override;
//Xforms Selection.
virtual QString MakeXformCaption(size_t i) override;
bool XformCheckboxAt(int i, std::function<void(QCheckBox*)> func);
bool XformCheckboxAt(Xform<T>* xform, std::function<void(QCheckBox*)> func);
@ -545,9 +551,6 @@ private:
//Xforms Color.
void FillCurvesControl();
//Xforms Selection.
QString MakeXformCaption(size_t i);
//Palette.
void UpdateAdjustedPaletteGUI(Palette<float>& palette);
@ -567,6 +570,7 @@ private:
deque<Ember<T>> m_UndoList;
vector<Xform<T>> m_CopiedXforms;
Xform<T> m_CopiedFinalXform;
Affine2D<T> m_CopiedAffine;
shared_ptr<VariationList<T>> m_VariationList;
unique_ptr<SheepTools<T, float>> m_SheepTools;
unique_ptr<GLEmberController<T>> m_GLController;

View File

@ -645,7 +645,7 @@ void FractoriumEmberController<T>::InterpTypeChanged(int i)
{
eInterp interp;
if (i == 0)//Need to make this work like animate flag where it sets the value but doesn't trigger and update.//TODO
if (i == 0)
interp = eInterp::EMBER_INTERP_LINEAR;
else if (i == 1)
interp = eInterp::EMBER_INTERP_SMOOTH;

View File

@ -159,13 +159,13 @@ void FractoriumEmberControllerBase::SaveCurrentRender(const QString& filename, c
{
vector<byte> rgba8Image(size * 4);
Rgba32ToRgba8(data, rgba8Image.data(), width, height, transparency);
b = WritePng(s.c_str(), rgba8Image.data(), width, height, 1, true, comments, id, url, nick);//Put an opt here for 1 or 2 bytes.//TODO
b = WritePng(s.c_str(), rgba8Image.data(), width, height, 1, true, comments, id, url, nick);
}
else
{
vector<glm::uint16> rgba16Image(size * 4);
Rgba32ToRgba16(data, rgba16Image.data(), width, height, transparency);
b = WritePng(s.c_str(), (byte*)rgba16Image.data(), width, height, 2, true, comments, id, url, nick);//Put an opt here for 1 or 2 bytes.//TODO
b = WritePng(s.c_str(), (byte*)rgba16Image.data(), width, height, 2, true, comments, id, url, nick);
}
}
else if (suffix.endsWith("exr", Qt::CaseInsensitive))

View File

@ -173,6 +173,9 @@ void FractoriumSettings::OpenClQuality(uint i) { setValue(OPEN
bool FractoriumSettings::LoadLast() { return value(LOADLAST).toBool(); }
void FractoriumSettings::LoadLast(bool b) { setValue(LOADLAST, b); }
bool FractoriumSettings::RotateAndScale() { return value(ROTSCALE).toBool(); }
void FractoriumSettings::RotateAndScale(bool b) { setValue(ROTSCALE, b); }
/// <summary>
/// Sequence generation settings.
/// </summary>

View File

@ -28,6 +28,7 @@
#define CPUQUALITY "render/cpuquality"
#define OPENCLQUALITY "render/openclquality"
#define LOADLAST "render/loadlastonstart"
#define ROTSCALE "render/rotateandscale"
#define STAGGER "sequence/stagger"
#define STAGGERMAX "sequence/staggermax"
@ -171,6 +172,9 @@ public:
bool LoadLast();
void LoadLast(bool b);
bool RotateAndScale();
void RotateAndScale(bool b);
double Stagger();
void Stagger(double i);

View File

@ -92,7 +92,7 @@ void Fractorium::FillXaosTable()
{
int count = int(m_Controller->XformCount());
QStringList hl, vl;
auto oldModel = m_XaosTableModel;
auto oldModel = std::make_unique<QStandardItemModel>(m_XaosTableModel);
hl.reserve(count);
vl.reserve(count);
m_XaosTableModel = new QStandardItemModel(count, count, this);
@ -101,7 +101,7 @@ void Fractorium::FillXaosTable()
for (int i = 0; i < count; i++)
{
auto s = QString::number(i + 1);
auto s = m_Controller->MakeXformCaption(i);
hl.push_back("F" + s);
vl.push_back("T" + s);
}
@ -113,10 +113,6 @@ void Fractorium::FillXaosTable()
SetTabOrder(this, ui.ClearXaosButton, ui.RandomXaosButton);
m_Controller->FillXaos();
ui.XaosTableView->blockSignals(false);
if (oldModel)
delete oldModel;
//Needed to get the dark stylesheet to correctly color the top left corner button.
auto widgetList = ui.XaosTableView->findChildren<QAbstractButton*>();

View File

@ -412,6 +412,7 @@ void FractoriumEmberController<T>::XformNameChanged(int row, int col)
XformCheckboxAt(int(xfindex), [&](QCheckBox * checkbox) { checkbox->setText(MakeXformCaption(xfindex)); });
}, eXformUpdate::UPDATE_CURRENT, false);
FillSummary();//Manually update because this does not trigger a render, which is where this would normally be called.
m_Fractorium->FillXaosTable();
}
void Fractorium::OnXformNameChanged(int row, int col)
@ -570,6 +571,7 @@ void FractoriumEmberController<T>::FillXforms(int index)
if (UseFinalXform())
{
auto cb = new QCheckBox(MakeXformCaption(i), m_Fractorium);
QObject::connect(cb, &QCheckBox::stateChanged, [&](int state) { m_Fractorium->ui.GLDisplay->update(); });
m_Fractorium->m_XformSelections.push_back(cb);
m_Fractorium->m_XformsSelectionLayout->addRow(cb, new QWidget(m_Fractorium));
combo->addItem("Final");
@ -600,7 +602,6 @@ void FractoriumEmberController<T>::FillXforms(int index)
/// Update the text in xforms combo box to show the name of Xform.
/// </summary>
/// <param name="index">The index of the Xform to update.</param>
///
template<typename T>
void FractoriumEmberController<T>::UpdateXformName(int index)
{

View File

@ -61,41 +61,47 @@ void Fractorium::InitXformsAffineUI()
moveList.append(ToString(0.01));
ui.PreMoveCombo->addItems(moveList);
ui.PostMoveCombo->addItems(moveList);
connect(ui.PreFlipHorizontalButton, SIGNAL(clicked(bool)), this, SLOT(OnFlipHorizontalButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PreFlipVerticalButton, SIGNAL(clicked(bool)), this, SLOT(OnFlipVerticalButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PreRotate90CButton, SIGNAL(clicked(bool)), this, SLOT(OnRotate90CButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PreRotate90CcButton, SIGNAL(clicked(bool)), this, SLOT(OnRotate90CcButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PreRotateCButton, SIGNAL(clicked(bool)), this, SLOT(OnRotateCButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PreRotateCcButton, SIGNAL(clicked(bool)), this, SLOT(OnRotateCcButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PreMoveUpButton, SIGNAL(clicked(bool)), this, SLOT(OnMoveUpButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PreMoveDownButton, SIGNAL(clicked(bool)), this, SLOT(OnMoveDownButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PreMoveLeftButton, SIGNAL(clicked(bool)), this, SLOT(OnMoveLeftButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PreMoveRightButton, SIGNAL(clicked(bool)), this, SLOT(OnMoveRightButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PreScaleDownButton, SIGNAL(clicked(bool)), this, SLOT(OnScaleDownButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PreScaleUpButton, SIGNAL(clicked(bool)), this, SLOT(OnScaleUpButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PreResetButton, SIGNAL(clicked(bool)), this, SLOT(OnResetAffineButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PreRandomButton, SIGNAL(clicked(bool)), this, SLOT(OnRandomAffineButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PostFlipHorizontalButton, SIGNAL(clicked(bool)), this, SLOT(OnFlipHorizontalButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PostFlipVerticalButton, SIGNAL(clicked(bool)), this, SLOT(OnFlipVerticalButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PostRotate90CcButton, SIGNAL(clicked(bool)), this, SLOT(OnRotate90CcButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PostRotateCcButton, SIGNAL(clicked(bool)), this, SLOT(OnRotateCcButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PostRotateCButton, SIGNAL(clicked(bool)), this, SLOT(OnRotateCButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PostRotate90CButton, SIGNAL(clicked(bool)), this, SLOT(OnRotate90CButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PostMoveUpButton, SIGNAL(clicked(bool)), this, SLOT(OnMoveUpButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PostMoveDownButton, SIGNAL(clicked(bool)), this, SLOT(OnMoveDownButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PostMoveLeftButton, SIGNAL(clicked(bool)), this, SLOT(OnMoveLeftButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PostMoveRightButton, SIGNAL(clicked(bool)), this, SLOT(OnMoveRightButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PostScaleDownButton, SIGNAL(clicked(bool)), this, SLOT(OnScaleDownButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PostScaleUpButton, SIGNAL(clicked(bool)), this, SLOT(OnScaleUpButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PostResetButton, SIGNAL(clicked(bool)), this, SLOT(OnResetAffineButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PostRandomButton, SIGNAL(clicked(bool)), this, SLOT(OnRandomAffineButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PreAffineGroupBox, SIGNAL(toggled(bool)), this, SLOT(OnAffineGroupBoxToggled(bool)), Qt::QueuedConnection);
connect(ui.PostAffineGroupBox, SIGNAL(toggled(bool)), this, SLOT(OnAffineGroupBoxToggled(bool)), Qt::QueuedConnection);
connect(ui.ShowPreAffineAllRadio, SIGNAL(toggled(bool)), this, SLOT(OnAffineDrawAllCurrentRadioButtonToggled(bool)), Qt::QueuedConnection);
connect(ui.ShowPreAffineCurrentRadio, SIGNAL(toggled(bool)), this, SLOT(OnAffineDrawAllCurrentRadioButtonToggled(bool)), Qt::QueuedConnection);
connect(ui.ShowPostAffineAllRadio, SIGNAL(toggled(bool)), this, SLOT(OnAffineDrawAllCurrentRadioButtonToggled(bool)), Qt::QueuedConnection);
connect(ui.ShowPostAffineCurrentRadio, SIGNAL(toggled(bool)), this, SLOT(OnAffineDrawAllCurrentRadioButtonToggled(bool)), Qt::QueuedConnection);
connect(ui.PolarAffineCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnPolarAffineCheckBoxStateChanged(int)), Qt::QueuedConnection);
connect(ui.PreFlipHorizontalButton, SIGNAL(clicked(bool)), this, SLOT(OnFlipHorizontalButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PreFlipVerticalButton, SIGNAL(clicked(bool)), this, SLOT(OnFlipVerticalButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PreRotate90CButton, SIGNAL(clicked(bool)), this, SLOT(OnRotate90CButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PreRotate90CcButton, SIGNAL(clicked(bool)), this, SLOT(OnRotate90CcButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PreRotateCButton, SIGNAL(clicked(bool)), this, SLOT(OnRotateCButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PreRotateCcButton, SIGNAL(clicked(bool)), this, SLOT(OnRotateCcButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PreMoveUpButton, SIGNAL(clicked(bool)), this, SLOT(OnMoveUpButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PreMoveDownButton, SIGNAL(clicked(bool)), this, SLOT(OnMoveDownButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PreMoveLeftButton, SIGNAL(clicked(bool)), this, SLOT(OnMoveLeftButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PreMoveRightButton, SIGNAL(clicked(bool)), this, SLOT(OnMoveRightButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PreScaleDownButton, SIGNAL(clicked(bool)), this, SLOT(OnScaleDownButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PreScaleUpButton, SIGNAL(clicked(bool)), this, SLOT(OnScaleUpButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PreResetButton, SIGNAL(clicked(bool)), this, SLOT(OnResetAffineButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PreCopyButton, SIGNAL(clicked(bool)), this, SLOT(OnCopyAffineButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PrePasteButton, SIGNAL(clicked(bool)), this, SLOT(OnPasteAffineButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PreRandomButton, SIGNAL(clicked(bool)), this, SLOT(OnRandomAffineButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PostFlipHorizontalButton, SIGNAL(clicked(bool)), this, SLOT(OnFlipHorizontalButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PostFlipVerticalButton, SIGNAL(clicked(bool)), this, SLOT(OnFlipVerticalButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PostRotate90CcButton, SIGNAL(clicked(bool)), this, SLOT(OnRotate90CcButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PostRotateCcButton, SIGNAL(clicked(bool)), this, SLOT(OnRotateCcButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PostRotateCButton, SIGNAL(clicked(bool)), this, SLOT(OnRotateCButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PostRotate90CButton, SIGNAL(clicked(bool)), this, SLOT(OnRotate90CButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PostMoveUpButton, SIGNAL(clicked(bool)), this, SLOT(OnMoveUpButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PostMoveDownButton, SIGNAL(clicked(bool)), this, SLOT(OnMoveDownButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PostMoveLeftButton, SIGNAL(clicked(bool)), this, SLOT(OnMoveLeftButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PostMoveRightButton, SIGNAL(clicked(bool)), this, SLOT(OnMoveRightButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PostScaleDownButton, SIGNAL(clicked(bool)), this, SLOT(OnScaleDownButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PostScaleUpButton, SIGNAL(clicked(bool)), this, SLOT(OnScaleUpButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PostResetButton, SIGNAL(clicked(bool)), this, SLOT(OnResetAffineButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PostCopyButton, SIGNAL(clicked(bool)), this, SLOT(OnCopyAffineButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PostPasteButton, SIGNAL(clicked(bool)), this, SLOT(OnPasteAffineButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PostRandomButton, SIGNAL(clicked(bool)), this, SLOT(OnRandomAffineButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PreAffineGroupBox, SIGNAL(toggled(bool)), this, SLOT(OnAffineGroupBoxToggled(bool)), Qt::QueuedConnection);
connect(ui.PostAffineGroupBox, SIGNAL(toggled(bool)), this, SLOT(OnAffineGroupBoxToggled(bool)), Qt::QueuedConnection);
connect(ui.ShowPreAffineAllRadio, SIGNAL(toggled(bool)), this, SLOT(OnAffineDrawAllCurrentRadioButtonToggled(bool)), Qt::QueuedConnection);
connect(ui.ShowPreAffineCurrentRadio, SIGNAL(toggled(bool)), this, SLOT(OnAffineDrawAllCurrentRadioButtonToggled(bool)), Qt::QueuedConnection);
connect(ui.ShowPreAffineSelectedRadio, SIGNAL(toggled(bool)), this, SLOT(OnAffineDrawAllCurrentRadioButtonToggled(bool)), Qt::QueuedConnection);
connect(ui.ShowPostAffineAllRadio, SIGNAL(toggled(bool)), this, SLOT(OnAffineDrawAllCurrentRadioButtonToggled(bool)), Qt::QueuedConnection);
connect(ui.ShowPostAffineCurrentRadio, SIGNAL(toggled(bool)), this, SLOT(OnAffineDrawAllCurrentRadioButtonToggled(bool)), Qt::QueuedConnection);
connect(ui.ShowPostAffineSelectedRadio, SIGNAL(toggled(bool)), this, SLOT(OnAffineDrawAllCurrentRadioButtonToggled(bool)), Qt::QueuedConnection);
connect(ui.PolarAffineCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnPolarAffineCheckBoxStateChanged(int)), Qt::QueuedConnection);
#ifndef _WIN32
//For some reason linux makes these 24x24, even though the designer explicitly says 16x16.
//Also, in order to get 4 pixels of spacing between elements in the grid layout, 0 must be specified.
@ -564,6 +570,7 @@ void Fractorium::OnScaleUpButtonClicked(bool checked)
/// Called when reset pre/post affine buttons are clicked.
/// Resets the rendering process.
/// </summary>
/// <param name="pre">True for pre affine, false for post.</param>
template <typename T>
void FractoriumEmberController<T>::ResetXformsAffine(bool pre)
{
@ -577,6 +584,42 @@ void FractoriumEmberController<T>::ResetXformsAffine(bool pre)
void Fractorium::OnResetAffineButtonClicked(bool checked) { m_Controller->ResetXformsAffine(sender() == ui.PreResetButton); }
/// <summary>
/// Copy the pre or post affine transform values from the current xform.
/// </summary>
/// <param name="pre">True for pre affine, false for post.</param>
template <typename T>
void FractoriumEmberController<T>::CopyXformsAffine(bool pre)
{
if (auto xform = CurrentXform())
m_CopiedAffine = pre ? xform->m_Affine : xform->m_Post;
}
void Fractorium::OnCopyAffineButtonClicked(bool checked)
{
m_Controller->CopyXformsAffine(sender() == ui.PreCopyButton);
}
/// <summary>
/// Paste the last copied affine transform values to the pre or post affine values in the current xform.
/// </summary>
/// <param name="pre">True for pre affine, false for post.</param>
template <typename T>
void FractoriumEmberController<T>::PasteXformsAffine(bool pre)
{
UpdateXform([&](Xform<T>* xform, size_t xfindex, size_t selIndex)
{
auto& affine = pre ? xform->m_Affine : xform->m_Post;
affine = m_CopiedAffine;
}, eXformUpdate::UPDATE_CURRENT);
FillAffineWithXform(CurrentXform(), pre);
}
void Fractorium::OnPasteAffineButtonClicked(bool checked)
{
m_Controller->PasteXformsAffine(sender() == ui.PrePasteButton);
}
/// <summary>
/// Randomize all values in selected pre/post affines to a range of -1 to 1.
/// Called when random pre/post affine buttons are clicked.
@ -731,7 +774,11 @@ void Fractorium::SetupAffineSpinner(QTableWidget* table, const QObject* receiver
/// GUI wrapper functions, getters only.
/// </summary>
bool Fractorium::DrawCurrentPre() { return !DrawAllPre() && !DrawSelectedPre(); }
bool Fractorium::DrawSelectedPre() { return ui.ShowPreAffineSelectedRadio->isChecked(); }
bool Fractorium::DrawAllPre() { return ui.ShowPreAffineAllRadio->isChecked(); }
bool Fractorium::DrawCurrentPost() { return !DrawAllPost() && !DrawSelectedPost(); }
bool Fractorium::DrawSelectedPost() { return ui.ShowPostAffineSelectedRadio->isChecked(); }
bool Fractorium::DrawAllPost() { return ui.ShowPostAffineAllRadio->isChecked(); }
bool Fractorium::LocalPivot() { return ui.LocalPivotRadio->isChecked(); }

View File

@ -14,8 +14,9 @@ void Fractorium::InitXformsVariationsUI()
connect(ui.VariationsFilterClearButton, SIGNAL(clicked(bool)), this, SLOT(OnVariationsFilterClearButtonClicked(bool)));
connect(ui.ActionVariationsDialog, SIGNAL(triggered(bool)), this, SLOT(OnActionVariationsDialog(bool)), Qt::QueuedConnection);
//Setting dimensions in the designer with a layout is futile, so must hard code here.
tree->setColumnWidth(0, 160);
tree->setColumnWidth(1, 23);
tree->setColumnWidth(0, 170);
tree->setColumnWidth(1, 80);
tree->setColumnWidth(2, 20);
//Set Default variation tree text and background colors for zero and non zero cases.
m_VariationTreeColorNonZero = Qt::black;
m_VariationTreeColorZero = Qt::black;
@ -106,11 +107,16 @@ void FractoriumEmberController<T>::SetupVariationsTree()
{
T fMin = TLOW;
T fMax = TMAX;
QSize hint0(75, 16);
QSize hint1(30, 16);
QSize hint0(170, 16);
QSize hint1(80, 16);
QSize hint2(20, 16);
static vector<string> dc{ "m_ColorX" };
static vector<string> assign{ "outPoint->m_X =", "outPoint->m_Y =", "outPoint->m_Z =",
"outPoint->m_X=", "outPoint->m_Y=", "outPoint->m_Z=" };
auto tree = m_Fractorium->ui.VariationsTree;
tree->clear();
tree->blockSignals(true);
int iconSize_ = 20;
for (size_t i = 0; i < m_VariationList->Size(); i++)
{
@ -122,6 +128,34 @@ void FractoriumEmberController<T>::SetupVariationsTree()
item->setText(0, QString::fromStdString(var->Name()));
item->setSizeHint(0, hint0);
item->setSizeHint(1, hint1);
item->setSizeHint(2, hint2);
QPixmap pixmap(iconSize_ * 3, iconSize_);
auto mask = pixmap.createMaskFromColor(QColor("transparent"), Qt::MaskOutColor);
pixmap.setMask(mask);
QPainter paint(&pixmap);
paint.fillRect(QRect(0, 0, iconSize_ * 3, iconSize_), QColor(0, 0, 0, 0));
if (var->VarType() == eVariationType::VARTYPE_REG)
{
if (SearchVar(var, assign, false))
paint.fillRect(QRect(0, 0, iconSize_, iconSize_), QColor(255, 0, 0));
}
else if (var->VarType() == eVariationType::VARTYPE_PRE || var->VarType() == eVariationType::VARTYPE_POST)
{
if (var->AssignType() == eVariationAssignType::ASSIGNTYPE_SUM)
paint.fillRect(QRect(0, 0, iconSize_, iconSize_), QColor(255, 0, 0));
}
bool isDc = SearchVar(var, dc, false);
if (isDc)
paint.fillRect(QRect(iconSize_, 0, iconSize_, iconSize_), QColor(0, 255, 0));
if (!var->StateOpenCLString().empty())
paint.fillRect(QRect(iconSize_ * 2, 0, iconSize_, iconSize_), QColor(0, 0, 255));
QIcon qi(pixmap);
item->setIcon(2, qi);
spinBox->setRange(fMin, fMax);
spinBox->DoubleClick(true);
spinBox->DoubleClickZero(1);
@ -358,11 +392,14 @@ void FractoriumEmberController<T>::FillVariationTreeWithXform(Xform<T>* xform)
/// <param name="logicalIndex">Column index of the header clicked. Sort by name if 0, sort by weight if 1.</param>
void Fractorium::OnTreeHeaderSectionClicked(int logicalIndex)
{
m_VarSortMode = logicalIndex;
ui.VariationsTree->sortItems(m_VarSortMode, m_VarSortMode == 0 ? Qt::AscendingOrder : Qt::DescendingOrder);
if (logicalIndex <= 1)
{
m_VarSortMode = logicalIndex;
ui.VariationsTree->sortItems(m_VarSortMode, m_VarSortMode == 0 ? Qt::AscendingOrder : Qt::DescendingOrder);
if (m_VarSortMode == 1)
ui.VariationsTree->scrollToTop();
if (m_VarSortMode == 1)
ui.VariationsTree->scrollToTop();
}
}
/// <summary>

View File

@ -175,7 +175,7 @@ typename v3T GLEmberController<T>::SnapToNormalizedAngle(v3T& vec, uint division
{
T rsq, theta;
T bestRsq = numeric_limits<T>::max();
v3T c, best;
v3T c(0, 0, 0), best;
best.x = 1;
best.y = 0;

View File

@ -118,7 +118,7 @@ public:
void CalcDragTranslation();
void SetSelectedXform(Xform<T>* xform);
void DrawGrid();
void DrawAffine(Xform<T>* xform, bool pre, bool selected);
void DrawAffine(Xform<T>* xform, bool pre, bool selected, bool hovered);
int UpdateHover(v3T& glCoords);
bool CheckXformHover(Xform<T>* xform, v3T& glCoords, T& bestDist, bool pre, bool post);

View File

@ -390,10 +390,9 @@ void GLEmberController<T>::SetSelectedXform(Xform<T>* xform)
{
//By doing this check, it prevents triggering unnecessary events when selecting an xform on this window with the mouse,
//which will set the combo box on the main window, which will trigger this call. However, if the xform has been selected
//here with the mouse, the window has already repainted, so there's no need to do it again.
if (m_SelectedXform != xform || m_HoverXform != xform)
//here with the mouse, the window has already been repainted, so there's no need to do it again.
if (m_SelectedXform != xform)
{
m_HoverXform = xform;
m_SelectedXform = xform;
if (m_GL->m_Init)
@ -513,7 +512,7 @@ void GLWidget::initializeGL()
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);//This should also apply to the final render dialog.//TODO
m_Fractorium->m_WidthSpin->setMaximum(m_MaxTexSize);
m_Fractorium->m_HeightSpin->setMaximum(m_MaxTexSize);
}
}
@ -535,9 +534,18 @@ void GLWidget::paintGL()
auto controller = m_Fractorium->m_Controller.get();
//Ensure there is a renderer and that it's supposed to be drawing, signified by the running timer.
if (controller && controller->Renderer())
if (controller && controller->Renderer()/* && controller->ProcessState() != eProcessState::NONE*/)//Need a way to determine if at leat one successful render has happened.
{
auto renderer = controller->Renderer();
float unitX = std::abs(renderer->UpperRightX(false) - renderer->LowerLeftX(false)) / 2.0f;
float unitY = std::abs(renderer->UpperRightY(false) - renderer->LowerLeftY(false)) / 2.0f;
if (unitX > 100000 || unitY > 100000)//Need a better way to do this.//TODO
{
qDebug() << unitX << " " << unitY;
//return;
}
m_Drawing = true;
if (m_Fractorium->DrawImage())
@ -553,8 +561,6 @@ void GLWidget::paintGL()
//Affine drawing.
bool pre = m_Fractorium->ui.PreAffineGroupBox->isChecked();
bool post = m_Fractorium->ui.PostAffineGroupBox->isChecked();
float unitX = std::abs(renderer->UpperRightX(false) - renderer->LowerLeftX(false)) / 2.0f;
float unitY = std::abs(renderer->UpperRightY(false) - renderer->LowerLeftY(false)) / 2.0f;
this->glEnable(GL_BLEND);
this->glEnable(GL_LINE_SMOOTH);
this->glEnable(GL_POINT_SMOOTH);
@ -635,7 +641,6 @@ void GLEmberController<T>::DrawAffines(bool pre, bool post)
{
auto dprf = m_GL->devicePixelRatioF();
auto world = ScrolledCenter(true);
m_GL->glLineWidth(1.0f * dprf);
GLfloat vertices[] =
{
@ -654,7 +659,7 @@ void GLEmberController<T>::DrawAffines(bool pre, bool post)
if (!m_Fractorium->m_Settings->ShowAllXforms() && dragging)
{
if (m_SelectedXform)
DrawAffine(m_SelectedXform, m_AffineType == eAffineType::AffinePre, true);
DrawAffine(m_SelectedXform, m_AffineType == eAffineType::AffinePre, true, false);
}
else//Show all while dragging, or not dragging just hovering/mouse move.
{
@ -664,13 +669,30 @@ void GLEmberController<T>::DrawAffines(bool pre, bool post)
while (auto xform = ember->GetTotalXform(i, forceFinal))
{
bool selected = m_Fractorium->IsXformSelected(i++) || (dragging ? (m_SelectedXform == xform) : (m_HoverXform == xform));
DrawAffine(xform, true, selected);
bool selected = m_Fractorium->IsXformSelected(i++) || m_SelectedXform == xform;
DrawAffine(xform, true, selected, !dragging && (m_HoverXform == xform));
}
}
else if (pre && m_HoverXform)//Only draw current pre affine.
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.
{
DrawAffine(m_HoverXform, true, true);
size_t i = 0;
bool any = false;
while (auto xform = ember->GetTotalXform(i, forceFinal))
{
if (m_Fractorium->IsXformSelected(i++))
{
DrawAffine(xform, true, true, !dragging && (m_HoverXform == xform));
any = true;
}
}
if (!any)
DrawAffine(m_FractoriumEmberController->CurrentXform(), true, true, !dragging && (m_HoverXform == m_FractoriumEmberController->CurrentXform()));
}
else if (pre)//Only draw current pre affine.
{
DrawAffine(m_SelectedXform, true, true, !dragging && (m_HoverXform == m_FractoriumEmberController->CurrentXform()));
}
if (post && m_Fractorium->DrawAllPost())//Draw all post affine if specified.
@ -679,13 +701,30 @@ void GLEmberController<T>::DrawAffines(bool pre, bool post)
while (auto xform = ember->GetTotalXform(i, forceFinal))
{
bool selected = m_Fractorium->IsXformSelected(i++) || (dragging ? (m_SelectedXform == xform) : (m_HoverXform == xform));
DrawAffine(xform, false, selected);
bool selected = m_Fractorium->IsXformSelected(i++) || m_SelectedXform == xform;
DrawAffine(xform, false, selected, !dragging && (m_HoverXform == xform));
}
}
else if (post && m_HoverXform)//Only draw current post affine.
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.
{
DrawAffine(m_HoverXform, false, true);
size_t i = 0;
bool any = false;
while (auto xform = ember->GetTotalXform(i, forceFinal))
{
if (m_Fractorium->IsXformSelected(i++))
{
DrawAffine(xform, false, true, !dragging && (m_HoverXform == xform));
any = true;
}
}
if (!any)
DrawAffine(m_FractoriumEmberController->CurrentXform(), false, true, !dragging && (m_HoverXform == m_FractoriumEmberController->CurrentXform()));
}
else if (post)//Only draw current post affine.
{
DrawAffine(m_SelectedXform, false, true, !dragging && (m_HoverXform == m_FractoriumEmberController->CurrentXform()));
}
}
@ -1032,7 +1071,9 @@ void GLEmberController<T>::MouseMove(QMouseEvent* e)
//Check if they weren't dragging and weren't hovering over any affine.
//In that case, nothing needs to be done.
if (UpdateHover(mouseFlipped) == -1)
draw = false;
{
m_HoverXform = nullptr;
}
}
//Only update if the user was dragging or hovered over a point.
@ -1128,7 +1169,7 @@ void GLWidget::DrawPointOrLine(const QVector4D& col, const GLfloat* vertices, in
{
#ifdef USE_GLSL
if (dashed && drawType == GL_LINES)
if (dashed && (drawType == GL_LINES || drawType == GL_LINE_LOOP))
{
glLineStipple(1, 0XFF00);
glEnable(GL_LINE_STIPPLE);
@ -1143,7 +1184,7 @@ void GLWidget::DrawPointOrLine(const QVector4D& col, const GLfloat* vertices, in
this->glDrawArrays(drawType, 0, size);
this->glDisableVertexAttribArray(0);
if (dashed && drawType == GL_LINES)
if (dashed && (drawType == GL_LINES || drawType == GL_LINE_LOOP))
glDisable(GL_LINE_STIPPLE);
#endif
@ -1370,7 +1411,13 @@ void GLEmberController<T>::DrawGrid()
//qDebug() << renderer->UpperRightX(false) << " " << renderer->LowerLeftX(false) << " " << renderer->UpperRightY(false) << " " << renderer->LowerLeftY(false);
float unitX = (std::abs(renderer->UpperRightX(false) - renderer->LowerLeftX(false)) / 2.0f) / scale;
float unitY = (std::abs(renderer->UpperRightY(false) - renderer->LowerLeftY(false)) / 2.0f) / scale;
//qDebug() << unitX << " " << unitY;
if (unitX > 100000 || unitY > 100000)//Need a better way to do this.//TODO
{
qDebug() << unitX << " " << unitY;
//return;
}
float xLow = std::floor(-unitX);
float xHigh = std::ceil(unitX);
float yLow = std::floor(-unitY);
@ -1479,8 +1526,9 @@ void GLEmberController<T>::DrawGrid()
/// <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(Xform<T>* xform, bool pre, bool selected)
void GLEmberController<T>::DrawAffine(Xform<T>* xform, bool pre, bool selected, bool hovered)
{
auto ember = m_FractoriumEmberController->CurrentEmber();
auto final = ember->IsFinalXform(xform);
@ -1498,9 +1546,9 @@ void GLEmberController<T>::DrawAffine(Xform<T>* xform, bool pre, bool selected)
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, pre, final, true);
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, pre, final, false);
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);
@ -1528,9 +1576,9 @@ void GLEmberController<T>::DrawAffine(Xform<T>* xform, bool pre, bool selected)
glm::tmat4x4<float, glm::defaultp> tempmat4 = mat;
m_GL->m_ModelViewMatrix = QMatrix4x4(glm::value_ptr(tempmat4));
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, pre, final, true);
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, pre, final, false);
m_GL->DrawAffineHelper(index, 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);
@ -1577,10 +1625,11 @@ void GLEmberController<T>::DrawAffine(Xform<T>* xform, bool pre, bool selected)
/// </summary>
/// <param name="index"></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, bool selected, bool pre, bool final, bool background)
void GLWidget::DrawAffineHelper(int index, bool selected, bool hovered, bool pre, bool final, bool background)
{
float px = 1.0f;
float py = 0.0f;
@ -1641,32 +1690,28 @@ void GLWidget::DrawAffineHelper(int index, bool selected, bool pre, bool final,
//Circle part.
if (!background)
{
color = QVector4D(col.redF(), col.greenF(), col.blueF(), 1.0f);//Draw pre affine transform with white.
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, 1.0f);//Draw pre affine transform outline with white.
color = QVector4D(0.0f, 0.0f, 0.0f, hovered ? 0.25f : 1.0f);//Draw pre affine transform outline with white.
}
m_Verts.clear();
if (selected)
if (selected || hovered)
{
for (size_t i = 1; i <= 64; i++)//The circle.
{
float theta = float(M_PI) * 2.0f * float(i % 64) / 64.0f;
float fx = std::cos(theta);
float fy = std::sin(theta);
m_Verts.push_back(px);
m_Verts.push_back(py);
m_Verts.push_back(fx);
m_Verts.push_back(fy);
px = fx;
py = fy;
}
}
DrawPointOrLine(color, m_Verts, GL_LINES, !pre);
DrawPointOrLine(color, m_Verts, hovered ? GL_TRIANGLE_FAN : GL_LINE_LOOP, !pre);
}
//Lines from center to circle.
if (!background)
@ -1712,8 +1757,6 @@ int GLEmberController<T>::UpdateHover(v3T& glCoords)
{
bool pre = m_Fractorium->ui.PreAffineGroupBox->isChecked();
bool post = m_Fractorium->ui.PostAffineGroupBox->isChecked();
bool preAll = pre && m_Fractorium->DrawAllPre();
bool postAll = post && m_Fractorium->DrawAllPost();
int i = 0, bestIndex = -1;
T bestDist = 10;
auto ember = m_FractoriumEmberController->CurrentEmber();
@ -1729,10 +1772,11 @@ int GLEmberController<T>::UpdateHover(v3T& glCoords)
//These checks prevent highlighting the pre/post selected xform circle, when one is set to show all, and the other
//is set to show current, and the user hovers over another xform, but doesn't select it, then moves the mouse
//back over the hidden circle for the pre/post that was set to only show current.
bool checkSelPre = preAll || (pre && m_HoverXform == m_SelectedXform);
bool checkSelPost = postAll || (post && m_HoverXform == m_SelectedXform);
bool isSel = m_Fractorium->IsXformSelected(ember->GetTotalXformIndex(m_SelectedXform));
bool checkPre = pre && (m_Fractorium->DrawAllPre() || (m_Fractorium->DrawSelectedPre() && isSel) || m_Fractorium->DrawCurrentPre());
bool checkPost = post && (m_Fractorium->DrawAllPost() || (m_Fractorium->DrawSelectedPost() && isSel) || m_Fractorium->DrawCurrentPost());
if (CheckXformHover(m_SelectedXform, glCoords, bestDist, checkSelPre, checkSelPost))
if (CheckXformHover(m_SelectedXform, glCoords, bestDist, checkPre, checkPost))
{
m_HoverXform = m_SelectedXform;
bestIndex = int(ember->GetTotalXformIndex(m_SelectedXform, forceFinal));
@ -1740,24 +1784,35 @@ int GLEmberController<T>::UpdateHover(v3T& glCoords)
}
//Check all xforms.
while (auto xform = ember->GetTotalXform(i, forceFinal))
{
if (preAll || (pre && m_HoverXform == xform))//Only check pre affine if they are shown.
bool isSel = m_Fractorium->IsXformSelected(i);
if (pre)
{
if (CheckXformHover(xform, glCoords, bestDist, true, false))
bool checkPre = m_Fractorium->DrawAllPre() || (m_Fractorium->DrawSelectedPre() && isSel) || (m_SelectedXform == xform);
if (checkPre)//Only check pre affine if they are shown.
{
m_HoverXform = xform;
bestIndex = i;
if (CheckXformHover(xform, glCoords, bestDist, true, false))
{
m_HoverXform = xform;
bestIndex = i;
}
}
}
if (postAll || (post && m_HoverXform == xform))//Only check post affine if they are shown.
if (post)
{
if (CheckXformHover(xform, glCoords, bestDist, false, true))
bool checkPost = m_Fractorium->DrawAllPost() || (m_Fractorium->DrawSelectedPost() && isSel) || (m_SelectedXform == xform);
if (checkPost)
{
m_HoverXform = xform;
bestIndex = i;
if (CheckXformHover(xform, glCoords, bestDist, false, true))
{
m_HoverXform = xform;
bestIndex = i;
}
}
}
@ -1885,15 +1940,15 @@ bool GLEmberController<T>::CheckXformHover(Xform<T>* xform, v3T& glCoords, T& be
/// <summary>
/// Calculate the new affine transform when dragging with the x axis with the left mouse button.
/// The value returned will depend on whether any modifier keys were held down.
/// None: Rotate and scale only.
/// None: Rotate only.
/// Local Pivot:
/// Shift: Rotate only about affine center.
/// Alt: Free transform.
/// Shift + Alt: Rotate single axis about affine center.
/// Control: Rotate and scale, snapping to grid.
/// Control + Shift: Rotate only, snapping to grid.
/// Control + Alt: Free transform, snapping to grid.
/// Control + Shift + Alt: Rotate single axis about affine center, snapping to grid.
/// 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.
@ -1907,61 +1962,18 @@ void GLEmberController<T>::CalcDragXAxis()
auto worldToAffineScale = m_FractoriumEmberController->AffineScaleCurrentToLocked();
bool pre = m_AffineType == eAffineType::AffinePre;
bool worldPivotShiftAlt = !m_Fractorium->LocalPivot() && GetShift() && GetAlt();
auto startDiff = (v2T(m_HoverHandlePos) * affineToWorldScale) - m_DragSrcTransform.O();
T startAngle = std::atan2(startDiff.y, startDiff.x);
auto worldRelAxisStartScaled = (v2T(m_HoverHandlePos) * affineToWorldScale) - m_DragSrcTransform.O();//World axis start position, relative, scaled by the zoom.
T startAngle = std::atan2(worldRelAxisStartScaled.y, worldRelAxisStartScaled.x);
v3T relScaled = (m_MouseWorldPos * affineToWorldScale) - v3T(m_DragSrcTransform.O(), 0);
if (GetShift())
if (!GetShift())
{
v3T snapped = GetControl() ? SnapToNormalizedAngle(m_MouseWorldPos, 24u) : m_MouseWorldPos;
auto endDiff = (v2T(snapped) * affineToWorldScale) - m_DragSrcTransform.O();
T endAngle = std::atan2(endDiff.y, endDiff.x);
T angle = startAngle - endAngle;
m_FractoriumEmberController->UpdateXform([&](Xform<T>* xform, size_t xfindex, size_t selIndex)
{
auto& affine = pre ? xform->m_Affine : xform->m_Post;
auto srcRotated = m_DragSrcTransforms[selIndex];
if (worldPivotShiftAlt)
{
srcRotated.X(srcRotated.O() + srcRotated.X());
srcRotated.O(v2T(0));
srcRotated.Rotate(angle);
affine.X(srcRotated.X() - affine.O());
}
else if (GetAlt())
{
srcRotated.Rotate(angle);
affine.X(srcRotated.X());
}
else
{
srcRotated.Rotate(angle);
affine = srcRotated;
}
if (xform == m_FractoriumEmberController->CurrentXform())
m_DragHandlePos = v3T((affine.O() + affine.X()) * worldToAffineScale, 0);
}, eXformUpdate::UPDATE_CURRENT_AND_SELECTED, false);//Calling code will update renderer.
}
else
{
v3T diff = m_MouseWorldPos - m_MouseDownWorldPos;
auto diffscale = diff * affineToWorldScale;
auto origmag = Zeps(glm::length(m_DragSrcTransform.X()));
auto origXPlusOff = v3T(m_DragSrcTransform.X(), 0) + diffscale;
if (GetControl())
{
auto o3 = v3T(m_DragSrcTransform.O(), 0);
auto o3x = origXPlusOff + o3;
origXPlusOff = SnapToGrid(o3x);
origXPlusOff -= o3;
relScaled = SnapToNormalizedAngle(relScaled, 24u);//relScaled is using the relative scaled position of the axis.
}
auto newmag = glm::length(origXPlusOff);
auto newprc = newmag / origmag;
auto endDiff = (v2T(origXPlusOff) * affineToWorldScale);
T endAngle = std::atan2(endDiff.y, endDiff.x);
T endAngle = std::atan2(relScaled.y, relScaled.x);
T angle = startAngle - endAngle;
m_FractoriumEmberController->UpdateXform([&](Xform<T>* xform, size_t xfindex, size_t selIndex)
{
@ -1970,12 +1982,55 @@ void GLEmberController<T>::CalcDragXAxis()
if (GetAlt())
{
affine.X(v2T(origXPlusOff));//Absolute, not ratio.
src.Rotate(angle);
affine.X(src.X());
}
else
{
src.Rotate(angle);
affine = src;
}
if (xform == m_FractoriumEmberController->CurrentXform())
m_DragHandlePos = v3T((affine.O() + affine.X()) * worldToAffineScale, 0);
}, eXformUpdate::UPDATE_CURRENT_AND_SELECTED, false);//Calling code will update renderer.
}
else
{
auto origmag = Zeps(glm::length(m_DragSrcTransform.X()));//Magnitude of original dragged axis before it was dragged.
if (GetControl())
{
relScaled = SnapToGrid(relScaled);
}
auto newmag = glm::length(relScaled);
auto newprc = newmag / origmag;
T endAngle = std::atan2(relScaled.y, relScaled.x);
T angle = startAngle - endAngle;
m_FractoriumEmberController->UpdateXform([&](Xform<T>* xform, size_t xfindex, size_t selIndex)
{
auto& affine = pre ? xform->m_Affine : xform->m_Post;
auto src = m_DragSrcTransforms[selIndex];
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);
src.Rotate(angle);
if (m_Fractorium->m_Settings->RotateAndScale())
src.Rotate(angle);
affine = src;
}
@ -1988,15 +2043,15 @@ void GLEmberController<T>::CalcDragXAxis()
/// <summary>
/// Calculate the new affine transform when dragging with the y axis with the left mouse button.
/// The value returned will depend on whether any modifier keys were held down.
/// None: Rotate and scale only.
/// None: Rotate only.
/// Local Pivot:
/// Shift: Rotate only about affine center.
/// Alt: Free transform.
/// Shift + Alt: Rotate single axis about affine center.
/// Control: Rotate and scale, snapping to grid.
/// Control + Shift: Rotate only, snapping to grid.
/// Control + Alt: Free transform, snapping to grid.
/// Control + Shift + Alt: Rotate single axis about affine center, snapping to grid.
/// 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.
@ -2010,61 +2065,18 @@ void GLEmberController<T>::CalcDragYAxis()
auto worldToAffineScale = m_FractoriumEmberController->AffineScaleCurrentToLocked();
bool pre = m_AffineType == eAffineType::AffinePre;
bool worldPivotShiftAlt = !m_Fractorium->LocalPivot() && GetShift() && GetAlt();
auto startDiff = (v2T(m_HoverHandlePos) * affineToWorldScale) - m_DragSrcTransform.O();
T startAngle = std::atan2(startDiff.y, startDiff.x);
auto worldRelAxisStartScaled = (v2T(m_HoverHandlePos) * affineToWorldScale) - m_DragSrcTransform.O();//World axis start position, relative, scaled by the zoom.
T startAngle = std::atan2(worldRelAxisStartScaled.y, worldRelAxisStartScaled.x);
v3T relScaled = (m_MouseWorldPos * affineToWorldScale) - v3T(m_DragSrcTransform.O(), 0);
if (GetShift())
if (!GetShift())
{
v3T snapped = GetControl() ? SnapToNormalizedAngle(m_MouseWorldPos, 24u) : m_MouseWorldPos;
auto endDiff = (v2T(snapped) * affineToWorldScale) - m_DragSrcTransform.O();
T endAngle = std::atan2(endDiff.y, endDiff.x);
T angle = startAngle - endAngle;
m_FractoriumEmberController->UpdateXform([&](Xform<T>* xform, size_t xfindex, size_t selIndex)
{
auto& affine = pre ? xform->m_Affine : xform->m_Post;
auto srcRotated = m_DragSrcTransforms[selIndex];
if (worldPivotShiftAlt)
{
srcRotated.Y(srcRotated.O() + srcRotated.Y());
srcRotated.O(v2T(0));
srcRotated.Rotate(angle);
affine.Y(srcRotated.Y() - affine.O());
}
else if (GetAlt())
{
srcRotated.Rotate(angle);
affine.Y(srcRotated.Y());
}
else
{
srcRotated.Rotate(angle);
affine = srcRotated;
}
if (xform == m_FractoriumEmberController->CurrentXform())
m_DragHandlePos = v3T((affine.O() + affine.Y()) * worldToAffineScale, 0);
}, eXformUpdate::UPDATE_CURRENT_AND_SELECTED, false);//Calling code will update renderer.
}
else
{
v3T diff = m_MouseWorldPos - m_MouseDownWorldPos;
auto diffscale = diff * affineToWorldScale;
auto origmag = Zeps(glm::length(m_DragSrcTransform.Y()));
auto origYPlusOff = v3T(m_DragSrcTransform.Y(), 0) + diffscale;
if (GetControl())
{
auto o3 = v3T(m_DragSrcTransform.O(), 0);
auto o3y = origYPlusOff + o3;
origYPlusOff = SnapToGrid(o3y);
origYPlusOff -= o3;
relScaled = SnapToNormalizedAngle(relScaled, 24u);//relScaled is using the relative scaled position of the axis.
}
auto newmag = glm::length(origYPlusOff);
auto newprc = newmag / origmag;
auto endDiff = (v2T(origYPlusOff) * affineToWorldScale);
T endAngle = std::atan2(endDiff.y, endDiff.x);
T endAngle = std::atan2(relScaled.y, relScaled.x);
T angle = startAngle - endAngle;
m_FractoriumEmberController->UpdateXform([&](Xform<T>* xform, size_t xfindex, size_t selIndex)
{
@ -2073,12 +2085,55 @@ void GLEmberController<T>::CalcDragYAxis()
if (GetAlt())
{
affine.Y(v2T(origYPlusOff));//Absolute, not ratio.
src.Rotate(angle);
affine.Y(src.Y());
}
else
{
src.Rotate(angle);
affine = src;
}
if (xform == m_FractoriumEmberController->CurrentXform())
m_DragHandlePos = v3T((affine.O() + affine.Y()) * worldToAffineScale, 0);
}, eXformUpdate::UPDATE_CURRENT_AND_SELECTED, false);//Calling code will update renderer.
}
else
{
auto origmag = Zeps(glm::length(m_DragSrcTransform.Y()));//Magnitude of original dragged axis before it was dragged.
if (GetControl())
{
relScaled = SnapToGrid(relScaled);
}
auto newmag = glm::length(relScaled);
auto newprc = newmag / origmag;
T endAngle = std::atan2(relScaled.y, relScaled.x);
T angle = startAngle - endAngle;
m_FractoriumEmberController->UpdateXform([&](Xform<T>* xform, size_t xfindex, size_t selIndex)
{
auto& affine = pre ? xform->m_Affine : xform->m_Post;
auto src = m_DragSrcTransforms[selIndex];
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);
src.Rotate(angle);
if (m_Fractorium->m_Settings->RotateAndScale())
src.Rotate(angle);
affine = src;
}

View File

@ -77,7 +77,7 @@ private:
bool Deallocate();
void SetViewport();
void DrawUnitSquare();
void DrawAffineHelper(int index, bool selected, bool pre, bool final, bool background);
void DrawAffineHelper(int index, bool selected, bool hovered, bool pre, bool final, bool background);
GLEmberControllerBase* GLController();
bool m_Init = false;

View File

@ -76,6 +76,7 @@ bool FractoriumOptionsDialog::ToggleType() { return ui.ToggleTypeCheckBox->isChe
bool FractoriumOptionsDialog::Png16Bit() { return ui.Png16BitCheckBox->isChecked(); }
bool FractoriumOptionsDialog::AutoUnique() { return ui.AutoUniqueCheckBox->isChecked(); }
bool FractoriumOptionsDialog::LoadLast() { return ui.LoadLastOnStartCheckBox->isChecked(); }
bool FractoriumOptionsDialog::RotateAndScale() { return ui.RotateAndScaleCheckBox->isChecked(); }
uint FractoriumOptionsDialog::ThreadCount() { return ui.ThreadCountSpin->value(); }
uint FractoriumOptionsDialog::RandomCount() { return ui.RandomCountSpin->value(); }
uint FractoriumOptionsDialog::CpuQuality() { return ui.CpuQualitySpin->value(); }
@ -192,6 +193,7 @@ void FractoriumOptionsDialog::GuiToData()
m_Settings->ThreadCount(ThreadCount());
m_Settings->RandomCount(RandomCount());
m_Settings->LoadLast(LoadLast());
m_Settings->RotateAndScale(RotateAndScale());
m_Settings->CpuQuality(CpuQuality());
m_Settings->OpenClQuality(OpenClQuality());
m_Settings->CpuSubBatch(ui.CpuSubBatchSpin->value());
@ -230,6 +232,7 @@ void FractoriumOptionsDialog::DataToGui()
ui.ThreadCountSpin->setValue(m_Settings->ThreadCount());
ui.RandomCountSpin->setValue(m_Settings->RandomCount());
ui.LoadLastOnStartCheckBox->setChecked(m_Settings->LoadLast());
ui.RotateAndScaleCheckBox->setChecked(m_Settings->RotateAndScale());
ui.CpuQualitySpin->setValue(m_Settings->CpuQuality());
ui.OpenCLQualitySpin->setValue(m_Settings->OpenClQuality());
ui.CpuSubBatchSpin->setValue(m_Settings->CpuSubBatch());

View File

@ -37,6 +37,7 @@ public:
bool Png16Bit();
bool AutoUnique();
bool LoadLast();
bool RotateAndScale();
uint ThreadCount();
uint RandomCount();
uint CpuQuality();

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>541</width>
<height>475</height>
<width>546</width>
<height>490</height>
</rect>
</property>
<property name="sizePolicy">
@ -496,6 +496,16 @@ in interactive mode for each mouse movement</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QCheckBox" name="RotateAndScaleCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Checked: scale and rotate when dragging affines while holding shift.&lt;/p&gt;&lt;p&gt;Unchecked: only scale when dragging affines while holding shift.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Scale and Rotate With Shift</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="OptionsXmlSavingTab">
@ -913,9 +923,15 @@ in interactive mode for each mouse movement</string>
<tabstops>
<tabstop>EarlyClipCheckBox</tabstop>
<tabstop>OpenCLCheckBox</tabstop>
<tabstop>YAxisUpCheckBox</tabstop>
<tabstop>SharedTextureCheckBox</tabstop>
<tabstop>TransparencyCheckBox</tabstop>
<tabstop>DoublePrecisionCheckBox</tabstop>
<tabstop>ContinuousUpdateCheckBox</tabstop>
<tabstop>ShowAllXformsCheckBox</tabstop>
<tabstop>Png16BitCheckBox</tabstop>
<tabstop>ToggleTypeCheckBox</tabstop>
<tabstop>RotateAndScaleCheckBox</tabstop>
<tabstop>LoadLastOnStartCheckBox</tabstop>
<tabstop>ThreadCountSpin</tabstop>
<tabstop>CpuSubBatchSpin</tabstop>