--User changes

-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.
This commit is contained in:
Person
2023-11-21 22:58:22 -07:00
parent 1a1cb8b0f2
commit 745f06d29d
47 changed files with 616 additions and 326 deletions

View File

@ -61,7 +61,7 @@
<enum>QFrame::NoFrame</enum>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p align=&quot;center&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Fractorium 23.23.8.1&lt;/span&gt;&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;A Qt-based fractal flame editor which uses a C++ re-write of the flam3 algorithm named Ember and a GPU capable version named EmberCL which implements a portion of the cuburn algorithm in OpenCL.&lt;/span&gt;&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;&lt;a href=&quot;http://fractorium.com&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;fractorium.com&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p align=&quot;center&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Fractorium 23.23.8.100&lt;/span&gt;&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;A Qt-based fractal flame editor which uses a C++ re-write of the flam3 algorithm named Ember and a GPU capable version named EmberCL which implements a portion of the cuburn algorithm in OpenCL.&lt;/span&gt;&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;&lt;a href=&quot;http://fractorium.com&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;fractorium.com&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>

View File

@ -219,7 +219,7 @@ void CurvesGraphicsView::mousePressEvent(QMouseEvent* e)
{
QGraphicsView::mousePressEvent(e);
if (e != nullptr)
if (e == nullptr)
return;
const auto thresh = devicePixelRatioF() * 4;

View File

@ -17,6 +17,12 @@ FinalRenderEmberControllerBase::FinalRenderEmberControllerBase(FractoriumFinalRe
m_Settings = FractoriumSettings::DefInstance();
}
template <typename T>
FinalRenderEmberController<T>::~FinalRenderEmberController()
{
m_ThreadedWriter.JoinAll();
}
/// <summary>
/// Cancel the render by calling Abort().
/// This will block until the cancelling is actually finished.
@ -91,7 +97,6 @@ void FinalRenderEmberControllerBase::Output(const QString& s)
QMetaObject::invokeMethod(m_FinalRenderDialog->ui.FinalRenderTextOutput, "append", Qt::QueuedConnection, Q_ARG(const QString&, s));
}
/// <summary>
/// Render a single ember.
/// </summary>
@ -103,36 +108,43 @@ template<typename T>
bool FinalRenderEmberController<T>::RenderSingleEmber(Ember<T>& ember, bool fullRender, size_t& stripForProgress)
{
if (!m_Renderer.get())
{
return false;
}
auto ret = true;
auto threadIndex = fullRender ? m_ThreadedWriter.Increment() : m_ThreadedWriter.Current();
auto threadImage = m_ThreadedWriter.GetImage(threadIndex);
ember.m_TemporalSamples = 1;//No temporal sampling.
m_Renderer->SetEmber(ember, fullRender ? eProcessAction::FULL_RENDER : eProcessAction::KEEP_ITERATING, /* updatePointer */ true);
m_Renderer->PrepFinalAccumVector(m_FinalImage);//Must manually call this first because it could be erroneously made smaller due to strips if called inside Renderer::Run().
m_Renderer->PrepFinalAccumVector(*threadImage);//Must manually call this first because it could be erroneously made smaller due to strips if called inside Renderer::Run().
m_Stats.Clear();
m_RenderTimer.Tic();//Toc() is called in RenderComplete().
StripsRender<T>(m_Renderer.get(), ember, m_FinalImage, 0, m_GuiState.m_Strips, m_GuiState.m_YAxisUp,
StripsRender<T>(m_Renderer.get(), ember, *threadImage, 0, m_GuiState.m_Strips, m_GuiState.m_YAxisUp,
[&](size_t strip) { stripForProgress = strip; },//Pre strip.
[&](size_t strip) { m_Stats += m_Renderer->Stats(); },//Post strip.
[&](size_t strip)//Error.
{
Output("Rendering failed.\n");
m_Fractorium->ErrorReportToQTextEdit(m_Renderer->ErrorReport(), m_FinalRenderDialog->ui.FinalRenderTextOutput, false);//Internally calls invoke.
ret = false;
m_Run = false;
},
[&](Ember<T>& finalEmber)
{
m_FinishedImageCount.fetch_add(1);
if (SaveCurrentRender(finalEmber) == "")
m_Run = ret = false;
auto stats = m_Renderer->Stats();
auto comments = m_Renderer->ImageComments(stats, 0, true);
auto rasw = m_Renderer->FinalRasW();
auto rash = m_Renderer->FinalRasH();
auto png16 = m_FinalRenderDialog->Png16Bit();
auto transparency = m_FinalRenderDialog->Transparency();
RenderComplete(finalEmber);
HandleFinishedProgress();
auto writeThread = std::thread([ = ](Ember<T> threadEmber)//Pass ember by value.
{
if (SaveCurrentRender(threadEmber, comments, *threadImage, rasw, rash, png16, transparency) == "")
m_Run = false;
}, finalEmber);
m_ThreadedWriter.SetThread(threadIndex, writeThread);
});//Final strip.
return ret;
return m_Run;
}
/// <summary>
@ -146,22 +158,17 @@ template<typename T>
bool FinalRenderEmberController<T>::RenderSingleEmberFromSeries(std::atomic<size_t>* atomfTime, size_t index)
{
if (m_Renderers.size() <= index)
{
return false;
}
size_t ftime;
size_t finalImageIndex = 0;
std::thread writeThread;
vector<v4F> finalImages[2];
EmberStats stats;
EmberImageComments comments;
Timing renderTimer;
const auto renderer = m_Renderers[index].get();
if (renderer == nullptr)
return false;
size_t ftime;
Timing renderTimer;
ThreadedWriter localThreadedWriter(16);//Use a local one for each renderer in a sequence instead of the class member.
//Render each image, cancelling if m_Run ever gets set to false.
//The conditions of this loop use atomics to synchronize when running on multiple GPUs.
//The order is reversed from the usual loop: rather than compare and increment the counter,
@ -176,9 +183,12 @@ bool FinalRenderEmberController<T>::RenderSingleEmberFromSeries(std::atomic<size
Output("Image " + ToString(ftime + 1ULL) + ":\n" + ComposePath(QString::fromStdString(m_EmberFile.Get(ftime)->m_Name)));
renderer->Reset();//Have to manually set this since the ember is not set each time through.
renderTimer.Tic();//Toc() is called in RenderComplete().
auto threadIndex = localThreadedWriter.Increment();
auto threadImage = localThreadedWriter.GetImage(threadIndex);
//renderer->PrepFinalAccumVector(threadImage);
//Can't use strips render here. Run() must be called directly for animation.
if (renderer->Run(finalImages[finalImageIndex], T(ftime)) != eRenderStatus::RENDER_OK)
if (renderer->Run(*threadImage, T(ftime)) != eRenderStatus::RENDER_OK)
{
Output("Rendering failed.\n");
m_Fractorium->ErrorReportToQTextEdit(renderer->ErrorReport(), m_FinalRenderDialog->ui.FinalRenderTextOutput, false);//Internally calls invoke.
@ -188,31 +198,33 @@ bool FinalRenderEmberController<T>::RenderSingleEmberFromSeries(std::atomic<size
}
else
{
Join(writeThread);
stats = renderer->Stats();
comments = renderer->ImageComments(stats, 0, true);
writeThread = std::thread([&](size_t tempTime, size_t threadFinalImageIndex)
{
if (SaveCurrentRender(*m_EmberFile.Get(tempTime),
comments,//These all don't change during the renders, so it's ok to access them in the thread.
finalImages[threadFinalImageIndex],
renderer->FinalRasW(),
renderer->FinalRasH(),
m_FinalRenderDialog->Png16Bit(),
m_FinalRenderDialog->Transparency()) == "")
m_Run = false;
}, ftime, finalImageIndex);
auto stats = renderer->Stats();
auto comments = renderer->ImageComments(stats, 0, true);
auto w = renderer->FinalRasW();
auto h = renderer->FinalRasH();
auto ember = m_EmberFile.Get(ftime);
m_FinishedImageCount.fetch_add(1);
RenderComplete(*m_EmberFile.Get(ftime), stats, renderTimer);
RenderComplete(*ember, stats, renderTimer);
if (!index)//Only first device has a progress callback, so it also makes sense to only manually set the progress on the first device as well.
HandleFinishedProgress();
}
finalImageIndex ^= 1;//Toggle the index.
auto writeThread = std::thread([ = ]()
{
if (SaveCurrentRender(*ember,
comments,//These all don't change during the renders, so it's ok to access them in the thread.
*threadImage,
w,
h,
m_FinalRenderDialog->Png16Bit(),
m_FinalRenderDialog->Transparency()) == "")
m_Run = false;
});
localThreadedWriter.SetThread(threadIndex, writeThread);
}
}
Join(writeThread);//One final check to make sure all writing is done before exiting this thread.
localThreadedWriter.JoinAll();//One final check to make sure all writing is done before exiting this thread.
return m_Run;
}
@ -223,7 +235,8 @@ bool FinalRenderEmberController<T>::RenderSingleEmberFromSeries(std::atomic<size
/// <param name="finalRender">Pointer to the final render dialog</param>
template<typename T>
FinalRenderEmberController<T>::FinalRenderEmberController(FractoriumFinalRenderDialog* finalRender)
: FinalRenderEmberControllerBase(finalRender)
: FinalRenderEmberControllerBase(finalRender),
m_ThreadedWriter(16)
{
m_FinalPreviewRenderer = make_unique<FinalRenderPreviewRenderer<T>>(this);
//The main rendering function which will be called in a Qt thread.
@ -321,7 +334,7 @@ FinalRenderEmberController<T>::FinalRenderEmberController(FractoriumFinalRenderD
break;
Output("Image " + ToString<qulonglong>(m_FinishedImageCount.load() + 1) + ":\n" + ComposePath(QString::fromStdString(it.m_Name)));
RenderSingleEmber(it, /* fullRender */ true, currentStripForProgress);
RenderSingleEmber(it, true, currentStripForProgress);
}
}
else
@ -335,18 +348,21 @@ FinalRenderEmberController<T>::FinalRenderEmberController(FractoriumFinalRenderD
m_ImageCount = 1;
m_Ember->m_TemporalSamples = 1;
m_Fractorium->m_Controller->ParamsToEmber(*m_Ember, true);//Update color and filter params from the main window controls, which only affect the filter and/or final accumulation stage.
RenderSingleEmber(*m_Ember, /* fullRender= */ !isBump, currentStripForProgress);
RenderSingleEmber(*m_Ember, !isBump, currentStripForProgress);
}
else
{
Output("No renderer present, aborting.");
}
m_ThreadedWriter.JoinAll();
const QString totalTimeString = m_Run ? "All renders completed in: " + QString::fromStdString(m_TotalTimer.Format(m_TotalTimer.Toc())) + "."
: "Render aborted.";
Output(totalTimeString);
QFile::remove(backup);
QMetaObject::invokeMethod(m_FinalRenderDialog, "Pause", Qt::QueuedConnection, Q_ARG(bool, false));
QMetaObject::invokeMethod(m_FinalRenderDialog->ui.FinalRenderSaveAgainAsButton, "setEnabled", Qt::QueuedConnection, Q_ARG(bool, !doAll && m_Renderer.get()));//Can do save again with variable number of strips.
QMetaObject::invokeMethod(m_FinalRenderDialog->ui.FinalRenderBumpQualityStartButton, "setEnabled", Qt::QueuedConnection, Q_ARG(bool, !doAll && m_Renderer.get() && m_GuiState.m_Strips == 1));
m_Run = false;
};
}
@ -815,6 +831,8 @@ QString FinalRenderEmberController<T>::SaveCurrentAgain()
minde != m_Ember->m_MinRadDE ||
maxde != m_Ember->m_MaxRadDE ||
curvede != m_Ember->m_CurveDE;
auto threadIndex = m_ThreadedWriter.Current();
auto threadImage = m_ThreadedWriter.GetImage(threadIndex);
//This is sort of a hack outside of the normal rendering process above.
if (dofilterandaccum ||
@ -831,14 +849,18 @@ QString FinalRenderEmberController<T>::SaveCurrentAgain()
m_Ember->m_TemporalSamples = 1;
m_Renderer->m_ProgressParameter = reinterpret_cast<void*>(&currentStripForProgress);//Need to reset this because it was set to a local variable within the render thread.
m_Renderer->SetEmber(*m_Ember, dofilterandaccum ? eProcessAction::FILTER_AND_ACCUM : eProcessAction::ACCUM_ONLY);
m_Renderer->Run(m_FinalImage, 0, m_GuiState.m_Strips, m_GuiState.m_YAxisUp);
m_Renderer->Run(*threadImage, 0, m_GuiState.m_Strips, m_GuiState.m_YAxisUp);
m_FinishedImageCount.fetch_add(1);
HandleFinishedProgress();
m_Run = false;
}
auto stats = m_Renderer->Stats();
auto comments = m_Renderer->ImageComments(stats, 0, true);
return SaveCurrentRender(*m_Ember, comments, *threadImage, m_Renderer->FinalRasW(), m_Renderer->FinalRasH(), m_FinalRenderDialog->Png16Bit(), m_FinalRenderDialog->Transparency());
}
return SaveCurrentRender(*m_Ember);
return "";
}
/// <summary>
@ -941,8 +963,6 @@ void FinalRenderEmberController<T>::HandleFinishedProgress()
QMetaObject::invokeMethod(m_FinalRenderDialog->ui.FinalRenderTotalProgress, "setValue", Qt::QueuedConnection, Q_ARG(int, static_cast<int>((float(finishedCountCached) / static_cast<float>(m_ImageCount)) * 100)));
QMetaObject::invokeMethod(m_FinalRenderDialog->ui.FinalRenderImageCountLabel, "setText", Qt::QueuedConnection, Q_ARG(const QString&, ToString<qulonglong>(finishedCountCached) + " / " + ToString<qulonglong>(m_ImageCount)));
QMetaObject::invokeMethod(m_FinalRenderDialog->ui.FinalRenderSaveAgainAsButton, "setEnabled", Qt::QueuedConnection, Q_ARG(bool, !doAll && m_Renderer.get()));//Can do save again with variable number of strips.
QMetaObject::invokeMethod(m_FinalRenderDialog->ui.FinalRenderBumpQualityStartButton, "setEnabled", Qt::QueuedConnection, Q_ARG(bool, !doAll && m_Renderer.get() && m_GuiState.m_Strips == 1));
}
/// <summary>

