mirror of
https://bitbucket.org/mfeemster/fractorium.git
synced 2025-01-21 21:20:07 -05:00
be8e9a5e02
Add the ability to apply operations to a selection of xforms, rather than just the current one. Update tool tips accordingly. --Bug fixes Fix NAN in equalized weight calculation when all weights are set to 0. --Code changes Clean up strange nested usage of Update([&]()) calls when changing xform color index via either the spinner or scroll bar. Made no sense. Make ISAAC RandBit() use RandByte() to be slightly more efficient. Put FillXforms() in the controller where it belongs, rather than the main window class. Add UpdateXform() function to the controller to handle applying operations to multiple xforms. Remove the word "Current" out of most xform related functions because they now operate on whatever is selected. Properly use Update() for various xform operations whereas before it was manually calling UpdateRender(). Also properly use Update() in places where it was erroneously using UpdateXform() for things that did not involve xforms. Block signals in FillXaosTable(). Add new file named FractoriumXformsSelect.cpp to handle new xform selection code.
352 lines
13 KiB
C++
352 lines
13 KiB
C++
#include "FractoriumPch.h"
|
|
#include "FractoriumEmberController.h"
|
|
#include "Fractorium.h"
|
|
#include "GLEmberController.h"
|
|
|
|
#define SAVE_EACH 1
|
|
|
|
/// <summary>
|
|
/// Constructor which initializes the non-templated members contained in this class.
|
|
/// The renderer, other templated members and GUI setup will be done in the templated derived controller class.
|
|
/// </summary>
|
|
/// <param name="fractorium">Pointer to the main window.</param>
|
|
FractoriumEmberControllerBase::FractoriumEmberControllerBase(Fractorium* fractorium)
|
|
{
|
|
Timing t;
|
|
|
|
m_Rendering = false;
|
|
m_Shared = true;
|
|
m_Platform = 0;
|
|
m_Device = 0;
|
|
m_FailedRenders = 0;
|
|
m_UndoIndex = 0;
|
|
m_RenderType = CPU_RENDERER;
|
|
m_OutputTexID = 0;
|
|
m_SubBatchCount = 1;//Will be ovewritten by the options on first render.
|
|
m_FinalImageIndex = 0;
|
|
m_Fractorium = fractorium;
|
|
m_RenderTimer = nullptr;
|
|
m_RenderRestartTimer = nullptr;
|
|
m_Rand = QTIsaac<ISAAC_SIZE, ISAAC_INT>(ISAAC_INT(t.Tic()), ISAAC_INT(t.Tic() * 2), ISAAC_INT(t.Tic() * 3));//Ensure a different rand seed on each instance.
|
|
|
|
m_RenderTimer = new QTimer(m_Fractorium);
|
|
m_RenderTimer->setInterval(0);
|
|
m_Fractorium->connect(m_RenderTimer, SIGNAL(timeout()), SLOT(IdleTimer()));
|
|
|
|
m_RenderRestartTimer = new QTimer(m_Fractorium);
|
|
m_Fractorium->connect(m_RenderRestartTimer, SIGNAL(timeout()), SLOT(StartRenderTimer()));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Destructor which stops rendering and deletes the timers.
|
|
/// All other memory is cleared automatically through the use of STL.
|
|
/// </summary>
|
|
FractoriumEmberControllerBase::~FractoriumEmberControllerBase()
|
|
{
|
|
StopRenderTimer(true);
|
|
|
|
if (m_RenderTimer)
|
|
{
|
|
m_RenderTimer->stop();
|
|
delete m_RenderTimer;
|
|
m_RenderTimer = nullptr;
|
|
}
|
|
|
|
if (m_RenderRestartTimer)
|
|
{
|
|
m_RenderRestartTimer->stop();
|
|
delete m_RenderRestartTimer;
|
|
m_RenderRestartTimer = nullptr;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructor which passes the main window parameter to the base, initializes the templated members contained in this class.
|
|
/// Then sets up the parts of the GUI that require templated Widgets, such as the variations tree and the palette table.
|
|
/// Note the renderer is not setup here automatically. Instead, it must be manually created by the caller later.
|
|
/// </summary>
|
|
/// <param name="fractorium">Pointer to the main window.</param>
|
|
template <typename T>
|
|
FractoriumEmberController<T>::FractoriumEmberController(Fractorium* fractorium)
|
|
: FractoriumEmberControllerBase(fractorium)
|
|
{
|
|
m_PreviewRun = false;
|
|
m_PreviewRunning = false;
|
|
m_SheepTools = unique_ptr<SheepTools<T, T>>(new SheepTools<T, T>("flam3-palettes.xml", new EmberNs::Renderer<T, T>()));
|
|
m_GLController = unique_ptr<GLEmberController<T>>(new GLEmberController<T>(fractorium, fractorium->ui.GLDisplay, this));
|
|
m_PreviewRenderer = unique_ptr<EmberNs::Renderer<T, T>>(new EmberNs::Renderer<T, T>());
|
|
SetupVariationTree();
|
|
|
|
//Initial combo change event to fill the palette table will be called automatically later.
|
|
if (!InitPaletteList("./"))
|
|
throw "No palettes found, exiting.";
|
|
|
|
BackgroundChanged(QColor(0, 0, 0));//Default to black.
|
|
ClearUndo();
|
|
|
|
m_PreviewRenderer->Callback(nullptr);
|
|
m_PreviewRenderer->NumChannels(4);
|
|
m_PreviewRenderer->EarlyClip(m_Fractorium->m_Settings->EarlyClip());
|
|
m_PreviewRenderer->YAxisUp(m_Fractorium->m_Settings->YAxisUp());
|
|
m_PreviewRenderer->SetEmber(m_Ember);//Give it an initial ember, will be updated many times later.
|
|
//m_PreviewRenderer->ThreadCount(1);//For debugging.
|
|
|
|
m_PreviewRenderFunc = [&](uint start, uint end)
|
|
{
|
|
while(m_PreviewRun || m_PreviewRunning)
|
|
{
|
|
}
|
|
|
|
m_PreviewRun = true;
|
|
m_PreviewRunning = true;
|
|
m_PreviewRenderer->ThreadCount(std::max(1u, Timing::ProcessorCount() - 1));//Leave one processor free so the GUI can breathe.
|
|
QTreeWidget* tree = m_Fractorium->ui.LibraryTree;
|
|
|
|
if (QTreeWidgetItem* top = tree->topLevelItem(0))
|
|
{
|
|
for (size_t i = start; m_PreviewRun && i < end && i < m_EmberFile.Size(); i++)
|
|
{
|
|
Ember<T> ember = m_EmberFile.m_Embers[i];
|
|
|
|
ember.SyncSize();
|
|
ember.SetSizeAndAdjustScale(PREVIEW_SIZE, PREVIEW_SIZE, false, SCALE_WIDTH);
|
|
ember.m_TemporalSamples = 1;
|
|
ember.m_Quality = 25;
|
|
ember.m_Supersample = 1;
|
|
m_PreviewRenderer->SetEmber(ember);
|
|
|
|
if (m_PreviewRenderer->Run(m_PreviewFinalImage) == RENDER_OK)
|
|
{
|
|
if (EmberTreeWidgetItem<T>* treeItem = dynamic_cast<EmberTreeWidgetItem<T>*>(top->child(i)))
|
|
{
|
|
//It is critical that Qt::BlockingQueuedConnection is passed because this is running on a different thread than the UI.
|
|
//This ensures the events are processed in order as each preview is updated, and that control does not return here
|
|
//until the update is complete.
|
|
QMetaObject::invokeMethod(m_Fractorium, "SetLibraryTreeItemData", Qt::BlockingQueuedConnection,
|
|
Q_ARG(EmberTreeWidgetItemBase*, dynamic_cast<EmberTreeWidgetItemBase*>(treeItem)),
|
|
Q_ARG(vector<byte>&, m_PreviewFinalImage),
|
|
Q_ARG(uint, PREVIEW_SIZE),
|
|
Q_ARG(uint, PREVIEW_SIZE));
|
|
|
|
//treeItem->SetImage(m_PreviewFinalImage, PREVIEW_SIZE, PREVIEW_SIZE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
m_PreviewRun = false;
|
|
m_PreviewRunning = false;
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Empty destructor that does nothing.
|
|
/// </summary>
|
|
template <typename T>
|
|
FractoriumEmberController<T>::~FractoriumEmberController() { }
|
|
|
|
/// <summary>
|
|
/// Setters for embers, ember files and palettes which convert between float and double types.
|
|
/// 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) { SetEmberPrivate<float>(ember, verbatim); }
|
|
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) { m_EmberFile = emberFile; }
|
|
template <typename T> void FractoriumEmberController<T>::CopyEmberFile(EmberFile<float>& emberFile, std::function<void(Ember<float>& ember)> perEmberOperation)
|
|
{
|
|
emberFile.m_Filename = m_EmberFile.m_Filename;
|
|
CopyVec(emberFile.m_Embers, m_EmberFile.m_Embers, perEmberOperation);
|
|
}
|
|
|
|
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) { SetEmberPrivate<double>(ember, verbatim); }
|
|
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) { m_EmberFile = emberFile; }
|
|
template <typename T> void FractoriumEmberController<T>::CopyEmberFile(EmberFile<double>& emberFile, std::function<void(Ember<double>& ember)> perEmberOperation)
|
|
{
|
|
emberFile.m_Filename = m_EmberFile.m_Filename;
|
|
CopyVec(emberFile.m_Embers, m_EmberFile.m_Embers, perEmberOperation);
|
|
}
|
|
|
|
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
|
|
template <typename T> Ember<T>* FractoriumEmberController<T>::CurrentEmber() { return &m_Ember; }
|
|
|
|
template <typename T>
|
|
void FractoriumEmberController<T>::ConstrainDimensions(Ember<T>& ember)
|
|
{
|
|
ember.m_FinalRasW = std::min<int>(m_Fractorium->ui.GLDisplay->MaxTexSize(), ember.m_FinalRasW);
|
|
ember.m_FinalRasH = std::min<int>(m_Fractorium->ui.GLDisplay->MaxTexSize(), ember.m_FinalRasH);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the ember at the specified index from the currently opened file as the current Ember.
|
|
/// Clears the undo state.
|
|
/// Resets the rendering process.
|
|
/// </summary>
|
|
/// <param name="index">The index in the file from which to retrieve the ember</param>
|
|
template <typename T>
|
|
void FractoriumEmberController<T>::SetEmber(size_t index)
|
|
{
|
|
if (index < m_EmberFile.Size())
|
|
{
|
|
if (QTreeWidgetItem* top = m_Fractorium->ui.LibraryTree->topLevelItem(0))
|
|
{
|
|
for (uint i = 0; i < top->childCount(); i++)
|
|
{
|
|
if (EmberTreeWidgetItem<T>* emberItem = dynamic_cast<EmberTreeWidgetItem<T>*>(top->child(i)))
|
|
emberItem->setSelected(i == index);
|
|
}
|
|
}
|
|
|
|
ClearUndo();
|
|
SetEmber(m_EmberFile.m_Embers[index]);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Wrapper to call a function, then optionally add the requested action to the rendering queue.
|
|
/// </summary>
|
|
/// <param name="func">The function to call</param>
|
|
/// <param name="updateRender">True to update renderer, else false. Default: true.</param>
|
|
/// <param name="action">The action to add to the rendering queue. Default: FULL_RENDER.</param>
|
|
template <typename T>
|
|
void FractoriumEmberController<T>::Update(std::function<void (void)> func, bool updateRender, eProcessAction action)
|
|
{
|
|
func();
|
|
|
|
if (updateRender)
|
|
UpdateRender(action);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Wrapper to call a function on the specified xforms, then optionally add the requested action to the rendering queue.
|
|
/// If no xforms are selected via the checkboxes, and the update type is UPDATE_SELECTED, then the function will be called only on the currently selected xform.
|
|
/// </summary>
|
|
/// <param name="func">The function to call</param>
|
|
/// <param name="updateType">Whether to apply this update operation on the current, all or selected xforms. Default: UPDATE_CURRENT.</param>
|
|
/// <param name="updateRender">True to update renderer, else false. Default: true.</param>
|
|
/// <param name="action">The action to add to the rendering queue. Default: FULL_RENDER.</param>
|
|
template <typename T>
|
|
void FractoriumEmberController<T>::UpdateXform(std::function<void(Xform<T>*)> func, eXformUpdate updateType, bool updateRender, eProcessAction action)
|
|
{
|
|
size_t i = 0;
|
|
bool isCurrentFinal = m_Ember.IsFinalXform(CurrentXform());
|
|
bool doFinal = updateType != eXformUpdate::UPDATE_SELECTED_EXCEPT_FINAL && updateType != eXformUpdate::UPDATE_ALL_EXCEPT_FINAL;
|
|
|
|
switch (updateType)
|
|
{
|
|
case eXformUpdate::UPDATE_CURRENT:
|
|
{
|
|
if (Xform<T>* xform = CurrentXform())
|
|
func(xform);
|
|
}
|
|
break;
|
|
|
|
case eXformUpdate::UPDATE_SELECTED:
|
|
case eXformUpdate::UPDATE_SELECTED_EXCEPT_FINAL:
|
|
{
|
|
bool anyUpdated = false;
|
|
|
|
while (Xform<T>* xform = (doFinal ? m_Ember.GetTotalXform(i) : m_Ember.GetXform(i)))
|
|
{
|
|
if (QLayoutItem* child = m_Fractorium->m_XformsSelectionLayout->itemAt(i))
|
|
{
|
|
if (auto* w = dynamic_cast<QCheckBox*>(child->widget()))
|
|
{
|
|
if (w->isChecked())
|
|
{
|
|
func(xform);
|
|
anyUpdated = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
if (!anyUpdated)//None were selected, so just apply to the current.
|
|
if (doFinal || !isCurrentFinal)//If do final, call func regardless. If not, only call if current is not final.
|
|
if (Xform<T>* xform = CurrentXform())
|
|
func(xform);
|
|
}
|
|
|
|
break;
|
|
|
|
case eXformUpdate::UPDATE_ALL:
|
|
{
|
|
while (Xform<T>* xform = m_Ember.GetTotalXform(i++))
|
|
func(xform);
|
|
}
|
|
|
|
break;
|
|
|
|
case eXformUpdate::UPDATE_ALL_EXCEPT_FINAL:
|
|
default:
|
|
{
|
|
while (Xform<T>* xform = m_Ember.GetXform(i++))
|
|
func(xform);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (updateRender)
|
|
UpdateRender(action);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the current ember, but use GUI values for the fields which make sense to
|
|
/// keep the same between ember selection changes.
|
|
/// Note the extra template parameter U allows for assigning ember of different types.
|
|
/// Resets the rendering process.
|
|
/// </summary>
|
|
/// <param name="ember">The ember to set as the current</param>
|
|
template <typename T>
|
|
template <typename U>
|
|
void FractoriumEmberController<T>::SetEmberPrivate(const Ember<U>& ember, bool verbatim)
|
|
{
|
|
if (ember.m_Name != m_Ember.m_Name)
|
|
m_LastSaveCurrent = "";
|
|
|
|
size_t w = m_Ember.m_FinalRasW;//Cache values for use below.
|
|
size_t h = m_Ember.m_FinalRasH;
|
|
|
|
m_Ember = ember;
|
|
|
|
if (!verbatim)
|
|
{
|
|
//m_Ember.SetSizeAndAdjustScale(m_Fractorium->ui.GLDisplay->width(), m_Fractorium->ui.GLDisplay->height(), true, SCALE_WIDTH);
|
|
m_Ember.m_TemporalSamples = 1;//Change once animation is supported.
|
|
m_Ember.m_Quality = m_Fractorium->m_QualitySpin->value();
|
|
m_Ember.m_Supersample = m_Fractorium->m_SupersampleSpin->value();
|
|
}
|
|
|
|
#ifdef SAVE_EACH
|
|
static EmberToXml<T> writer;
|
|
string filename = "last.flame";
|
|
|
|
writer.Save(filename.c_str(), m_Ember, 0, true, false, true);
|
|
#endif
|
|
|
|
m_GLController->ResetMouseState();
|
|
FillXforms();//Must do this first because the palette setup in FillParamTablesAndPalette() uses the xforms combo.
|
|
FillParamTablesAndPalette();
|
|
|
|
//If a resize happened, this won't do anything because the new size is not reflected in the scroll area yet.
|
|
//However, it will have been taken care of in SyncSizes() in that case, so it's ok.
|
|
//This is for when a new ember with the same size was loaded. If it was larger than the scroll area, and was scrolled, re-center it.
|
|
if (m_Ember.m_FinalRasW == w && m_Ember.m_FinalRasH == h)
|
|
m_Fractorium->CenterScrollbars();
|
|
}
|
|
|
|
template class FractoriumEmberController<float>;
|
|
|
|
#ifdef DO_DOUBLE
|
|
template class FractoriumEmberController<double>;
|
|
#endif
|