mirror of
https://bitbucket.org/mfeemster/fractorium.git
synced 2025-01-22 05:30:06 -05:00
90ec5b8246
-Show common folder locations such as documents, downloads, pictures in the sidebar in all file dialogs. -Warning message about exceeding memory in final render dialog now suggests strips as the solution to the problem. -Strips now has a tooltip explaining what it does. -Allow more digits in the spinners on the color section the flame tab. -Add manually adjustable size spinners in the final render dialog. Percentage scale and absolute size are fully synced. -Default prefix in final render is now the filename when doing animations (coming from sequence section of the library tab). -Changed the elliptic variation back to using a less precise version for float, and a more precise version for double. The last release had it always using double. -New applied xaos table that shows a read-only view of actual weights by taking the base xform weights and multiplying them by the xaos values. -New table in the xaos tab that gives a graphical representation of the probability that each xform is chosen, with and without xaos. -Add button to transpose the xaos rows and columns. -Add support for importing .chaos files from Chaotica. --Pasting back to Chaotica will work for most, but not all, variations due to incompatible parameter names in some. -Curves are now splines instead of Bezier. This adds compatibility with Chaotica, but breaks it for Apophysis. Xmls are still pastable, but the color curves will look different. --The curve editor on the palette tab can now add points by clicking on the lines and remove points by clicking on the points themselves, just like Chaotica. --Splines are saved in four new xml fields: overall_curve, red_curve, green_curve and blue_curve. -Allow for specifying the percentage of a sub batch each thread should iterate through per kernel call when running with OpenCL. This gives a roughly 1% performance increase due to having to make less kernel calls while iterating. --This field is present for interactive editing (where it's not very useful) and in the final render dialog. --On the command line, this is specified as --sbpctth for EmberRender and EmberAnimate. -Allow double clicking to toggle the supersample field in the flame tab between 1 and 2 for easily checking the effect of the field. -When showing affine values as polar coordinates, show angles normalized to 360 to match Chaotica. -Fuse Count spinner now toggles between 15 and 100 when double clicking for easily checking the effect of the field. -Added field for limiting the range in the x and y direction that the initial points are chosen from. -Added a field called K2 which is an alternative way to set brightness, ignored when zero. --This has no effect for many variations, but hs a noticeable effect for some. -Added new variations: arcsech arcsech2 arcsinh arctanh asteria block bwraps_rand circlecrop2 coth_spiral crackle2 depth_blur depth_blur2 depth_gaussian depth_gaussian2 depth_ngon depth_ngon2 depth_sine depth_sine2 dragonfire dspherical dust excinis exp2 flipx flowerdb foci_p gaussian glynnia2 glynnsim4 glynnsim5 henon henon hex_rand hex_truchet hypershift lazyjess lens lozi lozi modulusx modulusy oscilloscope2 point_symmetry pointsymmetry projective pulse rotate scry2 shift smartshape spher squares starblur2 swirl3 swirl3r tanh_spiral target0 target2 tile_hlp truchet_glyph truchet_inv truchet_knot unicorngaloshen vibration vibration2 --hex_truchet, hex_rand should always use double. They are extremely sensitive. --Bug fixes: -Bounds sign was flipped for x coordinate of world space when center was not zero. -Right clicking and dragging spinner showed menu on mouse up, even if it was very far away. -Text boxes for size in final render dialog were hard to type in. Same bug as xform weight used to be so fix the same way. -Fix spelling to be plural in toggle color speed box. -Stop using the blank user palette to generate flames. Either put colored palettes in it, or exclude it from randoms. -Clicking the random palette button for a palette file with only one palette in it would freeze the program. -Clicking none scale in final render did not re-render the preview. -Use less precision on random xaos. No need for 12 decimal places. -The term sub batch is overloaded in the options dialog. Change the naming and tooltip of those settings for cpu and opencl. --Also made clear in the tooltip for the default opencl quality setting that the value is per device. -The arrows spinner in palette editor appears like a read-only label. Made it look like a spinner. -Fix border colors for various spin boxes and table headers in the style sheet. Requires reload. -Fix a bug in the bwraps variation which would produce different results than Chaotica and Apophysis. -Synth was allowed to be selected for random flame generation when using an Nvidia card but it shouldn't have been because Nvidia has a hard time compiling synth. -A casting bug in the OpenCL kernels for log scaling and density filtering was preventing successful compilations on Intel iGPUs. Fixed even though we don't support anything other than AMD and Nvidia. -Palette rotation (click and drag) position was not being reset when loading a new flame. -When the xform circles were hidden, opening and closing the options dialog would improperly reshow them. -Double click toggle was broken on integer spin boxes. -Fixed tab order of some controls. -Creating a palette from a jpg in the palette editor only produced a single color. --Needed to package imageformats/qjpeg.dll with the Windows installer. -The basic memory benchmark test flame was not really testing memory. Make it more spread out. -Remove the temporal samples field from the flame tab, it was never used because it's only an animation parameter which is specified in the final render dialog or on the command line with EmberAnimate. --Code changes: -Add IsEmpty() to Palette to determine if a palette is all black. -Attempt to avoid selecting a blank palette in PaletteList::GetRandomPalette(). -Add function ScanForChaosNodes() and some associated helper functions in XmlToEmber. -Make variation param name correction be case insensitive in XmlToEmber. -Report error when assigning a variation param value in XmlToEmber. -Add SubBatchPercentPerThread() method to RendererCL. -Override enterEvent() and leaveEvent() in DoubleSpinBox and SpinBox to prevent the context menu from showing up on right mouse up after already leaving the spinner. -Filtering the mouse wheel event in TableWidget no longer appears to be needed. It was probably an old Qt bug that has been fixed. -Gui/ember syncing code in the final render dialog needed to be reworked to accommodate absolute sizes.
985 lines
34 KiB
C++
985 lines
34 KiB
C++
#include "FractoriumPch.h"
|
|
#include "Fractorium.h"
|
|
|
|
/// <summary>
|
|
/// Initialize the menus UI.
|
|
/// </summary>
|
|
void Fractorium::InitMenusUI()
|
|
{
|
|
//File menu.
|
|
connect(ui.ActionNewFlock, SIGNAL(triggered(bool)), this, SLOT(OnActionNewFlock(bool)), Qt::QueuedConnection);
|
|
connect(ui.ActionNewEmptyFlameInCurrentFile, SIGNAL(triggered(bool)), this, SLOT(OnActionNewEmptyFlameInCurrentFile(bool)), Qt::QueuedConnection);
|
|
connect(ui.ActionNewRandomFlameInCurrentFile, SIGNAL(triggered(bool)), this, SLOT(OnActionNewRandomFlameInCurrentFile(bool)), Qt::QueuedConnection);
|
|
connect(ui.ActionCopyFlameInCurrentFile, SIGNAL(triggered(bool)), this, SLOT(OnActionCopyFlameInCurrentFile(bool)), Qt::QueuedConnection);
|
|
connect(ui.ActionOpen, SIGNAL(triggered(bool)), this, SLOT(OnActionOpen(bool)), Qt::QueuedConnection);
|
|
connect(ui.ActionSaveCurrentAsXml, SIGNAL(triggered(bool)), this, SLOT(OnActionSaveCurrentAsXml(bool)), Qt::QueuedConnection);
|
|
connect(ui.ActionSaveEntireFileAsXml, SIGNAL(triggered(bool)), this, SLOT(OnActionSaveEntireFileAsXml(bool)), Qt::QueuedConnection);
|
|
connect(ui.ActionSaveCurrentScreen, SIGNAL(triggered(bool)), this, SLOT(OnActionSaveCurrentScreen(bool)), Qt::QueuedConnection);
|
|
connect(ui.ActionExit, SIGNAL(triggered(bool)), this, SLOT(OnActionExit(bool)), Qt::QueuedConnection);
|
|
//Edit menu.
|
|
connect(ui.ActionUndo, SIGNAL(triggered(bool)), this, SLOT(OnActionUndo(bool)), Qt::QueuedConnection);
|
|
connect(ui.ActionRedo, SIGNAL(triggered(bool)), this, SLOT(OnActionRedo(bool)), Qt::QueuedConnection);
|
|
connect(ui.ActionCopyXml, SIGNAL(triggered(bool)), this, SLOT(OnActionCopyXml(bool)), Qt::QueuedConnection);
|
|
connect(ui.ActionCopyAllXml, SIGNAL(triggered(bool)), this, SLOT(OnActionCopyAllXml(bool)), Qt::QueuedConnection);
|
|
connect(ui.ActionPasteXmlAppend, SIGNAL(triggered(bool)), this, SLOT(OnActionPasteXmlAppend(bool)), Qt::QueuedConnection);
|
|
connect(ui.ActionPasteXmlOver, SIGNAL(triggered(bool)), this, SLOT(OnActionPasteXmlOver(bool)), Qt::QueuedConnection);
|
|
connect(ui.ActionCopySelectedXforms, SIGNAL(triggered(bool)), this, SLOT(OnActionCopySelectedXforms(bool)), Qt::QueuedConnection);
|
|
connect(ui.ActionPasteSelectedXforms, SIGNAL(triggered(bool)), this, SLOT(OnActionPasteSelectedXforms(bool)), Qt::QueuedConnection);
|
|
connect(ui.ActionCopyKernel, SIGNAL(triggered(bool)), this, SLOT(OnActionCopyKernel(bool)), Qt::QueuedConnection);
|
|
ui.ActionPasteSelectedXforms->setEnabled(false);
|
|
//View menu.
|
|
connect(ui.ActionResetWorkspace, SIGNAL(triggered(bool)), this, SLOT(OnActionResetWorkspace(bool)), Qt::QueuedConnection);
|
|
connect(ui.ActionAlternateEditorImage, SIGNAL(triggered(bool)), this, SLOT(OnActionAlternateEditorImage(bool)), Qt::QueuedConnection);
|
|
connect(ui.ActionResetScale, SIGNAL(triggered(bool)), this, SLOT(OnActionResetScale(bool)), Qt::QueuedConnection);
|
|
//Tools menu.
|
|
connect(ui.ActionAddReflectiveSymmetry, SIGNAL(triggered(bool)), this, SLOT(OnActionAddReflectiveSymmetry(bool)), Qt::QueuedConnection);
|
|
connect(ui.ActionAddRotationalSymmetry, SIGNAL(triggered(bool)), this, SLOT(OnActionAddRotationalSymmetry(bool)), Qt::QueuedConnection);
|
|
connect(ui.ActionAddBothSymmetry, SIGNAL(triggered(bool)), this, SLOT(OnActionAddBothSymmetry(bool)), Qt::QueuedConnection);
|
|
connect(ui.ActionClearFlame, SIGNAL(triggered(bool)), this, SLOT(OnActionClearFlame(bool)), Qt::QueuedConnection);
|
|
connect(ui.ActionFlatten, SIGNAL(triggered(bool)), this, SLOT(OnActionFlatten(bool)), Qt::QueuedConnection);
|
|
connect(ui.ActionUnflatten, SIGNAL(triggered(bool)), this, SLOT(OnActionUnflatten(bool)), Qt::QueuedConnection);
|
|
connect(ui.ActionStopRenderingPreviews, SIGNAL(triggered(bool)), this, SLOT(OnActionStopRenderingPreviews(bool)), Qt::QueuedConnection);
|
|
connect(ui.ActionRenderPreviews, SIGNAL(triggered(bool)), this, SLOT(OnActionRenderPreviews(bool)), Qt::QueuedConnection);
|
|
connect(ui.ActionFinalRender, SIGNAL(triggered(bool)), this, SLOT(OnActionFinalRender(bool)), Qt::QueuedConnection);
|
|
connect(m_FinalRenderDialog, SIGNAL(finished(int)), this, SLOT(OnFinalRenderClose(int)), Qt::QueuedConnection);
|
|
connect(ui.ActionOptions, SIGNAL(triggered(bool)), this, SLOT(OnActionOptions(bool)), Qt::QueuedConnection);
|
|
//Help menu.
|
|
connect(ui.ActionAbout, SIGNAL(triggered(bool)), this, SLOT(OnActionAbout(bool)), Qt::QueuedConnection);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a new flock of random embers, with the specified length.
|
|
/// </summary>
|
|
/// <param name="count">The number of embers to include in the flock</param>
|
|
template <typename T>
|
|
void FractoriumEmberController<T>::NewFlock(size_t count)
|
|
{
|
|
bool nv = false;
|
|
Ember<T> ember;
|
|
StopAllPreviewRenderers();
|
|
m_EmberFile.Clear();
|
|
m_EmberFile.m_Filename = EmberFile<T>::DefaultFilename();
|
|
vector<eVariationId> filteredVariations;
|
|
vector<eVariationId>* filteredVariationsRef = &m_FilteredVariations;
|
|
auto& deviceNames = OpenCLInfo::Instance()->AllDeviceNames();
|
|
|
|
for (auto& dev : deviceNames)
|
|
if (Find(ToLower(dev), "nvidia"))
|
|
nv = true;
|
|
|
|
if (nv)//Nvidia cannot handle synth. It takes over a minute to compile and uses about 4GB of memory.
|
|
{
|
|
filteredVariations = m_FilteredVariations;
|
|
filteredVariations.erase(std::remove_if(filteredVariations.begin(), filteredVariations.end(),
|
|
[&](const eVariationId & id) -> bool
|
|
{
|
|
return id == eVariationId::VAR_SYNTH || id == eVariationId::VAR_PRE_SYNTH || id == eVariationId::VAR_POST_SYNTH;
|
|
}
|
|
), filteredVariations.end());
|
|
filteredVariationsRef = &filteredVariations;
|
|
}
|
|
|
|
for (size_t i = 0; i < count; i++)
|
|
{
|
|
m_SheepTools->Random(ember, *filteredVariationsRef, static_cast<intmax_t>(QTIsaac<ISAAC_SIZE, ISAAC_INT>::LockedFrand<T>(-2, 2)), 0, 8);
|
|
ParamsToEmber(ember);
|
|
ember.m_Index = i;
|
|
ember.m_Name = m_EmberFile.m_Filename.toStdString() + "_" + ToString(i + 1ULL).toStdString();
|
|
m_EmberFile.m_Embers.push_back(ember);
|
|
}
|
|
|
|
m_LastSaveAll = "";
|
|
FillLibraryTree();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a new flock and assign the first ember as the current one.
|
|
/// </summary>
|
|
/// <param name="checked">Ignored</param>
|
|
void Fractorium::OnActionNewFlock(bool checked)
|
|
{
|
|
m_Controller->NewFlock(m_Settings->RandomCount());
|
|
m_Controller->SetEmber(0, false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create and add a new empty ember in the currently opened file
|
|
/// and set it as the current one.
|
|
/// It will have one empty xform in it.
|
|
/// Resets the rendering process.
|
|
/// </summary>
|
|
template <typename T>
|
|
void FractoriumEmberController<T>::NewEmptyFlameInCurrentFile()
|
|
{
|
|
Ember<T> ember;
|
|
Xform<T> xform;
|
|
QDateTime local(QDateTime::currentDateTime());
|
|
StopAllPreviewRenderers();
|
|
ParamsToEmber(ember);
|
|
xform.m_Weight = T(0.25);
|
|
xform.m_ColorX = m_Rand.Frand01<T>();
|
|
xform.AddVariation(m_VariationList->GetVariationCopy(eVariationId::VAR_LINEAR));
|
|
ember.AddXform(xform);
|
|
ember.m_Palette = *m_PaletteList->GetRandomPalette();
|
|
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.
|
|
m_EmberFile.MakeNamesUnique();
|
|
UpdateLibraryTree();
|
|
SetEmber(m_EmberFile.Size() - 1, false);
|
|
}
|
|
|
|
void Fractorium::OnActionNewEmptyFlameInCurrentFile(bool checked) { m_Controller->NewEmptyFlameInCurrentFile(); }
|
|
|
|
/// <summary>
|
|
/// Create and add a new random ember in the currently opened file
|
|
/// and set it as the current one.
|
|
/// Resets the rendering process.
|
|
/// </summary>
|
|
template <typename T>
|
|
void FractoriumEmberController<T>::NewRandomFlameInCurrentFile()
|
|
{
|
|
Ember<T> ember;
|
|
StopAllPreviewRenderers();
|
|
m_SheepTools->Random(ember, m_FilteredVariations, static_cast<int>(QTIsaac<ISAAC_SIZE, ISAAC_INT>::LockedFrand<T>(-2, 2)), 0, 8);
|
|
ParamsToEmber(ember);
|
|
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.
|
|
m_EmberFile.MakeNamesUnique();
|
|
UpdateLibraryTree();
|
|
SetEmber(m_EmberFile.Size() - 1, false);
|
|
}
|
|
|
|
void Fractorium::OnActionNewRandomFlameInCurrentFile(bool checked) { m_Controller->NewRandomFlameInCurrentFile(); }
|
|
|
|
/// <summary>
|
|
/// Create and add a a copy of the current ember in the currently opened file
|
|
/// and set it as the current one.
|
|
/// Resets the rendering process.
|
|
/// </summary>
|
|
template <typename T>
|
|
void FractoriumEmberController<T>::CopyFlameInCurrentFile()
|
|
{
|
|
auto ember = m_Ember;
|
|
StopAllPreviewRenderers();
|
|
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.
|
|
m_EmberFile.MakeNamesUnique();
|
|
UpdateLibraryTree();
|
|
SetEmber(m_EmberFile.Size() - 1, false);
|
|
}
|
|
|
|
void Fractorium::OnActionCopyFlameInCurrentFile(bool checked) { m_Controller->CopyFlameInCurrentFile(); }
|
|
|
|
/// <summary>
|
|
/// Open a list of ember Xml files, apply various values from the GUI widgets.
|
|
/// Either append these newly read embers to the existing open embers,
|
|
/// or clear the current ember file first.
|
|
/// When appending, add the new embers the the end of library tree.
|
|
/// When not appending, clear and populate the library tree with the new embers.
|
|
/// Set the current ember to the first one in the newly opened list.
|
|
/// Clears the undo state.
|
|
/// Resets the rendering process.
|
|
/// </summary>
|
|
/// <param name="filenames">A list of full paths and filenames</param>
|
|
/// <param name="append">True to append the embers in the new files to the end of the currently open embers, false to clear and replace them</param>
|
|
template <typename T>
|
|
void FractoriumEmberController<T>::OpenAndPrepFiles(const QStringList& filenames, bool append)
|
|
{
|
|
if (!filenames.empty())
|
|
{
|
|
size_t i;
|
|
EmberFile<T> emberFile;
|
|
XmlToEmber<T> parser;
|
|
vector<Ember<T>> embers;
|
|
vector<string> errors;
|
|
uint previousSize = append ? uint(m_EmberFile.Size()) : 0u;
|
|
StopAllPreviewRenderers();
|
|
emberFile.m_Filename = filenames[0];
|
|
|
|
for (auto& filename : filenames)
|
|
{
|
|
embers.clear();
|
|
|
|
if (parser.Parse(filename.toStdString().c_str(), embers, true) && !embers.empty())
|
|
{
|
|
for (i = 0; i < embers.size(); i++)
|
|
{
|
|
ConstrainDimensions(embers[i]);//Do not exceed the max texture size.
|
|
|
|
//Also ensure it has a name.
|
|
if (embers[i].m_Name == "" || embers[i].m_Name == "No name")
|
|
embers[i].m_Name = ToString<qulonglong>(i).toStdString();
|
|
|
|
embers[i].m_Quality = m_Fractorium->m_QualitySpin->value();
|
|
embers[i].m_Supersample = m_Fractorium->m_SupersampleSpin->value();
|
|
}
|
|
|
|
m_LastSaveAll = "";
|
|
emberFile.m_Embers.insert(emberFile.m_Embers.end(), embers.begin(), embers.end());
|
|
errors = parser.ErrorReport();
|
|
}
|
|
else
|
|
{
|
|
errors = parser.ErrorReport();
|
|
m_Fractorium->ShowCritical("Open Failed", "Could not open file, see info tab for details.");
|
|
}
|
|
|
|
if (!errors.empty())
|
|
m_Fractorium->ErrorReportToQTextEdit(errors, m_Fractorium->ui.InfoFileOpeningTextEdit, false);//Concat errors from all files.
|
|
}
|
|
|
|
if (append)
|
|
{
|
|
if (m_EmberFile.m_Filename == "")
|
|
m_EmberFile.m_Filename = filenames[0];
|
|
|
|
m_EmberFile.m_Embers.insert(m_EmberFile.m_Embers.end(), emberFile.m_Embers.begin(), emberFile.m_Embers.end());
|
|
}
|
|
else if (emberFile.Size() > 0)//Ensure at least something was read.
|
|
m_EmberFile = std::move(emberFile);//Move the temp to avoid creating dupes because we no longer need it.
|
|
else if (!m_EmberFile.Size())
|
|
{
|
|
//Loading failed, so fill it with a dummy.
|
|
Ember<T> ember;
|
|
m_EmberFile.m_Embers.push_back(ember);
|
|
}
|
|
|
|
//Resync indices and names.
|
|
i = 0;
|
|
|
|
for (auto& it : m_EmberFile.m_Embers)
|
|
it.m_Index = i++;
|
|
|
|
m_EmberFile.MakeNamesUnique();
|
|
|
|
if (append)
|
|
UpdateLibraryTree();
|
|
else
|
|
FillLibraryTree(append ? previousSize - 1 : 0);
|
|
|
|
ClearUndo();
|
|
SetEmber(previousSize, false);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Show a file open dialog to open ember Xml files.
|
|
/// </summary>
|
|
/// <param name="checked">Ignored</param>
|
|
void Fractorium::OnActionOpen(bool checked) { m_Controller->OpenAndPrepFiles(SetupOpenXmlDialog(), false); }
|
|
|
|
/// <summary>
|
|
/// Save current ember as Xml, using the Xml saving template values from the options.
|
|
/// This will first save the current ember back to the opened file in memory before
|
|
/// saving it to disk.
|
|
/// </summary>
|
|
template <typename T>
|
|
void FractoriumEmberController<T>::SaveCurrentAsXml()
|
|
{
|
|
QString filename;
|
|
auto s = m_Fractorium->m_Settings;
|
|
|
|
if (s->SaveAutoUnique() && m_LastSaveCurrent != "")
|
|
{
|
|
filename = EmberFile<T>::UniqueFilename(m_LastSaveCurrent);
|
|
}
|
|
else if (QFile::exists(m_LastSaveCurrent))
|
|
{
|
|
filename = m_LastSaveCurrent;
|
|
}
|
|
else
|
|
{
|
|
if (m_EmberFile.Size() == 1)
|
|
filename = m_Fractorium->SetupSaveXmlDialog(m_EmberFile.m_Filename);//If only one ember present, just use parent filename.
|
|
else
|
|
filename = m_Fractorium->SetupSaveXmlDialog(QString::fromStdString(m_Ember.m_Name));//More than one ember present, use individual ember name.
|
|
}
|
|
|
|
if (filename != "")
|
|
{
|
|
auto ember = m_Ember;
|
|
EmberToXml<T> writer;
|
|
QFileInfo fileInfo(filename);
|
|
auto tempEdit = ember.m_Edits;
|
|
SaveCurrentToOpenedFile();//Save the current ember back to the opened file before writing to disk.
|
|
ApplyXmlSavingTemplate(ember);
|
|
ember.m_Edits = writer.CreateNewEditdoc(&ember, nullptr, "edit", s->Nick().toStdString(), s->Url().toStdString(), s->Id().toStdString(), "", 0, 0);
|
|
|
|
if (tempEdit)
|
|
xmlFreeDoc(tempEdit);
|
|
|
|
if (writer.Save(filename.toStdString().c_str(), ember, 0, true, true))
|
|
{
|
|
s->SaveFolder(fileInfo.canonicalPath());
|
|
|
|
if (!s->SaveAutoUnique() || m_LastSaveCurrent == "")//Only save filename on first time through when doing auto unique names.
|
|
m_LastSaveCurrent = filename;
|
|
}
|
|
else
|
|
m_Fractorium->ShowCritical("Save Failed", "Could not save file, try saving to a different folder.");
|
|
}
|
|
}
|
|
|
|
void Fractorium::OnActionSaveCurrentAsXml(bool checked) { m_Controller->SaveCurrentAsXml(); }
|
|
|
|
/// <summary>
|
|
/// Save entire opened file Xml, using the Xml saving template values from the options on each ember.
|
|
/// This will first save the current ember back to the opened file in memory before
|
|
/// saving all to disk.
|
|
/// </summary>
|
|
template <typename T>
|
|
void FractoriumEmberController<T>::SaveEntireFileAsXml()
|
|
{
|
|
QString filename;
|
|
auto s = m_Fractorium->m_Settings;
|
|
|
|
if (s->SaveAutoUnique() && m_LastSaveAll != "")
|
|
filename = EmberFile<T>::UniqueFilename(m_LastSaveAll);
|
|
else if (QFile::exists(m_LastSaveAll))
|
|
filename = m_LastSaveAll;
|
|
else
|
|
filename = m_Fractorium->SetupSaveXmlDialog(m_EmberFile.m_Filename);
|
|
|
|
if (filename != "")
|
|
{
|
|
EmberFile<T> emberFile;
|
|
EmberToXml<T> writer;
|
|
QFileInfo fileInfo(filename);
|
|
SaveCurrentToOpenedFile();//Save the current ember back to the opened file before writing to disk.
|
|
emberFile = m_EmberFile;
|
|
|
|
for (auto& ember : emberFile.m_Embers)
|
|
ApplyXmlSavingTemplate(ember);
|
|
|
|
if (writer.Save(filename.toStdString().c_str(), emberFile.m_Embers, 0, true, true, false, false, false))
|
|
{
|
|
if (!s->SaveAutoUnique() || m_LastSaveAll == "")//Only save filename on first time through when doing auto unique names.
|
|
m_LastSaveAll = filename;
|
|
|
|
s->SaveFolder(fileInfo.canonicalPath());
|
|
}
|
|
else
|
|
m_Fractorium->ShowCritical("Save Failed", "Could not save file, try saving to a different folder.");
|
|
}
|
|
}
|
|
|
|
void Fractorium::OnActionSaveEntireFileAsXml(bool checked) { m_Controller->SaveEntireFileAsXml(); }
|
|
|
|
template <typename T>
|
|
void FractoriumEmberController<T>::SaveCurrentFileOnShutdown()
|
|
{
|
|
EmberToXml<T> writer;
|
|
auto path = GetDefaultUserPath();
|
|
QDir dir(path);
|
|
|
|
if (!dir.exists())
|
|
dir.mkpath(".");
|
|
|
|
string filename = path.toStdString() + "/lastonshutdown.flame";
|
|
writer.Save(filename, m_EmberFile.m_Embers, 0, true, true, false, false, false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Show a file save dialog and save what is currently shown in the render window to disk as an image.
|
|
/// </summary>
|
|
/// <param name="checked">Ignored</param>
|
|
void Fractorium::OnActionSaveCurrentScreen(bool checked)
|
|
{
|
|
auto filename = SetupSaveImageDialog(m_Controller->Name());
|
|
auto renderer = m_Controller->Renderer();
|
|
auto& pixels = *m_Controller->FinalImage();
|
|
auto rendererCL = dynamic_cast<RendererCLBase*>(renderer);
|
|
auto stats = m_Controller->Stats();
|
|
auto comments = renderer->ImageComments(stats, 0, true);
|
|
auto settings = FractoriumSettings::Instance();
|
|
|
|
if (rendererCL && renderer->PrepFinalAccumVector(pixels))
|
|
{
|
|
if (!rendererCL->ReadFinal(pixels.data()))
|
|
{
|
|
ShowCritical("GPU Read Error", "Could not read image from the GPU, aborting image save.", false);
|
|
return;
|
|
}
|
|
}
|
|
|
|
m_Controller->SaveCurrentRender(filename, comments, pixels, renderer->FinalRasW(), renderer->FinalRasH(), settings->Png16Bit(), settings->Transparency());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Save the current ember back to its position in the opened file.
|
|
/// This will not take any action if the previews are still rendering because
|
|
/// this writes to memory the preview renderer might be reading, and also stops the
|
|
/// preview renderer.
|
|
/// This does not save to disk.
|
|
/// </summary>
|
|
/// <param name="render">Whether to re-render the preview thumbnail after saving to the open file. Default: true.</param>
|
|
template <typename T>
|
|
uint FractoriumEmberController<T>::SaveCurrentToOpenedFile(bool render)
|
|
{
|
|
uint i = 0;
|
|
bool fileFound = false;
|
|
|
|
if (!m_LibraryPreviewRenderer->Running())
|
|
{
|
|
for (auto& it : m_EmberFile.m_Embers)
|
|
{
|
|
if (&it == m_EmberFilePointer)//Just compare memory addresses.
|
|
{
|
|
it = m_Ember;//Save it to the opened file in memory.
|
|
fileFound = true;
|
|
break;
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
if (!fileFound)
|
|
{
|
|
StopAllPreviewRenderers();
|
|
m_EmberFile.m_Embers.push_back(m_Ember);
|
|
m_EmberFile.MakeNamesUnique();
|
|
|
|
if (render)
|
|
UpdateLibraryTree();
|
|
}
|
|
else if (render)
|
|
RenderLibraryPreviews(i, i + 1);
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Exit the application.
|
|
/// </summary>
|
|
/// <param name="checked">Ignore.</param>
|
|
void Fractorium::OnActionExit(bool checked)
|
|
{
|
|
closeEvent(nullptr);
|
|
QApplication::exit();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Undoes this instance.
|
|
/// </summary>
|
|
template <typename T>
|
|
void FractoriumEmberController<T>::Undo()
|
|
{
|
|
if (m_UndoList.size() > 1 && m_UndoIndex > 0)
|
|
{
|
|
bool forceFinal = m_Fractorium->HaveFinal();
|
|
auto current = CurrentXform();
|
|
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.
|
|
m_EditState = eEditUndoState::UNDO_REDO;
|
|
|
|
if (index >= 0 && index < m_Fractorium->ui.CurrentXformCombo->count())
|
|
m_Fractorium->CurrentXform(index);
|
|
|
|
m_Fractorium->ui.ActionUndo->setEnabled(m_UndoList.size() > 1 && (m_UndoIndex > 0));
|
|
m_Fractorium->ui.ActionRedo->setEnabled(m_UndoList.size() > 1 && !(m_UndoIndex == m_UndoList.size() - 1));
|
|
}
|
|
}
|
|
|
|
void Fractorium::OnActionUndo(bool checked) { m_Controller->Undo(); }
|
|
|
|
/// <summary>
|
|
/// Redoes this instance.
|
|
/// </summary>
|
|
template <typename T>
|
|
void FractoriumEmberController<T>::Redo()
|
|
{
|
|
if (m_UndoList.size() > 1 && m_UndoIndex < m_UndoList.size() - 1)
|
|
{
|
|
bool forceFinal = m_Fractorium->HaveFinal();
|
|
auto current = CurrentXform();
|
|
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.
|
|
m_EditState = eEditUndoState::UNDO_REDO;
|
|
|
|
if (index >= 0 && index < m_Fractorium->ui.CurrentXformCombo->count())
|
|
m_Fractorium->CurrentXform(index);
|
|
|
|
m_Fractorium->ui.ActionUndo->setEnabled(m_UndoList.size() > 1 && (m_UndoIndex > 0));
|
|
m_Fractorium->ui.ActionRedo->setEnabled(m_UndoList.size() > 1 && !(m_UndoIndex == m_UndoList.size() - 1));
|
|
}
|
|
}
|
|
|
|
void Fractorium::OnActionRedo(bool checked) { m_Controller->Redo(); }
|
|
|
|
/// <summary>
|
|
/// Copy the current ember Xml to the clipboard.
|
|
/// Apply Xml saving settings from the options first.
|
|
/// </summary>
|
|
template <typename T>
|
|
void FractoriumEmberController<T>::CopyXml()
|
|
{
|
|
auto ember = m_Ember;
|
|
EmberToXml<T> emberToXml;
|
|
auto settings = m_Fractorium->m_Settings;
|
|
ember.m_Quality = settings->XmlQuality();
|
|
ember.m_Supersample = settings->XmlSupersample();
|
|
ember.m_TemporalSamples = settings->XmlTemporalSamples();
|
|
QApplication::clipboard()->setText(QString::fromStdString(emberToXml.ToString(ember, "", 0, false, true)));
|
|
}
|
|
|
|
void Fractorium::OnActionCopyXml(bool checked) { m_Controller->CopyXml(); }
|
|
|
|
/// <summary>
|
|
/// Copy the Xmls for all open embers as a single string to the clipboard, enclosed with the <flames> tag.
|
|
/// Apply Xml saving settings from the options first for each ember.
|
|
/// </summary>
|
|
template <typename T>
|
|
void FractoriumEmberController<T>::CopyAllXml()
|
|
{
|
|
ostringstream os;
|
|
EmberToXml<T> emberToXml;
|
|
os << "<flames>\n";
|
|
|
|
for (auto& e : m_EmberFile.m_Embers)
|
|
{
|
|
Ember<T> ember = e;
|
|
ApplyXmlSavingTemplate(ember);
|
|
os << emberToXml.ToString(ember, "", 0, false, true);
|
|
}
|
|
|
|
os << "</flames>\n";
|
|
QApplication::clipboard()->setText(QString::fromStdString(os.str()));
|
|
}
|
|
|
|
void Fractorium::OnActionCopyAllXml(bool checked) { m_Controller->CopyAllXml(); }
|
|
|
|
/// <summary>
|
|
/// Convert the Xml text from the clipboard to an ember, add it to the end
|
|
/// of the current file and set it as the current ember. If multiple Xmls were
|
|
/// copied to the clipboard and were enclosed in <flames> tags, then all of them will be added.
|
|
/// Clears the undo state.
|
|
/// Resets the rendering process.
|
|
/// </summary>
|
|
template <typename T>
|
|
void FractoriumEmberController<T>::PasteXmlAppend()
|
|
{
|
|
size_t previousSize = m_EmberFile.Size();
|
|
string s, errors;
|
|
XmlToEmber<T> parser;
|
|
vector<Ember<T>> embers;
|
|
auto codec = QTextCodec::codecForName("UTF-8");
|
|
auto b = codec->fromUnicode(QApplication::clipboard()->text());
|
|
s.reserve(b.size());
|
|
|
|
for (auto i = 0; i < b.size(); i++)
|
|
{
|
|
if (uint(b[i]) < 128u)
|
|
s.push_back(b[i]);
|
|
}
|
|
|
|
b.clear();
|
|
StopAllPreviewRenderers();
|
|
parser.Parse(reinterpret_cast<byte*>(const_cast<char*>(s.c_str())), "", embers, true);
|
|
errors = parser.ErrorReportString();
|
|
|
|
if (errors != "")
|
|
{
|
|
m_Fractorium->ShowCritical("Paste Error", QString::fromStdString(errors));
|
|
}
|
|
|
|
if (!embers.empty())
|
|
{
|
|
for (auto i = 0; i < embers.size(); i++)
|
|
{
|
|
embers[i].m_Index = m_EmberFile.Size();
|
|
ConstrainDimensions(embers[i]);//Do not exceed the max texture size.
|
|
|
|
//Also ensure it has a name.
|
|
if (embers[i].m_Name == "" || embers[i].m_Name == "No name")
|
|
embers[i].m_Name = ToString<qulonglong>(embers[i].m_Index).toStdString();
|
|
|
|
m_EmberFile.m_Embers.push_back(embers[i]);//Will invalidate the pointers contained in the EmberTreeWidgetItems, UpdateLibraryTree() will resync.
|
|
}
|
|
|
|
m_EmberFile.MakeNamesUnique();
|
|
UpdateLibraryTree();
|
|
SetEmber(previousSize, false);
|
|
}
|
|
}
|
|
|
|
void Fractorium::OnActionPasteXmlAppend(bool checked) { m_Controller->PasteXmlAppend(); }
|
|
|
|
/// <summary>
|
|
/// Convert the Xml text from the clipboard to an ember, overwrite the
|
|
/// current file and set the first as the current ember. If multiple Xmls were
|
|
/// copied to the clipboard and were enclosed in <flames> tags, then the current file will contain all of them.
|
|
/// </summary>
|
|
template <typename T>
|
|
void FractoriumEmberController<T>::PasteXmlOver()
|
|
{
|
|
size_t i = 0;
|
|
string s, errors;
|
|
XmlToEmber<T> parser;
|
|
list<Ember<T>> embers;
|
|
auto backupEmber = *m_EmberFile.m_Embers.begin();
|
|
auto codec = QTextCodec::codecForName("UTF-8");
|
|
auto b = codec->fromUnicode(QApplication::clipboard()->text());
|
|
s.reserve(b.size());
|
|
|
|
for (auto i = 0; i < b.size(); i++)
|
|
{
|
|
if (uint(b[i]) < 128u)
|
|
s.push_back(b[i]);
|
|
}
|
|
|
|
b.clear();
|
|
StopAllPreviewRenderers();
|
|
parser.Parse(reinterpret_cast<byte*>(const_cast<char*>(s.c_str())), "", embers, true);
|
|
errors = parser.ErrorReportString();
|
|
|
|
if (errors != "")
|
|
{
|
|
m_Fractorium->ShowCritical("Paste Error", QString::fromStdString(errors));
|
|
}
|
|
|
|
if (embers.size())
|
|
{
|
|
m_EmberFile.m_Embers = std::move(embers);//Will invalidate the pointers contained in the EmberTreeWidgetItems, UpdateLibraryTree() will resync.
|
|
|
|
for (auto it : m_EmberFile.m_Embers)
|
|
{
|
|
it.m_Index = i++;
|
|
ConstrainDimensions(it);//Do not exceed the max texture size.
|
|
|
|
//Also ensure it has a name.
|
|
if (it.m_Name == "" || it.m_Name == "No name")
|
|
it.m_Name = ToString<qulonglong>(it.m_Index).toStdString();
|
|
}
|
|
|
|
m_EmberFile.MakeNamesUnique();
|
|
FillLibraryTree();
|
|
SetEmber(0, false);
|
|
}
|
|
}
|
|
|
|
void Fractorium::OnActionPasteXmlOver(bool checked) { m_Controller->PasteXmlOver(); }
|
|
|
|
/// <summary>
|
|
/// Copy the selected xforms.
|
|
/// Note this will also copy final if selected.
|
|
/// If none selected, just copy current.
|
|
/// </summary>
|
|
template <typename T>
|
|
void FractoriumEmberController<T>::CopySelectedXforms()
|
|
{
|
|
m_CopiedXforms.clear();
|
|
m_CopiedFinalXform.Clear();
|
|
UpdateXform([&](Xform<T>* xform, size_t xfindex, size_t selIndex)
|
|
{
|
|
if (m_Ember.IsFinalXform(xform))
|
|
m_CopiedFinalXform = *xform;
|
|
else
|
|
m_CopiedXforms.push_back(*xform);
|
|
}, eXformUpdate::UPDATE_SELECTED, false);
|
|
m_Fractorium->ui.ActionPasteSelectedXforms->setEnabled(true);
|
|
}
|
|
|
|
void Fractorium::OnActionCopySelectedXforms(bool checked)
|
|
{
|
|
m_Controller->CopySelectedXforms();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Paste the selected xforms.
|
|
/// Note this will also paste/overwrite final if previously copied.
|
|
/// Resets the rendering process.
|
|
/// </summary>
|
|
template <typename T>
|
|
void FractoriumEmberController<T>::PasteSelectedXforms()
|
|
{
|
|
Update([&]()
|
|
{
|
|
AddXformsWithXaos(m_Ember, m_CopiedXforms, true);
|
|
|
|
if (!m_CopiedFinalXform.Empty())
|
|
m_Ember.SetFinalXform(m_CopiedFinalXform);
|
|
|
|
FillXforms();
|
|
});
|
|
}
|
|
|
|
void Fractorium::OnActionPasteSelectedXforms(bool checked)
|
|
{
|
|
m_Controller->PasteSelectedXforms();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copy the text of the OpenCL iteration kernel to the clipboard.
|
|
/// This performs no action if the renderer is not of type RendererCL.
|
|
/// </summary>
|
|
template <typename T>
|
|
void FractoriumEmberController<T>::CopyKernel()
|
|
{
|
|
if (auto rendererCL = dynamic_cast<RendererCL<T, float>*>(m_Renderer.get()))
|
|
QApplication::clipboard()->setText(QString::fromStdString(rendererCL->IterKernel()));
|
|
}
|
|
|
|
void Fractorium::OnActionCopyKernel(bool checked) { m_Controller->CopyKernel(); }
|
|
|
|
/// <summary>
|
|
/// Reset dock widgets and tabs to their default position.
|
|
/// Note that there is a bug in Qt, where it will only move them all to the left side if at least
|
|
/// one is on the left side, or they are all floating. If one or more are docked right, and none are docked
|
|
/// left, then it will put them all on the right side. Hopefully this isn't too much of a problem.
|
|
/// </summary>
|
|
void Fractorium::OnActionResetWorkspace(bool checked)
|
|
{
|
|
QDockWidget* firstDock = nullptr;
|
|
|
|
for (auto dock : m_Docks)
|
|
{
|
|
dock->setFloating(true);
|
|
dock->setGeometry(QRect(100, 100, dock->width(), dock->height()));//Doesn't seem to have an effect.
|
|
dock->setFloating(false);
|
|
dock->show();
|
|
|
|
if (firstDock)
|
|
tabifyDockWidget(firstDock, dock);
|
|
|
|
firstDock = dock;
|
|
}
|
|
|
|
ui.LibraryDockWidget->raise();
|
|
ui.LibraryDockWidget->show();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Alternate between Editor/Image.
|
|
/// </summary>
|
|
/// <param name="checked">Ignored</param>
|
|
void Fractorium::OnActionAlternateEditorImage(bool checked)
|
|
{
|
|
if (DrawImage())
|
|
{
|
|
ui.ActionDrawImage->setChecked(false);
|
|
ui.ActionDrawXforms->setChecked(true);
|
|
ui.ActionDrawGrid->setChecked(true);
|
|
}
|
|
else
|
|
{
|
|
ui.ActionDrawXforms->setChecked(false);
|
|
ui.ActionDrawGrid->setChecked(false);
|
|
ui.ActionDrawImage->setChecked(true);
|
|
}
|
|
|
|
ui.GLDisplay->update();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reset the scale used to draw affines, which was adjusted by zooming with the Alt key pressed.
|
|
/// </summary>
|
|
/// <param name="checked">Ignored</param>
|
|
void Fractorium::OnActionResetScale(bool checked)
|
|
{
|
|
m_Controller->InitLockedScale();
|
|
ui.GLDisplay->update();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add reflective symmetry to the current ember.
|
|
/// Resets the rendering process.
|
|
/// </summary>
|
|
template <typename T>
|
|
void FractoriumEmberController<T>::AddReflectiveSymmetry()
|
|
{
|
|
bool forceFinal = m_Fractorium->HaveFinal();
|
|
Update([&]()
|
|
{
|
|
m_Ember.AddSymmetry(-1, m_Rand);
|
|
auto index = m_Ember.TotalXformCount(forceFinal) - (forceFinal ? 2 : 1);//Set index to the last item before final.
|
|
FillXforms(int(index));
|
|
});
|
|
}
|
|
|
|
void Fractorium::OnActionAddReflectiveSymmetry(bool checked) { m_Controller->AddReflectiveSymmetry(); }
|
|
|
|
/// <summary>
|
|
/// Add rotational symmetry to the current ember.
|
|
/// Resets the rendering process.
|
|
/// </summary>
|
|
template <typename T>
|
|
void FractoriumEmberController<T>::AddRotationalSymmetry()
|
|
{
|
|
bool forceFinal = m_Fractorium->HaveFinal();
|
|
Update([&]()
|
|
{
|
|
m_Ember.AddSymmetry(2, m_Rand);
|
|
auto index = m_Ember.TotalXformCount(forceFinal) - (forceFinal ? 2 : 1);//Set index to the last item before final.
|
|
FillXforms(int(index));
|
|
});
|
|
}
|
|
|
|
void Fractorium::OnActionAddRotationalSymmetry(bool checked) { m_Controller->AddRotationalSymmetry(); }
|
|
|
|
/// <summary>
|
|
/// Add both reflective and rotational symmetry to the current ember.
|
|
/// Resets the rendering process.
|
|
/// </summary>
|
|
template <typename T>
|
|
void FractoriumEmberController<T>::AddBothSymmetry()
|
|
{
|
|
bool forceFinal = m_Fractorium->HaveFinal();
|
|
Update([&]()
|
|
{
|
|
m_Ember.AddSymmetry(-2, m_Rand);
|
|
auto index = m_Ember.TotalXformCount(forceFinal) - (forceFinal ? 2 : 1);//Set index to the last item before final.
|
|
FillXforms(int(index));
|
|
});
|
|
}
|
|
|
|
void Fractorium::OnActionAddBothSymmetry(bool checked) { m_Controller->AddBothSymmetry(); }
|
|
|
|
/// <summary>
|
|
/// Adds a FlattenVariation to every xform in the current ember.
|
|
/// Resets the rendering process.
|
|
/// </summary>
|
|
template <typename T>
|
|
void FractoriumEmberController<T>::Flatten()
|
|
{
|
|
UpdateAll([&](Ember<T>& ember, bool isMain)
|
|
{
|
|
ember.Flatten(XmlToEmber<T>::m_FlattenNames);
|
|
}, true, eProcessAction::FULL_RENDER, m_Fractorium->ApplyAll());
|
|
FillVariationTreeWithCurrentXform();
|
|
}
|
|
void Fractorium::OnActionFlatten(bool checked) { m_Controller->Flatten(); }
|
|
|
|
/// <summary>
|
|
/// Removes pre/reg/post FlattenVariation from every xform in the current ember.
|
|
/// Resets the rendering process.
|
|
/// </summary>
|
|
template <typename T>
|
|
void FractoriumEmberController<T>::Unflatten()
|
|
{
|
|
UpdateAll([&](Ember<T>& ember, bool isMain)
|
|
{
|
|
ember.Unflatten();
|
|
}, true, eProcessAction::FULL_RENDER, m_Fractorium->ApplyAll());
|
|
FillVariationTreeWithCurrentXform();
|
|
}
|
|
void Fractorium::OnActionUnflatten(bool checked) { m_Controller->Unflatten(); }
|
|
|
|
/// <summary>
|
|
/// Delete all but one xform in the current ember.
|
|
/// Clear that xform's variations.
|
|
/// Resets the rendering process.
|
|
/// </summary>
|
|
template <typename T>
|
|
void FractoriumEmberController<T>::ClearFlame()
|
|
{
|
|
Update([&]()
|
|
{
|
|
while (m_Ember.TotalXformCount() > 1)
|
|
m_Ember.DeleteTotalXform(m_Ember.TotalXformCount() - 1);
|
|
|
|
if (m_Ember.XformCount() == 1)
|
|
{
|
|
if (auto xform = m_Ember.GetXform(0))
|
|
{
|
|
xform->Clear();
|
|
xform->AddVariation(m_VariationList->GetVariationCopy(eVariationId::VAR_LINEAR));
|
|
xform->ParentEmber(&m_Ember);
|
|
}
|
|
}
|
|
|
|
m_Ember.m_Curves.Init();
|
|
FillXforms();
|
|
FillCurvesControl();
|
|
});
|
|
}
|
|
|
|
void Fractorium::OnActionClearFlame(bool checked) { m_Controller->ClearFlame(); }
|
|
|
|
/// <summary>
|
|
/// Re-render all previews.
|
|
/// </summary>
|
|
void Fractorium::OnActionRenderPreviews(bool checked)
|
|
{
|
|
m_Controller->RenderLibraryPreviews();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stop all previews from being rendered. This is handy if the user
|
|
/// opens a large file with many embers in it, such as an animation sequence.
|
|
/// </summary>
|
|
void Fractorium::OnActionStopRenderingPreviews(bool checked) { m_Controller->StopLibraryPreviewRender(); }
|
|
|
|
/// <summary>
|
|
/// Show the final render dialog as a modeless dialog to allow
|
|
/// the user to minimize the main window while doing a lengthy final render.
|
|
/// Note: The user probably should not be otherwise interacting with the main GUI
|
|
/// while the final render is taking place.
|
|
/// </summary>
|
|
/// <param name="checked">Ignored</param>
|
|
void Fractorium::OnActionFinalRender(bool checked)
|
|
{
|
|
//First completely stop what the current rendering process is doing.
|
|
m_Controller->DeleteRenderer();//Delete the renderer, but not the controller.
|
|
m_Controller->StopAllPreviewRenderers();
|
|
m_Controller->SaveCurrentToOpenedFile(false);//Save whatever was edited back to the current open file.
|
|
m_RenderStatusLabel->setText("Renderer stopped.");
|
|
m_FinalRenderDialog->Show(false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when the final render dialog has been closed.
|
|
/// </summary>
|
|
/// <param name="result">Ignored</param>
|
|
void Fractorium::OnFinalRenderClose(int result)
|
|
{
|
|
m_RenderStatusLabel->setText("Renderer starting...");
|
|
StartRenderTimer(false);//Re-create the renderer and start rendering again.
|
|
ui.ActionStartStopRenderer->setChecked(false);//Re-enable any controls that might have been disabled.
|
|
OnActionStartStopRenderer(false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Show the final options dialog.
|
|
/// Restart rendering and sync options after the options dialog is dismissed with Ok.
|
|
/// Called when the options dialog is finished with ok.
|
|
/// </summary>
|
|
/// <param name="checked">Ignored</param>
|
|
void Fractorium::OnActionOptions(bool checked)
|
|
{
|
|
bool ec = m_Settings->EarlyClip();
|
|
bool yup = m_Settings->YAxisUp();
|
|
bool trans = m_Settings->Transparency();
|
|
|
|
if (m_OptionsDialog->exec())
|
|
{
|
|
bool updatePreviews = ec != m_Settings->EarlyClip() ||
|
|
yup != m_Settings->YAxisUp() ||
|
|
trans != m_Settings->Transparency();
|
|
SyncOptionsToToolbar();//This won't trigger a recreate, the call below handles it.
|
|
ShutdownAndRecreateFromOptions(updatePreviews);//This will recreate the controller and/or the renderer from the options if necessary, then start the render timer.
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Show the about dialog.
|
|
/// </summary>
|
|
/// <param name="checked">Ignored</param>
|
|
void Fractorium::OnActionAbout(bool checked)
|
|
{
|
|
m_AboutDialog->exec();
|
|
}
|
|
|
|
template class FractoriumEmberController<float>;
|
|
|
|
#ifdef DO_DOUBLE
|
|
template class FractoriumEmberController<double>;
|
|
#endif
|