View File

@ -106,7 +106,7 @@ class FinalRenderEmberController : public FinalRenderEmberControllerBase
public:
FinalRenderEmberController(FractoriumFinalRenderDialog* finalRender);
virtual ~FinalRenderEmberController() { }
virtual ~FinalRenderEmberController();
//Virtual functions overridden from FractoriumEmberControllerBase.
void SetEmberFile(const EmberFile<float>& emberFile, bool move) override;
@ -159,6 +159,7 @@ protected:
EmberToXml<T> m_XmlWriter;
unique_ptr<FinalRenderPreviewRenderer<T>> m_FinalPreviewRenderer;
vector<unique_ptr<EmberNs::Renderer<T, float>>> m_Renderers;
ThreadedWriter m_ThreadedWriter;
};
/// <summary>

View File

@ -69,7 +69,7 @@ Fractorium::Fractorium(QWidget* p)
m_VarDialog = new FractoriumVariationsDialog(this);
m_AboutDialog = new FractoriumAboutDialog(this);
//Put the about dialog in the screen center.
m_AboutDialogCentered = false;
m_AboutDialogCentered = false;
connect(m_ColorDialog, SIGNAL(colorSelected(const QColor&)), this, SLOT(OnColorSelected(const QColor&)), Qt::QueuedConnection);
m_XformComboColors[i++] = QColor(0XFF, 0X00, 0X00);
m_XformComboColors[i++] = QColor(0XCC, 0XCC, 0X00);
@ -755,11 +755,11 @@ void Fractorium::showEvent(QShowEvent* e)
emit qGuiApp->focusWindowChanged(windowHandle());
QMainWindow::showEvent(e);
if(!m_AboutDialogCentered)
{
m_AboutDialogCentered = true;
QTimer::singleShot(100, this, SLOT(WindowShown()));
}
if (!m_AboutDialogCentered)
{
m_AboutDialogCentered = true;
QTimer::singleShot(100, this, SLOT(WindowShown()));
}
}
/// <summary>
@ -769,10 +769,10 @@ void Fractorium::showEvent(QShowEvent* e)
/// </summary>
void Fractorium::WindowShown()
{
//Put the about dialog in the screen center.
auto screen = QGuiApplication::screenAt(pos());
auto geom = screen->availableGeometry();
m_AboutDialog->move(geom.center() - m_AboutDialog->rect().center());
//Put the about dialog in the screen center.
auto screen = QGuiApplication::screenAt(pos());
auto geom = screen->availableGeometry();
m_AboutDialog->move(geom.center() - m_AboutDialog->rect().center());
}
/// <summary>

View File

@ -358,6 +358,7 @@ public slots:
void OnRandomXaosButtonClicked(bool checked);
void OnAddLayerButtonClicked(bool checked);
void OnTransposeXaosButtonClicked(bool checked);
void OnToggleXaosButtonClicked(bool checked);
void OnXaosRowDoubleClicked(int logicalIndex);
void OnXaosColDoubleClicked(int logicalIndex);
void OnXaosHScrollValueChanged(int value);
@ -398,7 +399,7 @@ public slots:
void SetTreeItemData(EmberTreeWidgetItemBase* item, vv4F& v, uint w, uint h);
private slots:
void WindowShown();
void WindowShown();
public:
//template<typename spinType, typename valType>//See below.
@ -626,7 +627,7 @@ private:
std::unordered_map<QString, std::pair<int, int>> m_HeightPresets;
bool m_PreviousAffineState[4];
bool m_AboutDialogCentered;
bool m_AboutDialogCentered;
int m_FontSize;
int m_VarSortMode;
int m_PaletteSortMode;

View File

@ -2000,6 +2000,16 @@
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QPushButton" name="ToggleXaosButton">
<property name="toolTip">
<string>Toggle whether to use xaos</string>
</property>
<property name="text">
<string>Toggle Xaos</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
@ -6025,7 +6035,7 @@
<bool>false</bool>
</property>
<property name="columnCount">
<number>3</number>
<number>2</number>
</property>
<attribute name="headerMinimumSectionSize">
<number>10</number>
@ -6046,20 +6056,15 @@
<property name="text">
<string>Variation</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. Order matters for these variations, which can be adjusted by dragging them within the tree on the Summary Tab.&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>
<column>
<property name="text">
<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. Order matters for these variations, which can be adjusted by dragging them within the tree on the Summary Tab.&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

@ -20,7 +20,6 @@ FractoriumEmberControllerBase::FractoriumEmberControllerBase(Fractorium* fractor
m_RenderRestartTimer = make_unique<QTimer>(m_Fractorium);
m_AnimateTimer = make_unique<QTimer>(m_Fractorium);
m_AnimateTimer->stop();
m_Fractorium->connect(m_RenderRestartTimer.get(), &QTimer::timeout, [&]() { m_Fractorium->StartRenderTimer(false); });//It's ok to pass false for the first shot because creating the controller will start the preview renders.
// XXX: why not SLOT(SequenceAnimateNextFrame())?
m_Fractorium->connect(m_AnimateTimer.get(), &QTimer::timeout, [&]() { SequenceAnimateNextFrame(); });
@ -115,7 +114,7 @@ FractoriumEmberController<T>::~FractoriumEmberController() { }
/// These are used to preserve the current ember/file when switching between renderers.
/// Note that some precision will be lost when going from double to float.
/// </summary>
template <typename T> void FractoriumEmberController<T>::SetEmber(const Ember<float>& ember, bool verbatim, bool updatePointer) { SetEmberPrivate<float>(ember, verbatim, updatePointer); }
template <typename T> void FractoriumEmberController<T>::SetEmber(const Ember<float>& ember, bool verbatim, bool updatePointer, int xformIndex) { SetEmberPrivate<float>(ember, verbatim, updatePointer, xformIndex); }
template <typename T> void FractoriumEmberController<T>::CopyEmber(Ember<float>& ember, std::function<void(Ember<float>& ember)> perEmberOperation) { ember = m_Ember; perEmberOperation(ember); }
template <typename T> void FractoriumEmberController<T>::SetEmberFile(const EmberFile<float>& emberFile, bool move) { move ? m_EmberFile = std::move(emberFile) : m_EmberFile = emberFile; }
template <typename T> void FractoriumEmberController<T>::CopyEmberFile(EmberFile<float>& emberFile, bool sequence, std::function<void(Ember<float>& ember)> perEmberOperation)
@ -132,10 +131,12 @@ template <typename T> void FractoriumEmberController<T>::CopyEmberFile(EmberFile
}
}
template <typename T> void FractoriumEmberController<T>::CopyXaosToggleEmber(Ember<float>& ember) { ember = m_XaosToggleEmber; }
template <typename T> void FractoriumEmberController<T>::SetXaosToggleEmber(const Ember<float>& ember) { m_XaosToggleEmber = ember; }
template <typename T> void FractoriumEmberController<T>::SetTempPalette(const Palette<float>& palette) { m_TempPalette = palette; }
template <typename T> void FractoriumEmberController<T>::CopyTempPalette(Palette<float>& palette) { palette = m_TempPalette; }
#ifdef DO_DOUBLE
template <typename T> void FractoriumEmberController<T>::SetEmber(const Ember<double>& ember, bool verbatim, bool updatePointer) { SetEmberPrivate<double>(ember, verbatim, updatePointer); }
template <typename T> void FractoriumEmberController<T>::SetEmber(const Ember<double>& ember, bool verbatim, bool updatePointer, int xformIndex) { SetEmberPrivate<double>(ember, verbatim, updatePointer, xformIndex); }
template <typename T> void FractoriumEmberController<T>::CopyEmber(Ember<double>& ember, std::function<void(Ember<double>& ember)> perEmberOperation) { ember = m_Ember; perEmberOperation(ember); }
template <typename T> void FractoriumEmberController<T>::SetEmberFile(const EmberFile<double>& emberFile, bool move) { move ? m_EmberFile = std::move(emberFile) : m_EmberFile = emberFile; }
template <typename T> void FractoriumEmberController<T>::CopyEmberFile(EmberFile<double>& emberFile, bool sequence, std::function<void(Ember<double>& ember)> perEmberOperation)
@ -152,6 +153,8 @@ template <typename T> void FractoriumEmberController<T>::CopyEmberFile(EmberFile
}
}
template <typename T> void FractoriumEmberController<T>::CopyXaosToggleEmber(Ember<double>& ember) { ember = m_XaosToggleEmber; }
template <typename T> void FractoriumEmberController<T>::SetXaosToggleEmber(const Ember<double>& ember) { m_XaosToggleEmber = ember; }
template <typename T> void FractoriumEmberController<T>::SetTempPalette(const Palette<double>& palette) { m_TempPalette = palette; }
template <typename T> void FractoriumEmberController<T>::CopyTempPalette(Palette<double>& palette) { palette = m_TempPalette; }
#endif
@ -166,6 +169,8 @@ void FractoriumEmberController<T>::ConstrainDimensions(Ember<T>& ember)
/// <summary>
/// Set the ember at the specified index from the currently opened file as the current Ember.
/// Also attempt to keep the same xform inxed selected, which is useful when switching between many
/// similar embers when setting up animation keyframes.
/// Clears the undo state.
/// Resets the rendering process.
/// </summary>
@ -176,9 +181,10 @@ void FractoriumEmberController<T>::SetEmber(size_t index, bool verbatim)
{
if (index < m_EmberFile.Size())
{
auto currentXformIndex = m_Fractorium->ui.CurrentXformCombo->currentIndex();
m_Fractorium->SelectLibraryItem(index);
ClearUndo();
SetEmber(*m_EmberFile.Get(index), verbatim, true);
SetEmber(*m_EmberFile.Get(index), verbatim, true, currentXformIndex);
}
}
@ -329,9 +335,10 @@ void FractoriumEmberController<T>::UpdateXform(std::function<void(Xform<T>*, siz
/// <param name="ember">The ember to set as the current</param>
/// <param name="verbatim">If true, do not overwrite temporal samples, quality or supersample value, else overwrite.</param>
/// <param name="updatePointer">If true, update the current ember pointer to the address of the one passed in.</param>
/// <param name="xformIndex">The xform index to select as the current one</param>
template <typename T>
template <typename U>
void FractoriumEmberController<T>::SetEmberPrivate(const Ember<U>& ember, bool verbatim, bool updatePointer)
void FractoriumEmberController<T>::SetEmberPrivate(const Ember<U>& ember, bool verbatim, bool updatePointer, int xformIndex)
{
if (ember.m_Name != m_Ember.m_Name)
m_LastSaveCurrent = "";
@ -339,6 +346,7 @@ void FractoriumEmberController<T>::SetEmberPrivate(const Ember<U>& ember, bool v
const auto w = m_Ember.m_FinalRasW;//Cache values for use below.
const auto h = m_Ember.m_FinalRasH;
m_Ember = ember;
m_XaosToggleEmber.Clear();
if (updatePointer && (typeid(T) == typeid(U)))
m_EmberFilePointer = (Ember<T>*)&ember;
@ -360,7 +368,7 @@ void FractoriumEmberController<T>::SetEmberPrivate(const Ember<U>& ember, bool v
const auto filename = path.toStdString() + "/last.flame";
writer.Save(filename, m_Ember, 0, true, true, false, true, true);
m_GLController->ResetMouseState();
FillXforms();//Must do this first because the palette setup in FillParamTablesAndPalette() uses the xforms combo.
FillXforms(xformIndex);//Must do this first because the palette setup in FillParamTablesAndPalette() uses the xforms combo.
FillParamTablesAndPalette();
FillCurvesControl();
FillSummary();

View File

@ -57,17 +57,21 @@ public:
virtual ~FractoriumEmberControllerBase();
//Embers.
virtual void SetEmber(const Ember<float>& ember, bool verbatim, bool updatePointer) { }
virtual void SetEmber(const Ember<float>& ember, bool verbatim, bool updatePointer, int xformIndex) { }
virtual void CopyEmber(Ember<float>& ember, std::function<void(Ember<float>& ember)> perEmberOperation/* = [&](Ember<float>& ember) { }*/) { }//Uncomment default lambdas once LLVM fixes a crash in their compiler with default lambda parameters.//TODO
virtual void SetEmberFile(const EmberFile<float>& emberFile, bool move) { }
virtual void CopyEmberFile(EmberFile<float>& emberFile, bool sequence, std::function<void(Ember<float>& ember)> perEmberOperation/* = [&](Ember<float>& ember) { }*/) { }
virtual void CopyXaosToggleEmber(Ember<float>& ember) { }
virtual void SetXaosToggleEmber(const Ember<float>& ember) { }
virtual void SetTempPalette(const Palette<float>& palette) { }
virtual void CopyTempPalette(Palette<float>& palette) { }
#ifdef DO_DOUBLE
virtual void SetEmber(const Ember<double>& ember, bool verbatim, bool updatePointer) { }
virtual void SetEmber(const Ember<double>& ember, bool verbatim, bool updatePointer, int xformIndex) { }
virtual void CopyEmber(Ember<double>& ember, std::function<void(Ember<double>& ember)> perEmberOperation/* = [&](Ember<double>& ember) { }*/) { }
virtual void SetEmberFile(const EmberFile<double>& emberFile, bool move) { }
virtual void CopyEmberFile(EmberFile<double>& emberFile, bool sequence, std::function<void(Ember<double>& ember)> perEmberOperation/* = [&](Ember<double>& ember) { }*/) { }
virtual void CopyXaosToggleEmber(Ember<double>& ember) { }
virtual void SetXaosToggleEmber(const Ember<double>& ember) { }
virtual void SetTempPalette(const Palette<double>& palette) { }
virtual void CopyTempPalette(Palette<double>& palette) { }
#endif
@ -252,6 +256,7 @@ public:
virtual void RandomXaos() { }
virtual void AddLayer(int xforms) { }
virtual void TransposeXaos() { }
virtual void ToggleXaos() { }
//Palette.
virtual size_t InitPaletteList(const QString& s) { return 0; }
@ -352,17 +357,21 @@ public:
virtual ~FractoriumEmberController();
//Embers.
void SetEmber(const Ember<float>& ember, bool verbatim, bool updatePointer) override;
void SetEmber(const Ember<float>& ember, bool verbatim, bool updatePointer, int xformIndex) override;
void CopyEmber(Ember<float>& ember, std::function<void(Ember<float>& ember)> perEmberOperation/* = [&](Ember<float>& ember) { }*/) override;
void SetEmberFile(const EmberFile<float>& emberFile, bool move) override;
void CopyEmberFile(EmberFile<float>& emberFile, bool sequence, std::function<void(Ember<float>& ember)> perEmberOperation/* = [&](Ember<float>& ember) { }*/) override;
void CopyXaosToggleEmber(Ember<float>& ember) override;
void SetXaosToggleEmber(const Ember<float>& ember) override;
void SetTempPalette(const Palette<float>& palette) override;
void CopyTempPalette(Palette<float>& palette) override;
#ifdef DO_DOUBLE
void SetEmber(const Ember<double>& ember, bool verbatim, bool updatePointer) override;
void SetEmber(const Ember<double>& ember, bool verbatim, bool updatePointer, int xformIndex) override;
void CopyEmber(Ember<double>& ember, std::function<void(Ember<double>& ember)> perEmberOperation/* = [&](Ember<double>& ember) { }*/) override;
void SetEmberFile(const EmberFile<double>& emberFile, bool move) override;
void CopyEmberFile(EmberFile<double>& emberFile, bool sequence, std::function<void(Ember<double>& ember)> perEmberOperation/* = [&](Ember<double>& ember) { }*/) override;
void CopyXaosToggleEmber(Ember<double>& ember) override;
void SetXaosToggleEmber(const Ember<double>& ember) override;
void SetTempPalette(const Palette<double>& palette) override;
void CopyTempPalette(Palette<double>& palette) override;
#endif
@ -542,6 +551,7 @@ public:
void FilteredVariations() override;
void FillVariationTreeWithCurrentXform() override;
void FillVariationTreeWithXform(Xform<T>* xform);
QIcon MakeVariationIcon(const Variation<T>* var);
//Xforms Xaos.
void FillXaos() override;
@ -551,6 +561,7 @@ public:
void RandomXaos() override;
void AddLayer(int xforms) override;
void TransposeXaos() override;
void ToggleXaos() override;
//Xforms Selection.
virtual QString MakeXformCaption(size_t i) override;
@ -584,7 +595,7 @@ public:
private:
//Embers.
void ApplyXmlSavingTemplate(Ember<T>& ember);
template <typename U> void SetEmberPrivate(const Ember<U>& ember, bool verbatim, bool updatePointer);
template <typename U> void SetEmberPrivate(const Ember<U>& ember, bool verbatim, bool updatePointer, int xformIndex);
//Params.
template <typename U> void ParamsToEmberPrivate(Ember<U>& ember, bool imageParamsOnly);
@ -609,6 +620,7 @@ private:
vector<T> m_TempOpacities;
vector<T> m_NormalizedWeights;
Ember<T> m_Ember;
Ember<T> m_XaosToggleEmber;
Ember<T>* m_EmberFilePointer = nullptr;
EmberFile<T> m_EmberFile;
EmberFile<T> m_SequenceFile;

View File

@ -163,6 +163,8 @@ void FractoriumEmberController<T>::FillSummary()
vitem->setText(0, QString::fromStdString(var->Name()));
vitem->setText(1, QLocale::system().toString(var->m_Weight, pc, vp).rightJustified(vlen, ' '));
vitem->setFlags(draggable);
auto qi = MakeVariationIcon(var);
vitem->setIcon(0, qi);
if (const auto parVar = dynamic_cast<ParametricVariation<T>*>(var))
{

View File

@ -672,13 +672,13 @@ void FractoriumEmberController<T>::SequenceGenerateButtonClicked()
return;
}
if (framesPerRot > 1 && !rots)//Because framesPerRot control has a min value of 1, check greater than 1. Also don't need to check the inverse like in EmberGenome.
{
QMessageBox::critical(m_Fractorium, "Animation sequence parameters error",
"Frames per rot cannot be greater than one while Rotations is zero. Setting it to 1.");
ui.SequenceFramesPerRotSpinBox->setValue(1);
return;
}
//if (framesPerRot > 1 && !rots)//Because framesPerRot control has a min value of 1, check greater than 1. Also don't need to check the inverse like in EmberGenome.
//{
//QMessageBox::critical(m_Fractorium, "Animation sequence parameters error",
// "Frames per rot cannot be greater than one while Rotations is zero. Setting it to 1.");
//ui.SequenceFramesPerRotSpinBox->setValue(1);
//return;
//}
}
SheepTools<T, float> tools(palettePath, EmberCommon::CreateRenderer<T>(eRendererType::CPU_RENDERER, devices, false, 0, emberReport));
@ -695,8 +695,10 @@ void FractoriumEmberController<T>::SequenceGenerateButtonClicked()
if (randFramesRot)
frames = ui.SequenceRandomFramesPerRotMaxSpinBox->value();
else
else if (rots)
frames = ui.SequenceFramesPerRotSpinBox->value();
else
frames = 1;
if (randRot)
frames *= ui.SequenceRandomRotationsMaxSpinBox->value();

View File

@ -162,14 +162,15 @@ void Fractorium::OnActionNewRandomFlameInCurrentFile(bool checked) { m_Controlle
template <typename T>
void FractoriumEmberController<T>::CopyFlameInCurrentFile()
{
auto ember = m_Ember;
StopAllPreviewRenderers();
auto ember = m_Ember;
auto insertEmberIndex = m_Fractorium->ui.LibraryTree->currentIndex().row() + 1;
ember.m_Name = EmberFile<T>::DefaultEmberName(m_EmberFile.Size() + 1).toStdString();
ember.m_Index = m_EmberFile.Size();
m_EmberFile.m_Embers.push_back(ember);//Will invalidate the pointers contained in the EmberTreeWidgetItems, UpdateLibraryTree() will resync.
ember.m_Index = insertEmberIndex;//Will be overwritten below in UpdateLibraryTree().
m_EmberFile.m_Embers.insert(Advance(m_EmberFile.m_Embers.begin(), insertEmberIndex), ember);//Will invalidate the pointers contained in the EmberTreeWidgetItems, UpdateLibraryTree() will resync.
m_EmberFile.MakeNamesUnique();
UpdateLibraryTree();
SetEmber(m_EmberFile.Size() - 1, false);
FillLibraryTree(insertEmberIndex);
SetEmber(insertEmberIndex, false);
}
void Fractorium::OnActionCopyFlameInCurrentFile(bool checked) { m_Controller->CopyFlameInCurrentFile(); }
@ -590,7 +591,7 @@ void FractoriumEmberController<T>::Undo()
int index = m_Ember.GetTotalXformIndex(current, forceFinal);
m_LastEditWasUndoRedo = true;
m_UndoIndex = std::max<size_t>(0u, m_UndoIndex - 1u);
SetEmber(m_UndoList[m_UndoIndex], true, false);//Don't update pointer because it's coming from the undo list.
SetEmber(m_UndoList[m_UndoIndex], true, false, index);//Don't update pointer because it's coming from the undo list.
m_EditState = eEditUndoState::UNDO_REDO;
if (index >= 0 && index < m_Fractorium->ui.CurrentXformCombo->count())
@ -616,7 +617,7 @@ void FractoriumEmberController<T>::Redo()
int index = m_Ember.GetTotalXformIndex(current, forceFinal);
m_LastEditWasUndoRedo = true;
m_UndoIndex = std::min<size_t>(m_UndoIndex + 1, m_UndoList.size() - 1);
SetEmber(m_UndoList[m_UndoIndex], true, false);//Don't update pointer because it's coming from the undo list.
SetEmber(m_UndoList[m_UndoIndex], true, false, index);//Don't update pointer because it's coming from the undo list.
m_EditState = eEditUndoState::UNDO_REDO;
if (index >= 0 && index < m_Fractorium->ui.CurrentXformCombo->count())

View File

@ -438,7 +438,7 @@ void FractoriumEmberController<T>::ResizeAndScale(int width, int height, eScaleT
{
UpdateAll([&](Ember<T>& ember, bool isMain)
{
m_Ember.SetSizeAndAdjustScale(width, height, false, scaleType);
ember.SetSizeAndAdjustScale(width, height, false, scaleType);
}, true, eProcessAction::FULL_RENDER, m_Fractorium->ApplyAll());
m_Fractorium->m_ScaleSpin->SetValueStealth(m_Ember.m_PixelsPerUnit);
m_Fractorium->OnActionResetScale(true);
@ -521,11 +521,6 @@ template <typename T> void FractoriumEmberController<T>::RotateChanged(double d)
}
void Fractorium::OnRotateChanged(double d)
{
if (d < -180)
d = 180 - ((-d + m_RotateSpin->value()) - (180 + m_RotateSpin->value()));
else if (d > 180)
d = -180 + ((d - m_RotateSpin->value()) - (180 - m_RotateSpin->value()));
m_Controller->RotateChanged(d); // d is ever between -180 and +180
}

View File

@ -758,9 +758,11 @@ bool Fractorium::CreateControllerFromOptions()
#ifdef DO_DOUBLE
EmberFile<double> efd;
Palette<double> tempPalette;
Ember<double> xaosToggle;
#else
EmberFile<float> efd;
Palette<float> tempPalette;
Ember<float> xaosToggle;
#endif
const QModelIndex index = ui.LibraryTree->currentIndex();
ui.LibraryTree->clear();//This must be here before FillLibraryTree() is called below, else a spurious EmberTreeItemChanged event will be called on a deleted object.
@ -774,6 +776,7 @@ bool Fractorium::CreateControllerFromOptions()
current = m_Controller->SaveCurrentToOpenedFile(false);
//Replace below with this once LLVM fixes a crash in their compiler with default lambda parameters.//TODO
//m_Controller->CopyEmberFile(efd);
m_Controller->CopyXaosToggleEmber(xaosToggle);
#ifdef DO_DOUBLE
m_Controller->CopyEmberFile(efd, false, [&](Ember<double>& ember) { });
#else
@ -804,6 +807,7 @@ bool Fractorium::CreateControllerFromOptions()
//will apply the palette adjustments.
m_Controller->FillLibraryTree(index.row());
m_Controller->SetEmber(current, true);
m_Controller->SetXaosToggleEmber(xaosToggle);
m_Controller->LockedScale(scale);
//Setting these and updating the GUI overwrites the work of clearing them done in SetEmber() above.
//It's a corner case, but doesn't seem to matter.

View File

@ -11,6 +11,9 @@ void Fractorium::InitXaosUI()
int spinHeight = 20;
ui.XaosTableView->verticalHeader()->setSectionsClickable(true);
ui.XaosTableView->horizontalHeader()->setSectionsClickable(true);
auto ttstr = "<html><head/><body><p>Double click to toggle all table spinner values in one row/col.</p><p>If any cell in the row is non zero, set all cells to zero, else 1.</p><p>If shift is held down, reverse the logic.</p><p>If ctrl is held down, set each cell to a random 0 or 1.</p></body></html>";
ui.XaosTableView->horizontalHeader()->setToolTip(ttstr);
ui.XaosTableView->verticalHeader()->setToolTip(ttstr);
m_XaosSpinBox = new DoubleSpinBox(nullptr, spinHeight, 0.1, false);
m_XaosSpinBox->DoubleClick(true);
m_XaosSpinBox->DoubleClickZero(1);
@ -24,6 +27,7 @@ void Fractorium::InitXaosUI()
connect(ui.ClearXaosButton, SIGNAL(clicked(bool)), this, SLOT(OnClearXaosButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.RandomXaosButton, SIGNAL(clicked(bool)), this, SLOT(OnRandomXaosButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.TransposeXaosButton, SIGNAL(clicked(bool)), this, SLOT(OnTransposeXaosButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.ToggleXaosButton, SIGNAL(clicked(bool)), this, SLOT(OnToggleXaosButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.AddLayerButton, SIGNAL(clicked(bool)), this, SLOT(OnAddLayerButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.XaosTableView->verticalHeader(), SIGNAL(sectionDoubleClicked(int)), this, SLOT(OnXaosRowDoubleClicked(int)), Qt::QueuedConnection);
connect(ui.XaosTableView->horizontalHeader(), SIGNAL(sectionDoubleClicked(int)), this, SLOT(OnXaosColDoubleClicked(int)), Qt::QueuedConnection);
@ -59,22 +63,22 @@ void FractoriumEmberController<T>::FillXaos()
template <typename T>
void FractoriumEmberController<T>::FillAppliedXaos()
{
m_Ember.CalcNormalizedWeights(m_NormalizedWeights);
std::vector<T> weights;
auto ct = int(XformCount());
for (int i = 0, count = int(XformCount()); i < count; i++)//Column.
for (int i = 0, count = ct; i < count; i++)
weights.push_back(m_Ember.GetXform(i)->m_Weight);
for (int i = 0, count = ct; i < count; i++)//Column.
{
if (const auto xform = m_Ember.GetXform(i))
{
T norm = 0;
double start = 0, offset = 0;
auto tempweights = m_NormalizedWeights;
auto tempweights = weights;
for (int j = 0; j < count; j++)//Row.
{
tempweights[j] *= xform->Xaos(j);
QModelIndex index = m_Fractorium->m_AppliedXaosTableModel->index(j, i, QModelIndex());//j and i are intentionally swapped here.
m_Fractorium->m_AppliedXaosTableModel->setData(index, TruncPrecision(xform->Xaos(j) * xform->m_Weight, 4));//Applied xaos is just a read only table for display purposes.
}
QPixmap pixmap(m_Fractorium->ui.XaosAppliedTableView->columnWidth(i) - 8, m_Fractorium->ui.XaosTableView->rowHeight(0) * count);
QPainter painter(&pixmap);
@ -84,6 +88,13 @@ void FractoriumEmberController<T>::FillAppliedXaos()
for (auto& w : tempweights) w = norm == static_cast<T>(0) ? static_cast<T>(0) : w / norm;
for (int j = 0; j < count; j++)//Row.
{
auto normXaosAppliedWeight = tempweights[j];
QModelIndex index = m_Fractorium->m_AppliedXaosTableModel->index(j, i, QModelIndex());//j and i are intentionally swapped here.
m_Fractorium->m_AppliedXaosTableModel->setData(index, TruncPrecision(normXaosAppliedWeight, 4));//Applied xaos is just a read only table for display purposes.
}
if (norm)
{
for (size_t i = 0; i < tempweights.size() && offset <= pixmap.height(); i++)
@ -309,6 +320,48 @@ void FractoriumEmberController<T>::TransposeXaos()
void Fractorium::OnTransposeXaosButtonClicked(bool checked) { m_Controller->TransposeXaos(); }
/// <summary>
/// Toggle whether to use xaos or not by saving/restoring a backup flame.
/// Resets the rendering process.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::ToggleXaos()
{
Update([&]
{
if (m_Ember.XaosPresent())
{
m_XaosToggleEmber = m_Ember;
m_Ember.ClearXaos();
}
else if (m_XaosToggleEmber.XaosPresent())
{
size_t i = 0;
while (auto xform = m_Ember.GetXform(i))
{
auto backupXform = m_XaosToggleEmber.GetXform(i);
if (backupXform)
{
for (size_t j = 0; j < m_Ember.XformCount(); j++)
xform->SetXaos(j, TruncPrecision(backupXform->Xaos(j), XAOS_PREC));
}
else
break;
i++;
}
m_XaosToggleEmber.Clear();
}
});
FillXaos();
FillAppliedXaos();
}
void Fractorium::OnToggleXaosButtonClicked(bool checked) { m_Controller->ToggleXaos(); }
/// <summary>
/// Toggle all xaos values in one row on left mouse button double click and resize all cells to fit their data.
/// Skip toggling and only refit on right mouse button double click.

View File

@ -670,10 +670,8 @@ void FractoriumEmberController<T>::FillXforms(int index)
m_Fractorium->m_XformsSelectionLayout->blockSignals(false);
combo->blockSignals(false);
if (index < combo->count())
combo->setCurrentIndex(index);
index = index >= 0 && index < combo->count() ? index : 0;
combo->setCurrentIndex(index);
m_Fractorium->ui.SoloXformCheckBox->blockSignals(true);
if (m_Ember.m_Solo == combo->currentIndex())

View File

@ -110,13 +110,9 @@ void FractoriumEmberController<T>::SetupVariationsTree()
const QSize hint0(170, 16);
const QSize hint1(80, 16);
const 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++)
{
@ -129,33 +125,8 @@ void FractoriumEmberController<T>::SetupVariationsTree()
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);
auto qi = MakeVariationIcon(var);
item->setIcon(0, qi);
spinBox->setRange(fMin, fMax);
spinBox->DoubleClick(true);
spinBox->DoubleClickZero(1);
@ -173,6 +144,7 @@ void FractoriumEmberController<T>::SetupVariationsTree()
{
if (!params[j].IsPrecalc())
{
auto def = params[j].Def();
auto paramWidget = new VariationTreeWidgetItem(var->VariationId(), item);
auto varSpinBox = new VariationTreeDoubleSpinBox(tree, paramWidget, parVar->VariationId(), params[j].Name());
paramWidget->setText(0, params[j].Name().c_str());
@ -181,8 +153,9 @@ void FractoriumEmberController<T>::SetupVariationsTree()
varSpinBox->setRange(params[j].Min(), params[j].Max());
varSpinBox->setValue(params[j].ParamVal());
varSpinBox->DoubleClick(true);
varSpinBox->DoubleClickZero(1);
varSpinBox->DoubleClickNonZero(0);
varSpinBox->DoubleClickZero(def != 0 ? 0 : 1);
varSpinBox->DoubleClickLowVal(def);
varSpinBox->DoubleClickNonZero(def);
if (params[j].Type() == eParamType::INTEGER || params[j].Type() == eParamType::INTEGER_NONZERO)
{
@ -387,6 +360,50 @@ void FractoriumEmberController<T>::FillVariationTreeWithXform(Xform<T>* xform)
m_Fractorium->OnTreeHeaderSectionClicked(m_Fractorium->m_VarSortMode);
}
/// <summary>
/// Create an icon for the passed in variation which indicates the following:
/// Red: position matters because it uses non-standard assigning vs. summing.
/// Green: uses direct color.
/// Blue: maintains internal state, mostly of engineering interest.
/// </summary>
/// <param name="text">The variation to create the icon for</param>
/// <returns>The newly created icon</returns>
template <typename T>
QIcon FractoriumEmberController<T>::MakeVariationIcon(const Variation<T>* var)
{
const int iconSize = 20;
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=" };
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);
return qi;
}
/// <summary>
/// Change the sorting to be either by variation ID, or by weight.
/// If sorting by variation ID, repeated clicks will alternate ascending or descending.

View File

@ -99,8 +99,8 @@ template <typename T>
T GLEmberController<T>::CalcRotation()
{
const auto scrolledWorldCenter = ScrolledCenter(true);
const T rotStart = NormalizeDeg180<T>((std::atan2(m_MouseDownWorldPos.y - scrolledWorldCenter.y, m_MouseDownWorldPos.x - scrolledWorldCenter.x) * RAD_2_DEG_T));
const T rot = NormalizeDeg180<T>((std::atan2(m_MouseWorldPos.y - scrolledWorldCenter.y, m_MouseWorldPos.x - scrolledWorldCenter.x) * RAD_2_DEG_T));
const T rotStart = NormalizeDeg360<T>((std::atan2(m_MouseDownWorldPos.y - scrolledWorldCenter.y, m_MouseDownWorldPos.x - scrolledWorldCenter.x) * RAD_2_DEG_T));
const T rot = NormalizeDeg360<T>((std::atan2(m_MouseWorldPos.y - scrolledWorldCenter.y, m_MouseWorldPos.x - scrolledWorldCenter.x) * RAD_2_DEG_T));
return rotStart - rot;
}

View File

@ -1078,7 +1078,7 @@ void GLEmberController<T>::MouseMove(QMouseEvent* e)
{
const T rot = CalcRotation();
const T scale = CalcScale();
ember->m_Rotate = NormalizeDeg180<T>(m_RotationDown + rot);
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.
}

View File

@ -841,6 +841,9 @@
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="AutoUniqueCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;When resaving a file, use an auto generated unique name based on the first name supplied.&lt;/p&gt;&lt;p&gt;This allows you to keep snapshots of your changes instead of overwriting them with each save.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Auto Unique Filenames</string>
</property>