Initial source commit

Initial source commit
This commit is contained in:
mfeemster
2014-07-08 00:11:14 -07:00
parent 1493c22925
commit ef56c16b2b
214 changed files with 108754 additions and 0 deletions

View File

@ -0,0 +1,14 @@
#include "FractoriumPch.h"
#include "AboutDialog.h"
/// <summary>
/// Constructor that takes a parent widget and passes it to the base, then
/// sets up the GUI.
/// </summary>
/// <param name="parent">The parent widget. Default: NULL.</param>
/// <param name="f">The window flags. Default: 0.</param>
FractoriumAboutDialog::FractoriumAboutDialog(QWidget* parent, Qt::WindowFlags f)
: QDialog(parent, f)
{
ui.setupUi(this);
}

View File

@ -0,0 +1,22 @@
#pragma once
#include "ui_AboutDialog.h"
/// <summary>
/// FractoriumAboutDialog class.
/// </summary>
/// <summary>
/// The about dialog displays several group boxes showing the
/// code and icons used in this project and their respective authors
/// and licenses. It performs no other function.
/// </summary>
class FractoriumAboutDialog : public QDialog
{
Q_OBJECT
public:
FractoriumAboutDialog(QWidget* parent = 0, Qt::WindowFlags f = 0);
private:
Ui::AboutDialog ui;
};

View File

@ -0,0 +1,199 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AboutDialog</class>
<widget class="QDialog" name="AboutDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>488</width>
<height>580</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>488</width>
<height>580</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>488</width>
<height>580</height>
</size>
</property>
<property name="windowTitle">
<string>About</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item>
<widget class="QLabel" name="label_4">
<property name="font">
<font>
<pointsize>12</pointsize>
</font>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p align=&quot;center&quot;&gt;&lt;br/&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;Fractorium 0.4.0.2 Beta&lt;/span&gt;&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;&lt;br/&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;span style=&quot; font-size:10pt;&quot;&gt;Matt Feemster&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="scaledContents">
<bool>false</bool>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="CodeCopiedGroupBox">
<property name="title">
<string>Code Copied</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="topMargin">
<number>4</number>
</property>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;http://code.google.com/p/flam3&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;flam3&lt;/span&gt;&lt;/a&gt;: Scott Draves, Erik Reckase (GPL v2)&lt;br/&gt;&lt;a href=&quot;http://github.com/stevenrobertson/cuburn&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;cuburn&lt;/span&gt;&lt;/a&gt;: Steven Robertson, Michael Semeniuk, Matthew Znoj, Nicolas Mejia (GPL v3)&lt;br/&gt;&lt;a href=&quot;http://fractron9000.sourceforge.net&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Fractron 9000&lt;/span&gt;&lt;/a&gt;: Mike Thiesen (GPL)&lt;br/&gt;&lt;a href=&quot;http://sourceforge.net/projects/apophysis7x&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Apophysis&lt;/span&gt;&lt;/a&gt;: Mark Townsend, Ronald Hordijk, Peter Sdobnov, Piotr Borys, Georg Kiehne (GPL)&lt;br/&gt;&lt;a href=&quot;http://jwildfire.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;JWildfire&lt;/span&gt;&lt;/a&gt;: Andreas Maschke (LGPL)&lt;br/&gt;Numerous Apophysis plugin developers (GPL)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="LibrariesLinkedGroupBox">
<property name="title">
<string>Libraries Linked</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="topMargin">
<number>4</number>
</property>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;http://qt-project.org&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Qt&lt;/span&gt;&lt;/a&gt;: Digia Plc (GPL v3, LGPL v2)&lt;br/&gt;&lt;a href=&quot;http://g-truc.net&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;glm&lt;/span&gt;&lt;/a&gt;: Christophe Riccio (MIT License)&lt;br/&gt;&lt;a href=&quot;http://threadingbuildingblocks.org&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Threading Building Blocks&lt;/span&gt;&lt;/a&gt;: Intel Corporation (GPLv2)&lt;br/&gt;&lt;a href=&quot;http://libjpeg.sourceforge.net&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;libjpeg&lt;/span&gt;&lt;/a&gt;: Independent JPEG Group (Free Software License)&lt;br/&gt;&lt;a href=&quot;http://libpng.org&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;libpng&lt;/span&gt;&lt;/a&gt;: Glenn Randers-Pehrson et al (Libpng License)&lt;br/&gt;&lt;a href=&quot;http://xmlsoft.org&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;libxml2&lt;/span&gt;&lt;/a&gt;: Daniel Veillard (MIT License)&lt;br/&gt;&lt;a href=&quot;http://zlib.net&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;zlib&lt;/span&gt;&lt;/a&gt;: Jean-loup Gailly, Mark Adler (Zlib License)&lt;br/&gt;&lt;a href=&quot;http://burtleburtle.net/bob/rand/isaac.html&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;QTIsaac&lt;/span&gt;&lt;/a&gt;: Robert J. Jenkins, Quinn Tyler Jackson (Public Domain)&lt;br/&gt;&lt;a href=&quot;http://cas.ee.ic.ac.uk/people/dt10/index.html&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;MWC64X Random Number Generator&lt;/span&gt;&lt;/a&gt;: David Thomas (Public Domain)&lt;br/&gt;&lt;a href=&quot;http://code.jellycan.com/simpleopt/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;SimpleOpt&lt;/span&gt;&lt;/a&gt;: Brodie Thiesfield (MIT License)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="IconsUsedGroupBox">
<property name="title">
<string>Icons Used</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="topMargin">
<number>4</number>
</property>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;http://famfamfam.com&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Silk&lt;/span&gt;&lt;/a&gt;: Mark James (Creative Commons Attribution 2.5 License)&lt;br/&gt;&lt;a href=&quot;http://momentumdesignlab.com&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Momentum&lt;/span&gt;&lt;/a&gt;: Momentum Design Lab (Creative Commons Attribution-ShareAlike 3.5 License)&lt;br/&gt;&lt;a href=&quot;http://everaldo.com&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Crystal Clear&lt;/span&gt;&lt;/a&gt;: Everaldo Coelho (LGPL)&lt;br/&gt;&lt;a href=&quot;http://openiconlibrary.sourceforge.net&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Open Icon Library&lt;/span&gt;&lt;/a&gt;: Jeff Israel (GPL, LGPL, Creative Commons, Public Domain)&lt;br/&gt;&lt;a href=&quot;http://icons.mysitemyway.com/category/3d-transparent-glass-icons/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;3D Transparent Glass&lt;/span&gt;&lt;/a&gt;: iconsETC (Public Domain)&lt;br/&gt;&lt;a href=&quot;http://p.yusukekamiyamane.com&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Fugue&lt;/span&gt;&lt;/a&gt;: Yusuke Kamiyamane (Creative Commons Attribution 3.0 License)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QPushButton" name="okButton">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>OK</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>okButton</sender>
<signal>clicked()</signal>
<receiver>AboutDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>278</x>
<y>253</y>
</hint>
<hint type="destinationlabel">
<x>96</x>
<y>254</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,215 @@
#include "FractoriumPch.h"
#include "DoubleSpinBox.h"
/// <summary>
/// Constructor that passes parent to the base and sets up height and step.
/// Specific focus policy is used to allow the user to hover over the control
/// and change its value using the mouse wheel without explicitly having to click
/// inside of it.
/// </summary>
/// <param name="parent">The parent widget. Default: NULL.</param>
/// <param name="height">The height of the spin box. Default: 16.</param>
/// <param name="step">The step used to increment/decrement the spin box when using the mouse wheel. Default: 0.05.</param>
DoubleSpinBox::DoubleSpinBox(QWidget* parent, int height, double step)
: QDoubleSpinBox(parent)
{
m_Select = false;
m_DoubleClick = false;
m_DoubleClickNonZero = 0;
m_DoubleClickZero = 1;
m_Step = step;
m_SmallStep = step / 10.0;
setSingleStep(step);
setFrame(false);
setButtonSymbols(QAbstractSpinBox::NoButtons);
setFocusPolicy(Qt::StrongFocus);
setMinimumHeight(height);//setGeometry() has no effect, so must set both of these instead.
setMaximumHeight(height);
lineEdit()->installEventFilter(this);
lineEdit()->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
connect(this, SIGNAL(valueChanged(double)), this, SLOT(onSpinBoxValueChanged(double)), Qt::QueuedConnection);
}
/// <summary>
/// Set the value of the control without triggering signals.
/// </summary>
/// <param name="d">The value to set it to</param>
void DoubleSpinBox::SetValueStealth(double d)
{
blockSignals(true);
setValue(d);
blockSignals(false);
}
/// <summary>
/// Set whether to respond to double click events.
/// </summary>
/// <param name="b">True if this should respond to double click events, else false.</param>
void DoubleSpinBox::DoubleClick(bool b)
{
m_DoubleClick = b;
}
/// <summary>
/// Set the value to be used when the user double clicks the spinner while
/// it contains zero.
/// </summary>
/// <param name="val">The value to be used</param>
void DoubleSpinBox::DoubleClickZero(double val)
{
m_DoubleClickZero = val;
}
/// <summary>
/// Set the value to be used when the user double clicks the spinner while
/// it contains a non-zero value.
/// </summary>
/// <param name="val">The value to be used</param>
void DoubleSpinBox::DoubleClickNonZero(double val)
{
m_DoubleClickNonZero = val;
}
/// <summary>
/// Set the default step to be used when the user scrolls.
/// </summary>
/// <param name="step">The step to use for scrolling</param>
void DoubleSpinBox::Step(double step)
{
m_Step = step;
}
/// <summary>
/// Set the small step to be used when the user holds down shift while scrolling.
/// The default is step / 10, so use this if something else is needed.
/// </summary>
/// <param name="step">The small step to use for scrolling while the shift key is down</param>
void DoubleSpinBox::SmallStep(double step)
{
m_SmallStep = step;
}
/// <summary>
/// Expose the underlying QLineEdit control to the caller.
/// </summary>
/// <returns>A pointer to the QLineEdit</returns>
QLineEdit* DoubleSpinBox::lineEdit()
{
return QDoubleSpinBox::lineEdit();
}
/// <summary>
/// Another workaround for the persistent text selection bug in Qt.
/// </summary>
void DoubleSpinBox::onSpinBoxValueChanged(double d)
{
lineEdit()->deselect();//Gets rid of nasty "feature" that always has text selected.
}
/// <summary>
/// Event filter for taking special action on double click events.
/// </summary>
/// <param name="o">The object</param>
/// <param name="e">The eevent</param>
/// <returns>false</returns>
bool DoubleSpinBox::eventFilter(QObject* o, QEvent* e)
{
if (e->type() == QMouseEvent::MouseButtonPress && isEnabled())
{
QPoint pt;
if (QMouseEvent* me = (QMouseEvent*)e)
pt = me->localPos().toPoint();
int pos = lineEdit()->cursorPositionAt(pt);
if (lineEdit()->selectedText() != "")
{
lineEdit()->deselect();
lineEdit()->setCursorPosition(pos);
return true;
}
else if (m_Select)
{
lineEdit()->setCursorPosition(pos);
selectAll();
m_Select = false;
return true;
}
}
else if (m_DoubleClick && e->type() == QMouseEvent::MouseButtonDblClick && isEnabled())
{
if (IsNearZero(value()))
setValue(m_DoubleClickZero);
else
setValue(m_DoubleClickNonZero);
}
else
{
if (e->type() == QEvent::Wheel)
{
//Take special action for shift to reduce the scroll amount. Control already
//increases it automatically.
if (QWheelEvent* wheelEvent = dynamic_cast<QWheelEvent*>(e))
{
Qt::KeyboardModifiers mod = wheelEvent->modifiers();
if (mod.testFlag(Qt::ShiftModifier))
setSingleStep(m_SmallStep);
else
setSingleStep(m_Step);
}
}
}
return QDoubleSpinBox::eventFilter(o, e);
}
/// <summary>
/// Called when focus enters the spinner.
/// </summary>
/// <param name="e">The event</param>
void DoubleSpinBox::focusInEvent(QFocusEvent* e)
{
lineEdit()->setReadOnly(false);
QDoubleSpinBox::focusInEvent(e);
}
/// <summary>
/// Called when focus leaves the spinner.
/// Qt has a nasty "feature" that leaves the text in a spinner selected
/// and the cursor visible, regardless of whether it has the focus.
/// Manually clear both here.
/// </summary>
/// <param name="e">The event</param>
void DoubleSpinBox::focusOutEvent(QFocusEvent* e)
{
lineEdit()->deselect();//Clear selection when leaving.
lineEdit()->setReadOnly(true);//Clever hack to clear the cursor when leaving.
QDoubleSpinBox::focusOutEvent(e);
}
/// <summary>
/// Called when focus enters the spinner.
/// Must set the focus to make sure key down messages don't erroneously go to the GLWidget.
/// </summary>
/// <param name="e">The event</param>
void DoubleSpinBox::enterEvent(QEvent* e)
{
m_Select = true;
setFocus();
QDoubleSpinBox::enterEvent(e);
}
/// <summary>
/// Called when focus leaves the spinner.
/// Must clear the focus to make sure key down messages don't erroneously go to the GLWidget.
/// </summary>
/// <param name="e">The event</param>
void DoubleSpinBox::leaveEvent(QEvent* e)
{
m_Select = false;
clearFocus();
QDoubleSpinBox::leaveEvent(e);
}

View File

@ -0,0 +1,80 @@
#pragma once
#include "FractoriumPch.h"
/// <summary>
/// DoubleSpinBox and VariationTreeDoubleSpinBox classes.
/// </summary>
/// <summary>
/// A derivation to prevent the spin box from selecting its own text
/// when editing. Also to prevent multiple spin boxes from all having
/// selected text at once.
/// </summary>
class DoubleSpinBox : public QDoubleSpinBox
{
Q_OBJECT
public:
explicit DoubleSpinBox(QWidget* parent = 0, int height = 16, double step = 0.05);
virtual ~DoubleSpinBox() { }
void SetValueStealth(double d);
void DoubleClick(bool b);
void DoubleClickZero(double val);
void DoubleClickNonZero(double val);
void Step(double step);
void SmallStep(double step);
QLineEdit* lineEdit();
public slots:
void onSpinBoxValueChanged(double d);
protected:
bool eventFilter(QObject* o, QEvent* e);
virtual void focusInEvent(QFocusEvent* e);
virtual void focusOutEvent(QFocusEvent* e);
virtual void enterEvent(QEvent* e);
virtual void leaveEvent(QEvent* e);
private:
bool m_Select;
bool m_DoubleClick;
double m_DoubleClickNonZero;
double m_DoubleClickZero;
double m_Step;
double m_SmallStep;
};
/// <summary>
/// Derivation for the double spin boxes that are in the
/// variations tree.
/// </summary>
template <typename T>
class VariationTreeDoubleSpinBox : public DoubleSpinBox
{
public:
/// <summary>
/// Constructor that passes agruments to the base and assigns the m_Param and m_Variation members.
/// </summary>
/// <param name="parent">The parent widget</param>
/// <param name="var">The variation this spinner is for</param>
/// <param name="param">The name of the parameter this is for</param>
/// <param name="height">The height of the spin box. Default: 16.</param>
/// <param name="step">The step used to increment/decrement the spin box when using the mouse wheel. Default: 0.05.</param>
explicit VariationTreeDoubleSpinBox(QWidget* parent, Variation<T>* var, string param, int height = 16, double step = 0.05)
: DoubleSpinBox(parent, height, step)
{
m_Param = param;
m_Variation = var;
setDecimals(3);
}
virtual ~VariationTreeDoubleSpinBox() { }
bool IsParam() { return !m_Param.empty(); }
string ParamName() { return m_Param; }
Variation<T>* GetVariation() { return m_Variation; }
private:
string m_Param;
Variation<T>* m_Variation;
};

View File

@ -0,0 +1,145 @@
#pragma once
#include "FractoriumPch.h"
/// <summary>
/// EmberFile class.
/// </summary>
/// <summary>
/// Class for representing an ember Xml file in memory.
/// It contains a filename and a vector of embers.
/// It also provides static helper functions for creating
/// default names for the file and the embers in it.
/// </summary>
template <typename T>
class EmberFile
{
public:
/// <summary>
/// Empty constructor that does nothing.
/// </summary>
EmberFile()
{
}
/// <summary>
/// Default copy constructor.
/// </summary>
/// <param name="emberFile">The EmberFile object to copy</param>
EmberFile(const EmberFile<T>& emberFile)
{
EmberFile<T>::operator=<T>(emberFile);
}
/// <summary>
/// Copy constructor to copy an EmberFile object of type U.
/// </summary>
/// <param name="emberFile">The EmberFile object to copy</param>
template <typename U>
EmberFile(const EmberFile<U>& emberFile)
{
EmberFile<T>::operator=<U>(emberFile);
}
/// <summary>
/// Default assignment operator.
/// </summary>
/// <param name="emberFile">The EmberFile object to copy</param>
EmberFile<T>& operator = (const EmberFile<T>& emberFile)
{
if (this != &emberFile)
EmberFile<T>::operator=<T>(emberFile);
return *this;
}
/// <summary>
/// Assignment operator to assign a EmberFile object of type U.
/// </summary>
/// <param name="emberFile">The EmberFile object to copy.</param>
/// <returns>Reference to updated self</returns>
template <typename U>
EmberFile<T>& operator = (const EmberFile<U>& emberFile)
{
m_Filename = emberFile.m_Filename;
CopyVec(m_Embers, emberFile.m_Embers);
return *this;
}
/// <summary>
/// Clear the file name and the vector of embers.
/// </summary>
void Clear()
{
m_Filename.clear();
m_Embers.clear();
}
/// <summary>
/// Ensure all ember names are unique.
/// </summary>
void MakeNamesUnique()
{
int x = 0;
for (size_t i = 0; i < m_Embers.size(); i++)
{
for (size_t j = 0; j < m_Embers.size(); j++)
{
if (i != j && m_Embers[i].m_Name == m_Embers[j].m_Name)
{
m_Embers[j].m_Name = m_Embers[j].m_Name + "_" + QString::number(++x).toStdString();
j = 0;
}
}
}
}
/// <summary>
/// Return the default filename based on the current date/time.
/// </summary>
/// <returns>The default filename</returns>
static QString DefaultFilename()
{
return "Flame_" + QDateTime(QDateTime::currentDateTime()).toString("yyyy-MM-dd-hhmmss");
}
/// <summary>
/// Ensures a given input filename is unique by appending a count to the end.
/// </summary>
/// <returns>The passed in name if it was unique, else a uniquely made name.</returns>
static QString UniqueFilename(QString& filename)
{
if (!QFile::exists(filename))
return filename;
int counter = 2;
QString newPath;
QFileInfo original(filename);
QString base = original.completeBaseName();
QString extension = original.suffix();
do
{
newPath = base + "_" + QString::number(counter++) + "." + extension;
}
while (QFile::exists(newPath));
return newPath;
}
/// <summary>
/// Return the default ember name based on the current date/time and
/// the ember's index in the file.
/// </summary>
/// <param name="i">The index in the file of the ember</param>
/// <returns>The default ember name</returns>
static QString DefaultEmberName(unsigned int i)
{
return DefaultFilename() + "-" + QString::number(i);
}
QString m_Filename;
vector<Ember<T>> m_Embers;
};

View File

@ -0,0 +1,120 @@
#pragma once
#include "FractoriumPch.h"
/// <summary>
/// EmberTreeWidgetItem
/// </summary>
/// <summary>
/// A thin derivation of QTreeWidgetItem for a tree of embers in an open file.
/// The tree is intended to contain one open ember file at a time.
/// This is a non-templated base for casting purposes.
/// </summary>
class EmberTreeWidgetItemBase : public QTreeWidgetItem
{
public:
/// <summary>
/// Constructor that takes a pointer to a QTreeWidget as a parent widget.
/// This is meant to be a root level item.
/// </summary>
/// <param name="parent">The parent widget of this item</param>
explicit EmberTreeWidgetItemBase(QTreeWidget* parent = 0)
: QTreeWidgetItem(parent)
{
}
/// <summary>
/// Constructor that takes a pointer to a QTreeWidgetItem as a parent widget.
/// This is meant to be the child of a root level item.
/// </summary>
/// <param name="parent">The parent widget of this item</param>
explicit EmberTreeWidgetItemBase(QTreeWidgetItem* parent = 0)
: QTreeWidgetItem(parent)
{
}
/// <summary>
/// Set the preview image for the tree widget item.
/// </summary>
/// <param name="v">The vector containing the RGB pixels [0..255] which will make up the preview image</param>
/// <param name="width">The width of the image in pixels</param>
/// <param name="height">The height of the image in pixels</param>
void SetImage(vector<unsigned char>& v, unsigned int width, unsigned int height)
{
int size = 64;
m_Image = QImage(width, height, QImage::Format_RGBA8888);
memcpy(m_Image.scanLine(0), v.data(), v.size() * sizeof(v[0]));//Memcpy the data in.
m_Pixmap = QPixmap::fromImage(m_Image).scaled(QSize(size, size), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);//Create a QPixmap out of the QImage, scaled to size.
setData(0, Qt::DecorationRole, m_Pixmap);
}
protected:
QImage m_Image;
QPixmap m_Pixmap;
};
/// <summary>
/// A thin derivation of QTreeWidgetItem for a tree of embers in an open file.
/// The tree is intended to contain one open ember file at a time.
/// </summary>
template <typename T>
class EmberTreeWidgetItem : public EmberTreeWidgetItemBase
{
public:
/// <summary>
/// Constructor that takes a pointer to an ember and a QTreeWidget as a parent widget.
/// This is meant to be a root level item.
/// </summary>
/// <param name="ember">A pointer to the ember this item will represent</param>
/// <param name="parent">The parent widget of this item</param>
explicit EmberTreeWidgetItem(Ember<T>* ember, QTreeWidget* parent = 0)
: EmberTreeWidgetItemBase(parent)
{
m_Ember = ember;
}
/// <summary>
/// Constructor that takes a pointer to an ember and a QTreeWidgetItem as a parent widget.
/// This is meant to be the child of a root level item.
/// </summary>
/// <param name="ember">A pointer to the ember this item will represent</param>
/// <param name="parent">The parent widget of this item</param>
explicit EmberTreeWidgetItem(Ember<T>* ember, QTreeWidgetItem* parent = 0)
: EmberTreeWidgetItemBase(parent)
{
m_Ember = ember;
}
/// <summary>
/// Copy the text of the tree item to the name of the ember.
/// </summary>
void UpdateEmberName() { m_Ember->m_Name = text(0).toStdString(); }
/// <summary>
/// Set the text of the tree item.
/// </summary>
void UpdateEditText() { setText(0, QString::fromStdString(m_Ember->m_Name)); }
/// <summary>
/// Get a pointer to the ember held by the tree item.
/// </summary>
Ember<T>* GetEmber() const { return m_Ember; }
/// <summary>
/// Perform a deep copy from the passed in ember to the dereferenced
/// ember pointer of the tree item.
/// </summary>
/// <param name="ember">The ember to copy</param>
void SetEmber(Ember<T>& ember) { *m_Ember = ember; }
/// <summary>
/// Set the ember pointer member to point to the passed in ember pointer.
/// </summary>
/// <param name="ember">The ember to point to</param>
void SetEmberPointer(Ember<T>* ember) { m_Ember = ember; }
private:
Ember<T>* m_Ember;
};

View File

@ -0,0 +1,550 @@
#include "FractoriumPch.h"
#include "FinalRenderDialog.h"
#include "Fractorium.h"
/// <summary>
/// Constructor which sets up the GUI for the final rendering dialog.
/// Settings used to populate widgets with initial values.
/// This function contains the render function as a lambda.
/// </summary>
/// <param name="settings">Pointer to the global settings object to use</param>
/// <param name="parent">The parent widget</param>
/// <param name="f">The window flags. Default: 0.</param>
FractoriumFinalRenderDialog::FractoriumFinalRenderDialog(FractoriumSettings* settings, QWidget* parent, Qt::WindowFlags f)
: QDialog(parent, f)
{
ui.setupUi(this);
int row = 0, spinHeight = 20;
unsigned int i;
QTableWidget* table = ui.FinalRenderGeometryTable;
QTableWidgetItem* item = NULL;
m_Fractorium = (Fractorium*)parent;
m_Settings = settings;
ui.FinalRenderThreadCountSpin->setRange(1, Timing::ProcessorCount());
connect(ui.FinalRenderEarlyClipCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnEarlyClipCheckBoxStateChanged(int)), Qt::QueuedConnection);
connect(ui.FinalRenderTransparencyCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnTransparencyCheckBoxStateChanged(int)), Qt::QueuedConnection);
connect(ui.FinalRenderOpenCLCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnOpenCLCheckBoxStateChanged(int)), Qt::QueuedConnection);
connect(ui.FinalRenderDoublePrecisionCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnDoublePrecisionCheckBoxStateChanged(int)), Qt::QueuedConnection);
connect(ui.FinalRenderPlatformCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(OnPlatformComboCurrentIndexChanged(int)), Qt::QueuedConnection);
connect(ui.FinalRenderDoAllCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnDoAllCheckBoxStateChanged(int)), Qt::QueuedConnection);
connect(ui.FinalRenderKeepAspectCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnKeepAspectCheckBoxStateChanged(int)), Qt::QueuedConnection);
connect(ui.FinalRenderScaleNoneRadioButton, SIGNAL(toggled(bool)), this, SLOT(OnScaleRadioButtonChanged(bool)), Qt::QueuedConnection);
connect(ui.FinalRenderScaleWidthRadioButton, SIGNAL(toggled(bool)), this, SLOT(OnScaleRadioButtonChanged(bool)), Qt::QueuedConnection);
connect(ui.FinalRenderScaleHeightRadioButton, SIGNAL(toggled(bool)), this, SLOT(OnScaleRadioButtonChanged(bool)), Qt::QueuedConnection);
SetupSpinner<SpinBox, int>(table, this, row, 1, m_WidthSpin, spinHeight, 10, 100000, 50, SIGNAL(valueChanged(int)), SLOT(OnWidthChanged(int)), true, 1980);
SetupSpinner<SpinBox, int>(table, this, row, 1, m_HeightSpin, spinHeight, 10, 100000, 50, SIGNAL(valueChanged(int)), SLOT(OnHeightChanged(int)), true, 1080);
SetupSpinner<SpinBox, int>(table, this, row, 1, m_QualitySpin, spinHeight, 1, 200000, 50, SIGNAL(valueChanged(int)), SLOT(OnQualityChanged(int)), true, 1000);
SetupSpinner<SpinBox, int>(table, this, row, 1, m_TemporalSamplesSpin, spinHeight, 1, 5000, 50, SIGNAL(valueChanged(int)), SLOT(OnTemporalSamplesChanged(int)), true, 1000);
SetupSpinner<SpinBox, int>(table, this, row, 1, m_SupersampleSpin, spinHeight, 1, 4, 1, SIGNAL(valueChanged(int)), SLOT(OnSupersampleChanged(int)), true, 2);
row++;//Memory usage.
TwoButtonWidget* tbw = new TwoButtonWidget("...", "Open", 22, 40, 24, table);
table->setCellWidget(row, 1, tbw);
table->item(row++, 1)->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
connect(tbw->m_Button1, SIGNAL(clicked(bool)), this, SLOT(OnFileButtonClicked(bool)), Qt::QueuedConnection);
connect(tbw->m_Button2, SIGNAL(clicked(bool)), this, SLOT(OnShowFolderButtonClicked(bool)), Qt::QueuedConnection);
m_PrefixEdit = new QLineEdit(table);
table->setCellWidget(row++, 1, m_PrefixEdit);
m_SuffixEdit = new QLineEdit(table);
table->setCellWidget(row++, 1, m_SuffixEdit);
ui.StartRenderButton->disconnect(SIGNAL(clicked(bool)));
connect(ui.StartRenderButton, SIGNAL(clicked(bool)), this, SLOT(OnRenderClicked(bool)), Qt::QueuedConnection);
connect(ui.StopRenderButton, SIGNAL(clicked(bool)), this, SLOT(OnCancelRenderClicked(bool)), Qt::QueuedConnection);
if (m_Wrapper.CheckOpenCL())
{
vector<string> platforms = m_Wrapper.PlatformNames();
//Populate combo boxes with available OpenCL platforms and devices.
for (i = 0; i < platforms.size(); i++)
ui.FinalRenderPlatformCombo->addItem(QString::fromStdString(platforms[i]));
//If init succeeds, set the selected platform and device combos to match what was saved in the settings.
if (m_Wrapper.Init(m_Settings->FinalPlatformIndex(), m_Settings->FinalDeviceIndex()))
{
ui.FinalRenderOpenCLCheckBox->setChecked( m_Settings->FinalOpenCL());
ui.FinalRenderPlatformCombo->setCurrentIndex(m_Settings->FinalPlatformIndex());
ui.FinalRenderDeviceCombo->setCurrentIndex( m_Settings->FinalDeviceIndex());
}
else
{
OnPlatformComboCurrentIndexChanged(0);
ui.FinalRenderOpenCLCheckBox->setChecked(false);
}
}
else
{
ui.FinalRenderOpenCLCheckBox->setChecked(false);
ui.FinalRenderOpenCLCheckBox->setEnabled(false);
}
ui.FinalRenderEarlyClipCheckBox->setChecked( m_Settings->FinalEarlyClip());
ui.FinalRenderTransparencyCheckBox->setChecked( m_Settings->FinalTransparency());
ui.FinalRenderDoublePrecisionCheckBox->setChecked(m_Settings->FinalDouble());
ui.FinalRenderSaveXmlCheckBox->setChecked( m_Settings->FinalSaveXml());
ui.FinalRenderDoAllCheckBox->setChecked( m_Settings->FinalDoAll());
ui.FinalRenderDoSequenceCheckBox->setChecked( m_Settings->FinalDoSequence());
ui.FinalRenderKeepAspectCheckBox->setChecked( m_Settings->FinalKeepAspect());
ui.FinalRenderThreadCountSpin->setValue( m_Settings->FinalThreadCount());
m_WidthSpin->setValue(m_Settings->FinalWidth());
m_HeightSpin->setValue(m_Settings->FinalHeight());
m_QualitySpin->setValue(m_Settings->FinalQuality());
m_TemporalSamplesSpin->setValue(m_Settings->FinalTemporalSamples());
m_SupersampleSpin->setValue(m_Settings->FinalSupersample());
Scale((eScaleType)m_Settings->FinalScale());
if (m_Settings->FinalDoAllExt() == "jpg")
ui.FinalRenderJpgRadioButton->setChecked(true);
else
ui.FinalRenderPngRadioButton->setChecked(true);
//Explicitly call these to enable/disable the appropriate controls.
OnOpenCLCheckBoxStateChanged(ui.FinalRenderOpenCLCheckBox->isChecked());
OnDoAllCheckBoxStateChanged(ui.FinalRenderDoAllCheckBox->isChecked());
}
/// <summary>
/// GUI settings wrapper functions, getters only.
/// </summary>
bool FractoriumFinalRenderDialog::EarlyClip() { return ui.FinalRenderEarlyClipCheckBox->isChecked(); }
bool FractoriumFinalRenderDialog::Transparency() { return ui.FinalRenderTransparencyCheckBox->isChecked(); }
bool FractoriumFinalRenderDialog::OpenCL() { return ui.FinalRenderOpenCLCheckBox->isChecked(); }
bool FractoriumFinalRenderDialog::Double() { return ui.FinalRenderDoublePrecisionCheckBox->isChecked(); }
bool FractoriumFinalRenderDialog::SaveXml() { return ui.FinalRenderSaveXmlCheckBox->isChecked(); }
bool FractoriumFinalRenderDialog::DoAll() { return ui.FinalRenderDoAllCheckBox->isChecked(); }
bool FractoriumFinalRenderDialog::DoSequence() { return ui.FinalRenderDoSequenceCheckBox->isChecked(); }
bool FractoriumFinalRenderDialog::KeepAspect() { return ui.FinalRenderKeepAspectCheckBox->isChecked(); }
QString FractoriumFinalRenderDialog::DoAllExt() { return ui.FinalRenderJpgRadioButton->isChecked() ? "jpg" : "png"; }
QString FractoriumFinalRenderDialog::Path() { return ui.FinalRenderGeometryTable->item(6, 1)->text(); }
void FractoriumFinalRenderDialog::Path(QString s) { ui.FinalRenderGeometryTable->item(6, 1)->setText(s); }
QString FractoriumFinalRenderDialog::Prefix() { return m_PrefixEdit->text(); }
QString FractoriumFinalRenderDialog::Suffix() { return m_SuffixEdit->text(); }
unsigned int FractoriumFinalRenderDialog::PlatformIndex() { return ui.FinalRenderPlatformCombo->currentIndex(); }
unsigned int FractoriumFinalRenderDialog::DeviceIndex() { return ui.FinalRenderDeviceCombo->currentIndex(); }
unsigned int FractoriumFinalRenderDialog::ThreadCount() { return ui.FinalRenderThreadCountSpin->value(); }
unsigned int FractoriumFinalRenderDialog::Width() { return m_WidthSpin->value(); }
unsigned int FractoriumFinalRenderDialog::Height() { return m_HeightSpin->value(); }
unsigned int FractoriumFinalRenderDialog::Quality() { return m_QualitySpin->value(); }
unsigned int FractoriumFinalRenderDialog::TemporalSamples() { return m_TemporalSamplesSpin->value(); }
unsigned int FractoriumFinalRenderDialog::Supersample() { return m_SupersampleSpin->value(); }
/// <summary>
/// Capture the current state of the Gui.
/// Used to hold options for performing the final render.
/// </summary>
/// <returns>The state of the Gui as a struct</returns>
FinalRenderGuiState FractoriumFinalRenderDialog::State()
{
FinalRenderGuiState state;
state.m_EarlyClip = EarlyClip();
state.m_Transparency = Transparency();
state.m_OpenCL = OpenCL();
state.m_Double = Double();
state.m_SaveXml = SaveXml();
state.m_DoAll = DoAll();
state.m_DoSequence = DoSequence();
state.m_KeepAspect = KeepAspect();
state.m_Scale = Scale();
state.m_Path = Path();
state.m_DoAllExt = DoAllExt();
state.m_Prefix = Prefix();
state.m_Suffix = Suffix();
state.m_PlatformIndex = PlatformIndex();
state.m_DeviceIndex = DeviceIndex();
state.m_ThreadCount = ThreadCount();
state.m_Width = Width();
state.m_Height = Height();
state.m_Quality = Quality();
state.m_TemporalSamples = TemporalSamples();
state.m_Supersample = Supersample();
return state;
}
/// <summary>
/// Return the type of scaling desired based on what radio button has been selected.
/// </summary>
/// <returns>The type of scaling as an eScaleType enum</returns>
eScaleType FractoriumFinalRenderDialog::Scale()
{
if (ui.FinalRenderScaleNoneRadioButton->isChecked())
return SCALE_NONE;
else if (ui.FinalRenderScaleWidthRadioButton->isChecked())
return SCALE_WIDTH;
else if (ui.FinalRenderScaleHeightRadioButton->isChecked())
return SCALE_HEIGHT;
else
return SCALE_NONE;
}
/// <summary>
/// Set the type of scaling desired which will select the corresponding radio button.
/// </summary>
/// <param name="scale">The type of scaling to use</param>
void FractoriumFinalRenderDialog::Scale(eScaleType scale)
{
if (scale == SCALE_NONE)
ui.FinalRenderScaleNoneRadioButton->setChecked(true);
else if (scale == SCALE_WIDTH)
ui.FinalRenderScaleWidthRadioButton->setChecked(true);
else if (scale == SCALE_HEIGHT)
ui.FinalRenderScaleHeightRadioButton->setChecked(true);
else
ui.FinalRenderScaleNoneRadioButton->setChecked(true);
}
/// <summary>
/// Simple wrapper to put moving the cursor to the end in a signal
/// so it can be called from a thread.
/// </summary>
void FractoriumFinalRenderDialog::MoveCursorToEnd()
{
ui.FinalRenderTextOutput->moveCursor(QTextCursor::End, QTextCursor::MoveAnchor);
}
/// <summary>
/// Whether to use early clipping before spatial filtering.
/// </summary>
/// <param name="index">True to early clip, else don't.</param>
void FractoriumFinalRenderDialog::OnEarlyClipCheckBoxStateChanged(int state)
{
SetMemory();
}
/// <summary>
/// Whether to use transparency in png images.
/// </summary>
/// <param name="index">True to use transparency, else don't.</param>
void FractoriumFinalRenderDialog::OnTransparencyCheckBoxStateChanged(int state)
{
SetMemory();
}
/// <summary>
/// Set whether to use OpenCL in the rendering process or not.
/// </summary>
/// <param name="state">Use OpenCL if state == Qt::Checked, else don't.</param>
void FractoriumFinalRenderDialog::OnOpenCLCheckBoxStateChanged(int state)
{
bool checked = state == Qt::Checked;
ui.FinalRenderPlatformCombo->setEnabled(checked);
ui.FinalRenderDeviceCombo->setEnabled(checked);
ui.FinalRenderThreadCountSpin->setEnabled(!checked);
SetMemory();
}
/// <summary>
/// Set whether to use double or single precision in the rendering process or not.
/// This will recreate the entire controller.
/// </summary>
/// <param name="state">Use double if state == Qt::Checked, else float.</param>
void FractoriumFinalRenderDialog::OnDoublePrecisionCheckBoxStateChanged(int state)
{
SetMemory();
}
/// <summary>
/// Populate the the device combo box with all available
/// OpenCL devices for the selected platform.
/// Called when the platform combo box index changes.
/// </summary>
/// <param name="index">The selected index of the combo box</param>
void FractoriumFinalRenderDialog::OnPlatformComboCurrentIndexChanged(int index)
{
vector<string> devices = m_Wrapper.DeviceNames(index);
ui.FinalRenderDeviceCombo->clear();
for (size_t i = 0; i < devices.size(); i++)
ui.FinalRenderDeviceCombo->addItem(QString::fromStdString(devices[i]));
}
/// <summary>
/// The do all checkbox was changed.
/// If checked, render all embers available in the currently opened file, else
/// only render the current ember.
/// </summary>
/// <param name="state">The state of the checkbox</param>
void FractoriumFinalRenderDialog::OnDoAllCheckBoxStateChanged(int state)
{
ui.FinalRenderDoSequenceCheckBox->setEnabled(ui.FinalRenderDoAllCheckBox->isChecked());
ui.FinalRenderExtensionGroupBox->setEnabled(ui.FinalRenderDoAllCheckBox->isChecked());
}
/// <summary>
/// Whether to keep the aspect ratio of the desired width and height the same
/// as that of the original width and height.
/// </summary>
/// <param name="checked">The state of the checkbox</param>
void FractoriumFinalRenderDialog::OnKeepAspectCheckBoxStateChanged(int state)
{
if (state && m_Controller.get())
m_HeightSpin->SetValueStealth(m_WidthSpin->value() / m_Controller->OriginalAspect());
SetMemory();
}
/// <summary>
/// The scaling method radio button selection was changed.
/// </summary>
/// <param name="checked">The state of the radio button</param>
void FractoriumFinalRenderDialog::OnScaleRadioButtonChanged(bool checked)
{
if (checked)
SetMemory();
}
/// <summary>
/// The width spinner was changed, recompute required memory.
/// If the aspect ratio checkbox is checked, set the value of
/// the height spinner as well to be in proportion.
/// </summary>
/// <param name="d">Ignored</param>
void FractoriumFinalRenderDialog::OnWidthChanged(int d)
{
if (ui.FinalRenderKeepAspectCheckBox->isChecked() && m_Controller.get())
m_HeightSpin->SetValueStealth(m_WidthSpin->value() / m_Controller->OriginalAspect());
SetMemory();
}
/// <summary>
/// The height spinner was changed, recompute required memory.
/// If the aspect ratio checkbox is checked, set the value of
/// the width spinner as well to be in proportion.
/// </summary>
/// <param name="d">Ignored</param>
void FractoriumFinalRenderDialog::OnHeightChanged(int d)
{
if (ui.FinalRenderKeepAspectCheckBox->isChecked() && m_Controller.get())
m_WidthSpin->SetValueStealth(m_HeightSpin->value() * m_Controller->OriginalAspect());
SetMemory();
}
/// <summary>
/// The quality spinner was changed, recompute required memory.
/// </summary>
/// <param name="d">Ignored</param>
void FractoriumFinalRenderDialog::OnQualityChanged(int d)
{
SetMemory();
}
/// <summary>
/// The temporal samples spinner was changed, recompute required memory.
/// </summary>
/// <param name="d">Ignored</param>
void FractoriumFinalRenderDialog::OnTemporalSamplesChanged(int d)
{
SetMemory();
}
/// <summary>
/// The supersample spinner was changed, recompute required memory.
/// </summary>
/// <param name="d">Ignored</param>
void FractoriumFinalRenderDialog::OnSupersampleChanged(int d)
{
SetMemory();
}
/// <summary>
/// If a single ember is being rendered, show the save file dialog.
/// If a more than one is being rendered, show the save folder dialog.
/// Called when the ... file button is clicked.
/// </summary>
/// <param name="checked">Ignored</param>
void FractoriumFinalRenderDialog::OnFileButtonClicked(bool checked)
{
bool doAll = ui.FinalRenderDoAllCheckBox->isChecked();
QString filename;
if (doAll)
filename = m_Fractorium->SetupSaveFolderDialog();
else
filename = m_Fractorium->SetupSaveImageDialog(m_Controller->Name());
if (filename != "")
{
if (doAll)
{
if (!filename.endsWith(QDir::separator()))
filename += "/";
}
QFileInfo fileInfo(filename);
QString path = fileInfo.absolutePath();
m_Settings->SaveFolder(path);//Any time they exit the box with a valid value, preserve it in the settings.
Path(filename);
SetMemory();
}
}
/// <summary>
/// Show the folder where the last rendered image was saved to.
/// </summary>
/// <param name="checked">Ignored</param>
void FractoriumFinalRenderDialog::OnShowFolderButtonClicked(bool checked)
{
QString text = Path();
if (text != "")
{
QFileInfo fileInfo(text);
QString path = fileInfo.absolutePath();
QDir dir(path);
if (dir.exists())
QDesktopServices::openUrl(QUrl::fromLocalFile(path));
}
}
/// <summary>
/// Start the render process.
/// </summary>
/// <param name="checked">Ignored</param>
void FractoriumFinalRenderDialog::OnRenderClicked(bool checked)
{
if (CreateControllerFromGUI(true))
m_Controller->Render();
}
/// <summary>
/// Cancel the render.
/// </summary>
/// <param name="checked">Ignored</param>
void FractoriumFinalRenderDialog::OnCancelRenderClicked(bool checked)
{
if (m_Controller.get())
m_Controller->CancelRender();
}
/// <summary>
/// Uses the options and the ember to populate widgets.
/// Called when the dialog is initially shown.
/// </summary>
/// <param name="e">The event</param>
void FractoriumFinalRenderDialog::showEvent(QShowEvent* e)
{
#ifdef DO_DOUBLE
Ember<double> ed;
#else
Ember<float> ed;
#endif
if (CreateControllerFromGUI(true))
{
m_Fractorium->m_Controller->CopyEmber(ed);//Copy the current ember from the main window out in to a temp.
m_Controller->SetEmber(ed);//Copy the temp into the final render controller.
m_Controller->SetOriginalEmber(ed);
SetMemory();
m_Controller->ResetProgress();
}
QDir dir(m_Settings->SaveFolder());
QString name = m_Controller->Name();
if (dir.exists() && name != "")
Path(dir.absolutePath() + "/" + name + ".png");
ui.FinalRenderTextOutput->clear();
QDialog::showEvent(e);
}
/// <summary>
/// Close the dialog without running, or if running, cancel and exit.
/// Settings will not be saved.
/// Control will be returned to Fractorium::OnActionFinalRender().
/// </summary>
void FractoriumFinalRenderDialog::reject()
{
if (m_Controller.get())
{
m_Controller->CancelRender();
m_Controller->DeleteRenderer();
}
QDialog::reject();
}
/// <summary>
/// Create the controller from the options and optionally its underlying renderer.
/// </summary>
/// <returns>True if successful, else false.</returns>
bool FractoriumFinalRenderDialog::CreateControllerFromGUI(bool createRenderer)
{
bool ok = true;
#ifdef DO_DOUBLE
size_t size = Double() ? sizeof(double) : sizeof(float);
Ember<double> ed;
Ember<double> orig;
EmberFile<double> efd;
#else
size_t size = sizeof(float);
Ember<float> ed;
Ember<float> orig;
EmberFile<float> efd;
#endif
if (!m_Controller.get() || (m_Controller->SizeOfT() != size))
{
//First check if a controller has already been created, and if so, save its embers and gracefully shut it down.
if (m_Controller.get())
{
m_Controller->CopyEmber(ed);//Convert float to double or save double verbatim;
m_Controller->CopyEmberFile(efd);
m_Controller->Shutdown();
}
//Create a float or double controller based on the GUI.
#ifdef DO_DOUBLE
if (Double())
m_Controller = auto_ptr<FinalRenderEmberControllerBase>(new FinalRenderEmberController<double>(this));
else
#endif
m_Controller = auto_ptr<FinalRenderEmberControllerBase>(new FinalRenderEmberController<float>(this));
//Restore the ember and ember file.
if (m_Controller.get())
{
m_Controller->SetEmber(ed);//Convert float to double or set double verbatim;
m_Controller->SetEmberFile(efd);
m_Fractorium->m_Controller->CopyEmber(orig);//Copy the current ember from the main window out in to a temp.
m_Controller->SetOriginalEmber(orig);
}
}
if (m_Controller.get())
{
if (createRenderer)
return m_Controller->CreateRendererFromGUI();
else
return true;
}
else
return false;
}
/// <summary>
/// Compute the amount of memory needed via call to SyncAndComputeMemory(), then
/// assign the result to the table cell as text.
/// </summary>
void FractoriumFinalRenderDialog::SetMemory()
{
if (isVisible() && CreateControllerFromGUI(true))
ui.FinalRenderGeometryTable->item(5, 1)->setText(QLocale(QLocale::English).toString(m_Controller->SyncAndComputeMemory()));
}

View File

@ -0,0 +1,113 @@
#pragma once
#include "ui_FinalRenderDialog.h"
#include "SpinBox.h"
#include "DoubleSpinBox.h"
#include "TwoButtonWidget.h"
#include "FractoriumSettings.h"
#include "FinalRenderEmberController.h"
/// <summary>
/// FractoriumFinalRenderDialog class.
/// </summary>
class Fractorium;//Forward declaration since Fractorium uses this dialog.
/// <summary>
/// The final render dialog is for when the user is satisfied with the parameters they've
/// set and they want to do a final render at a higher quality and at a specific resolution
/// and save it out to a file.
/// It supports rendering either the current ember, or all of them in the opened file.
/// If a single ember is rendered, it will be saved to a single output file.
/// If multiple embers are rendered, they will all be saved to a specified directory using
/// default names.
/// The user can optionally save the Xml file with each ember.
/// They can be treated as individual images, or as an animation sequence in which case
/// motion blurring with temporal samples will be applied.
/// It keeps a pointer to the main window and the global settings object for convenience.
/// The settings used here are saved to the /finalrender portion of the settings file.
/// It has its own OpenCLWrapper member for populating the combo boxes.
/// Upon running, it will delete the main window's renderer to save memory/GPU resources and restore it to its
/// original state upon exiting.
/// This class uses a controller-based design similar to the main window.
/// </summary>
class FractoriumFinalRenderDialog : public QDialog
{
Q_OBJECT
friend Fractorium;
friend FinalRenderEmberControllerBase;
friend FinalRenderEmberController<float>;
#ifdef DO_DOUBLE
friend FinalRenderEmberController<double>;
#endif
public:
FractoriumFinalRenderDialog(FractoriumSettings* settings, QWidget* parent, Qt::WindowFlags f = 0);
bool EarlyClip();
bool Transparency();
bool OpenCL();
bool Double();
bool SaveXml();
bool DoAll();
bool DoSequence();
bool KeepAspect();
eScaleType Scale();
void Scale(eScaleType scale);
QString DoAllExt();
QString Path();
void Path(QString s);
QString Prefix();
QString Suffix();
unsigned int PlatformIndex();
unsigned int DeviceIndex();
unsigned int ThreadCount();
unsigned int Width();
unsigned int Height();
unsigned int Quality();
unsigned int TemporalSamples();
unsigned int Supersample();
FinalRenderGuiState State();
public Q_SLOTS:
void MoveCursorToEnd();
void OnEarlyClipCheckBoxStateChanged(int state);
void OnTransparencyCheckBoxStateChanged(int state);
void OnOpenCLCheckBoxStateChanged(int state);
void OnDoublePrecisionCheckBoxStateChanged(int state);
void OnPlatformComboCurrentIndexChanged(int index);
void OnDoAllCheckBoxStateChanged(int state);
void OnKeepAspectCheckBoxStateChanged(int state);
void OnScaleRadioButtonChanged(bool checked);
void OnWidthChanged(int d);
void OnHeightChanged(int d);
void OnQualityChanged(int d);
void OnTemporalSamplesChanged(int d);
void OnSupersampleChanged(int d);
void OnFileButtonClicked(bool checked);
void OnShowFolderButtonClicked(bool checked);
void OnRenderClicked(bool checked);
void OnCancelRenderClicked(bool checked);
protected:
virtual void reject();
virtual void showEvent(QShowEvent* e);
private:
bool CreateControllerFromGUI(bool createRenderer);
void SetMemory();
OpenCLWrapper m_Wrapper;
Timing m_RenderTimer;
SpinBox* m_WidthSpin;
SpinBox* m_HeightSpin;
SpinBox* m_QualitySpin;
SpinBox* m_TemporalSamplesSpin;
SpinBox* m_SupersampleSpin;
QLineEdit* m_PrefixEdit;
QLineEdit* m_SuffixEdit;
FractoriumSettings* m_Settings;
Fractorium* m_Fractorium;
auto_ptr<FinalRenderEmberControllerBase> m_Controller;
Ui::FinalRenderDialog ui;
};

View File

@ -0,0 +1,904 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FinalRenderDialog</class>
<widget class="QDialog" name="FinalRenderDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>519</width>
<height>801</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="windowTitle">
<string>Final Render</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item>
<layout class="QGridLayout" name="gridLayout" columnstretch="0,0">
<item row="1" column="1">
<widget class="QCheckBox" name="FinalRenderDoAllCheckBox">
<property name="toolTip">
<string>Render all open flames instead of just the current one</string>
</property>
<property name="text">
<string>Render All</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="FinalRenderDoSequenceCheckBox">
<property name="toolTip">
<string>Use temporal samples value to achieve motion blur effect between flames</string>
</property>
<property name="text">
<string>Render as Animation Sequence</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="FinalRenderDoublePrecisionCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Checked: use 64-bit double precision numbers (slower, but better image quality).&lt;/p&gt;&lt;p&gt;Unchecked: use 32-bit single precision numbers (faster, but worse image quality).&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Use Double Precision</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="FinalRenderOpenCLCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Use OpenCL to render if your video card supports it.&lt;/p&gt;&lt;p&gt;This is highly recommended as it will dramatically speed up render time.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Use OpenCL</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="FinalRenderEarlyClipCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Checked: clip colors and gamma correct after density filtering.&lt;/p&gt;&lt;p&gt;Unchecked: do it after spatial filtering.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Early Clip</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="FinalRenderSaveXmlCheckBox">
<property name="toolTip">
<string>Save an Xml parameter file for each flame rendered</string>
</property>
<property name="text">
<string>Save Xml</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="FinalRenderTransparencyCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Use transparency in the final image.&lt;/p&gt;&lt;p&gt;Only supported for Png format.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Transparency</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QGroupBox" name="FinalRenderScaleGroupBox">
<property name="minimumSize">
<size>
<width>0</width>
<height>40</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>40</height>
</size>
</property>
<property name="toolTip">
<string>The scaling to perform from the editor to the final rendered image</string>
</property>
<property name="title">
<string>Scale</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>4</number>
</property>
<property name="rightMargin">
<number>4</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item>
<widget class="QRadioButton" name="FinalRenderScaleNoneRadioButton">
<property name="text">
<string>None</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="FinalRenderScaleWidthRadioButton">
<property name="text">
<string>Width</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="FinalRenderScaleHeightRadioButton">
<property name="text">
<string>Height</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="4" column="1">
<widget class="QGroupBox" name="FinalRenderExtensionGroupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>110</width>
<height>40</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>40</height>
</size>
</property>
<property name="toolTip">
<string>The image type to save the final output as when rendering all open flames</string>
</property>
<property name="title">
<string>Render All Extension</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>4</number>
</property>
<property name="rightMargin">
<number>4</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item>
<widget class="QRadioButton" name="FinalRenderJpgRadioButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Jpg</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="FinalRenderPngRadioButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Png</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="3" column="1">
<widget class="QCheckBox" name="FinalRenderKeepAspectCheckBox">
<property name="toolTip">
<string>Maintain the aspect ratio between width and height to be equal to base width and base height</string>
</property>
<property name="text">
<string>Keep Aspect Ratio</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="FinalRenderPreviewLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>100</width>
<height>100</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>100</height>
</size>
</property>
<property name="sizeIncrement">
<size>
<width>1</width>
<height>1</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<property name="text">
<string/>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="FinalRenderPlatformCombo">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>320</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>320</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="FinalRenderDeviceCombo">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>320</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>320</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="FinalRenderThreadCountSpin">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The number of threads to use with CPU rendering.&lt;/p&gt;&lt;p&gt;Decrease for a more responsive system during rendering, increase for better performance.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="prefix">
<string>Threads </string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>64</number>
</property>
</widget>
</item>
<item>
<widget class="TableWidget" name="FinalRenderGeometryTable">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>218</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>218</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="frameShape">
<enum>QFrame::Panel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="tabKeyNavigation">
<bool>false</bool>
</property>
<property name="alternatingRowColors">
<bool>false</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::NoSelection</enum>
</property>
<property name="verticalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
<property name="horizontalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
<property name="showGrid">
<bool>true</bool>
</property>
<property name="gridStyle">
<enum>Qt::SolidLine</enum>
</property>
<property name="cornerButtonEnabled">
<bool>false</bool>
</property>
<property name="columnCount">
<number>2</number>
</property>
<attribute name="horizontalHeaderVisible">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderCascadingSectionResizes">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderDefaultSectionSize">
<number>110</number>
</attribute>
<attribute name="horizontalHeaderHighlightSections">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderMinimumSectionSize">
<number>35</number>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
<attribute name="verticalHeaderDefaultSectionSize">
<number>24</number>
</attribute>
<attribute name="verticalHeaderHighlightSections">
<bool>false</bool>
</attribute>
<attribute name="verticalHeaderMinimumSectionSize">
<number>24</number>
</attribute>
<attribute name="verticalHeaderStretchLastSection">
<bool>false</bool>
</attribute>
<row>
<property name="text">
<string>Width</string>
</property>
<property name="toolTip">
<string/>
</property>
</row>
<row>
<property name="text">
<string>Height</string>
</property>
</row>
<row>
<property name="text">
<string>Quality</string>
</property>
</row>
<row>
<property name="text">
<string>Temporal Samples</string>
</property>
</row>
<row>
<property name="text">
<string>Supersample</string>
</property>
</row>
<row>
<property name="text">
<string>Memory Usage</string>
</property>
</row>
<row>
<property name="text">
<string>Output</string>
</property>
</row>
<row>
<property name="text">
<string>Prefix</string>
</property>
</row>
<row>
<property name="text">
<string>Suffix</string>
</property>
</row>
<column>
<property name="text">
<string>Field</string>
</property>
</column>
<column>
<property name="text">
<string/>
</property>
</column>
<item row="0" column="0">
<property name="text">
<string>Width</string>
</property>
<property name="toolTip">
<string>The width in pixels of the final output image</string>
</property>
</item>
<item row="0" column="1">
<property name="text">
<string>0</string>
</property>
</item>
<item row="1" column="0">
<property name="text">
<string>Height</string>
</property>
<property name="toolTip">
<string>The height in pixels of the final output image</string>
</property>
</item>
<item row="1" column="1">
<property name="text">
<string>0</string>
</property>
</item>
<item row="2" column="0">
<property name="text">
<string>Quality</string>
</property>
<property name="toolTip">
<string>The quality in iterations per pixel of the final output image</string>
</property>
</item>
<item row="2" column="1">
<property name="text">
<string>0</string>
</property>
</item>
<item row="3" column="0">
<property name="text">
<string>Temporal Samples</string>
</property>
<property name="toolTip">
<string>The number of interpolated renders to do for each flame when rendering as an animation sequence</string>
</property>
</item>
<item row="3" column="1">
<property name="text">
<string>0</string>
</property>
</item>
<item row="4" column="0">
<property name="text">
<string>Supersample</string>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The number to multiply the dimensions of the histogram and density filtering buffer by to achieve anti-aliasing.&lt;/p&gt;&lt;p&gt;Use this very sparingly as it increases the required memory by n squared.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</item>
<item row="4" column="1">
<property name="text">
<string>0</string>
</property>
</item>
<item row="5" column="0">
<property name="text">
<string>Memory Usage</string>
</property>
<property name="toolTip">
<string>The amount of memory including the final output image required to perform this render</string>
</property>
</item>
<item row="5" column="1">
<property name="text">
<string>0</string>
</property>
</item>
<item row="6" column="0">
<property name="text">
<string>Output</string>
</property>
<property name="toolTip">
<string>The output file path for rendering a single flame, or folder location for rendering multiple flames</string>
</property>
</item>
<item row="6" column="1">
<property name="text">
<string/>
</property>
</item>
<item row="7" column="0">
<property name="text">
<string>Prefix</string>
</property>
<property name="toolTip">
<string>The prefix to attach to all image filenames</string>
</property>
</item>
<item row="7" column="1">
<property name="text">
<string/>
</property>
</item>
<item row="8" column="0">
<property name="text">
<string>Suffix</string>
</property>
<property name="toolTip">
<string>The suffix to attach to all image filenames</string>
</property>
</item>
<item row="8" column="1">
<property name="text">
<string/>
</property>
</item>
</widget>
</item>
<item>
<widget class="QLabel" name="FinalRenderImageCountLabel">
<property name="text">
<string>0 / 0</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout_2" rowstretch="0,0,0,0" columnstretch="1,4">
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
<item row="2" column="1">
<widget class="QProgressBar" name="FinalRenderFilteringProgress">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="FinalRenderIterationProgressLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Iteration:</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="FinalRenderAccumProgressLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Final Accumulation:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QProgressBar" name="FinalRenderIterationProgress">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QProgressBar" name="FinalRenderAccumProgress">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="FinalRenderFilteringProgressLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Density Filtering:</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="FinalRenderTotalProgressLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Total Progress:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QProgressBar" name="FinalRenderTotalProgress">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QTextEdit" name="FinalRenderTextOutput">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="FinalRenderButtonHBoxLayout">
<property name="spacing">
<number>6</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<spacer name="FinalRenderButtonSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>131</width>
<height>31</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="StartRenderButton">
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
<property name="text">
<string>Start</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="StopRenderButton">
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
<property name="text">
<string>Stop</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="CloseButton">
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
<property name="text">
<string>Close</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
<property name="default">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>TableWidget</class>
<extends>QTableWidget</extends>
<header>TableWidget.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>StartRenderButton</sender>
<signal>clicked()</signal>
<receiver>FinalRenderDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>278</x>
<y>253</y>
</hint>
<hint type="destinationlabel">
<x>96</x>
<y>254</y>
</hint>
</hints>
</connection>
<connection>
<sender>CloseButton</sender>
<signal>clicked()</signal>
<receiver>FinalRenderDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>369</x>
<y>253</y>
</hint>
<hint type="destinationlabel">
<x>179</x>
<y>282</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,553 @@
#include "FractoriumPch.h"
#include "FractoriumEmberController.h"
#include "FinalRenderEmberController.h"
#include "FinalRenderDialog.h"
#include "Fractorium.h"
/// <summary>
/// Constructor which accepts a pointer to the final render dialog.
/// It passes a pointer to the main window to the base and initializes members.
/// </summary>
/// <param name="finalRender">Pointer to the final render dialog</param>
FinalRenderEmberControllerBase::FinalRenderEmberControllerBase(FractoriumFinalRenderDialog* finalRender)
: FractoriumEmberControllerBase(finalRender->m_Fractorium)
{
m_Run = false;
m_PreviewRun = false;
m_ImageCount = 0;
m_FinishedImageCount = 0;
m_FinalRender = finalRender;
m_Settings = m_Fractorium->m_Settings;
}
/// <summary>
/// Cancel the render by calling Abort().
/// This will block until the cancelling is actually finished.
/// It should never take longer than a few milliseconds because the
/// renderer checks the m_Abort flag in many places during the process.
/// </summary>
void FinalRenderEmberControllerBase::CancelRender()
{
if (m_Result.isRunning())
{
tbb::task_group g;
g.run([&]
{
m_Run = false;
if (m_Renderer.get())
{
m_Renderer->Abort();
while (m_Renderer->InRender())
QApplication::processEvents();
m_Renderer->EnterRender();
m_Renderer->EnterFinalAccum();
m_Renderer->LeaveFinalAccum();
m_Renderer->LeaveRender();
}
});
g.wait();
while (m_Result.isRunning())
QApplication::processEvents();
m_FinalRender->ui.FinalRenderTextOutput->append("Render canceled.");
}
}
/// <summary>
/// Create a new renderer based on the options selected on the GUI.
/// If a renderer matching the options has already been created, no action is taken.
/// </summary>
/// <returns>True if a valid renderer is created or if no action is taken, else false.</returns>
bool FinalRenderEmberControllerBase::CreateRendererFromGUI()
{
bool useOpenCL = m_Wrapper.CheckOpenCL() && m_FinalRender->OpenCL();
return CreateRenderer(useOpenCL ? OPENCL_RENDERER : CPU_RENDERER,
m_FinalRender->PlatformIndex(),
m_FinalRender->DeviceIndex(),
false);//Not shared.
}
/// <summary>
/// Constructor which accepts a pointer to the final render dialog and passes it to the base.
/// The main final rendering lambda function is constructed here.
/// </summary>
/// <param name="finalRender">Pointer to the final render dialog</param>
template<typename T>
FinalRenderEmberController<T>::FinalRenderEmberController(FractoriumFinalRenderDialog* finalRender)
: FinalRenderEmberControllerBase(finalRender)
{
m_PreviewRenderer = auto_ptr<EmberNs::Renderer<T, T>>(new EmberNs::Renderer<T, T>());
m_PreviewRenderer->Callback(NULL);
m_PreviewRenderer->NumChannels(4);
m_PreviewRenderer->ReclaimOnResize(true);
m_PreviewRenderFunc = [&]()
{
m_PreviewCs.Enter();//Thread prep.
m_PreviewRun = true;
m_PreviewRenderer->Abort();
QLabel* widget = m_FinalRender->ui.FinalRenderPreviewLabel;
unsigned int maxDim = 100u;
T scalePercentage;
//Determine how to scale the scaled ember to fit in the label with a max of 100x100.
if (m_Ember.m_FinalRasW >= m_Ember.m_FinalRasH)
scalePercentage = T(maxDim) / m_Ember.m_FinalRasW;
else
scalePercentage = T(maxDim) / m_Ember.m_FinalRasH;
m_PreviewEmber = m_Ember;
m_PreviewEmber.m_Quality = 100;
m_PreviewEmber.m_Supersample = 1;
m_PreviewEmber.m_TemporalSamples = 1;
m_PreviewEmber.m_FinalRasW = min(maxDim, unsigned int(scalePercentage * m_Ember.m_FinalRasW));
m_PreviewEmber.m_FinalRasH = min(maxDim, unsigned int(scalePercentage * m_Ember.m_FinalRasH));
m_PreviewEmber.m_PixelsPerUnit = scalePercentage * m_Ember.m_PixelsPerUnit;
while (!m_PreviewRenderer->Aborted() || m_PreviewRenderer->InRender())
QApplication::processEvents();
m_PreviewRenderer->EarlyClip(m_FinalRender->EarlyClip());
m_PreviewRenderer->Transparency(m_FinalRender->Transparency());
m_PreviewRenderer->SetEmber(m_PreviewEmber);
if (m_PreviewRenderer->Run(m_PreviewFinalImage) == RENDER_OK)
{
QImage image(m_PreviewEmber.m_FinalRasW, m_PreviewEmber.m_FinalRasH, QImage::Format_RGBA8888);//The label wants RGBA.
memcpy(image.scanLine(0), m_PreviewFinalImage.data(), m_PreviewFinalImage.size() * sizeof(m_PreviewFinalImage[0]));//Memcpy the data in.
QPixmap pixmap = QPixmap::fromImage(image);
QMetaObject::invokeMethod(widget, "setPixmap", Qt::QueuedConnection, Q_ARG(QPixmap, pixmap));
}
m_PreviewRun = false;
m_PreviewCs.Leave();
};
//The main rendering function which will be called in a Qt thread.
//A backup Xml is made before the rendering process starts just in case it crashes before finishing.
//If it finishes successfully, delete the backup file.
m_RenderFunc = [&]()
{
size_t i;
m_Run = true;
m_TotalTimer.Tic();//Begin timing for progress.
m_GuiState = m_FinalRender->State();//Cache render settings from the GUI before running.
m_FinishedImageCount = 0;
QFileInfo original(m_GuiState.m_Path);
QString backup = original.absolutePath() + QDir::separator() + m_GuiState.m_Prefix + original.completeBaseName() + m_GuiState.m_Suffix + "_backup.flame";
QMetaObject::invokeMethod(m_Fractorium, "OnActionSaveCurrentToOpenedFile", Qt::QueuedConnection, Q_ARG(bool, true));//First, save the current ember back to its opened file.
m_Fractorium->m_Controller->CopyEmber(m_Ember);
m_Fractorium->m_Controller->CopyEmberFile(m_EmberFile);//Copy the whole file, will take about 0.2ms per ember in the file.
//Save backup Xml.
if (m_GuiState.m_DoAll && m_EmberFile.m_Embers.size() > 1)
m_XmlWriter.Save(backup.toStdString().c_str(), m_EmberFile.m_Embers, 0, true, false, true);
else
m_XmlWriter.Save(backup.toStdString().c_str(), m_Ember, 0, true, false, true);
QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderTextOutput, "setText", Qt::QueuedConnection, Q_ARG(QString, "Begin rendering..."));
m_Renderer->EarlyClip(m_GuiState.m_EarlyClip);
m_Renderer->ThreadCount(m_GuiState.m_ThreadCount);
m_Renderer->Transparency(m_GuiState.m_Transparency);
if (m_GuiState.m_Path.endsWith(".png", Qt::CaseInsensitive) || m_Renderer->RendererType() == OPENCL_RENDERER)
m_Renderer->NumChannels(4);
else
m_Renderer->NumChannels(3);
//The rendering process is different between doing a single image, and doing multiple.
if (m_GuiState.m_DoAll && m_EmberFile.m_Embers.size() > 1)
{
m_ImageCount = m_EmberFile.m_Embers.size();
ResetProgress();
//Different action required for rendering as animation or not.
if (m_GuiState.m_DoSequence)
{
//Need to loop through and set all w, h, q, ts, ss and t vals.
for (i = 0; i < m_EmberFile.m_Embers.size() && m_Run; i++)
{
Sync(m_EmberFile.m_Embers[i]);
if (i > 0)
{
if (m_EmberFile.m_Embers[i].m_Time <= m_EmberFile.m_Embers[i - 1].m_Time)
m_EmberFile.m_Embers[i].m_Time = m_EmberFile.m_Embers[i - 1].m_Time + 1;
}
else if (i == 0)
{
m_EmberFile.m_Embers[i].m_Time = 0;
}
m_EmberFile.m_Embers[i].m_TemporalSamples = m_GuiState.m_TemporalSamples;
}
m_Renderer->SetEmber(m_EmberFile.m_Embers);//Copy all embers to the local storage inside the renderer.
//Render each image, cancelling if m_Run ever gets set to false.
for (i = 0; i < m_EmberFile.m_Embers.size() && m_Run; i++)
{
m_Renderer->Reset();//Have to manually set this since the ember is not set each time through.
m_RenderTimer.Tic();//Toc() is called in the progress function.
if (m_Renderer->Run(m_FinalImage, i) != RENDER_OK)
{
QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderTextOutput, "append", Qt::QueuedConnection, Q_ARG(QString, "Renderering failed.\n"));
m_Fractorium->ErrorReportToQTextEdit(m_Renderer->ErrorReport(), m_FinalRender->ui.FinalRenderTextOutput, false);
}
}
}
else//Render all images, but not as an animation sequence (without temporal samples motion blur).
{
//Copy widget values to all embers
for (i = 0; i < m_EmberFile.m_Embers.size() && m_Run; i++)
{
Sync(m_EmberFile.m_Embers[i]);
m_EmberFile.m_Embers[i].m_TemporalSamples = 1;//No temporal sampling.
}
//Render each image, cancelling if m_Run ever gets set to false.
for (i = 0; i < m_EmberFile.m_Embers.size() && m_Run; i++)
{
m_Renderer->SetEmber(m_EmberFile.m_Embers[i]);
m_RenderTimer.Tic();//Toc() is called in the progress function.
if (m_Renderer->Run(m_FinalImage) != RENDER_OK)
{
QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderTextOutput, "append", Qt::QueuedConnection, Q_ARG(QString, "Renderering failed.\n"));
m_Fractorium->ErrorReportToQTextEdit(m_Renderer->ErrorReport(), m_FinalRender->ui.FinalRenderTextOutput, false);
}
}
}
}
else//Render a single image.
{
m_ImageCount = 1;
Sync(m_Ember);
ResetProgress();
m_Ember.m_TemporalSamples = 1;
m_Renderer->SetEmber(m_Ember);
m_RenderTimer.Tic();//Toc() is called in the progress function.
if (m_Renderer->Run(m_FinalImage) != RENDER_OK)
{
QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderTextOutput, "append", Qt::QueuedConnection, Q_ARG(QString, "Renderering failed.\n"));
m_Fractorium->ErrorReportToQTextEdit(m_Renderer->ErrorReport(), m_FinalRender->ui.FinalRenderTextOutput, false);
}
}
QFile::remove(backup);
m_Run = false;
};
}
/// <summary>
/// Setters for embers and ember files 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 FinalRenderEmberController<T>::SetEmber(const Ember<float>& ember, bool verbatim) { m_Ember = ember; }
template <typename T> void FinalRenderEmberController<T>::CopyEmber(Ember<float>& ember) { ember = m_Ember; }
template <typename T> void FinalRenderEmberController<T>::SetEmberFile(const EmberFile<float>& emberFile) { m_EmberFile = emberFile; }
template <typename T> void FinalRenderEmberController<T>::CopyEmberFile(EmberFile<float>& emberFile) { emberFile = m_EmberFile; }
template <typename T> double FinalRenderEmberController<T>::OriginalAspect() { return double(m_OriginalEmber.m_OrigFinalRasW) / m_OriginalEmber.m_OrigFinalRasH; }
#ifdef DO_DOUBLE
template <typename T> void FinalRenderEmberController<T>::SetEmber(const Ember<double>& ember, bool verbatim) { m_Ember = ember; }
template <typename T> void FinalRenderEmberController<T>::CopyEmber(Ember<double>& ember) { ember = m_Ember; }
template <typename T> void FinalRenderEmberController<T>::SetEmberFile(const EmberFile<double>& emberFile) { m_EmberFile = emberFile; }
template <typename T> void FinalRenderEmberController<T>::CopyEmberFile(EmberFile<double>& emberFile) { emberFile = m_EmberFile; }
template <typename T> void FinalRenderEmberController<T>::SetOriginalEmber(Ember<double>& ember) { m_OriginalEmber = ember; }
#else
template <typename T> void FinalRenderEmberController<T>::SetOriginalEmber(Ember<float>& ember) { m_OriginalEmber = ember; }
#endif
/// <summary>
/// Progress function.
/// Take special action to sync options upon finishing.
/// </summary>
/// <param name="ember">The ember currently being rendered</param>
/// <param name="foo">An extra dummy parameter</param>
/// <param name="fraction">The progress fraction from 0-100</param>
/// <param name="stage">The stage of iteration. 1 is iterating, 2 is density filtering, 2 is final accumulation.</param>
/// <param name="etaMs">The estimated milliseconds to completion of the current stage</param>
/// <returns>0 if the user has clicked cancel, else 1 to continue rendering.</returns>
template <typename T>
int FinalRenderEmberController<T>::ProgressFunc(Ember<T>& ember, void* foo, double fraction, int stage, double etaMs)
{
static int count = 0;
if (stage == 0)
QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderIterationProgress, "setValue", Qt::QueuedConnection, Q_ARG(int, int(fraction)));
else if (stage == 1)
QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderFilteringProgress, "setValue", Qt::QueuedConnection, Q_ARG(int, int(fraction)));
else if (stage == 2)
QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderAccumProgress, "setValue", Qt::QueuedConnection, Q_ARG(int, int(fraction)));
//Finished, so take special action.
if (stage == 2 && (int)fraction == 100)
{
string renderTimeString = m_RenderTimer.Format(m_RenderTimer.Toc()), totalTimeString;
QString status, filename = m_GuiState.m_Path;
QFileInfo original(filename);
EmberStats stats = m_Renderer->Stats();
QString iters = QLocale(QLocale::English).toString(stats.m_Iters);
if (m_GuiState.m_DoAll && m_EmberFile.m_Embers.size() > 1)
filename = original.absolutePath() + QDir::separator() + m_GuiState.m_Prefix + QString::fromStdString(m_EmberFile.m_Embers[m_FinishedImageCount].m_Name) + m_GuiState.m_Suffix + "." + m_GuiState.m_DoAllExt;
else
filename = original.absolutePath() + QDir::separator() + m_GuiState.m_Prefix + original.completeBaseName() + m_GuiState.m_Suffix + "." + original.suffix();
filename = EmberFile<T>::UniqueFilename(filename);
//Save whatever options were specified on the GUI to the settings.
m_Settings->FinalEarlyClip(m_GuiState.m_EarlyClip);
m_Settings->FinalTransparency(m_GuiState.m_Transparency);
m_Settings->FinalOpenCL(m_GuiState.m_OpenCL);
m_Settings->FinalDouble(m_GuiState.m_Double);
m_Settings->FinalPlatformIndex(m_GuiState.m_PlatformIndex);
m_Settings->FinalDeviceIndex(m_GuiState.m_DeviceIndex);
m_Settings->FinalSaveXml(m_GuiState.m_SaveXml);
m_Settings->FinalDoAll(m_GuiState.m_DoAll);
m_Settings->FinalDoSequence(m_GuiState.m_DoSequence);
m_Settings->FinalKeepAspect(m_GuiState.m_KeepAspect);
m_Settings->FinalScale(m_GuiState.m_Scale);
m_Settings->FinalDoAllExt(m_GuiState.m_DoAllExt);
m_Settings->FinalThreadCount(m_GuiState.m_ThreadCount);
m_Settings->FinalWidth(m_GuiState.m_Width);
m_Settings->FinalHeight(m_GuiState.m_Height);
m_Settings->FinalQuality(m_GuiState.m_Quality);
m_Settings->FinalTemporalSamples(m_GuiState.m_TemporalSamples);
m_Settings->FinalSupersample(m_GuiState.m_Supersample);
SaveCurrentRender(filename);
if (m_GuiState.m_SaveXml)
{
QFileInfo xmlFileInfo(filename);//Create another one in case it was modified for batch rendering.
QString newPath = xmlFileInfo.absolutePath() + QDir::separator() + xmlFileInfo.completeBaseName() + ".flame";
xmlDocPtr tempEdit = ember.m_Edits;
ember.m_Edits = m_XmlWriter.CreateNewEditdoc(&ember, NULL, "edit", m_Settings->Nick().toStdString(), m_Settings->Url().toStdString(), m_Settings->Id().toStdString(), "", 0, 0);
m_XmlWriter.Save(newPath.toStdString().c_str(), ember, 0, true, false, true);//Note that the ember passed is used, rather than m_Ember because it's what was actually rendered.
if (tempEdit != NULL)
xmlFreeDoc(tempEdit);
}
m_FinishedImageCount++;
QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderTotalProgress, "setValue", Qt::QueuedConnection, Q_ARG(int, int(((float)m_FinishedImageCount / (float)m_ImageCount) * 100)));
QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderImageCountLabel, "setText", Qt::QueuedConnection, Q_ARG(QString, QString::number(m_FinishedImageCount) + " / " + QString::number(m_ImageCount)));
status = "Image " + QString::number(m_FinishedImageCount) + ":\nPure render time: " + QString::fromStdString(renderTimeString);
QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderTextOutput, "append", Qt::QueuedConnection, Q_ARG(QString, status));
totalTimeString = m_TotalTimer.Format(m_TotalTimer.Toc());
status = "Total render time: " + QString::fromStdString(totalTimeString) + "\nTotal iters: " + iters + "\n";
QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderTextOutput, "append", Qt::QueuedConnection, Q_ARG(QString, status));
QMetaObject::invokeMethod(m_FinalRender, "MoveCursorToEnd", Qt::QueuedConnection);
if (m_FinishedImageCount != m_ImageCount)
{
ResetProgress(false);
}
}
QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderTextOutput, "update", Qt::QueuedConnection);
QApplication::processEvents();
return m_Run ? 1 : 0;
}
/// <summary>
/// Start the final rendering process.
/// Create the needed renderer from the GUI if it has not been created yet.
/// </summary>
/// <returns></returns>
template<typename T>
bool FinalRenderEmberController<T>::Render()
{
QString filename = m_FinalRender->Path();
if (filename == "")
{
QMessageBox::critical(m_FinalRender, "File Error", "Please enter a valid path and filename for the output.");
return false;
}
if (CreateRendererFromGUI())
{
m_FinalRender->ui.FinalRenderTextOutput->clear();
//Note that a Qt thread must be used, rather than a tbb task.
//This is because tbb does a very poor job of allocating thread resources
//and dedicates an entire core just to this thread which does nothing waiting for the
//parallel iteration loops inside of the CPU renderer to finish. The result is that
//the renderer ends up using ThreadCount - 1 to iterate, instead of ThreadCount.
//By using a Qt thread here, and tbb inside the renderer, all cores can be maxed out.
m_Result = QtConcurrent::run(m_RenderFunc);
m_Settings->sync();
return true;
}
else
return false;
}
/// <summary>
/// Stop rendering and initialize a new renderer, using the specified type and the options on the final render dialog.
/// </summary>
/// <param name="renderType">The type of render to create</param>
/// <param name="platform">The index platform of the platform to use</param>
/// <param name="device">The index device of the device to use</param>
/// <param name="outputTexID">The texture ID of the shared OpenGL texture if shared</param>
/// <param name="shared">True if shared with OpenGL, else false. Default: true.</param>
/// <returns>True if nothing went wrong, else false.</returns>
template <typename T>
bool FinalRenderEmberController<T>::CreateRenderer(eRendererType renderType, unsigned int platform, unsigned int device, bool shared)
{
bool ok = true;
vector<string> errorReport;
QString filename = m_FinalRender->Path();
unsigned int channels = filename.endsWith(".png", Qt::CaseInsensitive) ? 4 : 3;
CancelRender();
if (m_Renderer.get() &&
m_Renderer->Ok() &&
m_Renderer->RendererType() == renderType &&
m_Platform == platform &&
m_Device == device &&
m_Shared == shared)
{
return ok;
}
if (!m_Renderer.get() || (m_Renderer->RendererType() != renderType))
{
EmberReport emberReport;
vector<string> errorReport;
m_Platform = platform;//Store values for re-creation later on.
m_Device = device;
m_OutputTexID = 0;//Don't care about tex ID when doing final render.
m_Shared = shared;
m_Renderer = auto_ptr<EmberNs::RendererBase>(::CreateRenderer<T, T>(renderType, platform, device, shared, m_OutputTexID, emberReport));
errorReport = emberReport.ErrorReport();
if (!errorReport.empty())
{
ok = false;
QMessageBox::critical(m_Fractorium, "Renderer Creation Error", "Could not create requested renderer, fallback CPU renderer created. See info tab for details.");
m_Fractorium->ErrorReportToQTextEdit(errorReport, m_Fractorium->ui.InfoRenderingTextEdit);
}
}
if (m_Renderer.get())
{
if (m_Renderer->RendererType() == OPENCL_RENDERER)
channels = 4;//Always using 4 since the GL texture is RGBA.
m_Renderer->Callback(this);
m_Renderer->NumChannels(channels);
m_Renderer->ReclaimOnResize(false);
m_Renderer->EarlyClip(m_FinalRender->EarlyClip());
m_Renderer->ThreadCount(m_FinalRender->ThreadCount());
m_Renderer->Transparency(m_FinalRender->Transparency());
}
else
{
ok = false;
QMessageBox::critical(m_FinalRender, "Renderer Creation Error", "Could not create renderer, aborting. See info tab for details.");
}
return ok;
}
/// <summary>
/// Set various parameters in the renderer and current ember with the values
/// specified in the widgets and compute the amount of memory required to render.
/// This includes the memory needed for the final output image.
/// </summary>
/// <returns>If successful, memory required in bytes, else zero.</returns>
template <typename T>
unsigned __int64 FinalRenderEmberController<T>::SyncAndComputeMemory()
{
if (m_Renderer.get())
{
bool b = false;
QString filename = m_FinalRender->Path();
unsigned int channels = filename.endsWith(".png", Qt::CaseInsensitive) ? 4 : 3;//4 channels for Png, else 3.
Sync(m_Ember);
m_Renderer->SetEmber(m_Ember);
m_Renderer->CreateSpatialFilter(b);
m_Renderer->CreateTemporalFilter(b);
m_Renderer->NumChannels(channels);
m_Renderer->ComputeBounds();
CancelPreviewRender();
//m_PreviewResult = QtConcurrent::run(m_PreviewRenderFunc);
//while (!m_PreviewResult.isRunning()) { QApplication::processEvents(); }//Wait for it to start up.
m_PreviewRenderFunc();
return m_Renderer->MemoryRequired(true);
}
return 0;
}
/// <summary>
/// Reset the progress bars.
/// </summary>
/// <param name="total">True to reset render image and total progress bars, else false to only do iter, filter and accum bars.</param>
template <typename T>
void FinalRenderEmberController<T>::ResetProgress(bool total)
{
if (total)
{
QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderImageCountLabel, "setText", Qt::QueuedConnection, Q_ARG(QString, "0 / " + QString::number(m_ImageCount)));
QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderTotalProgress, "setValue", Qt::QueuedConnection, Q_ARG(int, 0));
}
QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderIterationProgress, "setValue", Qt::QueuedConnection, Q_ARG(int, 0));
QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderFilteringProgress, "setValue", Qt::QueuedConnection, Q_ARG(int, 0));
QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderAccumProgress, "setValue", Qt::QueuedConnection, Q_ARG(int, 0));
}
template <typename T>
void FinalRenderEmberController<T>::CancelPreviewRender()
{
m_PreviewRenderer->Abort();
while (m_PreviewRenderer->InRender()) { QApplication::processEvents(); }
while (m_PreviewRun) { QApplication::processEvents(); }
while (m_PreviewResult.isRunning()) { QApplication::processEvents(); }
}
/// <summary>
/// Copy widget values to the ember passed in.
/// </summary>
/// <param name="ember">The ember whose values will be modified</param>
template <typename T>
void FinalRenderEmberController<T>::Sync(Ember<T>& ember)
{
int w = m_FinalRender->m_WidthSpin->value();
int h = m_FinalRender->m_HeightSpin->value();
ember.m_FinalRasW = m_OriginalEmber.m_OrigFinalRasW;//Scale is always in terms of the original dimensions of the ember in the editor.
ember.m_FinalRasH = m_OriginalEmber.m_OrigFinalRasH;
ember.m_PixelsPerUnit = m_OriginalEmber.m_PixelsPerUnit;
ember.SetSizeAndAdjustScale(w, h, false, m_FinalRender->Scale());
ember.m_Quality = m_FinalRender->m_QualitySpin->value();
ember.m_Supersample = m_FinalRender->m_SupersampleSpin->value();
if (m_FinalRender->ui.FinalRenderDoSequenceCheckBox->isChecked())
ember.m_TemporalSamples = m_FinalRender->m_TemporalSamplesSpin->value();
}

View File

@ -0,0 +1,143 @@
#pragma once
#include "FractoriumSettings.h"
#include "FractoriumEmberController.h"
/// <summary>
/// FinalRenderEmberControllerBase and FinalRenderEmberController<T> classes.
/// </summary>
/// <summary>
/// FractoriumEmberController and Fractorium need each other, but each can't include the other.
/// So Fractorium includes this file, and Fractorium is declared as a forward declaration here.
/// </summary>
class Fractorium;
class FractoriumFinalRenderDialog;
/// <summary>
/// Used to hold the options specified in the current state of the Gui for performing the final render.
/// </summary>
struct FinalRenderGuiState
{
bool m_EarlyClip;
bool m_AlphaChannel;
bool m_Transparency;
bool m_OpenCL;
bool m_Double;
bool m_SaveXml;
bool m_DoAll;
bool m_DoSequence;
bool m_KeepAspect;
eScaleType m_Scale;
QString m_Path;
QString m_DoAllExt;
QString m_Prefix;
QString m_Suffix;
unsigned int m_PlatformIndex;
unsigned int m_DeviceIndex;
unsigned int m_ThreadCount;
unsigned int m_Width;
unsigned int m_Height;
unsigned int m_Quality;
unsigned int m_TemporalSamples;
unsigned int m_Supersample;
};
/// <summary>
/// FinalRenderEmberControllerBase serves as a non-templated base class with virtual
/// functions which will be overridden in a derived class that takes a template parameter.
/// Although not meant to be used as an interactive renderer, it derives from FractoriumEmberControllerBase
/// to access a few of its members to avoid having to redefine them here.
/// </summary>
class FinalRenderEmberControllerBase : public FractoriumEmberControllerBase
{
friend FractoriumFinalRenderDialog;
public:
FinalRenderEmberControllerBase(FractoriumFinalRenderDialog* finalRender);
virtual ~FinalRenderEmberControllerBase() { }
virtual unsigned __int64 SyncAndComputeMemory() { return 0; }
virtual QString Name() const { return ""; }
virtual void ResetProgress(bool total = true) { }
#ifdef DO_DOUBLE
virtual void SetOriginalEmber(Ember<double>& ember) { }
#else
virtual void SetOriginalEmber(Ember<float>& ember) { }
#endif
virtual double OriginalAspect() { return 1; }
void CancelRender();
bool CreateRendererFromGUI();
protected:
bool m_Run;
bool m_PreviewRun;
unsigned int m_ImageCount;
unsigned int m_FinishedImageCount;
QFuture<void> m_Result;
QFuture<void> m_PreviewResult;
std::function<void (void)> m_RenderFunc;
std::function<void (void)> m_PreviewRenderFunc;
vector<unsigned char> m_PreviewFinalImage;
FractoriumSettings* m_Settings;
FractoriumFinalRenderDialog* m_FinalRender;
FinalRenderGuiState m_GuiState;
OpenCLWrapper m_Wrapper;
CriticalSection m_PreviewCs;
Timing m_RenderTimer;
Timing m_TotalTimer;
};
/// <summary>
/// Templated derived class which implements all interaction functionality between the embers
/// of a specific template type and the final render dialog;
/// </summary>
template<typename T>
class FinalRenderEmberController : public FinalRenderEmberControllerBase
{
public:
FinalRenderEmberController(FractoriumFinalRenderDialog* finalRender);
virtual ~FinalRenderEmberController() { }
virtual void SetEmber(const Ember<float>& ember, bool verbatim = false);
virtual void CopyEmber(Ember<float>& ember);
virtual void SetEmberFile(const EmberFile<float>& emberFile);
virtual void CopyEmberFile(EmberFile<float>& emberFile);
#ifdef DO_DOUBLE
virtual void SetEmber(const Ember<double>& ember, bool verbatim = false);
virtual void CopyEmber(Ember<double>& ember);
virtual void SetEmberFile(const EmberFile<double>& emberFile);
virtual void CopyEmberFile(EmberFile<double>& emberFile);
virtual void SetOriginalEmber(Ember<double>& ember);
#else
virtual void SetOriginalEmber(Ember<float>& ember);
#endif
virtual double OriginalAspect();
virtual int ProgressFunc(Ember<T>& ember, void* foo, double fraction, int stage, double etaMs);
virtual bool Render();
virtual bool CreateRenderer(eRendererType renderType, unsigned int platform, unsigned int device, bool shared = true);
virtual unsigned int SizeOfT() { return sizeof(T); }
virtual unsigned __int64 SyncAndComputeMemory();
virtual QString Name() const { return QString::fromStdString(m_Ember.m_Name); }
virtual void ResetProgress(bool total = true);
void CancelPreviewRender();
protected:
void Sync(Ember<T>& ember);
Ember<T> m_Ember;
Ember<T> m_OriginalEmber;
Ember<T> m_PreviewEmber;
EmberFile<T> m_EmberFile;
EmberToXml<T> m_XmlWriter;
auto_ptr<EmberNs::Renderer<T, T>> m_PreviewRenderer;
};
template class FinalRenderEmberController<float>;
#ifdef DO_DOUBLE
template class FinalRenderEmberController<double>;
#endif

Binary file not shown.

View File

@ -0,0 +1,544 @@
#include "FractoriumPch.h"
#include "Fractorium.h"
/// <summary>
/// Constructor that initializes the entire program.
/// The setup process is very lengthy because it requires many custom modifications
/// to the GUI widgets that are not possible to do through the designer. So if something
/// is present here, it's safe to assume it can't be done in the designer.
/// </summary>
Fractorium::Fractorium(QWidget* parent)
: QMainWindow(parent)
{
int spinHeight = 20;
size_t i, j;
Timing t;
ui.setupUi(this);
qRegisterMetaType<QVector<int>>("QVector<int>");//For previews.
qRegisterMetaType<vector<unsigned char>>("vector<unsigned char>");
qRegisterMetaType<EmberTreeWidgetItemBase*>("EmberTreeWidgetItemBase*");
m_FontSize = 9;
m_VarSortMode = 1;//Sort by weight by default.
m_ColorDialog = new QColorDialog(this);
m_Settings = new FractoriumSettings(this);
m_FileDialog = NULL;//Use lazy instantiation upon first use.
m_FolderDialog = NULL;
m_FinalRenderDialog = new FractoriumFinalRenderDialog(m_Settings, this);
m_OptionsDialog = new FractoriumOptionsDialog(m_Settings, this);
m_AboutDialog = new FractoriumAboutDialog(this);
//The options dialog should be a fixed size without a size grip, however even if it's here, it still shows up. Perhaps Qt will fix it some day.
m_OptionsDialog->layout()->setSizeConstraint(QLayout::SetFixedSize);
m_OptionsDialog->setSizeGripEnabled(false);
connect(m_ColorDialog, SIGNAL(colorSelected(const QColor&)), this, SLOT(OnColorSelected(const QColor&)), Qt::QueuedConnection);
InitToolbarUI();
InitParamsUI();
InitXformsUI();
InitXformsColorUI();
InitXformsAffineUI();
InitXformsVariationsUI();
InitXformsXaosUI();
InitPaletteUI();
InitLibraryUI();
InitMenusUI();
//This will init the controller and fill in the variations and palette tables with template specific instances
//of their respective objects.
#ifdef DO_DOUBLE
if (m_Settings->Double())
m_Controller = auto_ptr<FractoriumEmberControllerBase>(new FractoriumEmberController<double>(this));
else
#endif
m_Controller = auto_ptr<FractoriumEmberControllerBase>(new FractoriumEmberController<float>(this));
if (m_Wrapper.CheckOpenCL() && m_Settings->OpenCL() && m_QualitySpin->value() < 30)
m_QualitySpin->setValue(30);
int statusBarHeight = 20;
ui.statusBar->setMinimumHeight(statusBarHeight);
ui.statusBar->setMaximumHeight(statusBarHeight);
m_RenderStatusLabel = new QLabel(this);
m_RenderStatusLabel->setMinimumWidth(200);
m_RenderStatusLabel->setAlignment(Qt::AlignRight);
ui.statusBar->addPermanentWidget(m_RenderStatusLabel);
m_CoordinateStatusLabel = new QLabel(this);
m_CoordinateStatusLabel->setMinimumWidth(300);
m_CoordinateStatusLabel->setMaximumWidth(300);
m_CoordinateStatusLabel->setAlignment(Qt::AlignLeft);
ui.statusBar->addWidget(m_CoordinateStatusLabel);
int progressBarHeight = 15;
int progressBarWidth = 300;
m_ProgressBar = new QProgressBar(this);
m_ProgressBar->setRange(0, 100);
m_ProgressBar->setValue(0);
m_ProgressBar->setMinimumHeight(progressBarHeight);
m_ProgressBar->setMaximumHeight(progressBarHeight);
m_ProgressBar->setMinimumWidth(progressBarWidth);
m_ProgressBar->setMaximumWidth(progressBarWidth);
ui.statusBar->addPermanentWidget(m_ProgressBar);
showMaximized();
connect(ui.DockWidget, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), this, SLOT(dockLocationChanged(Qt::DockWidgetArea)));
connect(ui.DockWidget, SIGNAL(topLevelChanged(bool)), this, SLOT(OnDockTopLevelChanged(bool)));
//Always ensure the library tab is selected, which will show preview renders.
ui.ParamsTabWidget->setCurrentIndex(0);
ui.XformsTabWidget->setCurrentIndex(2);//Make variations tab the currently selected one under the Xforms tab.
//Setting certain values will completely throw off the GUI, doing everything
//from setting strange margins, to arbitrarily changing the fonts used.
//For these cases, the only way to fix the problem is to use style sheets.
ui.ColorTable->setStyleSheet("QTableWidget::item { padding: 1px; }");
ui.GeometryTable->setStyleSheet("QTableWidget::item { padding: 1px; }");
ui.FilterTable->setStyleSheet("QTableWidget::item { padding: 1px; }");
ui.IterationTable->setStyleSheet("QTableWidget::item { padding: 1px; }");
ui.AffineTab->setStyleSheet("QTableWidget::item { padding: 1px; }");
ui.XformWeightNameTable->setStyleSheet("QTableWidget::item { padding: 0px; }");
ui.XformColorIndexTable->setStyleSheet("QTableWidget::item { padding: 1px; }");
ui.XformColorValuesTable->setStyleSheet("QTableWidget::item { padding: 1px; }");
ui.XformPaletteRefTable->setStyleSheet("QTableWidget::item { padding: 0px; border: none; margin: 0px; }");
ui.PaletteAdjustTable->setStyleSheet("QTableWidget::item { padding: 1px; }");//Need this to avoid covering the top border pixel with the spinners.
ui.statusBar->setStyleSheet("QStatusBar QLabel { padding-left: 2px; padding-right: 2px; }");
m_PreviousPaletteRow = -1;//Force click handler the first time through.
//Setup pointer in the GL window to point back to here.
ui.GLDisplay->SetMainWindow(this);
SetCoordinateStatus(0, 0, 0, 0);
//At this point, everything has been setup except the renderer. Shortly after
//this constructor exits, GLWidget::initializeGL() will create the initial flock and start the rendering timer
//which executes whenever the program is idle. Upon starting the timer, the renderer
//will be initialized.
}
/// <summary>
/// Destructor which saves out the settings file.
/// All other memory is cleared automatically through the use of STL.
/// </summary>
Fractorium::~Fractorium()
{
m_Settings->sync();
}
/// <summary>
/// Stop the render timer and start the delayed restart timer.
/// This is a massive hack because Qt has no way of detecting when a resize event
/// is started and stopped. Detecting if mouse buttons down is also not an option
/// because the documentation says it gives the wrong result.
/// </summary>
void Fractorium::Resize()
{
if (!QCoreApplication::closingDown())
m_Controller->DelayedStartRenderTimer();
}
/// <summary>
/// Set the coordinate text in the status bar.
/// </summary>
/// <param name="x">The raster x coordinate</param>
/// <param name="y">The raster y coordinate</param>
/// <param name="worldX">The cartesian world x coordinate</param>
/// <param name="worldY">The cartesian world y coordinate</param>
void Fractorium::SetCoordinateStatus(int x, int y, float worldX, float worldY)
{
//Use sprintf rather than allocating and concatenating 6 QStrings for efficiency since this is called on every mouse move.
sprintf_s(m_CoordinateString, 128, "Window: %4d, %4d World: %2.2f, %2.2f", x, y, worldX, worldY);
m_CoordinateStatusLabel->setText(QString(m_CoordinateString));
}
/// <summary>
/// Apply the settings for saving an ember to an Xml file to an ember (presumably about to be saved).
/// </summary>
/// <param name="ember">The ember to apply the settings to</param>
template <typename T>
void FractoriumEmberController<T>::ApplyXmlSavingTemplate(Ember<T>& ember)
{
ember.m_FinalRasW = m_Fractorium->m_Settings->XmlWidth();
ember.m_FinalRasH = m_Fractorium->m_Settings->XmlHeight();
ember.m_TemporalSamples = m_Fractorium->m_Settings->XmlTemporalSamples();
ember.m_Quality = m_Fractorium->m_Settings->XmlQuality();
ember.m_Supersample = m_Fractorium->m_Settings->XmlSupersample();
}
/// <summary>
/// Return whether the current ember contains a final xform and the GUI is aware of it.
/// </summary>
/// <returns>True if the current ember contains a final xform, else false.</returns>
bool Fractorium::HaveFinal()
{
QComboBox* combo = ui.CurrentXformCombo;
return (combo->count() > 0 && combo->itemText(combo->count() - 1) == "Final");
}
/// <summary>
/// Slots.
/// </summary>
/// <summary>
/// Empty placeholder for now.
/// Qt has a severe bug where the dock gets hidden behind the window.
/// Perhaps this will be used in the future if Qt ever fixes that bug.
/// Called when the top level dock is changed.
/// </summary>
/// <param name="topLevel">True if top level, else false.</param>
void Fractorium::OnDockTopLevelChanged(bool topLevel)
{
//if (topLevel)
//{
// if (ui.DockWidget->y() <= 0)
// ui.DockWidget->setGeometry(ui.DockWidget->x(), ui.DockWidget->y() + 100, ui.DockWidget->width(), ui.DockWidget->height());
//
// ui.DockWidget->setFloating(true);
//}
//else
// ui.DockWidget->setFloating(false);
}
/// <summary>
/// Empty placeholder for now.
/// Qt has a severe bug where the dock gets hidden behind the window.
/// Perhaps this will be used in the future if Qt ever fixes that bug.
/// Called when the dock location is changed.
/// </summary>
/// <param name="area">The dock widget area</param>
void Fractorium::dockLocationChanged(Qt::DockWidgetArea area)
{
//ui.DockWidget->resize(500, ui.DockWidget->height());
//ui.DockWidget->update();
//ui.dockWidget->setFloating(true);
//ui.dockWidget->setFloating(false);
}
/// <summary>
/// Virtual event overrides.
/// </summary>
/// <summary>
/// Resize event, just pass to base.
/// </summary>
/// <param name="e">The event</param>
void Fractorium::resizeEvent(QResizeEvent* e)
{
QMainWindow::resizeEvent(e);
}
/// <summary>
/// Stop rendering and block before exiting.
/// Called on program exit.
/// </summary>
/// <param name="e">The event</param>
void Fractorium::closeEvent(QCloseEvent* e)
{
if (m_Controller.get())
{
m_Controller->StopRenderTimer(true);//Will wait until fully exited and stopped.
m_Controller->StopPreviewRender();
}
if (e)
e->accept();
}
/// <summary>
/// Examine the files dragged when it first enters the window area.
/// Ok if at least one file is .flam3, .flam3 or .xml, else not ok.
/// Called when the user first drags files in.
/// </summary>
/// <param name="e">The event</param>
void Fractorium::dragEnterEvent(QDragEnterEvent* e)
{
if (e->mimeData()->hasUrls())
{
foreach (QUrl url, e->mimeData()->urls())
{
QString localFile = url.toLocalFile();
QFileInfo fileInfo(localFile);
QString suf = fileInfo.suffix();
if (suf == "flam3" || suf == "flame" || suf == "xml")
{
e->accept();
break;
}
}
}
}
/// <summary>
/// Always accept drag when moving, so that the drop event will correctly be called.
/// </summary>
/// <param name="e">The event</param>
void Fractorium::dragMoveEvent(QDragMoveEvent* e)
{
e->accept();
}
/// <summary>
/// Examine and open the dropped files.
/// Called when the user drops a file in.
/// </summary>
/// <param name="e">The event</param>
void Fractorium::dropEvent(QDropEvent* e)
{
QStringList filenames;
Qt::KeyboardModifiers mod = e->keyboardModifiers();
bool append = mod.testFlag(Qt::ControlModifier) ? true : false;
if (e->mimeData()->hasUrls())
{
foreach (QUrl url, e->mimeData()->urls())
{
QString localFile = url.toLocalFile();
QFileInfo fileInfo(localFile);
QString suf = fileInfo.suffix();
if (suf == "flam3" || suf == "flame" || suf == "xml")
filenames << localFile;
}
}
if (!filenames.empty())
m_Controller->OpenAndPrepFiles(filenames, append);
}
/// <summary>
/// Setup a combo box to be placed in a table cell.
/// </summary>
/// <param name="table">The table the combo box belongs to</param>
/// <param name="receiver">The receiver object</param>
/// <param name="row">The row in the table where this combo box resides</param>
/// <param name="col">The col in the table where this combo box resides</param>
/// <param name="comboBox">Double pointer to combo box which will hold the spinner upon exit</param>
/// <param name="vals">The string values to populate the combo box with</param>
/// <param name="signal">The signal the combo box emits</param>
/// <param name="slot">The slot to receive the signal</param>
/// <param name="connectionType">Type of the connection. Default: Qt::QueuedConnection.</param>
void Fractorium::SetupCombo(QTableWidget* table, const QObject* receiver, int& row, int col, StealthComboBox*& comboBox, vector<string>& vals, const char* signal, const char* slot, Qt::ConnectionType connectionType)
{
comboBox = new StealthComboBox(table);
std::for_each(vals.begin(), vals.end(), [&](string s) { comboBox->addItem(s.c_str()); });
table->setCellWidget(row, col, comboBox);
connect(comboBox, signal, receiver, slot, connectionType);
row++;
}
/// <summary>
/// Set the header of a table to be fixed.
/// </summary>
/// <param name="header">The header to set</param>
/// <param name="mode">The resizing mode to use. Default: QHeaderView::Fixed.</param>
void Fractorium::SetFixedTableHeader(QHeaderView* header, QHeaderView::ResizeMode mode)
{
header->setVisible(true);//For some reason, the designer keeps clobbering this value, so force it here.
header->setSectionsClickable(false);
header->setSectionResizeMode(mode);
}
/// <summary>
/// Setup and show the open XML dialog.
/// This will perform lazy instantiation.
/// </summary>
/// <returns>The filename selected</returns>
QStringList Fractorium::SetupOpenXmlDialog()
{
//Lazy instantiate since it takes a long time.
if (!m_FileDialog)
{
m_FileDialog = new QFileDialog(this);
m_FileDialog->setViewMode(QFileDialog::List);
}
if (!m_FileDialog)
return QStringList("");
QStringList filenames;
m_FileDialog->disconnect(SIGNAL(filterSelected(const QString&)));
connect(m_FileDialog, &QFileDialog::filterSelected, [=](const QString &filter) { m_Settings->OpenXmlExt(filter); });
m_FileDialog->setFileMode(QFileDialog::ExistingFiles);
m_FileDialog->setAcceptMode(QFileDialog::AcceptOpen);
m_FileDialog->setNameFilter("Flam3 (*.flam3);;Flame (*.flame);;Xml (*.xml)");
m_FileDialog->setWindowTitle("Open flame");
m_FileDialog->setDirectory(m_Settings->OpenFolder());
m_FileDialog->selectNameFilter(m_Settings->OpenXmlExt());
if (m_FileDialog->exec() == QDialog::Accepted)
{
filenames = m_FileDialog->selectedFiles();
if (!filenames.empty())
m_Settings->OpenFolder(QFileInfo(filenames[0]).canonicalPath());
}
return filenames;
}
/// <summary>
/// Setup and show the save XML dialog.
/// This will perform lazy instantiation.
/// </summary>
/// <param name="defaultFilename">The default filename to populate the text box with</param>
/// <returns>The filename selected</returns>
QString Fractorium::SetupSaveXmlDialog(QString defaultFilename)
{
//Lazy instantiate since it takes a long time.
if (!m_FileDialog)
{
m_FileDialog = new QFileDialog(this);
m_FileDialog->setViewMode(QFileDialog::List);
}
if (!m_FileDialog)
return "";
QString filename;
m_FileDialog->disconnect(SIGNAL(filterSelected(const QString&)));
connect(m_FileDialog, &QFileDialog::filterSelected, [=](const QString &filter) { m_Settings->SaveXmlExt(filter); });
//This must come first because it clears various internal states which allow the file text to be properly set.
//This is most likely a bug in QFileDialog.
m_FileDialog->setAcceptMode(QFileDialog::AcceptSave);
m_FileDialog->selectFile(defaultFilename);
m_FileDialog->setNameFilter("Flam3 (*.flam3);;Flame (*.flame);;Xml (*.xml)");
m_FileDialog->setWindowTitle("Save flame as xml");
m_FileDialog->setDirectory(m_Settings->SaveFolder());
m_FileDialog->selectNameFilter(m_Settings->SaveXmlExt());
if (m_FileDialog->exec() == QDialog::Accepted)
filename = m_FileDialog->selectedFiles().value(0);
return filename;
}
/// <summary>
/// Setup and show the save image dialog.
/// This will perform lazy instantiation.
/// </summary>
/// <param name="defaultFilename">The default filename to populate the text box with</param>
/// <returns>The filename selected</returns>
QString Fractorium::SetupSaveImageDialog(QString defaultFilename)
{
//Lazy instantiate since it takes a long time.
if (!m_FileDialog)
{
m_FileDialog = new QFileDialog(this);
m_FileDialog->setViewMode(QFileDialog::List);
}
if (!m_FileDialog)
return "";
QString filename;
m_FileDialog->disconnect(SIGNAL(filterSelected(const QString&)));
connect(m_FileDialog, &QFileDialog::filterSelected, [=](const QString &filter) { m_Settings->SaveImageExt(filter); });
//This must come first because it clears various internal states which allow the file text to be properly set.
//This is most likely a bug in QFileDialog.
m_FileDialog->setAcceptMode(QFileDialog::AcceptSave);
m_FileDialog->selectFile(defaultFilename);
m_FileDialog->setFileMode(QFileDialog::AnyFile);
m_FileDialog->setOption(QFileDialog::ShowDirsOnly, false);
m_FileDialog->setOption(QFileDialog::DontUseNativeDialog, false);
m_FileDialog->setNameFilter("Jpeg (*.jpg);;Png (*.png);;Bmp (*.bmp)");
m_FileDialog->setWindowTitle("Save image");
m_FileDialog->setDirectory(m_Settings->SaveFolder());
m_FileDialog->selectNameFilter(m_Settings->SaveImageExt());
if (m_FileDialog->exec() == QDialog::Accepted)
filename = m_FileDialog->selectedFiles().value(0);
return filename;
}
/// <summary>
/// Setup and show the save folder dialog.
/// This will perform lazy instantiation.
/// </summary>
/// <returns>The folder selected</returns>
QString Fractorium::SetupSaveFolderDialog()
{
//Lazy instantiate since it takes a long time.
if (!m_FolderDialog)
{
m_FolderDialog = new QFileDialog(this);
m_FolderDialog->setViewMode(QFileDialog::List);
}
if (!m_FolderDialog)
return "";
QString filename;
//This must come first because it clears various internal states which allow the file text to be properly set.
//This is most likely a bug in QFileDialog.
m_FolderDialog->setAcceptMode(QFileDialog::AcceptSave);
m_FolderDialog->setFileMode(QFileDialog::Directory);
m_FolderDialog->setOption(QFileDialog::ShowDirsOnly, true);
m_FolderDialog->setOption(QFileDialog::DontUseNativeDialog, true);
m_FolderDialog->selectFile("");
m_FolderDialog->setWindowTitle("Save to folder");
m_FolderDialog->setDirectory(m_Settings->SaveFolder());
if (m_FolderDialog->exec() == QDialog::Accepted)
filename = m_FolderDialog->selectedFiles().value(0);
return filename;
}
/// <summary>
/// This is no longer needed and was used to compensate for a different bug
/// however the code is interesting, so keep it around for possible future use.
/// This was used to correct a rotation bug where matrix rotation comes out in the wrong direction
/// if x1, y1 (a & d) are on the left side of the line from 0,0 to
/// x2, y2 (b, e). In that case, the angle must be flipped. In order
/// to determine which side of the line it's on, create a mat2
/// and find its determinant. Values > 0 are on the left side of the line.
/// </summary>
/// <param name="affine">The affine.</param>
/// <returns></returns>
int Fractorium::FlipDet(Affine2D<float>& affine)
{
float x1 = affine.A();
float y1 = affine.D();
float x2 = affine.B();
float y2 = affine.E();
//Just make the other end of the line be the center of the circle.
glm::mat2 mat( 0 - x1, 0 - y1,//Col 0.
x2 - x1, y2 - y1);//Col 1.
return (glm::determinant(mat) > 0) ? -1 : 1;
}
//template<typename spinType, typename valType>//See note at the end of Fractorium.h
//void Fractorium::SetupSpinner(QTableWidget* table, const QObject* receiver, int& row, int col, spinType*& spinBox, int height, valType min, valType max, valType step, const char* signal, const char* slot, bool incRow, valType val, valType doubleClickZero, valType doubleClickNonZero)
//{
// spinBox = new spinType(table, height, step);
// spinBox->setRange(min, max);
// spinBox->setValue(val);
// table->setCellWidget(row, col, spinBox);
//
// if (string(signal) != "" && string(slot) != "")
// connect(spinBox, signal, receiver, slot, connectionType);
//
// if (doubleClickNonZero != -999 && doubleClickZero != -999)
// {
// spinBox->DoubleClick(true);
// spinBox->DoubleClickZero((valType)doubleClickZero);
// spinBox->DoubleClickNonZero((valType)doubleClickNonZero);
// }
//
// if (incRow)
// row++;
//}

View File

@ -0,0 +1,445 @@
#pragma once
#include "ui_Fractorium.h"
#include "GLWidget.h"
#include "EmberTreeWidgetItem.h"
#include "VariationTreeWidgetItem.h"
#include "StealthComboBox.h"
#include "TableWidget.h"
#include "FinalRenderDialog.h"
#include "OptionsDialog.h"
#include "AboutDialog.h"
/// <summary>
/// Fractorium class.
/// </summary>
/// <summary>
/// Fractorium is the main window for the interactive renderer. The main viewable area
/// is a derivation of QGLWidget named GLWidget. The design uses the concept of a controller
/// to allow for both polymorphism and templating to coexist. Most processing functionality
/// is contained within the controller, and the GUI events just call the appropriate controller
/// member functions.
/// The rendering takes place on a timer with
/// a period of 0 which means it will trigger an event whenever the event input queue is idle.
/// As it's rendering, if the user changes anything on the GUI, a re-render will trigger. Since
/// certain parameters don't require a full render, the minimum necessary processing will be ran.
/// When the user changes something on the GUI, the required processing action is added to a vector.
/// Upon the next execution of the idle timer function, the most significant action will be extracted
/// and applied to the renderer. The vector is then cleared.
/// On the left side of the window is a dock widget which contains all controls needed for
/// manipulating embers.
/// Qt takes very long to create file dialog windows, so they are kept as members and initialized
/// upon first use with lazy instantiation and then kept around for the remainder of the program.
/// Additional dialogs are for the about box, options, and final rendering out to a file.
/// While all class member variables and functions are declared in this .h file, the implementation
/// for them is far too lengthy to put in a single .cpp file. So general functionality is placed in
/// Fractorium.cpp and the other functional areas are each broken out into their own files.
/// The order of the functions in each .cpp file should roughly match the order they appear in the .h file.
/// Future todo list:
/// Add all of the plugins/variations that work with Apophysis and are open source.
/// Allow specifying variations to include/exclude from random generation.
/// Allow the option to specify a different palette file rather than the default flam3-palettes.xml.
/// Implement more rendering types.
/// Add support for animation previewing.
/// Add support for full animation editing and rendering.
/// Possibly add some features from JWildFire.
/// </summary>
class Fractorium : public QMainWindow
{
Q_OBJECT
friend GLWidget;
friend FractoriumOptionsDialog;
friend FractoriumFinalRenderDialog;
friend FractoriumAboutDialog;
friend GLEmberControllerBase;
friend GLEmberController<float>;
friend GLEmberController<double>;
friend FractoriumEmberControllerBase;
friend FractoriumEmberController<float>;
friend FractoriumEmberController<double>;
friend FinalRenderEmberControllerBase;
friend FinalRenderEmberController<float>;
friend FinalRenderEmberController<double>;
public:
Fractorium(QWidget* parent = 0);
~Fractorium();
//Geometry.
void SetCenter(float x, float y);
void SetRotation(double rot, bool stealth);
void SetScale(double scale);
void Resize();
void SetCoordinateStatus(int x, int y, float worldX, float worldY);
//Xforms.
void CurrentXform(unsigned int i);
//Xforms Affine.
bool DrawAllPre();
bool DrawAllPost();
bool LocalPivot();
public slots:
//Dock.
void OnDockTopLevelChanged(bool topLevel);
void dockLocationChanged(Qt::DockWidgetArea area);
//Menu.
void OnActionNewFlock(bool checked);//File.
void OnActionNewEmptyFlameInCurrentFile(bool checked);
void OnActionNewRandomFlameInCurrentFile(bool checked);
void OnActionCopyFlameInCurrentFile(bool checked);
void OnActionOpen(bool checked);
void OnActionSaveCurrentAsXml(bool checked);
void OnActionSaveEntireFileAsXml(bool checked);
void OnActionSaveCurrentScreen(bool checked);
void OnActionSaveCurrentToOpenedFile(bool checked);
void OnActionExit(bool checked);
void OnActionUndo(bool checked);//Edit.
void OnActionRedo(bool checked);
void OnActionCopyXml(bool checked);
void OnActionCopyAllXml(bool checked);
void OnActionPasteXmlAppend(bool checked);
void OnActionPasteXmlOver(bool checked);
void OnActionAddReflectiveSymmetry(bool checked);//Tools.
void OnActionAddRotationalSymmetry(bool checked);
void OnActionAddBothSymmetry(bool checked);
void OnActionClearFlame(bool checked);
void OnActionRenderPreviews(bool checked);
void OnActionStopRenderingPreviews(bool checked);
void OnActionFinalRender(bool checked);
void OnFinalRenderClose(int result);
void OnActionOptions(bool checked);
void OnActionAbout(bool checked);//Help.
//Toolbar.
void OnSaveCurrentAsXmlButtonClicked(bool checked);
void OnSaveEntireFileAsXmlButtonClicked(bool checked);
void OnSaveCurrentToOpenedFileButtonClicked(bool checked);
//Params.
void OnBrightnessChanged(double d);//Color.
void OnGammaChanged(double d);
void OnGammaThresholdChanged(double d);
void OnVibrancyChanged(double d);
void OnHighlightPowerChanged(double d);
void OnBackgroundColorButtonClicked(bool checked);
void OnColorSelected(const QColor& color);
void OnPaletteModeComboCurrentIndexChanged(int index);
void OnWidthChanged(int d);//Geometry.
void OnHeightChanged(int d);
void OnCenterXChanged(double d);
void OnCenterYChanged(double d);
void OnScaleChanged(double d);
void OnZoomChanged(double d);
void OnRotateChanged(double d);
void OnZPosChanged(double d);
void OnPerspectiveChanged(double d);
void OnPitchChanged(double d);
void OnYawChanged(double d);
void OnDepthBlurChanged(double d);
void OnSpatialFilterWidthChanged(double d);//Filter.
void OnSpatialFilterTypeComboCurrentIndexChanged(const QString& text);
void OnTemporalFilterWidthChanged(double d);
void OnTemporalFilterTypeComboCurrentIndexChanged(const QString& text);
void OnDEFilterMinRadiusWidthChanged(double d);
void OnDEFilterMaxRadiusWidthChanged(double d);
void OnDEFilterCurveWidthChanged(double d);
void OnPassesChanged(int d);//Iteration.
void OnTemporalSamplesChanged(int d);
void OnQualityChanged(double d);
void OnSupersampleChanged(int d);
void OnAffineInterpTypeComboCurrentIndexChanged(int index);
void OnInterpTypeComboCurrentIndexChanged(int index);
//Xforms.
void OnCurrentXformComboChanged(int index);
void OnAddXformButtonClicked(bool checked);
void OnDuplicateXformButtonClicked(bool checked);
void OnClearXformButtonClicked(bool checked);
void OnDeleteXformButtonClicked(bool checked);
void OnAddFinalXformButtonClicked(bool checked);
void OnXformWeightChanged(double d);
void OnEqualWeightButtonClicked(bool checked);
void OnXformNameChanged(int row, int col);
//Xforms Affine.
void OnX1Changed(double d);
void OnX2Changed(double d);
void OnY1Changed(double d);
void OnY2Changed(double d);
void OnO1Changed(double d);
void OnO2Changed(double d);
void OnFlipHorizontalButtonClicked(bool checked);
void OnFlipVerticalButtonClicked(bool checked);
void OnRotate90CButtonClicked(bool checked);
void OnRotate90CcButtonClicked(bool checked);
void OnRotateCButtonClicked(bool checked);
void OnRotateCcButtonClicked(bool checked);
void OnMoveUpButtonClicked(bool checked);
void OnMoveDownButtonClicked(bool checked);
void OnMoveLeftButtonClicked(bool checked);
void OnMoveRightButtonClicked(bool checked);
void OnScaleDownButtonClicked(bool checked);
void OnScaleUpButtonClicked(bool checked);
void OnResetAffineButtonClicked(bool checked);
void OnAffineGroupBoxToggled(bool on);
void OnAffineDrawAllCurrentRadioButtonToggled(bool checked);
//Xforms Color.
void OnXformColorIndexChanged(double d);
void OnXformColorIndexChanged(double d, bool updateRender);
void OnXformScrollColorIndexChanged(int d);
void OnXformColorSpeedChanged(double d);
void OnXformOpacityChanged(double d);
void OnXformDirectColorChanged(double d);
void OnSoloXformCheckBoxStateChanged(int state);
void OnXformRefPaletteResized(int logicalIndex, int oldSize, int newSize);
//Xforms Variations.
void OnVariationSpinBoxValueChanged(double d);
void OnTreeHeaderSectionClicked(int);
void OnVariationsFilterLineEditTextChanged(const QString& text);
void OnVariationsFilterClearButtonClicked(bool checked);
//Xforms Xaos.
void OnXaosChanged(double d);
void OnXaosFromToToggled(bool checked);
void OnClearXaosButtonClicked(bool checked);
//Palette.
void OnPaletteAdjust(int d);
void OnPaletteCellClicked(int row, int col);
void OnPaletteCellDoubleClicked(int row, int col);
//Library.
void OnEmberTreeItemChanged(QTreeWidgetItem* item, int col);
void OnEmberTreeItemDoubleClicked(QTreeWidgetItem* item, int col);
//Rendering/progress.
void StartRenderTimer();
void IdleTimer();
bool ControllersOk();
//Can't have a template function be a slot.
void SetLibraryTreeItemData(EmberTreeWidgetItemBase* item, vector<unsigned char>& v, unsigned int width, unsigned int height);
public:
//template<typename spinType, typename valType>//See below.
//static void SetupSpinner(QTableWidget* table, const QObject* receiver, int& row, int col, spinType*& spinBox, int height, valType min, valType max, valType step, const char* signal, const char* slot, bool incRow = true, valType val = 0, valType doubleClickZero = -999, valType doubleClickNonZero = -999);
static void SetupAffineSpinner(QTableWidget* table, const QObject* receiver, int row, int col, DoubleSpinBox*& spinBox, int height, double min, double max, double step, double prec, const char* signal, const char* slot);
static void SetupCombo(QTableWidget* table, const QObject* receiver, int& row, int col, StealthComboBox*& comboBox, vector<string>& vals, const char* signal, const char* slot, Qt::ConnectionType connectionType = Qt::QueuedConnection);
static void SetFixedTableHeader(QHeaderView* header, QHeaderView::ResizeMode mode = QHeaderView::Fixed);
static int FlipDet(Affine2D<float>& affine);
protected:
virtual void resizeEvent(QResizeEvent* e);
virtual void closeEvent(QCloseEvent* e);
virtual void dragEnterEvent(QDragEnterEvent* e);
virtual void dragMoveEvent(QDragMoveEvent* e);
virtual void dropEvent(QDropEvent* e);
private:
void InitMenusUI();
void InitToolbarUI();
void InitParamsUI();
void InitXformsUI();
void InitXformsColorUI();
void InitXformsAffineUI();
void InitXformsVariationsUI();
void InitXformsXaosUI();
void InitPaletteUI();
void InitLibraryUI();
//Embers.
bool HaveFinal();
//Params.
//Xforms.
void FillXforms();
//Xforms Color.
//Xforms Affine.
//Xforms Variations.
//Xforms Xaos.
void FillXaosTable();
//Palette.
void ResetPaletteControls();
//Library.
//Info.
void UpdateHistogramBounds();
void ErrorReportToQTextEdit(vector<string>& errors, QTextEdit* textEdit, bool clear = true);
//Rendering/progress.
bool CreateRendererFromOptions();
bool CreateControllerFromOptions();
//Dialogs.
QStringList SetupOpenXmlDialog();
QString SetupSaveXmlDialog(QString defaultFilename);
QString SetupSaveImageDialog(QString defaultFilename);
QString SetupSaveFolderDialog();
QColorDialog* m_ColorDialog;
FractoriumFinalRenderDialog* m_FinalRenderDialog;
FractoriumOptionsDialog* m_OptionsDialog;
FractoriumAboutDialog* m_AboutDialog;
//Params.
DoubleSpinBox* m_BrightnessSpin;//Color.
DoubleSpinBox* m_GammaSpin;
DoubleSpinBox* m_GammaThresholdSpin;
DoubleSpinBox* m_VibrancySpin;
DoubleSpinBox* m_HighlightSpin;
QPushButton* m_BackgroundColorButton;
StealthComboBox* m_PaletteModeCombo;
SpinBox* m_WidthSpin;//Geometry.
SpinBox* m_HeightSpin;
DoubleSpinBox* m_CenterXSpin;
DoubleSpinBox* m_CenterYSpin;
DoubleSpinBox* m_ScaleSpin;
DoubleSpinBox* m_ZoomSpin;
DoubleSpinBox* m_RotateSpin;
DoubleSpinBox* m_ZPosSpin;
DoubleSpinBox* m_PerspectiveSpin;
DoubleSpinBox* m_PitchSpin;
DoubleSpinBox* m_YawSpin;
DoubleSpinBox* m_DepthBlurSpin;
DoubleSpinBox* m_SpatialFilterWidthSpin;//Filter.
StealthComboBox* m_SpatialFilterTypeCombo;
DoubleSpinBox* m_TemporalFilterWidthSpin;
StealthComboBox* m_TemporalFilterTypeCombo;
DoubleSpinBox* m_DEFilterMinRadiusSpin;
DoubleSpinBox* m_DEFilterMaxRadiusSpin;
DoubleSpinBox* m_DECurveSpin;
SpinBox* m_PassesSpin;//Iteration.
SpinBox* m_TemporalSamplesSpin;
DoubleSpinBox* m_QualitySpin;
SpinBox* m_SupersampleSpin;
StealthComboBox* m_AffineInterpTypeCombo;
StealthComboBox* m_InterpTypeCombo;
//Xforms.
DoubleSpinBox* m_XformWeightSpin;
SpinnerButtonWidget* m_XformWeightSpinnerButtonWidget;
//Xforms Color.
QTableWidgetItem* m_XformColorValueItem;
QTableWidgetItem* m_PaletteRefItem;
DoubleSpinBox* m_XformColorIndexSpin;
DoubleSpinBox* m_XformColorSpeedSpin;
DoubleSpinBox* m_XformOpacitySpin;
DoubleSpinBox* m_XformDirectColorSpin;
//Xforms Affine.
DoubleSpinBox* m_PreX1Spin;//Pre.
DoubleSpinBox* m_PreX2Spin;
DoubleSpinBox* m_PreY1Spin;
DoubleSpinBox* m_PreY2Spin;
DoubleSpinBox* m_PreO1Spin;
DoubleSpinBox* m_PreO2Spin;
DoubleSpinBox* m_PostX1Spin;//Post.
DoubleSpinBox* m_PostX2Spin;
DoubleSpinBox* m_PostY1Spin;
DoubleSpinBox* m_PostY2Spin;
DoubleSpinBox* m_PostO1Spin;
DoubleSpinBox* m_PostO2Spin;
//Palette.
SpinBox* m_PaletteHueSpin;
SpinBox* m_PaletteSaturationSpin;
SpinBox* m_PaletteBrightnessSpin;
SpinBox* m_PaletteContrastSpin;
SpinBox* m_PaletteBlurSpin;
SpinBox* m_PaletteFrequencySpin;
//Files.
QFileDialog* m_FileDialog;
QFileDialog* m_FolderDialog;
QString m_LastSaveAll;
QString m_LastSaveCurrent;
//QMenu* m_FileTreeMenu;
QProgressBar* m_ProgressBar;
QLabel* m_RenderStatusLabel;
QLabel* m_CoordinateStatusLabel;
FractoriumSettings* m_Settings;
char m_ULString[32];
char m_URString[32];
char m_LRString[32];
char m_LLString[32];
char m_WString[16];
char m_HString[16];
char m_DEString[16];
char m_CoordinateString[128];
int m_FontSize;
int m_VarSortMode;
int m_PreviousPaletteRow;
OpenCLWrapper m_Wrapper;
auto_ptr<FractoriumEmberControllerBase> m_Controller;
Ui::FractoriumClass ui;
};
/// <summary>
/// Setup a spinner to be placed in a table cell.
/// Due to a serious compiler bug in MSVC, this must be declared as an outside function instead of a static member of Fractorium.
/// The reason is that the default arguments of type valType will not be interpreted correctly by the compiler.
/// If the bug is ever fixed, put it back as a static member function.
/// </summary>
/// <param name="table">The table the spinner belongs to</param>
/// <param name="receiver">The receiver object</param>
/// <param name="row">The row in the table where this spinner resides</param>
/// <param name="col">The col in the table where this spinner resides</param>
/// <param name="spinBox">Double pointer to spin box which will hold the spinner upon exit</param>
/// <param name="height">The height of the spinner</param>
/// <param name="min">The minimum value of the spinner</param>
/// <param name="max">The maximum value of the spinner</param>
/// <param name="step">The step of the spinner</param>
/// <param name="signal">The signal the spinner emits</param>
/// <param name="slot">The slot to receive the signal</param>
/// <param name="incRow">Whether to increment the row value</param>
/// <param name="val">The default value for the spinner</param>
/// <param name="doubleClickZero">When the spinner has a value of zero and is double clicked, assign this value</param>
/// <param name="doubleClickNonZero">When the spinner has a value of non-zero and is double clicked, assign this value</param>
template<typename spinType, typename valType>
static void SetupSpinner(QTableWidget* table, const QObject* receiver, int& row, int col, spinType*& spinBox, int height, valType min, valType max, valType step, const char* signal, const char* slot, bool incRow = true, valType val = 0, valType doubleClickZero = -999, valType doubleClickNonZero = -999)
{
spinBox = new spinType(table, height, step);
spinBox->setRange(min, max);
spinBox->setValue(val);
if (col >= 0)
table->setCellWidget(row, col, spinBox);
if (string(signal) != "" && string(slot) != "")
receiver->connect(spinBox, signal, receiver, slot, Qt::QueuedConnection);
if (doubleClickNonZero != -999 && doubleClickZero != -999)
{
spinBox->DoubleClick(true);
spinBox->DoubleClickZero((valType)doubleClickZero);
spinBox->DoubleClickNonZero((valType)doubleClickNonZero);
}
if (incRow)
row++;
}
//template void Fractorium::SetupSpinner<SpinBox, int> (QTableWidget* table, const QObject* receiver, int& row, int col, SpinBox*& spinBox, int height, int min, int max, int step, const char* signal, const char* slot, bool incRow, int val, int doubleClickZero, int doubleClickNonZero);
//template void Fractorium::SetupSpinner<DoubleSpinBox, double>(QTableWidget* table, const QObject* receiver, int& row, int col, DoubleSpinBox*& spinBox, int height, double min, double max, double step, const char* signal, const char* slot, bool incRow, double val, double doubleClickZero, double doubleClickNonZero);

View File

@ -0,0 +1,44 @@
<RCC>
<qresource prefix="/Fractorium">
<file>Icons/arrow-undo.png</file>
<file>Icons/arrow-redo.png</file>
<file>Icons/stop.png</file>
<file>Icons/application_side_boxes.png</file>
<file>Icons/page_copy.png</file>
<file>Icons/page_paste.png</file>
<file>Icons/window-close.png</file>
<file>Icons/068123-3d-transparent-glass-icon-alphanumeric-question-mark3.png</file>
<file>Icons/layer--plus.png</file>
<file>Icons/layers.png</file>
<file>Icons/layers-stack.png</file>
<file>Icons/monitor.png</file>
<file>Icons/016938-3d-transparent-glass-icon-symbols-shapes-shape-square-clear-16.png</file>
<file>Icons/document-hf-insert.png</file>
<file>Icons/010425-3d-transparent-glass-icon-animals-spiderweb2.png</file>
<file>Icons/database-medium.png</file>
<file>Icons/databases.png</file>
<file>Icons/drive-harddisk-5.png</file>
<file>Icons/folder-visiting-4.png</file>
<file>Icons/display-brightness-off.png</file>
<file>Icons/cog.png</file>
<file>Icons/proxy.png</file>
<file>Icons/shape_flip_horizontal.png</file>
<file>Icons/shape_flip_vertical.png</file>
<file>Icons/arrow_down.png</file>
<file>Icons/arrow_in.png</file>
<file>Icons/arrow_left.png</file>
<file>Icons/arrow_out.png</file>
<file>Icons/arrow_right.png</file>
<file>Icons/arrow_rotate_anticlockwise.png</file>
<file>Icons/arrow_rotate_clockwise.png</file>
<file>Icons/arrow_turn_left.png</file>
<file>Icons/arrow_turn_right.png</file>
<file>Icons/arrow_up.png</file>
<file>Icons/configure.png</file>
<file>Icons/infomation.png</file>
<file>Icons/del.png</file>
<file>Icons/add.png</file>
<file>Icons/eraser.png</file>
<file>Icons/editraise.png</file>
</qresource>
</RCC>

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,244 @@
#include "FractoriumPch.h"
#include "FractoriumEmberController.h"
#include "Fractorium.h"
#include "GLEmberController.h"
/// <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_Fractorium = fractorium;
m_RenderTimer = NULL;
m_RenderRestartTimer = NULL;
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 = NULL;
}
if (m_RenderRestartTimer)
{
m_RenderRestartTimer->stop();
delete m_RenderRestartTimer;
m_RenderRestartTimer = NULL;
}
}
/// <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 = auto_ptr<SheepTools<T, T>>(new SheepTools<T, T>("flam3-palettes.xml", new EmberNs::Renderer<T, T>()));
m_GLController = auto_ptr<GLEmberController<T>>(new GLEmberController<T>(fractorium, fractorium->ui.GLDisplay, this));
m_PreviewRenderer = auto_ptr<EmberNs::Renderer<T, T>>(new EmberNs::Renderer<T, T>());
SetupVariationTree();
InitPaletteTable("flam3-palettes.xml");
BackgroundChanged(QColor(0, 0, 0));//Default to black.
ClearUndo();
m_PreviewRenderer->Callback(NULL);
m_PreviewRenderer->NumChannels(4);
m_PreviewRenderer->ReclaimOnResize(true);
m_PreviewRenderer->SetEmber(m_Ember);//Give it an initial ember, will be updated many times later.
//m_PreviewRenderer->ThreadCount(1);//For debugging.
m_PreviewRenderFunc = [&](unsigned int start, unsigned int end)
{
while(m_PreviewRun || m_PreviewRunning)
{
}
m_PreviewRun = true;
m_PreviewRunning = true;
m_PreviewRenderer->ThreadCount(max(1, 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.m_Embers.size(); i++)
{
Ember<T> ember = m_EmberFile.m_Embers[i];
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*, (EmberTreeWidgetItemBase*)treeItem),
Q_ARG(vector<unsigned char>&, m_PreviewFinalImage),
Q_ARG(unsigned int, PREVIEW_SIZE),
Q_ARG(unsigned int, 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) { ember = m_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) { emberFile = m_EmberFile; }
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) { ember = m_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) { emberFile = m_EmberFile; }
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; }
/// <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.m_Embers.size())
{
if (QTreeWidgetItem* top = m_Fractorium->ui.LibraryTree->topLevelItem(0))
{
for (unsigned int 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: false.</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 current xform, 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: false.</param>
/// <param name="action">The action to add to the rendering queue. Default: FULL_RENDER.</param>
template <typename T>
void FractoriumEmberController<T>::UpdateCurrentXform(std::function<void (Xform<T>*)> func, bool updateRender, eProcessAction action)
{
if (Xform<T>* xform = CurrentXform())
{
func(xform);
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 = "";
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();
}
m_Fractorium->FillXforms();//Must do this first because the palette setup in FillParamTablesAndPalette() uses the xforms combo.
FillParamTablesAndPalette();
}

View File

@ -0,0 +1,446 @@
#pragma once
#include "EmberFile.h"
#include "DoubleSpinBox.h"
#include "GLEmberController.h"
/// <summary>
/// FractoriumEmberControllerBase and FractoriumEmberController<T> classes.
/// </summary>
/// <summary>
/// An enum representing the type of edit being done.
/// </summary>
enum eEditUndoState : unsigned int { REGULAR_EDIT = 0, UNDO_REDO = 1, EDIT_UNDO = 2 };
/// <summary>
/// FractoriumEmberController and Fractorium need each other, but each can't include the other.
/// So Fractorium includes this file, and Fractorium is declared as a forward declaration here.
/// </summary>
class Fractorium;
#define PREVIEW_SIZE 256
#define UNDO_SIZE 128
/// <summary>
/// FractoriumEmberControllerBase serves as a non-templated base class with virtual
/// functions which will be overridden in a derived class that takes a template parameter.
/// The controller serves as a way to access both the Fractorium GUI and the underlying ember
/// objects through an interface that doesn't require template argument, but does allow
/// templated objects to be used underneath.
/// Note that there are a few functions which access a templated object, so for those both
/// versions for float and double must be provided, then overridden in the templated derived
/// class. It's definitely a design flaw, but C++ doesn't offer any alternative since
/// templated virtual functions are not supported.
/// The functions not implemented in this file can be found in the GUI files which use them.
/// </summary>
class FractoriumEmberControllerBase : public RenderCallback
{
public:
FractoriumEmberControllerBase(Fractorium* fractorium);
virtual ~FractoriumEmberControllerBase();
//Embers.
virtual void SetEmber(const Ember<float>& ember, bool verbatim = false) { }
virtual void CopyEmber(Ember<float>& ember) { }
virtual void SetEmberFile(const EmberFile<float>& emberFile) { }
virtual void CopyEmberFile(EmberFile<float>& emberFile) { }
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 = false) { }
virtual void CopyEmber(Ember<double>& ember) { }
virtual void SetEmberFile(const EmberFile<double>& emberFile) { }
virtual void CopyEmberFile(EmberFile<double>& emberFile) { }
virtual void SetTempPalette(const Palette<double>& palette) { }
virtual void CopyTempPalette(Palette<double>& palette) { }
#endif
virtual void SetEmber(size_t index) { }
virtual void Clear() { }
virtual void AddXform() { }
virtual void DuplicateXform() { }
virtual void ClearCurrentXform() { }
virtual void DeleteCurrentXform() { }
virtual void AddFinalXform() { }
virtual bool UseFinalXform() { return false; }
virtual size_t XformCount() { return 0; }
virtual size_t TotalXformCount() { return 0; }
virtual string Name() { return ""; }
virtual void Name(string s) { }
virtual unsigned int FinalRasW() { return 0; }
virtual void FinalRasW(unsigned int w) { }
virtual unsigned int FinalRasH() { return 0; }
virtual void FinalRasH(unsigned int h) { }
virtual void AddSymmetry(int sym, QTIsaac<ISAAC_SIZE, ISAAC_INT>& rand) { }
virtual void CalcNormalizedWeights() { }
//Menu.
virtual void NewFlock(unsigned int count) { }//File.
virtual void NewEmptyFlameInCurrentFile() { }
virtual void NewRandomFlameInCurrentFile() { }
virtual void CopyFlameInCurrentFile() { }
virtual void OpenAndPrepFiles(QStringList filenames, bool append) { }
virtual void SaveCurrentAsXml() { }
virtual void SaveEntireFileAsXml() { }
virtual void SaveCurrentToOpenedFile() { }
virtual void Undo() { }//Edit.
virtual void Redo() { }
virtual void CopyXml() { }
virtual void CopyAllXml() { }
virtual void PasteXmlAppend() { }
virtual void PasteXmlOver() { }
virtual void AddReflectiveSymmetry() { }//Tools.
virtual void AddRotationalSymmetry() { }
virtual void AddBothSymmetry() { }
virtual void ClearFlame() { }
//Toolbar.
//Params.
virtual void SetCenter(double x, double y) { }
virtual void FillParamTablesAndPalette() { }
virtual void BrightnessChanged(double d) { }
virtual void GammaChanged(double d) { }
virtual void GammaThresholdChanged(double d) { }
virtual void VibrancyChanged(double d) { }
virtual void HighlightPowerChanged(double d) { }
virtual void PaletteModeChanged(unsigned int i) { }
virtual void CenterXChanged(double d) { }
virtual void CenterYChanged(double d) { }
virtual void ScaleChanged(double d) { }
virtual void ZoomChanged(double d) { }
virtual void RotateChanged(double d) { }
virtual void ZPosChanged(double d) { }
virtual void PerspectiveChanged(double d) { }
virtual void PitchChanged(double d) { }
virtual void YawChanged(double d) { }
virtual void DepthBlurChanged(double d) { }
virtual void SpatialFilterWidthChanged(double d) { }
virtual void SpatialFilterTypeChanged(const QString& text) { }
virtual void TemporalFilterWidthChanged(double d) { }
virtual void TemporalFilterTypeChanged(const QString& text) { }
virtual void DEFilterMinRadiusWidthChanged(double d) { }
virtual void DEFilterMaxRadiusWidthChanged(double d) { }
virtual void DEFilterCurveWidthChanged(double d) { }
virtual void PassesChanged(int i) { }
virtual void TemporalSamplesChanged(int d) { }
virtual void QualityChanged(double d) { }
virtual void SupersampleChanged(int d) { }
virtual void AffineInterpTypeChanged(int i) { }
virtual void InterpTypeChanged(int i) { }
virtual void BackgroundChanged(const QColor& color) { }
//Xforms.
virtual void CurrentXformComboChanged(int index) { }
virtual void XformWeightChanged(double d) { }
virtual void EqualizeWeights() { }
virtual void XformNameChanged(int row, int col) { }
//Xforms Affine.
virtual void AffineSetHelper(double d, int index, bool pre) { }
virtual void FlipCurrentXform(bool horizontal, bool vertical, bool pre) { }
virtual void RotateCurrentXformByAngle(double angle, bool pre) { }
virtual void MoveCurrentXform(double x, double y, bool pre) { }
virtual void ScaleCurrentXform(double scale, bool pre) { }
virtual void ResetCurrentXformAffine(bool pre) { }
//Xforms Color.
virtual void XformColorIndexChanged(double d, bool updateRender) { }
virtual void XformScrollColorIndexChanged(int d) { }
virtual void XformColorSpeedChanged(double d) { }
virtual void XformOpacityChanged(double d) { }
virtual void XformDirectColorChanged(double d) { }
void SetPaletteRefTable(QPixmap* pixmap);
//Xforms Variations.
virtual void SetupVariationTree() { }
virtual void ClearVariationsTree() { }
virtual void VariationSpinBoxValueChanged(double d) { }
//Xforms Xaos.
virtual void FillXaosWithCurrentXform() { }
virtual QString MakeXaosNameString(unsigned int i) { return ""; }
virtual void XaosChanged(DoubleSpinBox* sender) { }
virtual void ClearXaos() { }
//Palette.
virtual bool InitPaletteTable(string s) { return false; }
virtual void ApplyPaletteToEmber() { }
virtual void PaletteAdjust() { }
virtual QRgb GetQRgbFromPaletteIndex(unsigned int i) { return QRgb(); }
virtual void PaletteCellClicked(int row, int col) { }
//Library.
virtual void SyncNames() { }
virtual void FillLibraryTree(int selectIndex = -1) { }
virtual void UpdateLibraryTree() { }
virtual void EmberTreeItemChanged(QTreeWidgetItem* item, int col) { }
virtual void EmberTreeItemDoubleClicked(QTreeWidgetItem* item, int col) { }
virtual void RenderPreviews(unsigned int start = UINT_MAX, unsigned int end = UINT_MAX) { }
virtual void StopPreviewRender() { }
//Info.
//Rendering/progress.
virtual bool Render() { return false; }
virtual bool CreateRenderer(eRendererType renderType, unsigned int platform, unsigned int device, bool shared = true) { return false; }
virtual unsigned int SizeOfT() { return 0; }
virtual void ClearUndo() { }
virtual GLEmberControllerBase* GLController() { return NULL; }
bool RenderTimerRunning();
void StartRenderTimer();
void DelayedStartRenderTimer();
void StopRenderTimer(bool wait);
void Shutdown();
void UpdateRender(eProcessAction action = FULL_RENDER);
void DeleteRenderer();
void SaveCurrentRender(QString filename);
RendererBase* Renderer() { return m_Renderer.get(); }
vector<unsigned char>* FinalImage() { return &m_FinalImage; }
vector<unsigned char>* PreviewFinalImage() { return &m_PreviewFinalImage; }
protected:
//Rendering/progress.
void AddProcessAction(eProcessAction action);
eProcessAction CondenseAndClearProcessActions();
eProcessState ProcessState() { return m_Renderer.get() ? m_Renderer->ProcessState() : NONE; }
//Non-templated members.
bool m_Rendering;
bool m_Shared;
bool m_LastEditWasUndoRedo;
unsigned int m_Platform;
unsigned int m_Device;
unsigned int m_SubBatchCount;
unsigned int m_FailedRenders;
unsigned int m_UndoIndex;
eRendererType m_RenderType;
eEditUndoState m_EditState;
GLuint m_OutputTexID;
Timing m_RenderElapsedTimer;
QImage m_FinalPaletteImage;
QString m_LastSaveAll;
QString m_LastSaveCurrent;
CriticalSection m_Cs;
vector<unsigned char> m_FinalImage;
vector<unsigned char> m_PreviewFinalImage;
vector<eProcessAction> m_ProcessActions;
auto_ptr<EmberNs::RendererBase> m_Renderer;
QTIsaac<ISAAC_SIZE, ISAAC_INT> m_Rand;
Fractorium* m_Fractorium;
QTimer* m_RenderTimer;
QTimer* m_RenderRestartTimer;
};
/// <summary>
/// Templated derived class which implements all interaction functionality between the embers
/// of a specific template type and the GUI.
/// Switching between template arguments requires complete re-creation of the controller and the
/// underlying renderer. Switching between CPU and OpenCL only requires re-creation of the renderer.
/// </summary>
template<typename T>
class FractoriumEmberController : public FractoriumEmberControllerBase
{
public:
FractoriumEmberController(Fractorium* fractorium);
virtual ~FractoriumEmberController();
//Embers.
virtual void SetEmber(const Ember<float>& ember, bool verbatim = false);
virtual void CopyEmber(Ember<float>& ember);
virtual void SetEmberFile(const EmberFile<float>& emberFile);
virtual void CopyEmberFile(EmberFile<float>& emberFile);
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 = false);
virtual void CopyEmber(Ember<double>& ember);
virtual void SetEmberFile(const EmberFile<double>& emberFile);
virtual void CopyEmberFile(EmberFile<double>& emberFile);
virtual void SetTempPalette(const Palette<double>& palette);
virtual void CopyTempPalette(Palette<double>& palette);
#endif
virtual void SetEmber(size_t index);
virtual void Clear() { }
virtual void AddXform();
virtual void DuplicateXform();
virtual void ClearCurrentXform();
virtual void DeleteCurrentXform();
virtual void AddFinalXform();
virtual bool UseFinalXform() { return m_Ember.UseFinalXform(); }
//virtual bool IsFinal(unsigned int i) { return false; }
virtual size_t XformCount() { return m_Ember.XformCount(); }
virtual size_t TotalXformCount() { return m_Ember.TotalXformCount(); }
virtual string Name() { return m_Ember.m_Name; }
virtual void Name(string s) { m_Ember.m_Name = s; }
virtual unsigned int FinalRasW() { return m_Ember.m_FinalRasW; }
virtual void FinalRasW(unsigned int w) { m_Ember.m_FinalRasW = w; }
virtual unsigned int FinalRasH() { return m_Ember.m_FinalRasH; }
virtual void FinalRasH(unsigned int h) { m_Ember.m_FinalRasH = h; }
virtual void AddSymmetry(int sym, QTIsaac<ISAAC_SIZE, ISAAC_INT>& rand) { m_Ember.AddSymmetry(sym, rand); }
virtual void CalcNormalizedWeights() { m_Ember.CalcNormalizedWeights(m_NormalizedWeights); }
Ember<T>* CurrentEmber();
//Menu.
virtual void NewFlock(unsigned int count);
virtual void NewEmptyFlameInCurrentFile();
virtual void NewRandomFlameInCurrentFile();
virtual void CopyFlameInCurrentFile();
virtual void OpenAndPrepFiles(QStringList filenames, bool append);
virtual void SaveCurrentAsXml();
virtual void SaveEntireFileAsXml();
virtual void SaveCurrentToOpenedFile();
virtual void Undo();
virtual void Redo();
virtual void CopyXml();
virtual void CopyAllXml();
virtual void PasteXmlAppend();
virtual void PasteXmlOver();
virtual void AddReflectiveSymmetry();
virtual void AddRotationalSymmetry();
virtual void AddBothSymmetry();
virtual void ClearFlame();
//Toolbar.
//Params.
virtual void SetCenter(double x, double y);
virtual void FillParamTablesAndPalette();
virtual void BrightnessChanged(double d);
virtual void GammaChanged(double d);
virtual void GammaThresholdChanged(double d);
virtual void VibrancyChanged(double d);
virtual void HighlightPowerChanged(double d);
virtual void PaletteModeChanged(unsigned int i);
virtual void CenterXChanged(double d);
virtual void CenterYChanged(double d);
virtual void ScaleChanged(double d);
virtual void ZoomChanged(double d);
virtual void RotateChanged(double d);
virtual void ZPosChanged(double d);
virtual void PerspectiveChanged(double d);
virtual void PitchChanged(double d);
virtual void YawChanged(double d);
virtual void DepthBlurChanged(double d);
virtual void SpatialFilterWidthChanged(double d);
virtual void SpatialFilterTypeChanged(const QString& text);
virtual void TemporalFilterWidthChanged(double d);
virtual void TemporalFilterTypeChanged(const QString& text);
virtual void DEFilterMinRadiusWidthChanged(double d);
virtual void DEFilterMaxRadiusWidthChanged(double d);
virtual void DEFilterCurveWidthChanged(double d);
virtual void PassesChanged(int d);
virtual void TemporalSamplesChanged(int d);
virtual void QualityChanged(double d);
virtual void SupersampleChanged(int d);
virtual void AffineInterpTypeChanged(int index);
virtual void InterpTypeChanged(int index);
virtual void BackgroundChanged(const QColor& col);
//Xforms.
virtual void CurrentXformComboChanged(int index);
virtual void XformWeightChanged(double d);
virtual void EqualizeWeights();
virtual void XformNameChanged(int row, int col);
void FillWithXform(Xform<T>* xform);
Xform<T>* CurrentXform();
//Xforms Affine.
virtual void AffineSetHelper(double d, int index, bool pre);
virtual void FlipCurrentXform(bool horizontal, bool vertical, bool pre);
virtual void RotateCurrentXformByAngle(double angle, bool pre);
virtual void MoveCurrentXform(double x, double y, bool pre);
virtual void ScaleCurrentXform(double scale, bool pre);
virtual void ResetCurrentXformAffine(bool pre);
void FillAffineWithXform(Xform<T>* xform, bool pre);
//Xforms Color.
virtual void XformColorIndexChanged(double d, bool updateRender);
virtual void XformScrollColorIndexChanged(int d);
virtual void XformColorSpeedChanged(double d);
virtual void XformOpacityChanged(double d);
virtual void XformDirectColorChanged(double d);
void FillColorWithXform(Xform<T>* xform);
//Xforms Variations.
virtual void SetupVariationTree();
virtual void ClearVariationsTree();
virtual void VariationSpinBoxValueChanged(double d);
void FillVariationTreeWithXform(Xform<T>* xform);
//Xforms Xaos.
virtual void FillXaosWithCurrentXform();
virtual QString MakeXaosNameString(unsigned int i);
virtual void XaosChanged(DoubleSpinBox* sender);
virtual void ClearXaos();
//Palette.
virtual bool InitPaletteTable(string s);
virtual void ApplyPaletteToEmber();
virtual void PaletteAdjust();
virtual QRgb GetQRgbFromPaletteIndex(unsigned int i) { return QRgb(); }
virtual void PaletteCellClicked(int row, int col);
//Library.
virtual void SyncNames();
virtual void FillLibraryTree(int selectIndex = -1);
virtual void UpdateLibraryTree();
virtual void EmberTreeItemChanged(QTreeWidgetItem* item, int col);
virtual void EmberTreeItemDoubleClicked(QTreeWidgetItem* item, int col);
virtual void RenderPreviews(unsigned int start = UINT_MAX, unsigned int end = UINT_MAX);
virtual void StopPreviewRender();
//Info.
//Rendering/progress.
virtual bool Render();
virtual bool CreateRenderer(eRendererType renderType, unsigned int platform, unsigned int device, bool shared = true);
virtual unsigned int SizeOfT() { return sizeof(T); }
virtual int ProgressFunc(Ember<T>& ember, void* foo, double fraction, int stage, double etaMs);
virtual void ClearUndo();
virtual GLEmberControllerBase* GLController() { return m_GLController.get(); }
private:
//Embers.
void ApplyXmlSavingTemplate(Ember<T>& ember);
template <typename U> void SetEmberPrivate(const Ember<U>& ember, bool verbatim);
//Params.
void ParamsToEmber(Ember<T>& ember);
//Xforms.
void SetNormalizedWeightText(Xform<T>* xform);
bool IsFinal(Xform<T>* xform);
//Xforms Color.
void SetCurrentXformColorIndex(double d);
//Palette.
void UpdateAdjustedPaletteGUI(Palette<T>& palette);
//Rendering/progress.
void Update(std::function<void (void)> func, bool updateRender = true, eProcessAction action = FULL_RENDER);
void UpdateCurrentXform(std::function<void (Xform<T>*)> func, bool updateRender = true, eProcessAction action = FULL_RENDER);
//Templated members.
bool m_PreviewRun;
bool m_PreviewRunning;
vector<T> m_TempOpacities;
vector<T> m_NormalizedWeights;
Ember<T> m_Ember;
EmberFile<T> m_EmberFile;
deque<Ember<T>> m_UndoList;
Palette<T> m_TempPalette;
PaletteList<T> m_PaletteList;
VariationList<T> m_VariationList;
auto_ptr<SheepTools<T, T>> m_SheepTools;
auto_ptr<GLEmberController<T>> m_GLController;
auto_ptr<EmberNs::Renderer<T, T>> m_PreviewRenderer;
QFuture<void> m_PreviewResult;
std::function<void (unsigned int, unsigned int)> m_PreviewRenderFunc;
};
template class FractoriumEmberController<float>;
#ifdef DO_DOUBLE
template class FractoriumEmberController<double>;
#endif

View File

@ -0,0 +1,58 @@
#include "FractoriumPch.h"
#include "Fractorium.h"
/// <summary>
/// Update the histogram bounds display labels.
/// This shows the user the actual bounds of what's
/// being rendered. Mostly of engineering interest.
/// </summary>
void Fractorium::UpdateHistogramBounds()
{
if (RendererBase* r = m_Controller->Renderer())
{
sprintf_s(m_ULString, 32, "UL: %3.3f, %3.3f", r->LowerLeftX(), r->UpperRightY());//These bounds include gutter padding.
sprintf_s(m_URString, 32, "UR: %3.3f, %3.3f", -r->LowerLeftX(), r->UpperRightY());
sprintf_s(m_LRString, 32, "LR: %3.3f, %3.3f", -r->LowerLeftX(), -r->UpperRightY());
sprintf_s(m_LLString, 32, "LL: %3.3f, %3.3f", r->LowerLeftX(), -r->UpperRightY());
sprintf_s(m_WString, 16, "W: %4d" , r->SuperRasW());
sprintf_s(m_HString, 16, "H: %4d" , r->SuperRasH());
ui.InfoBoundsLabelUL->setText(QString(m_ULString));
ui.InfoBoundsLabelUR->setText(QString(m_URString));
ui.InfoBoundsLabelLR->setText(QString(m_LRString));
ui.InfoBoundsLabelLL->setText(QString(m_LLString));
ui.InfoBoundsLabelW->setText(QString(m_WString));
ui.InfoBoundsLabelH->setText(QString(m_HString));
ui.InfoBoundsTable->item(0, 1)->setText(QString::number(r->GutterWidth()));
if (r->GetDensityFilter())
{
unsigned int deWidth = (r->GetDensityFilter()->FilterWidth() * 2) + 1;
sprintf_s(m_DEString, 16, "%d x %d", deWidth, deWidth);
ui.InfoBoundsTable->item(1, 1)->setText(QString(m_DEString));
}
else
ui.InfoBoundsTable->item(1, 1)->setText("N/A");
}
}
/// <summary>
/// Fill the passed in QTextEdit with the vector of strings.
/// Optionally clear first.
/// Serves as a convenience function because the error reports coming
/// from Ember and EmberCL use vector<string>.
/// Use invokeMethod() in case this is called from a thread.
/// </summary>
/// <param name="errors">The vector of error strings</param>
/// <param name="textEdit">The QTextEdit to fill</param>
/// <param name="clear">Clear if true, else don't.</param>
void Fractorium::ErrorReportToQTextEdit(vector<string>& errors, QTextEdit* textEdit, bool clear)
{
if (clear)
QMetaObject::invokeMethod(textEdit, "clear", Qt::QueuedConnection);
for (size_t i = 0; i < errors.size(); i++)
QMetaObject::invokeMethod(textEdit, "append", Qt::QueuedConnection, Q_ARG(QString, QString::fromStdString(errors[i]) + "\n"));
}

View File

@ -0,0 +1,277 @@
#include "FractoriumPch.h"
#include "Fractorium.h"
/// <summary>
/// Initialize the library tree UI.
/// </summary>
void Fractorium::InitLibraryUI()
{
connect(ui.LibraryTree, SIGNAL(itemChanged(QTreeWidgetItem*, int)), this, SLOT(OnEmberTreeItemChanged(QTreeWidgetItem*, int)), Qt::QueuedConnection);
connect(ui.LibraryTree, SIGNAL(itemDoubleClicked(QTreeWidgetItem*, int)), this, SLOT(OnEmberTreeItemDoubleClicked(QTreeWidgetItem*, int)), Qt::QueuedConnection);
}
/// <summary>
/// Slot function to be called via QMetaObject::invokeMethod() to update preview images in the preview thread.
/// </summary>
/// <param name="item">The item double clicked on</param>
/// <param name="v">The vector holding the RGBA bitmap</param>
/// <param name="width">The width of the bitmap</param>
/// <param name="height">The height of the bitmap</param>
void Fractorium::SetLibraryTreeItemData(EmberTreeWidgetItemBase* item, vector<unsigned char>& v, unsigned int width, unsigned int height)
{
item->SetImage(v, width, height);
}
/// <summary>
/// Set all libary tree entries to the name of the corresponding ember they represent.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::SyncNames()
{
EmberTreeWidgetItem<T>* item;
QTreeWidget* tree = m_Fractorium->ui.LibraryTree;
QTreeWidgetItem* top = tree->topLevelItem(0);
tree->blockSignals(true);
if (top)
{
for (int i = 0; i < top->childCount(); i++)//Iterate through all of the children, which will represent the open embers.
{
if ((item = dynamic_cast<EmberTreeWidgetItem<T>*>(top->child(i))) && i < m_EmberFile.m_Embers.size())//Cast the child widget to the EmberTreeWidgetItem type.
item->setText(0, QString::fromStdString(m_EmberFile.m_Embers[i].m_Name));
}
}
tree->blockSignals(false);
}
/// <summary>
/// Fill the library tree with the names of the embers in the
/// currently opened file.
/// Start preview render thread.
/// </summary>
/// <param name="selectIndex">After the tree is filled, select this index. Pass -1 to omit selecting an index.</param>
template <typename T>
void FractoriumEmberController<T>::FillLibraryTree(int selectIndex)
{
unsigned int i, j, size = 64;
QTreeWidget* tree = m_Fractorium->ui.LibraryTree;
vector<unsigned char> v(size * size * 4);
StopPreviewRender();
tree->clear();
QCoreApplication::flush();
tree->blockSignals(true);
QTreeWidgetItem* fileItem = new QTreeWidgetItem(tree);
QFileInfo info(m_EmberFile.m_Filename);
fileItem->setText(0, info.fileName());
fileItem->setToolTip(0, m_EmberFile.m_Filename);
fileItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable);
for (j = 0; j < m_EmberFile.m_Embers.size(); j++)
{
Ember<T>* ember = &m_EmberFile.m_Embers[j];
EmberTreeWidgetItem<T>* emberItem = new EmberTreeWidgetItem<T>(ember, fileItem);
emberItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable);
if (ember->m_Name.empty())
emberItem->setText(0, QString::number(j));
else
emberItem->setText(0, ember->m_Name.c_str());
emberItem->setToolTip(0, emberItem->text(0));
emberItem->SetImage(v, size, size);
}
tree->blockSignals(false);
if (selectIndex != -1)
if (QTreeWidgetItem* top = tree->topLevelItem(0))
if (EmberTreeWidgetItem<T>* emberItem = dynamic_cast<EmberTreeWidgetItem<T>*>(top->child(selectIndex)))
emberItem->setSelected(true);
QCoreApplication::flush();
RenderPreviews(0, m_EmberFile.m_Embers.size());
tree->expandAll();
}
/// <summary>
/// Update the library tree with the newly added embers (most likely from pasting) and
/// only render previews for the new ones, without clearing the entire tree.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::UpdateLibraryTree()
{
unsigned int i, size = 64;
QTreeWidget* tree = m_Fractorium->ui.LibraryTree;
vector<unsigned char> v(size * size * 4);
if (QTreeWidgetItem* top = tree->topLevelItem(0))
{
int childCount = top->childCount();
tree->blockSignals(true);
for (i = childCount; i < m_EmberFile.m_Embers.size(); i++)
{
Ember<T>* ember = &m_EmberFile.m_Embers[i];
EmberTreeWidgetItem<T>* emberItem = new EmberTreeWidgetItem<T>(ember, top);
emberItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable);
if (ember->m_Name.empty())
emberItem->setText(0, QString::number(i));
else
emberItem->setText(0, ember->m_Name.c_str());
emberItem->setToolTip(0, emberItem->text(0));
emberItem->SetImage(v, size, size);
}
//When adding elements to the vector, they may have been reshuffled which will have invalidated
//the pointers contained in the EmberTreeWidgetItems. So reassign all pointers here.
for (i = 0; i < m_EmberFile.m_Embers.size(); i++)
{
if (EmberTreeWidgetItem<T>* emberItem = dynamic_cast<EmberTreeWidgetItem<T>*>(top->child(i)))
emberItem->SetEmberPointer(&m_EmberFile.m_Embers[i]);
}
tree->blockSignals(false);
RenderPreviews(childCount, m_EmberFile.m_Embers.size());
}
}
/// <summary>
/// Copy the text of the item which was changed to the name of the current ember.
/// Ensure all names are unique in the opened file.
/// This seems to be called spuriously, so we do a check inside to make sure
/// the text was actually changed.
/// We also have to wrap the dynamic_cast call in a try/catch block because this can
/// be called on a widget that has already been deleted.
/// </summary>
/// <param name="item">The libary tree item changed</param>
/// <param name="col">The column clicked, ignored.</param>
template <typename T>
void FractoriumEmberController<T>::EmberTreeItemChanged(QTreeWidgetItem* item, int col)
{
try
{
QTreeWidget* tree = m_Fractorium->ui.LibraryTree;
EmberTreeWidgetItem<T>* emberItem = dynamic_cast<EmberTreeWidgetItem<T>*>(item);
if (emberItem)
{
string oldName = emberItem->GetEmber()->m_Name;//First preserve the previous name.
tree->blockSignals(true);
emberItem->UpdateEmberName();//Copy edit text to the ember's name variable.
m_EmberFile.MakeNamesUnique();//Ensure all names remain unique.
SyncNames();//Copy all ember names to the tree items since some might have changed to be made unique.
string newName = emberItem->GetEmber()->m_Name;//Get the new, final, unique name.
if (m_Ember.m_Name == oldName && oldName != newName)//If the ember edited was the current one, and the name was indeed changed, update the name of the current one.
{
m_Ember.m_Name = newName;
m_LastSaveCurrent = "";//Reset will force the dialog to show on the next save current since the user probably wants a different name.
}
tree->blockSignals(false);
}
else if (QTreeWidgetItem* parentItem = dynamic_cast<QTreeWidgetItem*>(item))
{
QString text = parentItem->text(0);
if (text != "")
{
m_EmberFile.m_Filename = text;
m_LastSaveAll = "";//Reset will force the dialog to show on the next save all since the user probably wants a different name.
}
}
}
catch(std::exception& e)
{
qDebug() << "FractoriumEmberController<T>::EmberTreeItemChanged() : Exception thrown: " << e.what();
}
}
void Fractorium::OnEmberTreeItemChanged(QTreeWidgetItem* item, int col) { m_Controller->EmberTreeItemChanged(item, col); }
/// <summary>
/// Set the current ember to the selected item.
/// Clears the undo state.
/// Resets the rendering process.
/// Called when the user double clicks on a library tree item.
/// </summary>
/// <param name="item">The item double clicked on</param>
/// <param name="col">The column clicked, ignored.</param>
template <typename T>
void FractoriumEmberController<T>::EmberTreeItemDoubleClicked(QTreeWidgetItem* item, int col)
{
if (EmberTreeWidgetItem<T>* emberItem = dynamic_cast<EmberTreeWidgetItem<T>*>(item))
{
ClearUndo();
SetEmber(*emberItem->GetEmber());
}
}
void Fractorium::OnEmberTreeItemDoubleClicked(QTreeWidgetItem* item, int col) { m_Controller->EmberTreeItemDoubleClicked(item, col); }
/// <summary>
/// Stop the preview renderer if it's already running.
/// Clear all of the existing preview images, then start the preview rendering thread.
/// Optionally only render previews for a subset of all open embers.
/// </summary>
/// <param name="start">The 0-based index to start rendering previews for</param>
/// <param name="end">The 0-based index which is one beyond the last ember to render a preview for</param>
template <typename T>
void FractoriumEmberController<T>::RenderPreviews(unsigned int start, unsigned int end)
{
StopPreviewRender();
if (start == UINT_MAX && end == UINT_MAX)
{
QTreeWidget* tree = m_Fractorium->ui.LibraryTree;
tree->blockSignals(true);
if (QTreeWidgetItem* top = tree->topLevelItem(0))
{
int childCount = top->childCount();
vector<unsigned char> emptyPreview(PREVIEW_SIZE * PREVIEW_SIZE * 3);
for (int i = 0; i < childCount; i++)
if (EmberTreeWidgetItem<T>* treeItem = dynamic_cast<EmberTreeWidgetItem<T>*>(top->child(i)))
treeItem->SetImage(emptyPreview, PREVIEW_SIZE, PREVIEW_SIZE);
}
tree->blockSignals(false);
m_PreviewResult = QtConcurrent::run(m_PreviewRenderFunc, 0, m_EmberFile.m_Embers.size());
}
else
m_PreviewResult = QtConcurrent::run(m_PreviewRenderFunc, start, end);
}
/// <summary>
/// Stop the preview rendering thread.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::StopPreviewRender()
{
m_PreviewRun = false;
while (m_PreviewRunning)
QApplication::processEvents();
m_PreviewResult.cancel();
while (m_PreviewResult.isRunning())
QApplication::processEvents();
QCoreApplication::sendPostedEvents(m_Fractorium->ui.LibraryTree);
QCoreApplication::flush();
}

View File

@ -0,0 +1,752 @@
#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.ActionSaveCurrentToOpenedFile, SIGNAL(triggered(bool)), this, SLOT(OnActionSaveCurrentToOpenedFile(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);
//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.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(unsigned int count)
{
Ember<T> ember;
StopPreviewRender();
m_EmberFile.Clear();
m_EmberFile.m_Embers.reserve(count);
m_EmberFile.m_Filename = EmberFile<T>::DefaultFilename();
for (unsigned int i = 0; i < count; i++)
{
m_SheepTools->Random(ember);
ParamsToEmber(ember);
ember.m_OrigFinalRasW = ember.m_FinalRasW;
ember.m_OrigFinalRasH = ember.m_FinalRasH;
ember.m_Name = m_EmberFile.m_Filename.toStdString() + "-" + QString::number(i + 1).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(10);
m_Controller->SetEmber(0);
}
/// <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());
StopPreviewRender();
ParamsToEmber(ember);
ember.m_OrigFinalRasW = ember.m_FinalRasW;
ember.m_OrigFinalRasH = ember.m_FinalRasH;
xform.m_Weight = T(0.25);
xform.m_ColorX = m_Rand.Frand01<T>();
ember.AddXform(xform);
ember.m_Palette = *m_PaletteList.GetPalette(-1);
ember.m_Name = EmberFile<T>::DefaultEmberName(m_EmberFile.m_Embers.size() + 1).toStdString();
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.m_Embers.size() - 1);
}
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;
StopPreviewRender();
m_SheepTools->Random(ember);
ParamsToEmber(ember);
ember.m_OrigFinalRasW = ember.m_FinalRasW;
ember.m_OrigFinalRasH = ember.m_FinalRasH;
ember.m_Name = EmberFile<T>::DefaultEmberName(m_EmberFile.m_Embers.size() + 1).toStdString();
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.m_Embers.size() - 1);
}
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()
{
Ember<T> ember = m_Ember;
StopPreviewRender();
ember.m_Name = EmberFile<T>::DefaultEmberName(m_EmberFile.m_Embers.size() + 1).toStdString();
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.m_Embers.size() - 1);
}
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(QStringList filenames, bool append)
{
if (!filenames.empty())
{
size_t i;
EmberFile<T> emberFile;
XmlToEmber<T> parser;
vector<Ember<T>> embers;
unsigned int previousSize = append ? m_EmberFile.m_Embers.size() : 0;
StopPreviewRender();
emberFile.m_Filename = filenames[0];
foreach (QString filename, filenames)
{
embers.clear();
if (parser.Parse(filename.toStdString().c_str(), embers) && !embers.empty())
{
//Disregard whatever size was specified in the file and fit it to the output window size.
for (i = 0; i < embers.size(); i++)
{
embers[i].SetSizeAndAdjustScale(m_Fractorium->ui.GLDisplay->width(), m_Fractorium->ui.GLDisplay->height(), true, SCALE_WIDTH);
//Also ensure it has a name.
if (embers[i].m_Name == "" || embers[i].m_Name == "No name")
embers[i].m_Name = QString::number(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());
}
else
{
vector<string> errors = parser.ErrorReport();
m_Fractorium->ErrorReportToQTextEdit(errors, m_Fractorium->ui.InfoFileOpeningTextEdit);
QMessageBox::critical(m_Fractorium, "Open Failed", "Could not open file, see info tab for details.");
}
}
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
m_EmberFile = emberFile;
//Resync indices and names.
for (i = 0; i < m_EmberFile.m_Embers.size(); i++)
m_EmberFile.m_Embers[i].m_Index = i;
m_EmberFile.MakeNamesUnique();
if (append)
UpdateLibraryTree();
else
FillLibraryTree(append ? previousSize - 1 : 0);
ClearUndo();
SetEmber(previousSize);
}
}
/// <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;
FractoriumSettings* s = m_Fractorium->m_Settings;
if (QFile::exists(m_LastSaveCurrent))
{
filename = m_LastSaveCurrent;
}
else
{
if (m_EmberFile.m_Embers.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 != "")
{
Ember<T> ember = m_Ember;
EmberToXml<T> writer;
QFileInfo fileInfo(filename);
xmlDocPtr 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, NULL, "edit", s->Nick().toStdString(), s->Url().toStdString(), s->Id().toStdString(), "", 0, 0);
if (tempEdit != NULL)
xmlFreeDoc(tempEdit);
if (writer.Save(filename.toStdString().c_str(), ember, 0, true, false, true))
{
s->SaveFolder(fileInfo.canonicalPath());
m_LastSaveCurrent = filename;
}
else
QMessageBox::critical(m_Fractorium, "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;
FractoriumSettings* s = m_Fractorium->m_Settings;
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 (size_t i = 0; i < emberFile.m_Embers.size(); i++)
ApplyXmlSavingTemplate(emberFile.m_Embers[i]);
if (writer.Save(filename.toStdString().c_str(), emberFile.m_Embers, 0, true, false, true))
{
m_LastSaveAll = filename;
s->SaveFolder(fileInfo.canonicalPath());
}
else
QMessageBox::critical(m_Fractorium, "Save Failed", "Could not save file, try saving to a different folder.");
}
}
void Fractorium::OnActionSaveEntireFileAsXml(bool checked) { m_Controller->SaveEntireFileAsXml(); }
/// <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)
{
QString filename = SetupSaveImageDialog(QString::fromStdString(m_Controller->Name()));
m_Controller->SaveCurrentRender(filename);
}
/// <summary>
/// Save the current ember back to its position in the opened file.
/// This does not save to disk.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::SaveCurrentToOpenedFile()
{
size_t i;
bool fileFound = false;
for (i = 0; i < m_EmberFile.m_Embers.size(); i++)
{
if (m_Ember.m_Name == m_EmberFile.m_Embers[i].m_Name)
{
m_EmberFile.m_Embers[i] = m_Ember;
fileFound = true;
break;
}
}
if (!fileFound)
{
StopPreviewRender();
m_EmberFile.m_Embers.push_back(m_Ember);
m_EmberFile.MakeNamesUnique();
UpdateLibraryTree();
}
else
{
RenderPreviews(i, i + 1);
}
}
void Fractorium::OnActionSaveCurrentToOpenedFile(bool checked) { m_Controller->SaveCurrentToOpenedFile(); }
/// <summary>
/// Exit the application.
/// </summary>
/// <param name="checked">Ignore.</param>
void Fractorium::OnActionExit(bool checked)
{
closeEvent(NULL);
QApplication::exit();
}
/// <summary>
/// Undoes this instance.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::Undo()
{
if (m_UndoList.size() > 1 && m_UndoIndex > 0)
{
int index = m_Ember.GetTotalXformIndex(CurrentXform());
m_LastEditWasUndoRedo = true;
m_UndoIndex = max(0u, m_UndoIndex - 1u);
SetEmber(m_UndoList[m_UndoIndex], true);
m_EditState = UNDO_REDO;
if (index >= 0)
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)
{
int index = m_Ember.GetTotalXformIndex(CurrentXform());
m_LastEditWasUndoRedo = true;
m_UndoIndex = min<unsigned int>(m_UndoIndex + 1, m_UndoList.size() - 1);
SetEmber(m_UndoList[m_UndoIndex], true);
m_EditState = UNDO_REDO;
if (index >= 0)
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()
{
Ember<T> ember = m_Ember;
EmberToXml<T> emberToXml;
FractoriumSettings* settings = m_Fractorium->m_Settings;
ember.m_FinalRasW = settings->XmlWidth();
ember.m_FinalRasH = settings->XmlHeight();
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, 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;
FractoriumSettings* settings = m_Fractorium->m_Settings;
os << "<flames>\n";
for (size_t i = 0; i < m_EmberFile.m_Embers.size(); i++)
{
Ember<T> ember = m_EmberFile.m_Embers[i];
ember.m_FinalRasW = settings->XmlWidth();
ember.m_FinalRasH = settings->XmlHeight();
ember.m_Quality = settings->XmlQuality();
ember.m_Supersample = settings->XmlSupersample();
ember.m_TemporalSamples = settings->XmlTemporalSamples();
os << emberToXml.ToString(ember, "", 0, false, 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()
{
unsigned int i, previousSize = m_EmberFile.m_Embers.size();
string s, errors;
XmlToEmber<T> parser;
vector<Ember<T>> embers;
QTextCodec* codec = QTextCodec::codecForName("UTF-8");
QByteArray b = codec->fromUnicode(QApplication::clipboard()->text());
s.reserve(b.size());
for (i = 0; i < b.size(); i++)
{
if ((unsigned int)b[i] < 128u)
s.push_back(b[i]);
}
b.clear();
StopPreviewRender();
parser.Parse((unsigned char*)s.c_str(), "", embers);
errors = parser.ErrorReportString();
if (errors != "")
{
QMessageBox::critical(m_Fractorium, "Paste Error", QString::fromStdString(errors));
}
if (!embers.empty())
{
for (i = 0; i < embers.size(); i++)
{
//Disregard whatever size was specified in the file and fit it to the output window size.
embers[i].m_Index = m_EmberFile.m_Embers.size();
embers[i].SetSizeAndAdjustScale(m_Fractorium->ui.GLDisplay->width(), m_Fractorium->ui.GLDisplay->height(), true, SCALE_WIDTH);
//Also ensure it has a name.
if (embers[i].m_Name == "" || embers[i].m_Name == "No name")
embers[i].m_Name = QString::number(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);
}
}
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()
{
unsigned int i;
string s, errors;
XmlToEmber<T> parser;
Ember<T> backupEmber = m_EmberFile.m_Embers[0];
QTextCodec* codec = QTextCodec::codecForName("UTF-8");
QByteArray b = codec->fromUnicode(QApplication::clipboard()->text());
s.reserve(b.size());
for (i = 0; i < b.size(); i++)
{
if ((unsigned int)b[i] < 128u)
s.push_back(b[i]);
}
b.clear();
StopPreviewRender();
m_EmberFile.m_Embers.clear();//Will invalidate the pointers contained in the EmberTreeWidgetItems, UpdateLibraryTree() will resync.
parser.Parse((unsigned char*)s.c_str(), "", m_EmberFile.m_Embers);
errors = parser.ErrorReportString();
if (errors != "")
{
QMessageBox::critical(m_Fractorium, "Paste Error", QString::fromStdString(errors));
}
if (!m_EmberFile.m_Embers.empty())
{
for (i = 0; i < m_EmberFile.m_Embers.size(); i++)
{
//Disregard whatever size was specified in the file and fit it to the output window size.
m_EmberFile.m_Embers[i].m_Index = i;
m_EmberFile.m_Embers[i].SetSizeAndAdjustScale(m_Fractorium->ui.GLDisplay->width(), m_Fractorium->ui.GLDisplay->height(), true, SCALE_WIDTH);
//Also ensure it has a name.
if (m_EmberFile.m_Embers[i].m_Name == "" || m_EmberFile.m_Embers[i].m_Name == "No name")
m_EmberFile.m_Embers[i].m_Name = QString::number(m_EmberFile.m_Embers[i].m_Index).toStdString();
}
}
else
{
backupEmber.m_Index = 0;
m_EmberFile.m_Embers.push_back(backupEmber);
}
m_EmberFile.MakeNamesUnique();
FillLibraryTree();
SetEmber(0);
}
void Fractorium::OnActionPasteXmlOver(bool checked) { m_Controller->PasteXmlOver(); }
/// <summary>
/// Add reflective symmetry to the current ember.
/// Resets the rendering process.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::AddReflectiveSymmetry()
{
QComboBox* combo = m_Fractorium->ui.CurrentXformCombo;
m_Ember.AddSymmetry(-1, m_Rand);
m_Fractorium->FillXforms();
combo->setCurrentIndex(combo->count() - (m_Fractorium->HaveFinal() ? 2 : 1));//Set index to the last item before final.
UpdateRender();
}
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()
{
QComboBox* combo = m_Fractorium->ui.CurrentXformCombo;
m_Ember.AddSymmetry(2, m_Rand);
m_Fractorium->FillXforms();
combo->setCurrentIndex(combo->count() - (m_Fractorium->HaveFinal() ? 2 : 1));//Set index to the last item before final.
UpdateRender();
}
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()
{
QComboBox* combo = m_Fractorium->ui.CurrentXformCombo;
m_Ember.AddSymmetry(-2, m_Rand);
m_Fractorium->FillXforms();
combo->setCurrentIndex(combo->count() - (m_Fractorium->HaveFinal() ? 2 : 1));//Set index to the last item before final.
UpdateRender();
}
void Fractorium::OnActionAddBothSymmetry(bool checked) { m_Controller->AddBothSymmetry(); }
/// <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()
{
while (m_Ember.TotalXformCount() > 1)
m_Ember.DeleteTotalXform(m_Ember.TotalXformCount() - 1);
if (m_Ember.XformCount() == 1)
{
if (Xform<T>* xform = m_Ember.GetXform(0))
{
xform->Clear();
xform->ParentEmber(&m_Ember);
}
}
m_Fractorium->FillXforms();
m_Fractorium->ui.CurrentXformCombo->setCurrentIndex(0);
UpdateRender();
}
void Fractorium::OnActionClearFlame(bool checked) { m_Controller->ClearFlame(); }
/// <summary>
/// Re-render all previews.
/// </summary>
void Fractorium::OnActionRenderPreviews(bool checked)
{
m_Controller->RenderPreviews();
}
/// <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->StopPreviewRender(); }
/// <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_RenderStatusLabel->setText("Renderer stopped.");
m_FinalRenderDialog->show();
}
/// <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();//Re-create the renderer and start rendering again.
}
/// <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)
{
if (m_OptionsDialog->exec())
{
//First completely stop what the current rendering process is doing.
m_Controller->Shutdown();
StartRenderTimer();//This will recreate the controller and/or the renderer from the options if necessary, then start the render timer.
m_Settings->sync();
}
}
/// <summary>
/// Show the about dialog.
/// </summary>
/// <param name="checked">Ignored</param>
void Fractorium::OnActionAbout(bool checked)
{
m_AboutDialog->exec();
}

View File

@ -0,0 +1,235 @@
#include "FractoriumPch.h"
#include "Fractorium.h"
#define PALETTE_CELL_HEIGHT 16
/// <summary>
/// Initialize the palette UI.
/// </summary>
void Fractorium::InitPaletteUI()
{
int spinHeight = 20, row = 0;
QTableWidget* paletteTable = ui.PaletteListTable;
QTableWidget* palettePreviewTable = ui.PalettePreviewTable;
connect(paletteTable, SIGNAL(cellClicked(int, int)), this, SLOT(OnPaletteCellClicked(int, int)), Qt::QueuedConnection);
connect(paletteTable, SIGNAL(cellDoubleClicked(int, int)), this, SLOT(OnPaletteCellDoubleClicked(int, int)), Qt::QueuedConnection);
//Palette adjustment table.
QTableWidget* table = ui.PaletteAdjustTable;
table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);//Split width over all columns evenly.
SetupSpinner<SpinBox, int>(table, this, row, 1, m_PaletteHueSpin, spinHeight, -180, 180, 1, SIGNAL(valueChanged(int)), SLOT(OnPaletteAdjust(int)), true, 0, 0, 0);
SetupSpinner<SpinBox, int>(table, this, row, 1, m_PaletteSaturationSpin, spinHeight, -100, 100, 1, SIGNAL(valueChanged(int)), SLOT(OnPaletteAdjust(int)), true, 0, 0, 0);
SetupSpinner<SpinBox, int>(table, this, row, 1, m_PaletteBrightnessSpin, spinHeight, -255, 255, 1, SIGNAL(valueChanged(int)), SLOT(OnPaletteAdjust(int)), true, 0, 0, 0);
row = 0;
SetupSpinner<SpinBox, int>(table, this, row, 3, m_PaletteContrastSpin, spinHeight, -100, 100, 1, SIGNAL(valueChanged(int)), SLOT(OnPaletteAdjust(int)), true, 0, 0, 0);
SetupSpinner<SpinBox, int>(table, this, row, 3, m_PaletteBlurSpin, spinHeight, 0, 127, 1, SIGNAL(valueChanged(int)), SLOT(OnPaletteAdjust(int)), true, 0, 0, 0);
SetupSpinner<SpinBox, int>(table, this, row, 3, m_PaletteFrequencySpin, spinHeight, 1, 10, 1, SIGNAL(valueChanged(int)), SLOT(OnPaletteAdjust(int)), true, 1, 1, 1);
}
/// <summary>
/// Read a palette Xml file and populate the palette table with the contents.
/// This will clear any previous contents.
/// Called upon initialization, or controller type change.
/// </summary>
/// <param name="s">The full path to the palette file</param>
/// <returns>True if successful, else false.</returns>
template <typename T>
bool FractoriumEmberController<T>::InitPaletteTable(string s)
{
QTableWidget* paletteTable = m_Fractorium->ui.PaletteListTable;
QTableWidget* palettePreviewTable = m_Fractorium->ui.PalettePreviewTable;
paletteTable->clear();
if (m_PaletteList.Init(s))//Default to this, but add an option later.//TODO
{
//Preview table.
palettePreviewTable->setRowCount(1);
palettePreviewTable->setColumnWidth(1, 260);//256 plus small margin on each side.
QTableWidgetItem* previewNameCol = new QTableWidgetItem("");
palettePreviewTable->setItem(0, 0, previewNameCol);
QTableWidgetItem* previewPaletteItem = new QTableWidgetItem();
palettePreviewTable->setItem(0, 1, previewPaletteItem);
//Palette list table.
paletteTable->setRowCount(m_PaletteList.Count());
paletteTable->setColumnWidth(1, 260);//256 plus small margin on each side.
paletteTable->horizontalHeader()->setSectionsClickable(false);
//Headers get removed when clearing, so must re-create here.
QTableWidgetItem* nameHeader = new QTableWidgetItem("Name");
QTableWidgetItem* paletteHeader = new QTableWidgetItem("Palette");
nameHeader->setTextAlignment(Qt::AlignLeft|Qt::AlignVCenter);
paletteHeader->setTextAlignment(Qt::AlignLeft|Qt::AlignVCenter);
paletteTable->setHorizontalHeaderItem(0, nameHeader);
paletteTable->setHorizontalHeaderItem(1, paletteHeader);
//Palette list table.
for (size_t i = 0; i < m_PaletteList.Count(); i++)
{
Palette<T>* p = m_PaletteList.GetPalette(i);
vector<unsigned char> v = p->MakeRgbPaletteBlock(PALETTE_CELL_HEIGHT);
QTableWidgetItem* nameCol = new QTableWidgetItem(p->m_Name.c_str());
nameCol->setToolTip(p->m_Name.c_str());
paletteTable->setItem(i, 0, nameCol);
QImage image(v.data(), p->Size(), PALETTE_CELL_HEIGHT, QImage::Format_RGB888);
QTableWidgetItem* paletteItem = new QTableWidgetItem();
paletteItem->setData(Qt::DecorationRole, QPixmap::fromImage(image));
paletteTable->setItem(i, 1, paletteItem);
}
return true;
}
else
{
vector<string> errors = m_PaletteList.ErrorReport();
m_Fractorium->ErrorReportToQTextEdit(errors, m_Fractorium->ui.InfoFileOpeningTextEdit);
QMessageBox::critical(m_Fractorium, "Palette Read Error", "Could not load palette file, all images will be black. See info tab for details.");
}
return false;
}
/// <summary>
/// Apply adjustments to the current ember's palette.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::ApplyPaletteToEmber()
{
int i, rot = 0;
unsigned int blur = m_Fractorium->m_PaletteBlurSpin->value();
unsigned int freq = m_Fractorium->m_PaletteFrequencySpin->value();
double sat = (double)m_Fractorium->m_PaletteSaturationSpin->value() / 100.0;
double brightness = (double)m_Fractorium->m_PaletteBrightnessSpin->value() / 255.0;
double contrast = (double)(m_Fractorium->m_PaletteContrastSpin->value() > 0 ? (m_Fractorium->m_PaletteContrastSpin->value() * 2) : m_Fractorium->m_PaletteContrastSpin->value()) / 100.0;
m_Ember.m_Hue = (double)(m_Fractorium->m_PaletteHueSpin->value()) / 360.0;//This is the only palette adjustment value that gets saved with the ember, so just assign it here.
//Use the temp palette as the base and apply the adjustments gotten from the GUI and save the result in the ember palette.
m_TempPalette.MakeAdjustedPalette(m_Ember.m_Palette, 0, m_Ember.m_Hue, sat, brightness, contrast, blur, freq);
}
/// <summary>
/// Use adjusted palette to update all related GUI controls with new color values.
/// Resets the rendering process.
/// </summary>
/// <param name="palette">The palette to use</param>
/// <param name="paletteName">Name of the palette</param>
template <typename T>
void FractoriumEmberController<T>::UpdateAdjustedPaletteGUI(Palette<T>& palette)
{
Xform<T>* xform = CurrentXform();
QTableWidget* palettePreviewTable = m_Fractorium->ui.PalettePreviewTable;
QTableWidgetItem* previewPaletteItem = palettePreviewTable->item(0, 1);
QString paletteName = QString::fromStdString(m_Ember.m_Palette.m_Name);
if (previewPaletteItem)//This can be null if the palette file was moved or corrupted.
{
//Use the adjusted palette to fill the preview palette control so the user can see the effects of applying the adjustements.
vector<unsigned char> v = palette.MakeRgbPaletteBlock(PALETTE_CELL_HEIGHT);//Make the palette repeat for PALETTE_CELL_HEIGHT rows.
m_FinalPaletteImage = QImage(palette.Size(), PALETTE_CELL_HEIGHT, QImage::Format_RGB888);//Create a QImage out of it.
memcpy(m_FinalPaletteImage.scanLine(0), v.data(), v.size() * sizeof(v[0]));//Memcpy the data in.
QPixmap pixmap = QPixmap::fromImage(m_FinalPaletteImage);//Create a QPixmap out of the QImage.
previewPaletteItem->setData(Qt::DecorationRole, pixmap.scaled(QSize(pixmap.width(), palettePreviewTable->rowHeight(0) + 2), Qt::IgnoreAspectRatio, Qt::SmoothTransformation));//Set the pixmap on the palette tab.
SetPaletteRefTable(&pixmap);//Set the palette ref table on the xforms | color tab.
QTableWidgetItem* previewNameItem = palettePreviewTable->item(0, 0);
previewNameItem->setText(paletteName);//Finally, set the name of the palette to be both the text and the tooltip.
previewNameItem->setToolTip(paletteName);
}
//Update the current xform's color and reset the rendering process.
if (xform)
XformColorIndexChanged(xform->m_ColorX, true);
}
/// <summary>
/// Apply all adjustments to the selected palette, show it
/// and assign it to the current ember.
/// Called when any adjustment spinner is modified.
/// Resets the rendering process.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::PaletteAdjust()
{
UpdateCurrentXform([&] (Xform<T>* xform)
{
ApplyPaletteToEmber();
UpdateAdjustedPaletteGUI(m_Ember.m_Palette);
});
}
void Fractorium::OnPaletteAdjust(int d) { m_Controller->PaletteAdjust(); }
/// <summary>
/// Set the selected palette as the current one,
/// applying any adjustments previously specified.
/// Called when a palette cell is clicked. Unfortunately,
/// this will get called twice on a double click when moving
/// from one palette to another. It happens quickly so it shouldn't
/// be too much of a problem.
/// Resets the rendering process.
/// </summary>
/// <param name="row">The table row clicked</param>
/// <param name="col">The table col clicked</param>
template <typename T>
void FractoriumEmberController<T>::PaletteCellClicked(int row, int col)
{
Palette<T>* palette = m_PaletteList.GetPalette(row);
QTableWidgetItem* nameItem = m_Fractorium->ui.PaletteListTable->item(row, 0);
if (palette)
{
m_TempPalette = *palette;//Deep copy.
ApplyPaletteToEmber();//Copy temp palette to ember palette and apply adjustments.
UpdateAdjustedPaletteGUI(m_Ember.m_Palette);//Show the adjusted palette.
}
}
void Fractorium::OnPaletteCellClicked(int row, int col)
{
if (m_PreviousPaletteRow != row)
{
m_Controller->PaletteCellClicked(row, col);
m_PreviousPaletteRow = row;//Save for comparison on next click.
}
}
/// <summary>
/// Set the selected palette as the current one,
/// resetting any adjustments previously specified.
/// Called when a palette cell is double clicked.
/// Resets the rendering process.
/// </summary>
/// <param name="row">The table row clicked</param>
/// <param name="col">The table col clicked</param>
void Fractorium::OnPaletteCellDoubleClicked(int row, int col)
{
ResetPaletteControls();
m_PreviousPaletteRow = -1;
OnPaletteCellClicked(row, col);
}
/// <summary>
/// Reset the palette controls.
/// Usually in response to a palette cell double click.
/// </summary>
void Fractorium::ResetPaletteControls()
{
m_PaletteHueSpin->SetValueStealth(0);
m_PaletteSaturationSpin->SetValueStealth(0);
m_PaletteBrightnessSpin->SetValueStealth(0);
m_PaletteContrastSpin->SetValueStealth(0);
m_PaletteBlurSpin->SetValueStealth(0);
m_PaletteFrequencySpin->SetValueStealth(1);
}

View File

@ -0,0 +1,627 @@
#include "FractoriumPch.h"
#include "Fractorium.h"
/// <summary>
/// Initialize the parameters UI.
/// </summary>
void Fractorium::InitParamsUI()
{
int row = 0;
int spinHeight = 20;
vector<string> comboVals;
QTableWidget* table = ui.ColorTable;
//Because QTableWidget does not allow for a single title bar/header
//at the top of a multi-column table, the workaround hack is to just
//make another single column table with no rows, and use the single
//column header as the title bar. Then positioning it right above the table
//that holds the data. Disallow selecting and resizing of the title bar.
SetFixedTableHeader(ui.ColorTableHeader->horizontalHeader());
SetFixedTableHeader(ui.GeometryTableHeader->horizontalHeader());
SetFixedTableHeader(ui.FilterTableHeader->horizontalHeader());
SetFixedTableHeader(ui.IterationTableHeader->horizontalHeader());
//Color.
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_BrightnessSpin, spinHeight, 0.05, 100, 1, SIGNAL(valueChanged(double)), SLOT(OnBrightnessChanged(double)), true, 4.0, 4.0, 4.0);
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_GammaSpin, spinHeight, 1, 9999, 0.5, SIGNAL(valueChanged(double)), SLOT(OnGammaChanged(double)), true, 4.0, 4.0, 4.0);
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_GammaThresholdSpin, spinHeight, 0, 10, 0.1, SIGNAL(valueChanged(double)), SLOT(OnGammaThresholdChanged(double)), true, 0.1, 0.1, 0.1);
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_VibrancySpin, spinHeight, 0, 1, 0.01, SIGNAL(valueChanged(double)), SLOT(OnVibrancyChanged(double)), true, 1.0, 1.0, 1.0);
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_HighlightSpin, spinHeight, -1.0, 2.0, 0.1, SIGNAL(valueChanged(double)), SLOT(OnHighlightPowerChanged(double)), true, -1.0, -1.0, -1.0);
m_BackgroundColorButton = new QPushButton("...", table);
m_BackgroundColorButton->setMinimumWidth(21);
m_BackgroundColorButton->setMaximumWidth(21);
table->setCellWidget(row, 1, m_BackgroundColorButton);
table->item(row, 1)->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
connect(m_BackgroundColorButton, SIGNAL(clicked(bool)), this, SLOT(OnBackgroundColorButtonClicked(bool)), Qt::QueuedConnection);
row++;
comboVals.push_back("Step");
comboVals.push_back("Linear");
SetupCombo(table, this, row, 1, m_PaletteModeCombo, comboVals, SIGNAL(currentIndexChanged(int)), SLOT(OnPaletteModeComboCurrentIndexChanged(int)));
//Geometry.
row = 0;
table = ui.GeometryTable;
SetupSpinner<SpinBox, int> (table, this, row, 1, m_WidthSpin, spinHeight, 10, 100000, 50, SIGNAL(valueChanged(int)), SLOT(OnWidthChanged(int)));
SetupSpinner<SpinBox, int> (table, this, row, 1, m_HeightSpin, spinHeight, 10, 100000, 50, SIGNAL(valueChanged(int)), SLOT(OnHeightChanged(int)));
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_CenterXSpin, spinHeight, -10, 10, 0.05, SIGNAL(valueChanged(double)), SLOT(OnCenterXChanged(double)), true, 0, 0, 0);
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_CenterYSpin, spinHeight, -10, 10, 0.05, SIGNAL(valueChanged(double)), SLOT(OnCenterYChanged(double)), true, 0, 0, 0);
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_ScaleSpin, spinHeight, 10, 3000, 20, SIGNAL(valueChanged(double)), SLOT(OnScaleChanged(double)), true, 240, 240, 240);
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_ZoomSpin, spinHeight, 0, 5, 0.2, SIGNAL(valueChanged(double)), SLOT(OnZoomChanged(double)), true, 0, 0, 0);
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_RotateSpin, spinHeight, -180, 180, 10, SIGNAL(valueChanged(double)), SLOT(OnRotateChanged(double)), true, 0, 0, 0);
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_ZPosSpin, spinHeight, -1000, 1000, 1, SIGNAL(valueChanged(double)), SLOT(OnZPosChanged(double)), true, 0, 1, 0);
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_PerspectiveSpin, spinHeight, -500, 500, 0.01, SIGNAL(valueChanged(double)), SLOT(OnPerspectiveChanged(double)), true, 0, 1, 0);
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_PitchSpin, spinHeight, -180, 180, 1, SIGNAL(valueChanged(double)), SLOT(OnPitchChanged(double)), true, 0, 45, 0);
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_YawSpin, spinHeight, -180, 180, 1, SIGNAL(valueChanged(double)), SLOT(OnYawChanged(double)), true, 0, 45, 0);
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_DepthBlurSpin, spinHeight, -100, 100, 0.01, SIGNAL(valueChanged(double)), SLOT(OnDepthBlurChanged(double)), true, 0, 1, 0);
m_WidthSpin->setEnabled(false);//Will programatically change these to match the window size, but the user should never be allowed to.
m_HeightSpin->setEnabled(false);
m_CenterXSpin->setDecimals(3);
m_CenterYSpin->setDecimals(3);
m_ZPosSpin->setDecimals(3);
m_PerspectiveSpin->setDecimals(4);
m_DepthBlurSpin->setDecimals(3);
//Filter.
row = 0;
table = ui.FilterTable;
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_SpatialFilterWidthSpin, spinHeight, 0.1, 10, 0.1, SIGNAL(valueChanged(double)), SLOT(OnSpatialFilterWidthChanged(double)), true, 1.0, 1.0, 1.0);
comboVals = SpatialFilterCreator<float>::FilterTypes();
SetupCombo(table, this, row, 1, m_SpatialFilterTypeCombo, comboVals, SIGNAL(currentIndexChanged(const QString&)), SLOT(OnSpatialFilterTypeComboCurrentIndexChanged(const QString&)));
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_TemporalFilterWidthSpin, spinHeight, 1, 10, 1, SIGNAL(valueChanged(double)), SLOT(OnTemporalFilterWidthChanged(double)), true, 1);
comboVals = TemporalFilterCreator<float>::FilterTypes();
SetupCombo(table, this, row, 1, m_TemporalFilterTypeCombo, comboVals, SIGNAL(currentIndexChanged(const QString&)), SLOT(OnTemporalFilterTypeComboCurrentIndexChanged(const QString&)));
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_DEFilterMinRadiusSpin, spinHeight, 0, 25, 1, SIGNAL(valueChanged(double)), SLOT(OnDEFilterMinRadiusWidthChanged(double)), true, 0, 0, 0);
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_DEFilterMaxRadiusSpin, spinHeight, 0, 25, 1, SIGNAL(valueChanged(double)), SLOT(OnDEFilterMaxRadiusWidthChanged(double)), true, 9.0, 9.0, 0);
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_DECurveSpin, spinHeight, 0.15, 5, 0.1, SIGNAL(valueChanged(double)), SLOT(OnDEFilterCurveWidthChanged(double)), true, 0.4, 0.4, 0.4);
//Iteration.
row = 0;
table = ui.IterationTable;
SetupSpinner<SpinBox, int> (table, this, row, 1, m_PassesSpin, spinHeight, 1, 3, 1, SIGNAL(valueChanged(int)), SLOT(OnPassesChanged(int)), true, 1, 1, 1);
SetupSpinner<SpinBox, int> (table, this, row, 1, m_TemporalSamplesSpin, spinHeight, 1, 5000, 50, SIGNAL(valueChanged(int)), SLOT(OnTemporalSamplesChanged(int)), true, 1000);
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_QualitySpin, spinHeight, 1, 200000, 50, SIGNAL(valueChanged(double)), SLOT(OnQualityChanged(double)), true, 10, 10, 10);
SetupSpinner<SpinBox, int> (table, this, row, 1, m_SupersampleSpin, spinHeight, 1, 4, 1, SIGNAL(valueChanged(int)), SLOT(OnSupersampleChanged(int)), true, 1, 1, 1);
comboVals.clear();
comboVals.push_back("Step");
comboVals.push_back("Linear");
SetupCombo(table, this, row, 1, m_AffineInterpTypeCombo, comboVals, SIGNAL(currentIndexChanged(int)), SLOT(OnAffineInterpTypeComboCurrentIndexChanged(int)));
comboVals.clear();
comboVals.push_back("Linear");
comboVals.push_back("Smooth");
SetupCombo(table, this, row, 1, m_InterpTypeCombo, comboVals, SIGNAL(currentIndexChanged(int)), SLOT(OnInterpTypeComboCurrentIndexChanged(int)));
}
/// <summary>
/// Color.
/// </summary>
/// <summary>
/// Set the brightness to be used for calculating K1 and K2 for filtering and final accum.
/// Called when brightness spinner is changed.
/// Resets the rendering process to the filtering stage.
/// </summary>
/// <param name="d">The brightness</param>
template <typename T>
void FractoriumEmberController<T>::BrightnessChanged(double d) { Update([&] { m_Ember.m_Brightness = d; }, true, FILTER_AND_ACCUM); }
void Fractorium::OnBrightnessChanged(double d) { m_Controller->BrightnessChanged(d); }
/// <summary>
/// Set the gamma to be used for final accum.
/// Called when gamma spinner is changed.
/// Resets the rendering process if temporal samples is greater than 1,
/// else if early clip is true, filter and accum, else final accum only.
/// </summary>
/// <param name="d">The gamma value</param>
template <typename T> void FractoriumEmberController<T>::GammaChanged(double d) { Update([&] { m_Ember.m_Gamma = d; }, true, m_Ember.m_TemporalSamples > 1 ? FULL_RENDER : (m_Renderer->EarlyClip() ? FILTER_AND_ACCUM : ACCUM_ONLY)); }
void Fractorium::OnGammaChanged(double d) { m_Controller->GammaChanged(d); }
/// <summary>
/// Set the gamma threshold to be used for final accum.
/// Called when gamma threshold spinner is changed.
/// Resets the rendering process to the final accumulation stage.
/// </summary>
/// <param name="d">The gamma threshold</param>
template <typename T> void FractoriumEmberController<T>::GammaThresholdChanged(double d) { Update([&] { m_Ember.m_GammaThresh = d; }, true, m_Ember.m_TemporalSamples > 1 ? FULL_RENDER : (m_Renderer->EarlyClip() ? FILTER_AND_ACCUM : ACCUM_ONLY)); }
void Fractorium::OnGammaThresholdChanged(double d) { m_Controller->GammaThresholdChanged(d); }
/// <summary>
/// Set the vibrancy to be used for final accum.
/// Called when vibrancy spinner is changed.
/// Resets the rendering process to the final accumulation stage if temporal samples is 1, else full reset.
/// </summary>
/// <param name="d">The vibrancy</param>
template <typename T> void FractoriumEmberController<T>::VibrancyChanged(double d) { Update([&] { m_Ember.m_Vibrancy = d; }, true, m_Ember.m_TemporalSamples > 1 ? FULL_RENDER : (m_Renderer->EarlyClip() ? FILTER_AND_ACCUM : ACCUM_ONLY)); }
void Fractorium::OnVibrancyChanged(double d) { m_Controller->VibrancyChanged(d); }
/// <summary>
/// Set the highlight power to be used for final accum.
/// Called when highlight power spinner is changed.
/// Resets the rendering process to the final accumulation stage.
/// </summary>
/// <param name="d">The highlight power</param>
template <typename T> void FractoriumEmberController<T>::HighlightPowerChanged(double d) { Update([&] { m_Ember.m_HighlightPower = d; }, true, m_Ember.m_TemporalSamples > 1 ? FULL_RENDER : (m_Renderer->EarlyClip() ? FILTER_AND_ACCUM : ACCUM_ONLY)); }
void Fractorium::OnHighlightPowerChanged(double d) { m_Controller->HighlightPowerChanged(d); }
/// <summary>
/// Show the color selection dialog.
/// Called when background color button is clicked.
/// </summary>
/// <param name="checked">Ignored</param>
void Fractorium::OnBackgroundColorButtonClicked(bool checked)
{
m_ColorDialog->show();
}
/// <summary>
/// Set a new ember background color when the user accepts the color dialog.
/// Also change the background and foreground colors of the color cell in the
/// color params table.
/// Resets the rendering process.
/// </summary>
/// <param name="color">The color to set, RGB in the 0-255 range</param>
template <typename T>
void FractoriumEmberController<T>::BackgroundChanged(const QColor& color)
{
Update([&]
{
int itemRow = 5;
QTableWidget* colorTable = m_Fractorium->ui.ColorTable;
colorTable->item(itemRow, 1)->setBackgroundColor(color);
QString r = QString::number(color.red());
QString g = QString::number(color.green());
QString b = QString::number(color.blue());
int threshold = 105;
int delta = (color.red() * 0.299) + //Magic numbers gotten from a Stack Overflow post.
(color.green() * 0.587) +
(color.blue() * 0.114);
QColor textColor = (255 - delta < threshold) ? QColor(0, 0, 0) : QColor(255, 255, 255);
colorTable->item(itemRow, 1)->setTextColor(textColor);
colorTable->item(itemRow, 1)->setText("rgb(" + r + ", " + g + ", " + b + ")");
//Color is 0-255, normalize to 0-1.
m_Ember.m_Background.r = color.red() / 255.0;
m_Ember.m_Background.g = color.green() / 255.0;
m_Ember.m_Background.b = color.blue() / 255.0;
});
}
void Fractorium::OnColorSelected(const QColor& color) { m_Controller->BackgroundChanged(color); }
/// <summary>
/// Set the palette index interpolation mode.
/// Called when palette mode combo box index is changed.
/// Resets the rendering process.
/// </summary>
/// <param name="index">The index of the palette mode combo box</param>
template <typename T> void FractoriumEmberController<T>::PaletteModeChanged(unsigned int i) { Update([&] { m_Ember.m_PaletteMode = i == 0 ? PALETTE_STEP : PALETTE_LINEAR; }); }
void Fractorium::OnPaletteModeComboCurrentIndexChanged(int index) { m_Controller->PaletteModeChanged(index); }
/// <summary>
/// Geometry.
/// </summary>
/// <summary>
/// Placeholder, do nothing.
/// Dimensions are set automatically to match the dimensions of GLWidget.
/// </summary>
/// <param name="d">Ignored</param>
void Fractorium::OnWidthChanged(int d) { }
/// <summary>
/// Placeholder, do nothing.
/// Dimensions are set automatically to match the dimensions of GLWidget.
/// </summary>
/// <param name="d">Ignored</param>
void Fractorium::OnHeightChanged(int d) { }
/// <summary>
/// Set the x offset applied to the center of the image.
/// Resets the rendering process.
/// </summary>
/// <param name="d">The x offset value</param>
template <typename T> void FractoriumEmberController<T>::CenterXChanged(double d) { Update([&] { m_Ember.m_CenterX = d; }); }
void Fractorium::OnCenterXChanged(double d) { m_Controller->CenterXChanged(d); }
/// <summary>
/// Set the y offset applied to the center of the image.
/// Resets the rendering process.
/// </summary>
/// <param name="d">The y offset value</param>
template <typename T> void FractoriumEmberController<T>::CenterYChanged(double d) { Update([&] { m_Ember.m_CenterY = d; }); }
void Fractorium::OnCenterYChanged(double d) { m_Controller->CenterYChanged(d); }
/// <summary>
/// Set the scale (pixels per unit) value of the image.
/// Note this will not increase the number of iters ran, but will degrade quality.
/// To preserve quality, but exponentially increase iters, use zoom.
/// Called when scale spinner is changed.
/// Resets the rendering process.
/// </summary>
/// <param name="d">The scale value</param>
template <typename T> void FractoriumEmberController<T>::ScaleChanged(double d) { Update([&] { m_Ember.m_PixelsPerUnit = d; }); }
void Fractorium::OnScaleChanged(double d) { m_Controller->ScaleChanged(d); }
/// <summary>
/// Set the zoom value of the image.
/// Note this will increase the number of iters ran exponentially.
/// To zoom in without increasing iters, but sacrifice quality, use scale.
/// Called when zoom spinner is changed.
/// Resets the rendering process.
/// </summary>
/// <param name="d">The zoom value</param>
template <typename T> void FractoriumEmberController<T>::ZoomChanged(double d) { Update([&] { m_Ember.m_Zoom = d; }); }
void Fractorium::OnZoomChanged(double d) { m_Controller->ZoomChanged(d); }
/// <summary>
/// Set the angular rotation of the image.
/// Called when rotate spinner is changed.
/// Resets the rendering process.
/// </summary>
/// <param name="d">The rotation in angles</param>
template <typename T> void FractoriumEmberController<T>::RotateChanged(double d) { Update([&] { m_Ember.m_Rotate = d; }); }
void Fractorium::OnRotateChanged(double d) { m_Controller->RotateChanged(d); }
template <typename T> void FractoriumEmberController<T>::ZPosChanged(double d) { Update([&] { m_Ember.m_CamZPos = d; }); }
void Fractorium::OnZPosChanged(double d) { m_Controller->ZPosChanged(d); }
template <typename T> void FractoriumEmberController<T>::PerspectiveChanged(double d) { Update([&] { m_Ember.m_CamPerspective = d; }); }
void Fractorium::OnPerspectiveChanged(double d) { m_Controller->PerspectiveChanged(d); }
template <typename T> void FractoriumEmberController<T>::PitchChanged(double d) { Update([&] { m_Ember.m_CamPitch = d * DEG_2_RAD; }); }
void Fractorium::OnPitchChanged(double d) { m_Controller->PitchChanged(d); }
template <typename T> void FractoriumEmberController<T>::YawChanged(double d) { Update([&] { m_Ember.m_CamYaw = d * DEG_2_RAD; }); }
void Fractorium::OnYawChanged(double d) { m_Controller->YawChanged(d); }
template <typename T> void FractoriumEmberController<T>::DepthBlurChanged(double d) { Update([&] { m_Ember.m_CamDepthBlur = d; }); }
void Fractorium::OnDepthBlurChanged(double d) { m_Controller->DepthBlurChanged(d); }
/// <summary>
/// Filter.
/// </summary>
/// <summary>
/// Set the spatial filter width.
/// Called when the spatial filter width spinner is changed.
/// Resets the rendering process.
/// </summary>
/// <param name="d">The spatial filter width</param>
template <typename T> void FractoriumEmberController<T>::SpatialFilterWidthChanged(double d) { Update([&] { m_Ember.m_SpatialFilterRadius = d; }); }//Must fully reset because it's used to create bounds.
void Fractorium::OnSpatialFilterWidthChanged(double d) { m_Controller->SpatialFilterWidthChanged(d); }
/// <summary>
/// Set the spatial filter type.
/// Called when the spatial filter type combo box index is changed.
/// Resets the rendering process.
/// </summary>
/// <param name="text">The spatial filter type</param>
template <typename T> void FractoriumEmberController<T>::SpatialFilterTypeChanged(const QString& text) { Update([&] { m_Ember.m_SpatialFilterType = SpatialFilterCreator<T>::FromString(text.toStdString()); }); }//Must fully reset because it's used to create bounds.
void Fractorium::OnSpatialFilterTypeComboCurrentIndexChanged(const QString& text) { m_Controller->SpatialFilterTypeChanged(text); }
/// <summary>
/// Set the temporal filter width to be used with animation.
/// Called when the temporal filter width spinner is changed.
/// Does not reset anything because this is only used for animation.
/// In the future, when animation is implemented, this will have an effect.
/// </summary>
/// <param name="d">The temporal filter width</param>
template <typename T> void FractoriumEmberController<T>::TemporalFilterWidthChanged(double d) { Update([&] { m_Ember.m_TemporalFilterWidth = d; }, true, NOTHING); }//Don't do anything until animation is implemented.
void Fractorium::OnTemporalFilterWidthChanged(double d) { m_Controller->TemporalFilterWidthChanged(d); }
/// <summary>
/// Set the temporal filter type to be used with animation.
/// Called when the temporal filter combo box index is changed.
/// Does not reset anything because this is only used for animation.
/// In the future, when animation is implemented, this will have an effect.
/// </summary>
/// <param name="text">The name of the temporal filter</param>
template <typename T> void FractoriumEmberController<T>::TemporalFilterTypeChanged(const QString& text) { Update([&] { m_Ember.m_TemporalFilterType = TemporalFilterCreator<T>::FromString(text.toStdString()); }, true, NOTHING); }//Don't do anything until animation is implemented.
void Fractorium::OnTemporalFilterTypeComboCurrentIndexChanged(const QString& text) { m_Controller->TemporalFilterTypeChanged(text); }
/// <summary>
/// Set the density estimation filter min radius value.
/// Resets the rendering process.
/// </summary>
/// <param name="d">The min radius value</param>
template <typename T>
void FractoriumEmberController<T>::DEFilterMinRadiusWidthChanged(double d)
{
Update([&]
{
if (m_Fractorium->m_DEFilterMinRadiusSpin->value() > m_Fractorium->m_DEFilterMaxRadiusSpin->value())
{
m_Fractorium->m_DEFilterMinRadiusSpin->setValue(m_Fractorium->m_DEFilterMaxRadiusSpin->value() - 1);
return;
}
m_Ember.m_MinRadDE = d;
});
}
void Fractorium::OnDEFilterMinRadiusWidthChanged(double d) { m_Controller->DEFilterMinRadiusWidthChanged(d); }
/// <summary>
/// Set the density estimation filter max radius value.
/// Resets the rendering process.
/// </summary>
/// <param name="d">The max radius value</param>
template <typename T>
void FractoriumEmberController<T>::DEFilterMaxRadiusWidthChanged(double d)
{
Update([&]
{
if (m_Fractorium->m_DEFilterMaxRadiusSpin->value() < m_Fractorium->m_DEFilterMinRadiusSpin->value())
{
m_Fractorium->m_DEFilterMaxRadiusSpin->setValue(m_Fractorium->m_DEFilterMinRadiusSpin->value() + 1);
return;
}
m_Ember.m_MaxRadDE = d;
});
}
void Fractorium::OnDEFilterMaxRadiusWidthChanged(double d) { m_Controller->DEFilterMaxRadiusWidthChanged(d); }
/// <summary>
/// Set the density estimation filter curve value.
/// Resets the rendering process.
/// </summary>
/// <param name="d">The curve value</param>
template <typename T> void FractoriumEmberController<T>::DEFilterCurveWidthChanged(double d) { Update([&] { m_Ember.m_CurveDE = d; }); }
void Fractorium::OnDEFilterCurveWidthChanged(double d) { m_Controller->DEFilterCurveWidthChanged(d); }
/// <summary>
/// Iteration.
/// </summary>
/// <summary>
/// Set the number of passes.
/// This is a feature that is mostly useless and unused, and may even be removed soon.
/// It should never be set to a value greater than 1.
/// Resets the rendering process.
/// </summary>
/// <param name="d">The passes value</param>
template <typename T> void FractoriumEmberController<T>::PassesChanged(int i) { Update([&] { m_Ember.m_Passes = i; }); }
void Fractorium::OnPassesChanged(int d) { m_Controller->PassesChanged(d); }
/// <summary>
/// Set the temporal samples to be used with animation.
/// Called when the temporal samples spinner is changed.
/// Does not reset anything because this is only used for animation.
/// In the future, when animation is implemented, this will have an effect.
/// </summary>
/// <param name="d">The temporal samples value</param>
template <typename T> void FractoriumEmberController<T>::TemporalSamplesChanged(int i) { Update([&] { m_Ember.m_TemporalSamples = i; }, true, NOTHING); }//Don't do anything until animation is implemented.
void Fractorium::OnTemporalSamplesChanged(int d) { m_Controller->TemporalSamplesChanged(d); }
/// <summary>
/// Set the quality.
/// 10 is good for interactive rendering on the CPU.
/// 20-50 is good for OpenCL.
/// Above 500 seems to offer little additional value for final renders.
/// Called when the quality spinner is changed.
/// If rendering is done, and the value is greater than the last value,
/// the rendering process is continued, else it's reset.
/// </summary>
/// <param name="d">The quality in terms of iterations per pixel</param>
template <typename T> void FractoriumEmberController<T>::QualityChanged(double d) { Update([&] { m_Ember.m_Quality = d; }, true, d > m_Ember.m_Quality ? KEEP_ITERATING : FULL_RENDER); }
void Fractorium::OnQualityChanged(double d) { m_Controller->QualityChanged(d); }
/// <summary>
/// Set the supersample.
/// Note this will dramatically degrade performance, especially in
/// OpenCL, while only giving a minor improvement in visual quality.
/// Values above 2 add no noticeable difference.
/// The user should only use this for a final render, or a quick preview, and then
/// reset it back to 1 when done.
/// Called when the supersample spinner is changed.
/// Resets the rendering process.
/// </summary>
/// <param name="d">The supersample value to set</param>
template <typename T> void FractoriumEmberController<T>::SupersampleChanged(int d) { Update([&] { m_Ember.m_Supersample = d; }); }
void Fractorium::OnSupersampleChanged(int d) { m_Controller->SupersampleChanged(d); }
/// <summary>
/// Set the affine interpolation type.
/// Does not reset anything because this is only used for animation.
/// In the future, when animation is implemented, this will have an effect.
/// Called when the affine interp type combo box index is changed.
/// </summary>
/// <param name="index">The index</param>
template <typename T>
void FractoriumEmberController<T>::AffineInterpTypeChanged(int i)
{
Update([&]
{
if (i == 0)
m_Ember.m_AffineInterp = INTERP_LINEAR;
else if (i == 1)
m_Ember.m_AffineInterp = INTERP_LOG;
else
m_Ember.m_AffineInterp = INTERP_LINEAR;
}, true, NOTHING);
}
void Fractorium::OnAffineInterpTypeComboCurrentIndexChanged(int index) { m_Controller->AffineInterpTypeChanged(index); }
/// <summary>
/// Set the interpolation type.
/// Does not reset anything because this is only used for animation.
/// In the future, when animation is implemented, this will have an effect.
/// Called when the interp type combo box index is changed.
/// </summary>
/// <param name="i">The index</param>
template <typename T>
void FractoriumEmberController<T>::InterpTypeChanged(int i)
{
Update([&]
{
if (i == 0)
m_Ember.m_Interp = EMBER_INTERP_LINEAR;
else if (i == 1)
m_Ember.m_Interp = EMBER_INTERP_SMOOTH;
else
m_Ember.m_Interp = EMBER_INTERP_LINEAR;
}, true, NOTHING);
}
void Fractorium::OnInterpTypeComboCurrentIndexChanged(int index) { m_Controller->InterpTypeChanged(index); }
/// <summary>
/// Set the center.
/// This updates the spinners as well as the current ember center.
/// Resets the renering process.
/// </summary>
/// <param name="x">The x offset</param>
/// <param name="y">The y offset</param>
template <typename T>
void FractoriumEmberController<T>::SetCenter(double x, double y)
{
m_Ember.m_CenterX = x;
m_Ember.m_CenterY = y;
m_Fractorium->m_CenterXSpin->SetValueStealth(x);//Don't trigger a redraw twice.
m_Fractorium->m_CenterYSpin->SetValueStealth(y);
if (m_Renderer.get())//On startup, this will be null at first because a resize takes place before the rendering thread is started.
CenterXChanged(m_Ember.m_CenterX);//Trigger update using both new values.
}
/// <summary>
/// Fill the parameter tables and palette widgets with values from the current ember.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::FillParamTablesAndPalette()
{
m_Fractorium->m_BrightnessSpin->SetValueStealth(m_Ember.m_Brightness);//Color.
m_Fractorium->m_GammaSpin->SetValueStealth(m_Ember.m_Gamma);
m_Fractorium->m_GammaThresholdSpin->SetValueStealth(m_Ember.m_GammaThresh);
m_Fractorium->m_VibrancySpin->SetValueStealth(m_Ember.m_Vibrancy);
m_Fractorium->m_HighlightSpin->SetValueStealth(m_Ember.m_HighlightPower);
m_Fractorium->m_ColorDialog->setCurrentColor(QColor(m_Ember.m_Background.r * 255, m_Ember.m_Background.g * 255, m_Ember.m_Background.b * 255));
m_Fractorium->ui.ColorTable->item(5, 1)->setBackgroundColor(m_Fractorium->m_ColorDialog->currentColor());
m_Fractorium->m_PaletteModeCombo->SetCurrentIndexStealth((int)m_Ember.m_PaletteMode);
m_Fractorium->m_WidthSpin->SetValueStealth(m_Ember.m_FinalRasW);//Geometry.
m_Fractorium->m_HeightSpin->SetValueStealth(m_Ember.m_FinalRasH);
m_Fractorium->m_CenterXSpin->SetValueStealth(m_Ember.m_CenterX);
m_Fractorium->m_CenterYSpin->SetValueStealth(m_Ember.m_CenterY);
m_Fractorium->m_ScaleSpin->SetValueStealth(m_Ember.m_PixelsPerUnit);
m_Fractorium->m_RotateSpin->SetValueStealth(m_Ember.m_Rotate);
m_Fractorium->m_ZPosSpin->SetValueStealth(m_Ember.m_CamZPos);
m_Fractorium->m_PerspectiveSpin->SetValueStealth(m_Ember.m_CamPerspective);
m_Fractorium->m_PitchSpin->SetValueStealth(m_Ember.m_CamPitch * RAD_2_DEG_T);
m_Fractorium->m_YawSpin->SetValueStealth(m_Ember.m_CamYaw * RAD_2_DEG_T);
m_Fractorium->m_DepthBlurSpin->SetValueStealth(m_Ember.m_CamDepthBlur);
m_Fractorium->m_SpatialFilterWidthSpin->SetValueStealth(m_Ember.m_SpatialFilterRadius);//Filter.
m_Fractorium->m_SpatialFilterTypeCombo->SetCurrentIndexStealth((int)m_Ember.m_SpatialFilterType);
m_Fractorium->m_TemporalFilterWidthSpin->SetValueStealth(m_Ember.m_TemporalFilterWidth);
m_Fractorium->m_TemporalFilterTypeCombo->SetCurrentIndexStealth((int)m_Ember.m_TemporalFilterType);
m_Fractorium->m_DEFilterMinRadiusSpin->SetValueStealth(m_Ember.m_MinRadDE);
m_Fractorium->m_DEFilterMaxRadiusSpin->SetValueStealth(m_Ember.m_MaxRadDE);
m_Fractorium->m_DECurveSpin->SetValueStealth(m_Ember.m_CurveDE);
m_Fractorium->m_PassesSpin->SetValueStealth(m_Ember.m_Passes);//Iteration.
m_Fractorium->m_TemporalSamplesSpin->SetValueStealth(m_Ember.m_TemporalSamples);
m_Fractorium->m_QualitySpin->SetValueStealth(m_Ember.m_Quality);
m_Fractorium->m_SupersampleSpin->SetValueStealth(m_Ember.m_Supersample);
m_Fractorium->m_AffineInterpTypeCombo->SetCurrentIndexStealth(m_Ember.m_AffineInterp);
m_Fractorium->m_InterpTypeCombo->SetCurrentIndexStealth(m_Ember.m_Interp);
//Palette.
m_Fractorium->ResetPaletteControls();
m_Fractorium->m_PaletteHueSpin->SetValueStealth(NormalizeDeg180<double>(m_Ember.m_Hue * 360.0));//Convert -0.5 to 0.5 range to -180 - 180.
//Use -1 as a placeholder to mean either generate a random palette from the list or
//to just use the values "as-is" without looking them up in the list.
if (m_Ember.m_Palette.m_Index >= 0)
{
m_Fractorium->OnPaletteCellClicked(Clamp<int>(m_Ember.m_Palette.m_Index, 0, m_Fractorium->ui.PaletteListTable->rowCount() - 1), 1);
}
else
{
//An ember with an embedded palette was loaded, rather than one from the list, so assign it directly to the controls without applying adjustments.
//Normally, temp palette is assigned whenever the user clicks on a palette cell. But since that is skipped here just make a copy of the ember's palette.
m_TempPalette = m_Ember.m_Palette;
UpdateAdjustedPaletteGUI(m_Ember.m_Palette);//Will clear name string since embedded palettes have no name. This will trigger a full render.
}
}
/// <summary>
/// Copy all GUI widget values on the parameters tab to the passed in ember.
/// </summary>
/// <param name="ember">The ember to copy values to.</param>
template <typename T>
void FractoriumEmberController<T>::ParamsToEmber(Ember<T>& ember)
{
QColor color = m_Fractorium->ui.ColorTable->item(5, 1)->backgroundColor();
ember.m_Brightness = m_Fractorium->m_BrightnessSpin->value();//Color.
ember.m_Gamma = m_Fractorium->m_GammaSpin->value();
ember.m_GammaThresh = m_Fractorium->m_GammaThresholdSpin->value();
ember.m_Vibrancy = m_Fractorium->m_VibrancySpin->value();
ember.m_HighlightPower = m_Fractorium->m_HighlightSpin->value();
ember.m_Background.r = color.red() / 255.0;
ember.m_Background.g = color.green() / 255.0;
ember.m_Background.b = color.blue() / 255.0;
ember.m_PaletteMode = (ePaletteMode)m_Fractorium->m_PaletteModeCombo->currentIndex();
ember.m_FinalRasW = m_Fractorium->m_WidthSpin->value();//Geometry.
ember.m_FinalRasH = m_Fractorium->m_HeightSpin->value();
ember.m_CenterX = m_Fractorium->m_CenterXSpin->value();
ember.m_CenterY = m_Fractorium->m_CenterYSpin->value();
ember.m_PixelsPerUnit = m_Fractorium->m_ScaleSpin->value();
ember.m_Rotate = m_Fractorium->m_RotateSpin->value();
ember.m_CamZPos = m_Fractorium->m_ZPosSpin->value();
ember.m_CamPerspective = m_Fractorium->m_PerspectiveSpin->value();
ember.m_CamPitch = m_Fractorium->m_PitchSpin->value() * DEG_2_RAD_T;
ember.m_CamYaw = m_Fractorium->m_YawSpin->value() * DEG_2_RAD_T;
ember.m_CamDepthBlur = m_Fractorium->m_DepthBlurSpin->value();
ember.m_SpatialFilterRadius = m_Fractorium->m_SpatialFilterWidthSpin->value();//Filter.
ember.m_SpatialFilterType = (eSpatialFilterType)m_Fractorium->m_SpatialFilterTypeCombo->currentIndex();
ember.m_TemporalFilterWidth = m_Fractorium->m_TemporalFilterWidthSpin->value();
ember.m_TemporalFilterType = (eTemporalFilterType)m_Fractorium->m_TemporalFilterTypeCombo->currentIndex();
ember.m_MinRadDE = m_Fractorium->m_DEFilterMinRadiusSpin->value();
ember.m_MaxRadDE = m_Fractorium->m_DEFilterMaxRadiusSpin->value();
ember.m_CurveDE = m_Fractorium->m_DECurveSpin->value();
ember.m_Passes = m_Fractorium->m_PassesSpin->value();
ember.m_TemporalSamples = m_Fractorium->m_TemporalSamplesSpin->value();
ember.m_Quality = m_Fractorium->m_QualitySpin->value();
ember.m_Supersample = m_Fractorium->m_SupersampleSpin->value();
ember.m_AffineInterp = (eAffineInterp)m_Fractorium->m_AffineInterpTypeCombo->currentIndex();
ember.m_Interp = (eInterp)m_Fractorium->m_InterpTypeCombo->currentIndex();
}
/// <summary>
/// Set the rotation.
/// This updates the spinner, optionally stealth.
/// </summary>
/// <param name="rot">The rotation value in angles to set</param>
/// <param name="stealth">True if stealth to skip re-rendering, else false to trigger a new render</param>
void Fractorium::SetRotation(double rot, bool stealth)
{
if (stealth)
m_RotateSpin->SetValueStealth(rot);
else
m_RotateSpin->setValue(rot);
}
/// <summary>
/// Set the scale.
/// This is the number of raster pixels that correspond to the distance
/// between 0-1 in the cartesian plane. The higher the number, the more
/// zoomed in the image is.
/// Resets the rendering process.
/// </summary>
/// <param name="scale">The scale value</param>
void Fractorium::SetScale(double scale)
{
m_ScaleSpin->setValue(scale);
}

View File

@ -0,0 +1 @@
#include "FractoriumPch.h"

View File

@ -0,0 +1,44 @@
#pragma once
#define GL_GLEXT_PROTOTYPES
#define GLM_FORCE_INLINE 1
#include "Renderer.h"
#include "RendererCL.h"
#include "VariationList.h"
#include "OpenCLWrapper.h"
#include "XmlToEmber.h"
#include "EmberToXml.h"
#include "SheepTools.h"
#include "JpegUtils.h"
#include "EmberCommon.h"
#include <deque>
#undef QT_OPENGL_ES_2//Make absolutely sure OpenGL ES is not used.
#define QT_NO_OPENGL_ES_2
#include <QtWidgets>
#include <QLineEdit>
#include <QSpinBox>
#include <QDoubleSpinBox>
#include <QPushButton>
#include <QComboBox>
#include <QColorDialog>
#include <QTreeWidget>
#include <QWheelEvent>
#include <QItemDelegate>
#include <QApplication>
#include <QSettings>
#include <QGLWidget>
#include <QOpenGLFunctions_2_0.h>
#include <QtWidgets/QMainWindow>
#include <QFuture>
#include <QtConcurrentRun>
#define GLM_FORCE_RADIANS
#include "glm/glm.hpp"
#include "glm/gtc/matrix_transform.hpp"
#include "glm/gtc/type_ptr.hpp"
using namespace std;
using namespace EmberNs;
using namespace EmberCLns;

View File

@ -0,0 +1,630 @@
#include "FractoriumPch.h"
#include "Fractorium.h"
/// <summary>
/// Return whether the render timer is running.
/// </summary>
/// <returns>True if running, else false.</returns>
bool FractoriumEmberControllerBase::RenderTimerRunning()
{
return m_RenderTimer && m_RenderTimer->isActive();
}
/// <summary>
/// Start the render timer.
/// If a renderer has not been created yet, it will be created from the options.
/// </summary>
void FractoriumEmberControllerBase::StartRenderTimer()
{
if (m_RenderTimer)
{
UpdateRender();
m_RenderTimer->start();
m_RenderElapsedTimer.Tic();
}
}
/// <summary>
/// Start the render timer after a short delay.
/// If the timer is already running, stop it first.
/// This is useful for stopping and restarting the render
/// process in response to things like a window resize.
/// </summary>
void FractoriumEmberControllerBase::DelayedStartRenderTimer()
{
DeleteRenderer();
if (m_RenderRestartTimer)
{
m_RenderRestartTimer->setSingleShot(true);
m_RenderRestartTimer->start(300);//Will stop the timer if it's already running, and start again.
}
}
/// <summary>
/// Stop the render timer and abort the rendering process.
/// Optionally block until stopping is complete.
/// </summary>
/// <param name="wait">True to block, else false.</param>
void FractoriumEmberControllerBase::StopRenderTimer(bool wait)
{
if (m_RenderTimer)
m_RenderTimer->stop();
if (m_Renderer.get())
m_Renderer->Abort();
if (wait)
{
while (m_Rendering || RenderTimerRunning() || (Renderer() && (!m_Renderer->Aborted() || m_Renderer->InRender())))
QApplication::processEvents();
}
}
/// <summary>
/// Stop all timers, rendering and drawing and block until they are done.
/// </summary>
void FractoriumEmberControllerBase::Shutdown()
{
StopRenderTimer(true);
while(m_Fractorium->ui.GLDisplay->Drawing())
QApplication::processEvents();
}
/// <summary>
/// Update the state of the renderer.
/// Upon changing values, some intelligence is used to avoid blindly restarting the
/// entire iteration proceess every time a value changes. This is because some values don't affect the
/// iteration, and only affect filtering and final accumulation. They are broken into three categories:
/// 1) Restart the entire process.
/// 2) Log/density filter, then final accum.
/// 3) Final accum only.
/// 4) Continue iterating.
/// </summary>
/// <param name="action">The action to take</param>
void FractoriumEmberControllerBase::UpdateRender(eProcessAction action)
{
AddProcessAction(action);
m_RenderElapsedTimer.Tic();
}
/// <summary>
/// Call Shutdown() then delete the renderer and clear the textures in the output window if there is one.
/// </summary>
void FractoriumEmberControllerBase::DeleteRenderer()
{
Shutdown();
m_Renderer.reset();
if (GLController())
GLController()->ClearWindow();
}
/// <summary>
/// Save the current contents of the GL window to a file.
/// This could benefit from QImageWriter, however it's compression capabilities are
/// severely lacking. A Png file comes out larger than a bitmap, so instead use the
/// Png and Jpg wrapper functions from the command line programs.
/// This will embed the id, url and nick fields from the options in the image comments.
/// </summary>
/// <param name="filename">The full path and filename</param>
void FractoriumEmberControllerBase::SaveCurrentRender(QString filename)
{
if (filename != "")
{
bool b = false;
unsigned int i, j;
unsigned int width = m_Renderer->FinalRasW();
unsigned int height = m_Renderer->FinalRasH();
unsigned char* data = NULL;
vector<unsigned char> vecRgb;
QFileInfo fileInfo(filename);
QString suffix = fileInfo.suffix();
FractoriumSettings* settings = m_Fractorium->m_Settings;
RendererCLBase* rendererCL = dynamic_cast<RendererCLBase*>(m_Renderer.get());
if (rendererCL && m_Renderer->PrepFinalAccumVector(m_FinalImage))
{
if (!rendererCL->ReadFinal(m_FinalImage.data()))
{
QMessageBox::critical(m_Fractorium, "GPU Read Error", "Could not read image from the GPU, aborting image save.");
return;
}
}
data = m_FinalImage.data();//Png and channels = 4.
if ((suffix == "jpg" || suffix == "bmp") && m_Renderer->NumChannels() == 4)
{
EmberNs::RgbaToRgb(m_FinalImage, vecRgb, width, height);
data = vecRgb.data();
}
string s = filename.toStdString();
string id = settings->Id().toStdString();
string url = settings->Url().toStdString();
string nick = settings->Nick().toStdString();
EmberImageComments comments = m_Renderer->ImageComments(0, false, true);
if (suffix == "png")
b = WritePng(s.c_str(), data, width, height, 1, true, comments, id, url, nick);
else if (suffix == "jpg")
b = WriteJpeg(s.c_str(), data, width, height, 100, true, comments, id, url, nick);
else if (suffix == "bmp")
b = WriteBmp(s.c_str(), data, width, height);
else
QMessageBox::critical(m_Fractorium, "Save Failed", "Unrecognized format " + suffix + ", not saving.");
if (b)
settings->SaveFolder(fileInfo.canonicalPath());
else
QMessageBox::critical(m_Fractorium, "Save Failed", "Could not save file, try saving to a different folder.");
}
}
/// <summary>
/// Add a process action to the list of actions to take.
/// Called in response to the user changing something on the GUI.
/// </summary>
/// <param name="action">The action for the renderer to take</param>
void FractoriumEmberControllerBase::AddProcessAction(eProcessAction action)
{
m_Cs.Enter();
m_ProcessActions.push_back(action);
if (m_Renderer.get())
m_Renderer->Abort();
m_Cs.Leave();
}
/// <summary>
/// Condense and clear the process actions into a single action and return.
/// Many actions may be specified, but only the one requiring the greatest amount
/// of processing matters. Extract and return the greatest and clear the vector.
/// </summary>
/// <returns>The most significant processing action desired</returns>
eProcessAction FractoriumEmberControllerBase::CondenseAndClearProcessActions()
{
m_Cs.Enter();
eProcessAction action = NOTHING;
for (size_t i = 0; i < m_ProcessActions.size(); i++)
if (m_ProcessActions[i] > action)
action = m_ProcessActions[i];
m_ProcessActions.clear();
m_Cs.Leave();
return action;
}
/// <summary>
/// Render progress callback function to update progress bar.
/// </summary>
/// <param name="ember">The ember currently being rendered</param>
/// <param name="foo">An extra dummy parameter</param>
/// <param name="fraction">The progress fraction from 0-100</param>
/// <param name="stage">The stage of iteration. 1 is iterating, 2 is density filtering, 2 is final accumulation.</param>
/// <param name="etaMs">The estimated milliseconds to completion of the current stage</param>
/// <returns>0 if the user has changed anything on the GUI, else 1 to continue rendering.</returns>
template <typename T>
int FractoriumEmberController<T>::ProgressFunc(Ember<T>& ember, void* foo, double fraction, int stage, double etaMs)
{
QString status;
m_Fractorium->m_ProgressBar->setValue((int)fraction);//Only really applies to iter and filter, because final accum only gives progress 0 and 100.
if (stage == 0)
status = "Iterating";
else if (stage == 1)
status = "Density Filtering";
else if (stage == 2)
status = "Spatial Filtering + Final Accumulation";
m_Fractorium->m_RenderStatusLabel->setText(status);
return m_ProcessActions.empty() ? 1 : 0;//If they've done anything, abort.
}
/// <summary>
/// Clear the undo list as well as the undo/redo index and state.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::ClearUndo()
{
m_UndoIndex = 0;
m_UndoList.clear();
m_EditState = REGULAR_EDIT;
m_LastEditWasUndoRedo = false;
m_Fractorium->ui.ActionUndo->setEnabled(false);
m_Fractorium->ui.ActionRedo->setEnabled(false);
}
/// <summary>
/// The main rendering function.
/// Called whenever the event loop is idle.
/// </summary>
template <typename T>
bool FractoriumEmberController<T>::Render()
{
bool success = true;
m_Rendering = true;
GLWidget* gl = m_Fractorium->ui.GLDisplay;
eProcessAction action = CondenseAndClearProcessActions();
//Set dimensions first.
if ((m_Ember.m_FinalRasW != gl->width() ||
m_Ember.m_FinalRasH != gl->height()))
{
m_Ember.SetSizeAndAdjustScale(gl->width(), gl->height(), false, SCALE_WIDTH);
m_Fractorium->m_ScaleSpin->SetValueStealth(m_Ember.m_PixelsPerUnit);
action = FULL_RENDER;
}
//Force temporal samples to always be 1. Perhaps change later when animation is implemented.
m_Ember.m_TemporalSamples = 1;
//Take care of solo xforms and set the current ember and action.
if (action != NOTHING)
{
int i, solo = m_Fractorium->ui.CurrentXformCombo->property("soloxform").toInt();
if (solo != -1)
{
m_TempOpacities.resize(m_Ember.TotalXformCount());
for (i = 0; i < m_Ember.TotalXformCount(); i++)
{
m_TempOpacities[i] = m_Ember.GetTotalXform(i)->m_Opacity;
m_Ember.GetTotalXform(i)->m_Opacity = i == solo ? 1 : 0;
}
}
m_Renderer->SetEmber(m_Ember, action);
if (solo != -1)
{
for (i = 0; i < m_Ember.TotalXformCount(); i++)
{
m_Ember.GetTotalXform(i)->m_Opacity = m_TempOpacities[i];
}
}
}
//Determining if a completely new rendering process is being started.
bool iterBegin = ProcessState() == NONE;
if (iterBegin)
{
if (m_Renderer->RendererType() == CPU_RENDERER)
m_SubBatchCount = m_Fractorium->m_Settings->CpuSubBatch();
else if (m_Renderer->RendererType() == OPENCL_RENDERER)
m_SubBatchCount = m_Fractorium->m_Settings->OpenCLSubBatch();
m_Fractorium->m_ProgressBar->setValue(0);
m_Fractorium->m_RenderStatusLabel->setText("Starting");
}
//If the rendering process hasn't finished, render with the current specified action.
if (ProcessState() != ACCUM_DONE)
{
//if (m_Renderer->Run(m_FinalImage, 0) == RENDER_OK)//Full, non-incremental render for debugging.
if (m_Renderer->Run(m_FinalImage, 0, m_SubBatchCount, iterBegin) == RENDER_OK)//Force output on iterBegin.
{
//The amount to increment sub batch while rendering proceeds is purely empirical.
//Change later if better values can be derived/observed.
if (m_Renderer->RendererType() == OPENCL_RENDERER)
{
if (m_SubBatchCount < 3)//More than 3 with OpenCL gives a sluggish UI.
m_SubBatchCount++;
}
else
{
if (m_SubBatchCount < 5)
m_SubBatchCount++;
else if (m_SubBatchCount < 105)//More than 105 with CPU gives a sluggish UI.
m_SubBatchCount += 25;
}
//Rendering has finished, update final stats.
if (ProcessState() == ACCUM_DONE)
{
EmberStats stats = m_Renderer->Stats();
m_Fractorium->m_ProgressBar->setValue(100);
QString iters = QLocale(QLocale::English).toString(stats.m_Iters);
QString scaledQuality = QString::number((unsigned int)m_Renderer->ScaledQuality());
string renderTime = m_RenderElapsedTimer.Format(m_RenderElapsedTimer.Toc());
//Only certain status can be reported with OpenCL.
if (m_Renderer->RendererType() == OPENCL_RENDERER)
{
m_Fractorium->m_RenderStatusLabel->setText("Iters: " + iters + ". Scaled quality: " + scaledQuality + ". Total time: " + QString::fromStdString(renderTime));
}
else
{
double percent = (double)stats.m_Badvals / (double)stats.m_Iters;
QString badVals = QLocale(QLocale::English).toString(stats.m_Badvals);
QString badPercent = QLocale(QLocale::English).toString(percent * 100, 'f', 2);
m_Fractorium->m_RenderStatusLabel->setText("Iters: " + iters + ". Scaled quality: " + scaledQuality + ". Bad values: " + badVals + " (" + badPercent + "%). Total time: " + QString::fromStdString(renderTime));
}
if (m_LastEditWasUndoRedo && (m_UndoIndex == m_UndoList.size() - 1))//Traversing through undo list, reached the end, so put back in regular edit mode.
{
m_EditState = REGULAR_EDIT;
}
else if (m_EditState == REGULAR_EDIT)//Regular edit, just add to the end of the undo list.
{
m_UndoList.push_back(m_Ember);
m_UndoIndex = m_UndoList.size() - 1;
m_Fractorium->ui.ActionUndo->setEnabled(m_UndoList.size() > 1);
m_Fractorium->ui.ActionRedo->setEnabled(false);
if (m_UndoList.size() >= UNDO_SIZE)
m_UndoList.pop_front();
}
else if (!m_LastEditWasUndoRedo && m_UndoIndex != m_UndoList.size() - 1)//They were in the middle of the undo list, then did a manual edit, so clear the undo list.
{
ClearUndo();
m_UndoList.push_back(m_Ember);
}
m_LastEditWasUndoRedo = false;
m_Fractorium->UpdateHistogramBounds();//Mostly of engineering interest.
}
//Update the GL window on start because the output will be forced.
//Update it on finish because the rendering process is completely done.
if (iterBegin || ProcessState() == ACCUM_DONE)
{
if (m_FinalImage.size() == m_Renderer->FinalBufferSize())//Make absolutely sure the correct amount of data is passed.
gl->repaint();
//Uncomment for debugging kernel build and execution errors.
//m_Fractorium->ui.InfoRenderingTextEdit->setText(QString::fromStdString(m_Fractorium->m_Wrapper.DumpInfo()));
//if (RendererCL<T>* rendererCL = dynamic_cast<RendererCL<T>*>(m_Renderer.get()))
// m_Fractorium->ui.InfoRenderingTextEdit->setText(QString::fromStdString(rendererCL->IterKernel()));
}
}
else//Something went very wrong, show error report.
{
vector<string> errors = m_Renderer->ErrorReport();
success = false;
m_FailedRenders++;
m_Fractorium->m_RenderStatusLabel->setText("Rendering failed, see info tab. Try changing parameters.");
m_Fractorium->ErrorReportToQTextEdit(errors, m_Fractorium->ui.InfoRenderingTextEdit);
m_Renderer->ClearErrorReport();
if (m_FailedRenders >= 3)
{
m_Rendering = false;
StopRenderTimer(true);
m_Fractorium->m_RenderStatusLabel->setText("Rendering failed 3 or more times, stopping all rendering, see info tab. Try changing renderer types.");
memset(m_FinalImage.data(), 0, m_FinalImage.size());
if (m_Renderer->RendererType() == OPENCL_RENDERER)
((RendererCL<T>*)m_Renderer.get())->ClearFinal();
m_GLController->ClearWindow();
}
}
}
//Upon finishing, or having nothing to do, rest.
if (ProcessState() == ACCUM_DONE)
QThread::msleep(1);
m_Rendering = false;
return success;
}
/// <summary>
/// Stop rendering and initialize a new renderer, using the specified type.
/// Rendering will be left in a stopped state. The caller is responsible for restarting the render loop again.
/// </summary>
/// <param name="renderType">The type of render to create</param>
/// <param name="platform">The index platform of the platform to use</param>
/// <param name="device">The index device of the device to use</param>
/// <param name="outputTexID">The texture ID of the shared OpenGL texture if shared</param>
/// <param name="shared">True if shared with OpenGL, else false. Default: true.</param>
/// <returns>True if nothing went wrong, else false.</returns>
template <typename T>
bool FractoriumEmberController<T>::CreateRenderer(eRendererType renderType, unsigned int platform, unsigned int device, bool shared)
{
bool ok = true;
FractoriumSettings* s = m_Fractorium->m_Settings;
GLWidget* gl = m_Fractorium->ui.GLDisplay;
if (!m_Renderer.get() || (m_Renderer->RendererType() != renderType) || (m_Platform != platform) || (m_Device != device))
{
EmberReport emberReport;
vector<string> errorReport;
DeleteRenderer();//Delete the renderer and refresh the textures.
//Before starting, must take care of allocations.
gl->Allocate(true);//Forcing a realloc of the texture is necessary on AMD, but not on nVidia.
m_Renderer = auto_ptr<EmberNs::RendererBase>(::CreateRenderer<T, T>(renderType, platform, device, shared, gl->OutputTexID(), emberReport));
errorReport = emberReport.ErrorReport();
if (errorReport.empty())
{
m_Platform = platform;//Store values for re-creation later on.
m_Device = device;
m_OutputTexID = gl->OutputTexID();
m_Shared = shared;
}
else
{
ok = false;
QMessageBox::critical(m_Fractorium, "Renderer Creation Error", "Could not create requested renderer, fallback CPU renderer created. See info tab for details.");
m_Fractorium->ErrorReportToQTextEdit(errorReport, m_Fractorium->ui.InfoRenderingTextEdit);
}
}
if (m_Renderer.get())
{
m_RenderType = m_Renderer->RendererType();
if (m_RenderType == OPENCL_RENDERER && m_Fractorium->m_QualitySpin->value() < 30)
m_Fractorium->m_QualitySpin->setValue(30);
m_Renderer->Callback(this);
m_Renderer->NumChannels(4);//Always using 4 since the GL texture is RGBA.
m_Renderer->ReclaimOnResize(true);
m_Renderer->SetEmber(m_Ember);//Give it an initial ember, will be updated many times later.
m_Renderer->EarlyClip(s->EarlyClip());
m_Renderer->ThreadCount(s->ThreadCount());
m_Renderer->Transparency(s->Transparency());
if (m_Renderer->RendererType() == CPU_RENDERER)
m_Renderer->InteractiveFilter(s->CpuDEFilter() ? FILTER_DE : FILTER_LOG);
else
m_Renderer->InteractiveFilter(s->OpenCLDEFilter() ? FILTER_DE : FILTER_LOG);
m_FailedRenders = 0;
m_RenderElapsedTimer.Tic();
//Leave rendering in a stopped state. The caller is responsible for restarting the render loop again.
}
else
{
ok = false;
QMessageBox::critical(m_Fractorium, "Renderer Creation Error", "Creating a basic CPU renderer failed, something is catastrophically wrong. Exiting program.");
QApplication::quit();
}
return ok;
}
/// <summary>
/// Create a new renderer from the options.
/// </summary>
/// <returns>True if nothing went wrong, else false.</returns>
bool Fractorium::CreateRendererFromOptions()
{
bool ok = true;
bool useOpenCL = m_Wrapper.CheckOpenCL() && m_Settings->OpenCL();
//The most important option to process is what kind of renderer is desired, so do it first.
if (!m_Controller->CreateRenderer(useOpenCL ? OPENCL_RENDERER : CPU_RENDERER,
m_Settings->PlatformIndex(),
m_Settings->DeviceIndex()))
{
//If using OpenCL, will only get here if creating RendererCL failed, but creating a backup CPU Renderer succeeded.
QMessageBox::critical(this, "Renderer Creation Error", "Error creating renderer, most likely a GPU problem. Using CPU instead.");
m_Settings->OpenCL(false);
m_OptionsDialog->ui.OpenCLCheckBox->setChecked(false);
m_FinalRenderDialog->ui.FinalRenderOpenCLCheckBox->setChecked(false);
ok = false;
}
return ok;
}
/// <summary>
/// Create a new controller from the options.
/// This does not create the internal renderer or start the timers.
/// </summary>
/// <returns>True if successful, else false.</returns>
bool Fractorium::CreateControllerFromOptions()
{
bool ok = true;
size_t size =
#ifdef DO_DOUBLE
m_Settings->Double() ? sizeof(double) :
#endif
sizeof(float);
if (!m_Controller.get() || (m_Controller->SizeOfT() != size))
{
double hue = m_PaletteHueSpin->value();
double sat = m_PaletteSaturationSpin->value();
double bright = m_PaletteBrightnessSpin->value();
double con = m_PaletteContrastSpin->value();
double blur = m_PaletteBlurSpin->value();
double freq = m_PaletteFrequencySpin->value();
#ifdef DO_DOUBLE
Ember<double> ed;
EmberFile<double> efd;
Palette<double> tempPalette;
#else
Ember<float> ed;
EmberFile<float> efd;
Palette<float> tempPalette;
#endif
QModelIndex index = ui.LibraryTree->currentIndex();
//First check if a controller has already been created, and if so, save its embers and gracefully shut it down.
if (m_Controller.get())
{
m_Controller->CopyTempPalette(tempPalette);//Convert float to double or save double verbatim;
m_Controller->CopyEmber(ed);
m_Controller->CopyEmberFile(efd);
m_Controller->Shutdown();
}
#ifdef DO_DOUBLE
if (m_Settings->Double())
m_Controller = auto_ptr<FractoriumEmberControllerBase>(new FractoriumEmberController<double>(this));
else
#endif
m_Controller = auto_ptr<FractoriumEmberControllerBase>(new FractoriumEmberController<float>(this));
//Restore the ember and ember file.
if (m_Controller.get())
{
m_Controller->SetEmber(ed);//Convert float to double or set double verbatim;
m_Controller->SetEmberFile(efd);
//Template specific palette table and variations tree setup in controller constructor, but
//must manually setup the library tree here because it's after the embers were assigned.
m_Controller->FillLibraryTree(index.row());//Passing row re-selects the item that was previously selected.
m_Controller->SetTempPalette(tempPalette);//Restore palette.
m_PaletteHueSpin->SetValueStealth(hue);
m_PaletteSaturationSpin->SetValueStealth(sat);
m_PaletteBrightnessSpin->SetValueStealth(bright);
m_PaletteContrastSpin->SetValueStealth(con);
m_PaletteBlurSpin->SetValueStealth(blur);
m_PaletteFrequencySpin->SetValueStealth(freq);
m_Controller->PaletteAdjust();//Fills in the palette.
}
}
return m_Controller.get() != NULL;
}
/// <summary>
/// Start the render timer.
/// If a renderer has not been created yet, or differs form the options, it will first be created from the options.
/// </summary>
void Fractorium::StartRenderTimer()
{
//Starting the render timer, either for the first time
//or from a paused state, such as resizing or applying new options.
CreateControllerFromOptions();
if (m_Controller.get())
{
//On program startup, the renderer does not get initialized until now.
CreateRendererFromOptions();
if (m_Controller->Renderer())
m_Controller->StartRenderTimer();
}
}
/// <summary>
/// Idle timer event which calls the controller's Render() function.
/// </summary>
void Fractorium::IdleTimer() { m_Controller->Render(); }
/// <summary>
/// Thin wrapper to determine if the controllers have been properly initialized.
/// </summary>
/// <returns>True if the ember controller and GL controllers are both not NULL, else false.</returns>
bool Fractorium::ControllersOk() { return m_Controller.get() && m_Controller->GLController(); }

View File

@ -0,0 +1,239 @@
#include "FractoriumPch.h"
#include "FractoriumSettings.h"
/// <summary>
/// Constructor that passes the parent to the base and sets up reasonable defaults
/// if the settings file was not present or corrupted.
/// </summary>
/// <param name="parent">The parent widget</param>
FractoriumSettings::FractoriumSettings(QObject* parent)
: QSettings(QSettings::IniFormat, QSettings::UserScope, "Fractorium", "Fractorium", parent)
{
EnsureDefaults();
}
/// <summary>
/// Make sure options have reasonable values in them first.
/// </summary>
void FractoriumSettings::EnsureDefaults()
{
if (FinalWidth() == 0)
FinalWidth(1920);
if (FinalHeight() == 0)
FinalHeight(1080);
if (FinalQuality() == 0)
FinalQuality(1000);
if (FinalTemporalSamples() == 0)
FinalTemporalSamples(1000);
if (FinalSupersample() == 0)
FinalSupersample(2);
if (XmlWidth() == 0)
XmlWidth(1920);
if (XmlHeight() == 0)
XmlHeight(1080);
if (XmlTemporalSamples() == 0)
XmlTemporalSamples(1000);
if (XmlQuality() == 0)
XmlQuality(1000);
if (XmlSupersample() == 0)
XmlSupersample(2);
if (ThreadCount() == 0 || ThreadCount() > Timing::ProcessorCount())
ThreadCount(max(1, Timing::ProcessorCount() - 1));//Default to one less to keep the UI responsive for first time users.
if (FinalThreadCount() == 0 || FinalThreadCount() > Timing::ProcessorCount())
FinalThreadCount(Timing::ProcessorCount());
if (CpuSubBatch() < 1)
CpuSubBatch(10);
if (OpenCLSubBatch() < 1)
OpenCLSubBatch(1);
//There normally wouldn't be any more than 10 OpenCL platforms and devices
//on the system, so if a value greater than that is read, then the settings file
//was corrupted.
if (PlatformIndex() > 10)
PlatformIndex(0);
if (DeviceIndex() > 10)
DeviceIndex(0);
if (FinalScale() > SCALE_HEIGHT)
FinalScale(0);
if (FinalPlatformIndex() > 10)
FinalPlatformIndex(0);
if (FinalDeviceIndex() > 10)
FinalDeviceIndex(0);
if (OpenXmlExt() == "")
OpenXmlExt("Flame (*.flame)");
if (SaveXmlExt() == "")
SaveXmlExt("Flame (*.flame)");
if (OpenImageExt() == "")
OpenImageExt("Png (*.png)");
if (SaveImageExt() == "")
SaveImageExt("Png (*.png)");
if (FinalDoAllExt() != "jpg" && FinalDoAllExt() != "png")
FinalDoAllExt("png");
}
/// <summary>
/// Interactive renderer settings.
/// </summary>
bool FractoriumSettings::EarlyClip() { return value(EARLYCLIP).toBool(); }
void FractoriumSettings::EarlyClip(bool b) { setValue(EARLYCLIP, b); }
bool FractoriumSettings::Transparency() { return value(TRANSPARENCY).toBool(); }
void FractoriumSettings::Transparency(bool b) { setValue(TRANSPARENCY, b); }
bool FractoriumSettings::Double() { return value(DOUBLEPRECISION).toBool(); }
void FractoriumSettings::Double(bool b) { setValue(DOUBLEPRECISION, b); }
bool FractoriumSettings::OpenCL() { return value(OPENCL).toBool(); }
void FractoriumSettings::OpenCL(bool b) { setValue(OPENCL, b); }
unsigned int FractoriumSettings::PlatformIndex() { return value(PLATFORMINDEX).toUInt(); }
void FractoriumSettings::PlatformIndex(unsigned int i) { setValue(PLATFORMINDEX, i); }
unsigned int FractoriumSettings::DeviceIndex() { return value(DEVICEINDEX).toUInt(); }
void FractoriumSettings::DeviceIndex(unsigned int i) { setValue(DEVICEINDEX, i); }
unsigned int FractoriumSettings::ThreadCount() { return value(THREADCOUNT).toUInt(); }
void FractoriumSettings::ThreadCount(unsigned int i) { setValue(THREADCOUNT, i); }
bool FractoriumSettings::CpuDEFilter() { return value(CPUDEFILTER).toBool(); }
void FractoriumSettings::CpuDEFilter(bool b) { setValue(CPUDEFILTER, b); }
bool FractoriumSettings::OpenCLDEFilter() { return value(OPENCLDEFILTER).toBool(); }
void FractoriumSettings::OpenCLDEFilter(bool b) { setValue(OPENCLDEFILTER, b); }
unsigned int FractoriumSettings::CpuSubBatch() { return value(CPUSUBBATCH).toUInt(); }
void FractoriumSettings::CpuSubBatch(unsigned int b) { setValue(CPUSUBBATCH, b); }
unsigned int FractoriumSettings::OpenCLSubBatch() { return value(OPENCLSUBBATCH).toUInt(); }
void FractoriumSettings::OpenCLSubBatch(unsigned int b) { setValue(OPENCLSUBBATCH, b); }
/// <summary>
/// Final render settings.
/// </summary>
bool FractoriumSettings::FinalEarlyClip() { return value(FINALEARLYCLIP).toBool(); }
void FractoriumSettings::FinalEarlyClip(bool b) { setValue(FINALEARLYCLIP, b); }
bool FractoriumSettings::FinalTransparency() { return value(FINALTRANSPARENCY).toBool(); }
void FractoriumSettings::FinalTransparency(bool b) { setValue(FINALTRANSPARENCY, b); }
bool FractoriumSettings::FinalOpenCL() { return value(FINALOPENCL).toBool(); }
void FractoriumSettings::FinalOpenCL(bool b) { setValue(FINALOPENCL, b); }
bool FractoriumSettings::FinalDouble() { return value(FINALDOUBLEPRECISION).toBool(); }
void FractoriumSettings::FinalDouble(bool b) { setValue(FINALDOUBLEPRECISION, b); }
bool FractoriumSettings::FinalSaveXml() { return value(FINALSAVEXML).toBool(); }
void FractoriumSettings::FinalSaveXml(bool b) { setValue(FINALSAVEXML, b); }
bool FractoriumSettings::FinalDoAll() { return value(FINALDOALL).toBool(); }
void FractoriumSettings::FinalDoAll(bool b) { setValue(FINALDOALL, b); }
bool FractoriumSettings::FinalDoSequence() { return value(FINALDOSEQUENCE).toBool(); }
void FractoriumSettings::FinalDoSequence(bool b) { setValue(FINALDOSEQUENCE, b); }
bool FractoriumSettings::FinalKeepAspect() { return value(FINALKEEPASPECT).toBool(); }
void FractoriumSettings::FinalKeepAspect(bool b) { setValue(FINALKEEPASPECT, b); }
unsigned int FractoriumSettings::FinalScale() { return value(FINALSCALE).toUInt(); }
void FractoriumSettings::FinalScale(unsigned int i) { setValue(FINALSCALE, i); }
QString FractoriumSettings::FinalDoAllExt() { return value(FINALDOALLEXT).toString(); }
void FractoriumSettings::FinalDoAllExt(QString s) { setValue(FINALDOALLEXT, s); }
unsigned int FractoriumSettings::FinalPlatformIndex() { return value(FINALPLATFORMINDEX).toUInt(); }
void FractoriumSettings::FinalPlatformIndex(unsigned int i) { setValue(FINALPLATFORMINDEX, i); }
unsigned int FractoriumSettings::FinalDeviceIndex() { return value(FINALDEVICEINDEX).toUInt(); }
void FractoriumSettings::FinalDeviceIndex(unsigned int i) { setValue(FINALDEVICEINDEX, i); }
unsigned int FractoriumSettings::FinalThreadCount() { return value(FINALTHREADCOUNT).toUInt(); }
void FractoriumSettings::FinalThreadCount(unsigned int i) { setValue(FINALTHREADCOUNT, i); }
unsigned int FractoriumSettings::FinalWidth() { return value(FINALWIDTH).toUInt(); }
void FractoriumSettings::FinalWidth(unsigned int i) { setValue(FINALWIDTH, i); }
unsigned int FractoriumSettings::FinalHeight() { return value(FINALHEIGHT).toUInt(); }
void FractoriumSettings::FinalHeight(unsigned int i) { setValue(FINALHEIGHT, i); }
unsigned int FractoriumSettings::FinalQuality() { return value(FINALQUALITY).toUInt(); }
void FractoriumSettings::FinalQuality(unsigned int i) { setValue(FINALQUALITY, i); }
unsigned int FractoriumSettings::FinalTemporalSamples() { return value(FINALTEMPORALSAMPLES).toUInt(); }
void FractoriumSettings::FinalTemporalSamples(unsigned int i) { setValue(FINALTEMPORALSAMPLES, i); }
unsigned int FractoriumSettings::FinalSupersample() { return value(FINALSUPERSAMPLE).toUInt(); }
void FractoriumSettings::FinalSupersample(unsigned int i) { setValue(FINALSUPERSAMPLE, i); }
/// <summary>
/// Xml file saving settings.
/// </summary>
unsigned int FractoriumSettings::XmlWidth() { return value(XMLWIDTH).toUInt(); }
void FractoriumSettings::XmlWidth(unsigned int i) { setValue(XMLWIDTH, i); }
unsigned int FractoriumSettings::XmlHeight() { return value(XMLHEIGHT).toUInt(); }
void FractoriumSettings::XmlHeight(unsigned int i) { setValue(XMLHEIGHT, i); }
unsigned int FractoriumSettings::XmlTemporalSamples() { return value(XMLTEMPORALSAMPLES).toUInt(); }
void FractoriumSettings::XmlTemporalSamples(unsigned int i) { setValue(XMLTEMPORALSAMPLES, i); }
unsigned int FractoriumSettings::XmlQuality() { return value(XMLQUALITY).toUInt(); }
void FractoriumSettings::XmlQuality(unsigned int i) { setValue(XMLQUALITY, i); }
unsigned int FractoriumSettings::XmlSupersample() { return value(XMLSUPERSAMPLE).toUInt(); }
void FractoriumSettings::XmlSupersample(unsigned int i) { setValue(XMLSUPERSAMPLE, i); }
QString FractoriumSettings::Id() { return value(IDENTITYID).toString(); }
void FractoriumSettings::Id(QString s) { setValue(IDENTITYID, s); }
QString FractoriumSettings::Url() { return value(IDENTITYURL).toString(); }
void FractoriumSettings::Url(QString s) { setValue(IDENTITYURL, s); }
QString FractoriumSettings::Nick() { return value(IDENTITYNICK).toString(); }
void FractoriumSettings::Nick(QString s) { setValue(IDENTITYNICK, s); }
/// <summary>
/// General operations settings.
/// </summary>
QString FractoriumSettings::OpenFolder() { return value(OPENFOLDER).toString(); }
void FractoriumSettings::OpenFolder(QString s) { setValue(OPENFOLDER, s); }
QString FractoriumSettings::SaveFolder() { return value(SAVEFOLDER).toString(); }
void FractoriumSettings::SaveFolder(QString s) { setValue(SAVEFOLDER, s); }
QString FractoriumSettings::OpenXmlExt() { return value(OPENXMLEXT).toString(); }
void FractoriumSettings::OpenXmlExt(QString s) { setValue(OPENXMLEXT, s); }
QString FractoriumSettings::SaveXmlExt() { return value(SAVEXMLEXT).toString(); }
void FractoriumSettings::SaveXmlExt(QString s) { setValue(SAVEXMLEXT, s); }
QString FractoriumSettings::OpenImageExt() { return value(OPENIMAGEEXT).toString(); }
void FractoriumSettings::OpenImageExt(QString s) { setValue(OPENIMAGEEXT, s); }
QString FractoriumSettings::SaveImageExt() { return value(SAVEIMAGEEXT).toString(); }
void FractoriumSettings::SaveImageExt(QString s) { setValue(SAVEIMAGEEXT, s); }

View File

@ -0,0 +1,198 @@
#pragma once
#include "FractoriumPch.h"
/// <summary>
/// FractoriumSettings class.
/// </summary>
#define EARLYCLIP "render/earlyclip"
#define TRANSPARENCY "render/transparency"
#define OPENCL "render/opencl"
#define DOUBLEPRECISION "render/dp64"
#define PLATFORMINDEX "render/platformindex"
#define DEVICEINDEX "render/deviceindex"
#define THREADCOUNT "render/threadcount"
#define CPUDEFILTER "render/cpudefilter"
#define OPENCLDEFILTER "render/opencldefilter"
#define CPUSUBBATCH "render/cpusubbatch"
#define OPENCLSUBBATCH "render/openclsubbatch"
#define FINALEARLYCLIP "finalrender/earlyclip"
#define FINALTRANSPARENCY "finalrender/transparency"
#define FINALOPENCL "finalrender/opencl"
#define FINALDOUBLEPRECISION "finalrender/dp64"
#define FINALSAVEXML "finalrender/savexml"
#define FINALDOALL "finalrender/doall"
#define FINALDOSEQUENCE "finalrender/dosequence"
#define FINALKEEPASPECT "finalrender/keepaspect"
#define FINALSCALE "finalrender/scale"
#define FINALDOALLEXT "finalrender/doallext"
#define FINALPLATFORMINDEX "finalrender/platformindex"
#define FINALDEVICEINDEX "finalrender/deviceindex"
#define FINALTHREADCOUNT "finalrender/threadcount"
#define FINALWIDTH "finalrender/width"
#define FINALHEIGHT "finalrender/height"
#define FINALQUALITY "finalrender/quality"
#define FINALTEMPORALSAMPLES "finalrender/temporalsamples"
#define FINALSUPERSAMPLE "finalrender/supersample"
#define XMLWIDTH "xml/width"
#define XMLHEIGHT "xml/height"
#define XMLTEMPORALSAMPLES "xml/temporalsamples"
#define XMLQUALITY "xml/quality"
#define XMLSUPERSAMPLE "xml/supersample"
#define OPENFOLDER "path/open"
#define SAVEFOLDER "path/save"
#define OPENXMLEXT "file/openxmlext"
#define SAVEXMLEXT "file/savexmlext"
#define OPENIMAGEEXT "file/openimageext"
#define SAVEIMAGEEXT "file/saveimageext"
#define IDENTITYID "identity/id"
#define IDENTITYURL "identity/url"
#define IDENTITYNICK "identity/nick"
/// <summary>
/// Class for preserving various program options between
/// runs of Fractorium. Each of these generally corresponds
/// to items in the options dialog and the final render dialog.
/// </summary>
class FractoriumSettings : public QSettings
{
Q_OBJECT
public:
FractoriumSettings(QObject* parent);
void EnsureDefaults();
bool EarlyClip();
void EarlyClip(bool b);
bool Transparency();
void Transparency(bool b);
bool OpenCL();
void OpenCL(bool b);
bool Double();
void Double(bool b);
unsigned int PlatformIndex();
void PlatformIndex(unsigned int b);
unsigned int DeviceIndex();
void DeviceIndex(unsigned int b);
unsigned int ThreadCount();
void ThreadCount(unsigned int b);
bool CpuDEFilter();
void CpuDEFilter(bool b);
bool OpenCLDEFilter();
void OpenCLDEFilter(bool b);
unsigned int CpuSubBatch();
void CpuSubBatch(unsigned int b);
unsigned int OpenCLSubBatch();
void OpenCLSubBatch(unsigned int b);
bool FinalEarlyClip();
void FinalEarlyClip(bool b);
bool FinalTransparency();
void FinalTransparency(bool b);
bool FinalOpenCL();
void FinalOpenCL(bool b);
bool FinalDouble();
void FinalDouble(bool b);
bool FinalSaveXml();
void FinalSaveXml(bool b);
bool FinalDoAll();
void FinalDoAll(bool b);
bool FinalDoSequence();
void FinalDoSequence(bool b);
bool FinalKeepAspect();
void FinalKeepAspect(bool b);
unsigned int FinalScale();
void FinalScale(unsigned int i);
QString FinalDoAllExt();
void FinalDoAllExt(QString s);
unsigned int FinalPlatformIndex();
void FinalPlatformIndex(unsigned int b);
unsigned int FinalDeviceIndex();
void FinalDeviceIndex(unsigned int b);
unsigned int FinalThreadCount();
void FinalThreadCount(unsigned int b);
unsigned int FinalWidth();
void FinalWidth(unsigned int i);
unsigned int FinalHeight();
void FinalHeight(unsigned int i);
unsigned int FinalQuality();
void FinalQuality(unsigned int i);
unsigned int FinalTemporalSamples();
void FinalTemporalSamples(unsigned int i);
unsigned int FinalSupersample();
void FinalSupersample(unsigned int i);
unsigned int XmlWidth();
void XmlWidth(unsigned int i);
unsigned int XmlHeight();
void XmlHeight(unsigned int i);
unsigned int XmlTemporalSamples();
void XmlTemporalSamples(unsigned int i);
unsigned int XmlQuality();
void XmlQuality(unsigned int i);
unsigned int XmlSupersample();
void XmlSupersample(unsigned int i);
QString OpenFolder();
void OpenFolder(QString s);
QString SaveFolder();
void SaveFolder(QString s);
QString OpenXmlExt();
void OpenXmlExt(QString s);
QString SaveXmlExt();
void SaveXmlExt(QString s);
QString OpenImageExt();
void OpenImageExt(QString s);
QString SaveImageExt();
void SaveImageExt(QString s);
QString Id();
void Id(QString s);
QString Url();
void Url(QString s);
QString Nick();
void Nick(QString s);
};

View File

@ -0,0 +1,21 @@
#include "FractoriumPch.h"
#include "Fractorium.h"
/// <summary>
/// Initialize the toolbar UI.
/// </summary>
void Fractorium::InitToolbarUI()
{
//These aren't menus but duplicate menu functionality in a pseudo-toolbar.
connect(ui.SaveCurrentAsXmlButton, SIGNAL(clicked(bool)), this, SLOT(OnSaveCurrentAsXmlButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.SaveEntireFileAsXmlButton, SIGNAL(clicked(bool)), this, SLOT(OnSaveEntireFileAsXmlButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.SaveCurrentToOpenedFileButton, SIGNAL(clicked(bool)), this, SLOT(OnSaveCurrentToOpenedFileButtonClicked(bool)), Qt::QueuedConnection);
}
/// <summary>
/// Wrappers around calls to menu items.
/// </summary>
void Fractorium::OnSaveCurrentAsXmlButtonClicked(bool checked) { OnActionSaveCurrentAsXml(checked); }
void Fractorium::OnSaveEntireFileAsXmlButtonClicked(bool checked) { OnActionSaveEntireFileAsXml(checked); }
void Fractorium::OnSaveCurrentToOpenedFileButtonClicked(bool checked) { OnActionSaveCurrentToOpenedFile(checked); }

View File

@ -0,0 +1,345 @@
#include "FractoriumPch.h"
#include "Fractorium.h"
/// <summary>
/// Initialize the xforms UI.
/// </summary>
void Fractorium::InitXformsUI()
{
int spinHeight = 20, row = 0;
connect(ui.AddXformButton, SIGNAL(clicked(bool)), this, SLOT(OnAddXformButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.DuplicateXformButton, SIGNAL(clicked(bool)), this, SLOT(OnDuplicateXformButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.ClearXformButton, SIGNAL(clicked(bool)), this, SLOT(OnClearXformButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.DeleteXformButton, SIGNAL(clicked(bool)), this, SLOT(OnDeleteXformButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.AddFinalXformButton, SIGNAL(clicked(bool)), this, SLOT(OnAddFinalXformButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.CurrentXformCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(OnCurrentXformComboChanged(int)), Qt::QueuedConnection);
SetFixedTableHeader(ui.XformWeightNameTable->horizontalHeader());
//Use SetupSpinner() just to create the spinner, but use col of -1 to prevent it from being added to the table.
SetupSpinner<DoubleSpinBox, double>(ui.XformWeightNameTable, this, row, -1, m_XformWeightSpin, spinHeight, 0, 1000, 0.05, SIGNAL(valueChanged(double)), SLOT(OnXformWeightChanged(double)), false, 0, 1, 0);
m_XformWeightSpin->setDecimals(3);
m_XformWeightSpin->SmallStep(0.001);
m_XformWeightSpinnerButtonWidget = new SpinnerButtonWidget(m_XformWeightSpin, "=", 20, 19, ui.XformWeightNameTable);
m_XformWeightSpinnerButtonWidget->m_Button->setToolTip("Equalize weights");
m_XformWeightSpinnerButtonWidget->m_Button->setStyleSheet("text-align: center center");
connect(m_XformWeightSpinnerButtonWidget->m_Button, SIGNAL(clicked(bool)), this, SLOT(OnEqualWeightButtonClicked(bool)), Qt::QueuedConnection);
ui.XformWeightNameTable->setCellWidget(0, 0, m_XformWeightSpinnerButtonWidget);
ui.XformWeightNameTable->setItem(0, 1, new QTableWidgetItem());
connect(ui.XformWeightNameTable, SIGNAL(cellChanged(int, int)), this, SLOT(OnXformNameChanged(int, int)), Qt::QueuedConnection);
ui.CurrentXformCombo->setProperty("soloxform", -1);
}
/// <summary>
/// Get the current xform.
/// </summary>
/// <returns>The current xform as specified by the current xform combo box index. NULL if out of range (should never happen).</returns>
template <typename T>
Xform<T>* FractoriumEmberController<T>::CurrentXform()
{
return m_Ember.GetTotalXform(m_Fractorium->ui.CurrentXformCombo->currentIndex());
}
/// <summary>
/// Set the current xform to the index passed in.
/// </summary>
/// <param name="i">The index to set the current xform to</param>
void Fractorium::CurrentXform(unsigned int i)
{
if (i < ui.CurrentXformCombo->count())
ui.CurrentXformCombo->setCurrentIndex(i);
}
/// <summary>
/// Set the current xform and populate all GUI widgets.
/// Called when the current xform combo box index changes.
/// </summary>
/// <param name="index">The selected combo box index</param>
template <typename T>
void FractoriumEmberController<T>::CurrentXformComboChanged(int index)
{
if (Xform<T>* xform = m_Ember.GetTotalXform(index))
{
FillWithXform(xform);
m_GLController->SetSelectedXform(xform);
int solo = m_Fractorium->ui.CurrentXformCombo->property("soloxform").toInt();
m_Fractorium->ui.SoloXformCheckBox->blockSignals(true);
m_Fractorium->ui.SoloXformCheckBox->setChecked(solo == index);
m_Fractorium->ui.SoloXformCheckBox->blockSignals(false);
bool enable = !IsFinal(CurrentXform());
m_Fractorium->ui.DuplicateXformButton->setEnabled(enable);
m_Fractorium->m_XformWeightSpin->setEnabled(enable);
m_Fractorium->ui.SoloXformCheckBox->setEnabled(enable);
m_Fractorium->ui.AddFinalXformButton->setEnabled(enable);
}
}
void Fractorium::OnCurrentXformComboChanged(int index) { m_Controller->CurrentXformComboChanged(index); }
/// <summary>
/// Add an empty xform in the current ember and set it as the current xform.
/// Called when the add xform button is clicked.
/// Resets the rendering process.
/// </summary>
/// <param name="checked">Ignored</param>
template <typename T>
void FractoriumEmberController<T>::AddXform()
{
UpdateCurrentXform([&] (Xform<T>* xform)
{
Xform<T> newXform;
QComboBox* combo = m_Fractorium->ui.CurrentXformCombo;
newXform.m_Weight = 0.25;
newXform.m_ColorX = m_Rand.Frand01<T>();
m_Ember.AddXform(newXform);
m_Fractorium->FillXforms();
combo->setCurrentIndex(combo->count() - (m_Fractorium->HaveFinal() ? 2 : 1));//Set index to the last item before final.
});
}
void Fractorium::OnAddXformButtonClicked(bool checked) { m_Controller->AddXform(); }
/// <summary>
/// Duplicate the current xform in the current ember, and set it as the current xform.
/// Called when the duplicate xform button is clicked.
/// Resets the rendering process.
/// </summary>
/// <param name="checked">Ignored</param>
template <typename T>
void FractoriumEmberController<T>::DuplicateXform()
{
UpdateCurrentXform([&] (Xform<T>* xform)
{
QComboBox* combo = m_Fractorium->ui.CurrentXformCombo;
if (xform && !IsFinal(xform))
{
m_Ember.AddXform(*xform);
m_Fractorium->FillXforms();
combo->setCurrentIndex(combo->count() - (m_Fractorium->HaveFinal() ? 2 : 1));//Set index to the last item before final.
}
});
}
void Fractorium::OnDuplicateXformButtonClicked(bool checked) { m_Controller->DuplicateXform(); }
/// <summary>
/// Clear all variations from the current xform, affine, palette and xaos are left untouched.
/// Called when the clear xform button is clicked.
/// Resets the rendering process.
/// </summary>
/// <param name="checked">Ignored</param>
template <typename T>
void FractoriumEmberController<T>::ClearCurrentXform()
{
UpdateCurrentXform([&] (Xform<T>* xform)
{
xform->ClearAndDeleteVariations();
//Note xaos is left alone.
FillVariationTreeWithXform(xform);
});
}
void Fractorium::OnClearXformButtonClicked(bool checked) { m_Controller->ClearCurrentXform(); }
/// <summary>
/// Delete the current xform.
/// Will not delete the last remaining non-final xform.
/// Called when the delete xform button is clicked.
/// Resets the rendering process.
/// </summary>
/// <param name="checked">Ignored</param>
template <typename T>
void FractoriumEmberController<T>::DeleteCurrentXform()
{
UpdateCurrentXform([&] (Xform<T>* xform)
{
bool haveFinal = m_Fractorium->HaveFinal();
QComboBox* combo = m_Fractorium->ui.CurrentXformCombo;
int count = combo->count();
int index = combo->currentIndex();
//Do not allow deleting the only remaining non-final xform.
if (haveFinal && count <= 2 && index == 0)
return;
if (!haveFinal && count == 1)
return;
m_Ember.DeleteTotalXform(index);
m_Fractorium->FillXforms();
combo->setCurrentIndex(combo->count() - (haveFinal ? 2 : 1));//Set index to the last item before final.
});
}
void Fractorium::OnDeleteXformButtonClicked(bool checked) { m_Controller->DeleteCurrentXform(); }
/// <summary>
/// Add a final xform to the ember and set it as the current xform.
/// Will only take action if a final xform is not already present.
/// Called when the add final xform button is clicked.
/// Resets the rendering process.
/// </summary>
/// <param name="checked">Ignored</param>
template <typename T>
void FractoriumEmberController<T>::AddFinalXform()
{
QComboBox* combo = m_Fractorium->ui.CurrentXformCombo;
//Check to see if a final xform is already present.
if (!m_Fractorium->HaveFinal())
{
Xform<T> xform;
xform.AddVariation(new LinearVariation<T>());//Just a placeholder so other parts of the code don't see it as being empty.
m_Ember.SetFinalXform(xform);
combo->addItem("Final");
combo->setCurrentIndex(combo->count() - 1);//Set index to the last item.
UpdateRender();
}
}
void Fractorium::OnAddFinalXformButtonClicked(bool checked) { m_Controller->AddFinalXform(); }
/// <summary>
/// Set the weight of the current xform.
/// Called when weight spinner changes.
/// Resets the rendering process.
/// </summary>
/// <param name="d">The weight</param>
template <typename T>
void FractoriumEmberController<T>::XformWeightChanged(double d)
{
UpdateCurrentXform([&] (Xform<T>* xform)
{
xform->m_Weight = d;
SetNormalizedWeightText(xform);
});
}
void Fractorium::OnXformWeightChanged(double d) { m_Controller->XformWeightChanged(d); }
/// <summary>
/// Equalize the weights of all xforms in the ember.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::EqualizeWeights()
{
UpdateCurrentXform([&] (Xform<T>* xform)
{
m_Ember.EqualizeWeights();
m_Fractorium->m_XformWeightSpin->setValue(xform->m_Weight);//Will trigger an update, so pass false to updateRender below.
}, false);
}
void Fractorium::OnEqualWeightButtonClicked(bool checked) { m_Controller->EqualizeWeights(); }
/// <summary>
/// Set the name of the current xform.
/// Called when the user types in the name cell of the table.
/// </summary>
/// <param name="row">The row of the cell</param>
/// <param name="col">The col of the cell</param>
template <typename T>
void FractoriumEmberController<T>::XformNameChanged(int row, int col)
{
UpdateCurrentXform([&] (Xform<T>* xform)
{
int index = m_Ember.GetXformIndex(xform);
xform->m_Name = m_Fractorium->ui.XformWeightNameTable->item(row, col)->text().toStdString();
if (index != -1)
{
if (QTableWidgetItem* xformNameItem = m_Fractorium->ui.XaosTable->item(index, 0))
xformNameItem->setText(MakeXaosNameString(index));
}
}, false);
}
void Fractorium::OnXformNameChanged(int row, int col) { m_Controller->XformNameChanged(row, col); }
/// <summary>
/// Fill all GUI widgets with values from the passed in xform.
/// </summary>
/// <param name="xform">The xform whose values will be used to populate the widgets</param>
template <typename T>
void FractoriumEmberController<T>::FillWithXform(Xform<T>* xform)
{
m_Fractorium->m_XformWeightSpin->SetValueStealth(xform->m_Weight);
SetNormalizedWeightText(xform);
if (QTableWidgetItem* item = m_Fractorium->ui.XformWeightNameTable->item(0, 1))
{
m_Fractorium->ui.XformWeightNameTable->blockSignals(true);
item->setText(QString::fromStdString(xform->m_Name));
m_Fractorium->ui.XformWeightNameTable->blockSignals(false);
}
FillVariationTreeWithXform(xform);
FillColorWithXform(xform);
FillAffineWithXform(xform, true);
FillAffineWithXform(xform, false);
FillXaosWithCurrentXform();
}
/// <summary>
/// Set the normalized weight of the current xform as the suffix text of the weight spinner.
/// </summary>
/// <param name="xform">The current xform whose normalized weight will be shown</param>
template <typename T>
void FractoriumEmberController<T>::SetNormalizedWeightText(Xform<T>* xform)
{
if (xform)
{
int index = m_Ember.GetXformIndex(xform);
m_Ember.CalcNormalizedWeights(m_NormalizedWeights);
if (index != -1 && index < m_NormalizedWeights.size())
m_Fractorium->m_XformWeightSpin->setSuffix(QString(" (") + QString::number((double)m_NormalizedWeights[index], 'g', 3) + ")");
}
}
/// <summary>
/// Determine whether the specified xform is the final xform in the ember.
/// </summary>
/// <param name="xform">The xform to examine</param>
/// <returns>True if final, else false.</returns>
template <typename T>
bool FractoriumEmberController<T>::IsFinal(Xform<T>* xform)
{
return (m_Fractorium->HaveFinal() && (xform == m_Ember.FinalXform()));
}
/// <summary>
/// Fill the xforms combo box with the xforms in the current ember.
/// Select the first one and fill all widgets with its values.
/// </summary>
void Fractorium::FillXforms()
{
int spinHeight = 20;
QComboBox* combo = ui.CurrentXformCombo;
combo->blockSignals(true);
combo->clear();
for (int i = 0; i < m_Controller->XformCount(); i++)
combo->addItem(QString::number(i + 1));
if (m_Controller->UseFinalXform())
combo->addItem("Final");
combo->blockSignals(false);
combo->setCurrentIndex(0);
FillXaosTable();
OnSoloXformCheckBoxStateChanged(Qt::Unchecked);
OnCurrentXformComboChanged(0);//Make sure the event gets called, because it won't if the zero index is already selected.
}

View File

@ -0,0 +1,492 @@
#include "FractoriumPch.h"
#include "Fractorium.h"
/// <summary>
/// Initialize the xforms affine UI.
/// </summary>
void Fractorium::InitXformsAffineUI()
{
int row = 0, affinePrec = 6, spinHeight = 20;
double affineStep = 0.01, affineMin = std::numeric_limits<double>::lowest(), affineMax = std::numeric_limits<double>::max();
QTableWidget* table = ui.PreAffineTable;
SetFixedTableHeader(table->horizontalHeader(), QHeaderView::Stretch);//The designer continually clobbers these values, so must manually set them here.
SetFixedTableHeader(table->verticalHeader());
//Pre affine spinners.
SetupAffineSpinner(table, this, 0, 0, m_PreX1Spin, spinHeight, affineMin, affineMax, affineStep, affinePrec, SIGNAL(valueChanged(double)), SLOT(OnX1Changed(double)));
SetupAffineSpinner(table, this, 0, 1, m_PreX2Spin, spinHeight, affineMin, affineMax, affineStep, affinePrec, SIGNAL(valueChanged(double)), SLOT(OnX2Changed(double)));
SetupAffineSpinner(table, this, 1, 0, m_PreY1Spin, spinHeight, affineMin, affineMax, affineStep, affinePrec, SIGNAL(valueChanged(double)), SLOT(OnY1Changed(double)));
SetupAffineSpinner(table, this, 1, 1, m_PreY2Spin, spinHeight, affineMin, affineMax, affineStep, affinePrec, SIGNAL(valueChanged(double)), SLOT(OnY2Changed(double)));
SetupAffineSpinner(table, this, 2, 0, m_PreO1Spin, spinHeight, affineMin, affineMax, affineStep, affinePrec, SIGNAL(valueChanged(double)), SLOT(OnO1Changed(double)));
SetupAffineSpinner(table, this, 2, 1, m_PreO2Spin, spinHeight, affineMin, affineMax, affineStep, affinePrec, SIGNAL(valueChanged(double)), SLOT(OnO2Changed(double)));
table = ui.PostAffineTable;
SetFixedTableHeader(table->horizontalHeader(), QHeaderView::Stretch);//The designer continually clobbers these values, so must manually set them here.
SetFixedTableHeader(table->verticalHeader());
//Post affine spinners.
SetupAffineSpinner(table, this, 0, 0, m_PostX1Spin, spinHeight, affineMin, affineMax, affineStep, affinePrec, SIGNAL(valueChanged(double)), SLOT(OnX1Changed(double)));
SetupAffineSpinner(table, this, 0, 1, m_PostX2Spin, spinHeight, affineMin, affineMax, affineStep, affinePrec, SIGNAL(valueChanged(double)), SLOT(OnX2Changed(double)));
SetupAffineSpinner(table, this, 1, 0, m_PostY1Spin, spinHeight, affineMin, affineMax, affineStep, affinePrec, SIGNAL(valueChanged(double)), SLOT(OnY1Changed(double)));
SetupAffineSpinner(table, this, 1, 1, m_PostY2Spin, spinHeight, affineMin, affineMax, affineStep, affinePrec, SIGNAL(valueChanged(double)), SLOT(OnY2Changed(double)));
SetupAffineSpinner(table, this, 2, 0, m_PostO1Spin, spinHeight, affineMin, affineMax, affineStep, affinePrec, SIGNAL(valueChanged(double)), SLOT(OnO1Changed(double)));
SetupAffineSpinner(table, this, 2, 1, m_PostO2Spin, spinHeight, affineMin, affineMax, affineStep, affinePrec, SIGNAL(valueChanged(double)), SLOT(OnO2Changed(double)));
ui.PreRotateCombo->setValidator(new QDoubleValidator(ui.PreRotateCombo));
ui.PreMoveCombo->setValidator( new QDoubleValidator(ui.PreMoveCombo));
ui.PreScaleCombo->setValidator( new QDoubleValidator(ui.PreScaleCombo));
ui.PostRotateCombo->setValidator(new QDoubleValidator(ui.PostRotateCombo));
ui.PostMoveCombo->setValidator( new QDoubleValidator(ui.PostMoveCombo));
ui.PostScaleCombo->setValidator( new QDoubleValidator(ui.PostScaleCombo));
connect(ui.PreFlipHorizontalButton, SIGNAL(clicked(bool)), this, SLOT(OnFlipHorizontalButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PreFlipVerticalButton, SIGNAL(clicked(bool)), this, SLOT(OnFlipVerticalButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PreRotate90CButton, SIGNAL(clicked(bool)), this, SLOT(OnRotate90CButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PreRotate90CcButton, SIGNAL(clicked(bool)), this, SLOT(OnRotate90CcButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PreRotateCButton, SIGNAL(clicked(bool)), this, SLOT(OnRotateCButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PreRotateCcButton, SIGNAL(clicked(bool)), this, SLOT(OnRotateCcButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PreMoveUpButton, SIGNAL(clicked(bool)), this, SLOT(OnMoveUpButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PreMoveDownButton, SIGNAL(clicked(bool)), this, SLOT(OnMoveDownButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PreMoveLeftButton, SIGNAL(clicked(bool)), this, SLOT(OnMoveLeftButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PreMoveRightButton, SIGNAL(clicked(bool)), this, SLOT(OnMoveRightButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PreScaleDownButton, SIGNAL(clicked(bool)), this, SLOT(OnScaleDownButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PreScaleUpButton, SIGNAL(clicked(bool)), this, SLOT(OnScaleUpButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PreResetButton, SIGNAL(clicked(bool)), this, SLOT(OnResetAffineButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PostFlipHorizontalButton, SIGNAL(clicked(bool)), this, SLOT(OnFlipHorizontalButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PostFlipVerticalButton, SIGNAL(clicked(bool)), this, SLOT(OnFlipVerticalButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PostRotate90CcButton, SIGNAL(clicked(bool)), this, SLOT(OnRotate90CcButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PostRotateCcButton, SIGNAL(clicked(bool)), this, SLOT(OnRotateCcButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PostRotateCButton, SIGNAL(clicked(bool)), this, SLOT(OnRotateCButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PostRotate90CButton, SIGNAL(clicked(bool)), this, SLOT(OnRotate90CButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PostMoveUpButton, SIGNAL(clicked(bool)), this, SLOT(OnMoveUpButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PostMoveDownButton, SIGNAL(clicked(bool)), this, SLOT(OnMoveDownButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PostMoveLeftButton, SIGNAL(clicked(bool)), this, SLOT(OnMoveLeftButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PostMoveRightButton, SIGNAL(clicked(bool)), this, SLOT(OnMoveRightButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PostScaleDownButton, SIGNAL(clicked(bool)), this, SLOT(OnScaleDownButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PostScaleUpButton, SIGNAL(clicked(bool)), this, SLOT(OnScaleUpButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PostResetButton, SIGNAL(clicked(bool)), this, SLOT(OnResetAffineButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PreAffineGroupBox, SIGNAL(toggled(bool)), this, SLOT(OnAffineGroupBoxToggled(bool)), Qt::QueuedConnection);
connect(ui.PostAffineGroupBox, SIGNAL(toggled(bool)), this, SLOT(OnAffineGroupBoxToggled(bool)), Qt::QueuedConnection);
connect(ui.ShowPreAffineAllRadio, SIGNAL(toggled(bool)), this, SLOT(OnAffineDrawAllCurrentRadioButtonToggled(bool)), Qt::QueuedConnection);
connect(ui.ShowPreAffineCurrentRadio, SIGNAL(toggled(bool)), this, SLOT(OnAffineDrawAllCurrentRadioButtonToggled(bool)), Qt::QueuedConnection);
connect(ui.ShowPostAffineAllRadio, SIGNAL(toggled(bool)), this, SLOT(OnAffineDrawAllCurrentRadioButtonToggled(bool)), Qt::QueuedConnection);
connect(ui.ShowPostAffineCurrentRadio, SIGNAL(toggled(bool)), this, SLOT(OnAffineDrawAllCurrentRadioButtonToggled(bool)), Qt::QueuedConnection);
ui.PostAffineGroupBox->setChecked(true);//Flip it once to force the disabling of the group box.
ui.PostAffineGroupBox->setChecked(false);
}
/// <summary>
/// Helper for setting the value of a single pre/post affine coefficient.
/// Resets the rendering process.
/// </summary>
/// <param name="d">The value to set</param>
/// <param name="index">The index to set, 0-5, corresponding to a, b, c, d, e, f</param>
/// <param name="pre">True if pre affine, false if post affine.</param>
template <typename T>
void FractoriumEmberController<T>::AffineSetHelper(double d, int index, bool pre)
{
UpdateCurrentXform([&] (Xform<T>* xform)
{
QObject* objSender = m_Fractorium->sender();
DoubleSpinBox* spinBox = dynamic_cast<DoubleSpinBox*>(objSender);
if (spinBox)
{
Affine2D<T>* affine = pre ? &xform->m_Affine : &xform->m_Post;
switch (index)
{
case 0:
affine->A(spinBox->value());
break;
case 1:
affine->B(spinBox->value());
break;
case 2:
affine->C(spinBox->value());
break;
case 3:
affine->D(spinBox->value());
break;
case 4:
affine->E(spinBox->value());
break;
case 5:
affine->F(spinBox->value());
break;
}
}
});
}
/// <summary>
/// Pre and post affine spinner changed events.
/// Resets the rendering process.
/// </summary>
void Fractorium::OnX1Changed(double d) { m_Controller->AffineSetHelper(d, 0, sender() == m_PreX1Spin); }
void Fractorium::OnX2Changed(double d) { m_Controller->AffineSetHelper(d, 3, sender() == m_PreX2Spin); }
void Fractorium::OnY1Changed(double d) { m_Controller->AffineSetHelper(d, 1, sender() == m_PreY1Spin); }
void Fractorium::OnY2Changed(double d) { m_Controller->AffineSetHelper(d, 4, sender() == m_PreY2Spin); }
void Fractorium::OnO1Changed(double d) { m_Controller->AffineSetHelper(d, 2, sender() == m_PreO1Spin); }
void Fractorium::OnO2Changed(double d) { m_Controller->AffineSetHelper(d, 5, sender() == m_PreO2Spin); }
/// <summary>
/// Flip the current pre/post affine vertically and/or horizontally.
/// Resets the rendering process.
/// </summary>
/// <param name="horizontal">True to flip horizontally</param>
/// <param name="vertical">True to flip vertically</param>
/// <param name="pre">True if pre affine, else post affine.</param>
template <typename T>
void FractoriumEmberController<T>::FlipCurrentXform(bool horizontal, bool vertical, bool pre)
{
UpdateCurrentXform([&] (Xform<T>* xform)
{
Affine2D<T>* affine = pre ? &xform->m_Affine : &xform->m_Post;
if (horizontal)
{
affine->A(-affine->A());
affine->B(-affine->B());
if (!m_Fractorium->LocalPivot())
affine->C(-affine->C());
}
if (vertical)
{
affine->D(-affine->D());
affine->E(-affine->E());
if (!m_Fractorium->LocalPivot())
affine->F(-affine->F());
}
FillAffineWithXform(xform, pre);
});
}
void Fractorium::OnFlipHorizontalButtonClicked(bool checked) { m_Controller->FlipCurrentXform(true, false, sender() == ui.PreFlipHorizontalButton); }
void Fractorium::OnFlipVerticalButtonClicked(bool checked) { m_Controller->FlipCurrentXform(false, true, sender() == ui.PreFlipVerticalButton); }
/// <summary>
/// Rotate the current pre/post affine transform x degrees.
/// Resets the rendering process.
/// </summary>
/// <param name="angle">The angle to rotate by</param>
/// <param name="pre">True if pre affine, else post affine.</param>
template <typename T>
void FractoriumEmberController<T>::RotateCurrentXformByAngle(double angle, bool pre)
{
UpdateCurrentXform([&] (Xform<T>* xform)
{
Affine2D<T>* affine = pre ? &xform->m_Affine : &xform->m_Post;
affine->Rotate(angle);
FillAffineWithXform(xform, pre);
});
}
/// <summary>
/// Rotate the selected pre/post affine transform 90 degrees clockwise/counter clockwise.
/// Called when the rotate 90 c/cc pre/post buttons are clicked.
/// Resets the rendering process.
/// </summary>
/// <param name="checked">Ignored</param>
void Fractorium::OnRotate90CButtonClicked(bool checked) { m_Controller->RotateCurrentXformByAngle(90, sender() == ui.PreRotate90CButton); }
void Fractorium::OnRotate90CcButtonClicked(bool checked) { m_Controller->RotateCurrentXformByAngle(-90, sender() == ui.PreRotate90CcButton); }
/// <summary>
/// Rotate the selected pre/post affine transform x degrees clockwise.
/// Called when the rotate x c pre/post buttons are clicked.
/// Resets the rendering process.
/// </summary>
/// <param name="checked">Ignored</param>
void Fractorium::OnRotateCButtonClicked(bool checked)
{
bool ok;
bool pre = sender() == ui.PreRotateCButton;
QComboBox* combo = pre ? ui.PreRotateCombo : ui.PostRotateCombo;
double d = combo->currentText().toDouble(&ok);
if (ok)
m_Controller->RotateCurrentXformByAngle(d, pre);
}
/// <summary>
/// Rotate the selected pre/post affine transform x degrees counter clockwise.
/// Called when the rotate x cc pre/post buttons are clicked.
/// Resets the rendering process.
/// </summary>
/// <param name="checked">Ignored</param>
void Fractorium::OnRotateCcButtonClicked(bool checked)
{
bool ok;
bool pre = sender() == ui.PreRotateCcButton;
QComboBox* combo = pre ? ui.PreRotateCombo : ui.PostRotateCombo;
double d = combo->currentText().toDouble(&ok);
if (ok)
m_Controller->RotateCurrentXformByAngle(-d, pre);
}
/// <summary>
/// Move the current pre/post affine in the x and y directions by the specified amount.
/// Resets the rendering process.
/// </summary>
/// <param name="x">The x direction to move</param>
/// <param name="y">The y direction to move</param>
/// <param name="pre">True if pre affine, else post affine.</param>
template <typename T>
void FractoriumEmberController<T>::MoveCurrentXform(double x, double y, bool pre)
{
UpdateCurrentXform([&] (Xform<T>* xform)
{
Affine2D<T>* affine = pre ? &xform->m_Affine : &xform->m_Post;
affine->C(affine->C() + x);
affine->F(affine->F() + y);
FillAffineWithXform(xform, pre);
});
}
/// <summary>
/// Move the selected pre/post affine transform x units up.
/// Called when the move pre/post up buttons are clicked.
/// Resets the rendering process.
/// </summary>
/// <param name="checked">Ignored</param>
void Fractorium::OnMoveUpButtonClicked(bool checked)
{
bool ok;
bool pre = sender() == ui.PreMoveUpButton;
QComboBox* combo = pre ? ui.PreMoveCombo : ui.PostMoveCombo;
double d = combo->currentText().toDouble(&ok);
if (ok)
m_Controller->MoveCurrentXform(0, d, pre);
}
/// <summary>
/// Move the selected pre/post affine transform x units down.
/// Called when the move pre/post down buttons are clicked.
/// Resets the rendering process.
/// </summary>
/// <param name="checked">Ignored</param>
void Fractorium::OnMoveDownButtonClicked(bool checked)
{
bool ok;
bool pre = sender() == ui.PreMoveDownButton;
QComboBox* combo = pre ? ui.PreMoveCombo : ui.PostMoveCombo;
double d = combo->currentText().toDouble(&ok);
if (ok)
m_Controller->MoveCurrentXform(0, -d, pre);
}
/// <summary>
/// Move the selected pre/post affine transform x units left.
/// Called when the move pre/post left buttons are clicked.
/// Resets the rendering process.
/// </summary>
/// <param name="checked">Ignored</param>
void Fractorium::OnMoveLeftButtonClicked(bool checked)
{
bool ok;
bool pre = sender() == ui.PreMoveLeftButton;
QComboBox* combo = pre ? ui.PreMoveCombo : ui.PostMoveCombo;
double d = combo->currentText().toDouble(&ok);
if (ok)
m_Controller->MoveCurrentXform(-d, 0, pre);
}
/// <summary>
/// Move the selected pre/post affine transform x units right.
/// Called when the move pre/post right buttons are clicked.
/// Resets the rendering process.
/// </summary>
/// <param name="checked">Ignored</param>
void Fractorium::OnMoveRightButtonClicked(bool checked)
{
bool ok;
bool pre = sender() == ui.PreMoveRightButton;
QComboBox* combo = pre ? ui.PreMoveCombo : ui.PostMoveCombo;
double d = combo->currentText().toDouble(&ok);
if (ok)
m_Controller->MoveCurrentXform(d, 0, pre);
}
/// <summary>
/// Scale the current pre/post affine by the specified amount.
/// Resets the rendering process.
/// </summary>
/// <param name="scale">The scale value</param>
/// <param name="pre">True if pre affine, else post affine.</param>
template <typename T>
void FractoriumEmberController<T>::ScaleCurrentXform(double scale, bool pre)
{
UpdateCurrentXform([&] (Xform<T>* xform)
{
Affine2D<T>* affine = pre ? &xform->m_Affine : &xform->m_Post;
affine->A(affine->A() * scale);
affine->B(affine->B() * scale);
affine->D(affine->D() * scale);
affine->E(affine->E() * scale);
FillAffineWithXform(xform, pre);
});
}
/// <summary>
/// Scale the selected pre/post affine transform x units down.
/// Called when the scale pre/post down buttons are clicked.
/// Resets the rendering process.
/// </summary>
/// <param name="checked">Ignored</param>
void Fractorium::OnScaleDownButtonClicked(bool checked)
{
bool ok;
bool pre = sender() == ui.PreScaleDownButton;
QComboBox* combo = pre ? ui.PreScaleCombo : ui.PostScaleCombo;
double d = combo->currentText().toDouble(&ok);
if (ok)
m_Controller->ScaleCurrentXform(1.0 / (d / 100.0), pre);
}
/// <summary>
/// Scale the selected pre/post affine transform x units up.
/// Called when the scale pre/post up buttons are clicked.
/// Resets the rendering process.
/// </summary>
/// <param name="checked">Ignored</param>
void Fractorium::OnScaleUpButtonClicked(bool checked)
{
bool ok;
bool pre = sender() == ui.PreScaleUpButton;
QComboBox* combo = pre ? ui.PreScaleCombo : ui.PostScaleCombo;
double d = combo->currentText().toDouble(&ok);
if (ok)
m_Controller->ScaleCurrentXform(d / 100.0, pre);
}
/// <summary>
/// Reset pre/post affine to the identity matrix.
/// Called when reset pre/post affine buttons are clicked.
/// Resets the rendering process.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::ResetCurrentXformAffine(bool pre)
{
UpdateCurrentXform([&] (Xform<T>* xform)
{
Affine2D<T>* affine = pre ? &xform->m_Affine : &xform->m_Post;
affine->MakeID();
FillAffineWithXform(xform, pre);
});
}
/// <summary>
/// Reset pre/post affine to the identity matrix.
/// Called when reset pre/post affine buttons are clicked.
/// Resets the rendering process.
/// </summary>
void Fractorium::OnResetAffineButtonClicked(bool checked) { m_Controller->ResetCurrentXformAffine(sender() == ui.PreResetButton); }
/// <summary>
/// Fill the GUI with the pre/post affine xform values.
/// </summary>
/// <param name="xform">The xform to fill with</param>
/// <param name="pre">True if pre affine, else post affine.</param>
template <typename T>
void FractoriumEmberController<T>::FillAffineWithXform(Xform<T>* xform, bool pre)
{
if (pre)
{
m_Fractorium->m_PreX1Spin->SetValueStealth(xform->m_Affine.A());
m_Fractorium->m_PreY1Spin->SetValueStealth(xform->m_Affine.B());
m_Fractorium->m_PreO1Spin->SetValueStealth(xform->m_Affine.C());
m_Fractorium->m_PreX2Spin->SetValueStealth(xform->m_Affine.D());
m_Fractorium->m_PreY2Spin->SetValueStealth(xform->m_Affine.E());
m_Fractorium->m_PreO2Spin->SetValueStealth(xform->m_Affine.F());
}
else
{
m_Fractorium->m_PostX1Spin->SetValueStealth(xform->m_Post.A());
m_Fractorium->m_PostY1Spin->SetValueStealth(xform->m_Post.B());
m_Fractorium->m_PostO1Spin->SetValueStealth(xform->m_Post.C());
m_Fractorium->m_PostX2Spin->SetValueStealth(xform->m_Post.D());
m_Fractorium->m_PostY2Spin->SetValueStealth(xform->m_Post.E());
m_Fractorium->m_PostO2Spin->SetValueStealth(xform->m_Post.F());
}
}
/// <summary>
/// Trigger a redraw which will show or hide the circle affine transforms
/// based on whether each group box is checked or not.
/// Called when the group box check box for pre or post affine is checked.
/// </summary>
/// <param name="on">Ignored</param>
void Fractorium::OnAffineGroupBoxToggled(bool on)
{
ui.GLDisplay->update();
}
/// <summary>
/// Trigger a redraw which will show all, or only the current, circle affine transforms
/// based on which radio buttons are selected.
/// Called when and pre/post show all/current radio buttons are checked.
/// </summary>
/// <param name="on">Ignored</param>
void Fractorium::OnAffineDrawAllCurrentRadioButtonToggled(bool checked)
{
OnCurrentXformComboChanged(ui.CurrentXformCombo->currentIndex());
ui.GLDisplay->update();
}
/// <summary>
/// Setup a spinner to be placed in a table cell.
/// Special setup function for affine spinners which differs slightly from the regular
/// SetupSpinner() function.
/// </summary>
/// <param name="table">The table the spinner belongs to</param>
/// <param name="receiver">The receiver object</param>
/// <param name="row">The row in the table where this spinner resides</param>
/// <param name="col">The col in the table where this spinner resides</param>
/// <param name="spinBox">Double pointer to spin box which will hold the spinner upon exit</param>
/// <param name="height">The height of the spinner</param>
/// <param name="min">The minimum value of the spinner</param>
/// <param name="max">The maximum value of the spinner</param>
/// <param name="step">The step of the spinner</param>
/// <param name="prec">The precision of the spinner</param>
/// <param name="signal">The signal the spinner emits</param>
/// <param name="slot">The slot to receive the signal</param>
void Fractorium::SetupAffineSpinner(QTableWidget* table, const QObject* receiver, int row, int col, DoubleSpinBox*& spinBox, int height, double min, double max, double step, double prec, const char* signal, const char* slot)
{
spinBox = new DoubleSpinBox(table, height, step);
spinBox->setRange(min, max);
spinBox->setDecimals(prec);
table->setCellWidget(row, col, spinBox);
connect(spinBox, signal, receiver, slot, Qt::QueuedConnection);
spinBox->DoubleClick(true);
spinBox->DoubleClickNonZero(0);
spinBox->DoubleClickZero(1);
}
/// <summary>
/// GUI wrapper functions, getters only.
/// </summary>
bool Fractorium::DrawAllPre() { return ui.ShowPreAffineAllRadio->isChecked(); }
bool Fractorium::DrawAllPost() { return ui.ShowPostAffineAllRadio->isChecked(); }
bool Fractorium::LocalPivot() { return ui.LocalPivotRadio->isChecked(); }

View File

@ -0,0 +1,204 @@
#include "FractoriumPch.h"
#include "Fractorium.h"
/// <summary>
/// Initialize the xforms color UI.
/// </summary>
void Fractorium::InitXformsColorUI()
{
int spinHeight = 20, row = 0;
m_XformColorValueItem = new QTableWidgetItem();
ui.XformColorIndexTable->setItem(0, 0, m_XformColorValueItem);
m_PaletteRefItem = new QTableWidgetItem();
ui.XformPaletteRefTable->setItem(0, 0, m_PaletteRefItem);
ui.XformPaletteRefTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
connect(ui.XformPaletteRefTable->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), this, SLOT(OnXformRefPaletteResized(int, int, int)), Qt::QueuedConnection);
SetupSpinner<DoubleSpinBox, double>(ui.XformColorIndexTable, this, row, 1, m_XformColorIndexSpin, spinHeight, 0, 1, 0.01, SIGNAL(valueChanged(double)), SLOT(OnXformColorIndexChanged(double)), false, 0, 1, 0);
SetupSpinner<DoubleSpinBox, double>(ui.XformColorValuesTable, this, row, 1, m_XformColorSpeedSpin, spinHeight, -1, 1, 0.1, SIGNAL(valueChanged(double)), SLOT(OnXformColorSpeedChanged(double)), true, 0.5, 0.5, 0.5);
SetupSpinner<DoubleSpinBox, double>(ui.XformColorValuesTable, this, row, 1, m_XformOpacitySpin, spinHeight, 0, 1, 0.1, SIGNAL(valueChanged(double)), SLOT(OnXformOpacityChanged(double)), true, 1, 1, 0);
SetupSpinner<DoubleSpinBox, double>(ui.XformColorValuesTable, this, row, 1, m_XformDirectColorSpin, spinHeight, 0, 1, 0.1, SIGNAL(valueChanged(double)), SLOT(OnXformDirectColorChanged(double)), true, 1, 1, 0);
m_XformColorIndexSpin->setDecimals(3);
m_XformColorSpeedSpin->setDecimals(3);
m_XformOpacitySpin->setDecimals(3);
m_XformDirectColorSpin->setDecimals(3);
connect(ui.XformColorScroll, SIGNAL(valueChanged(int)), this, SLOT(OnXformScrollColorIndexChanged(int)), Qt::QueuedConnection);
connect(ui.SoloXformCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnSoloXformCheckBoxStateChanged(int)), Qt::QueuedConnection);
}
/// <summary>
/// Set the color index of the current xform.
/// Update the color index scrollbar to match.
/// Called when spinner in the color index cell in the palette ref table is changed.
/// Optionally resets the rendering process.
/// </summary>
/// <param name="d">The color index, 0-1/</param>
/// <param name="updateRender">True to reset the rendering process, else don't.</param>
template <typename T>
void FractoriumEmberController<T>::XformColorIndexChanged(double d, bool updateRender)
{
UpdateCurrentXform([&] (Xform<T>* xform)
{
QScrollBar* scroll = m_Fractorium->ui.XformColorScroll;
int scrollVal = d * scroll->maximum();
scroll->blockSignals(true);
scroll->setValue(scrollVal);
scroll->blockSignals(false);
SetCurrentXformColorIndex(d);
}, updateRender);
}
void Fractorium::OnXformColorIndexChanged(double d) { OnXformColorIndexChanged(d, true); }
void Fractorium::OnXformColorIndexChanged(double d, bool updateRender) { m_Controller->XformColorIndexChanged(d, updateRender); }
/// <summary>
/// Set the color index of the current xform.
/// Update the color index cell in the palette ref table to match.
/// Called when color index scrollbar is changed.
/// Resets the rendering process.
/// </summary>
/// <param name="d">The color index, 0-1.</param>
template <typename T>
void FractoriumEmberController<T>::XformScrollColorIndexChanged(int d)
{
UpdateCurrentXform([&] (Xform<T>* xform)
{
m_Fractorium->m_XformColorIndexSpin->setValue(d / (double)m_Fractorium->ui.XformColorScroll->maximum());//Will trigger an update.
}, false);
}
void Fractorium::OnXformScrollColorIndexChanged(int d) { m_Controller->XformScrollColorIndexChanged(d); }
/// <summary>
/// Set the color speed of the current xform.
/// Called when xform color speed spinner is changed.
/// Resets the rendering process.
/// </summary>
/// <param name="d">The color speed, -1-1.</param>
template <typename T>
void FractoriumEmberController<T>::XformColorSpeedChanged(double d) { UpdateCurrentXform([&] (Xform<T>* xform) { xform->m_ColorSpeed = d; }); }
void Fractorium::OnXformColorSpeedChanged(double d) { m_Controller->XformColorSpeedChanged(d); }
/// <summary>
/// Set the opacity of the current xform.
/// Called when xform opacity spinner is changed.
/// Resets the rendering process.
/// </summary>
/// <param name="d">The opacity, 0-1.</param>
template <typename T>
void FractoriumEmberController<T>::XformOpacityChanged(double d) { UpdateCurrentXform([&] (Xform<T>* xform) { xform->m_Opacity = d; }); }
void Fractorium::OnXformOpacityChanged(double d) { m_Controller->XformOpacityChanged(d); }
/// <summary>
/// Set the direct color percentage of the current xform.
/// Called when xform direct color spinner is changed.
/// Note this only affects xforms that include a dc_ variation.
/// Resets the rendering process.
/// </summary>
/// <param name="d">The direct color percentage, 0-1.</param>
template <typename T>
void FractoriumEmberController<T>::XformDirectColorChanged(double d) { UpdateCurrentXform([&] (Xform<T>* xform) { xform->m_DirectColor = d; }); }
void Fractorium::OnXformDirectColorChanged(double d) { m_Controller->XformDirectColorChanged(d); }
/// <summary>
/// Set whether the current xform should be rendered solo.
/// If checked, current is solo, if unchecked, none are solo.
/// Solo means that all other xforms will have their opacity temporarily
/// set to zero while rendering so that only the effect of current xform is visible.
/// This will not permanently alter the ember, as the temporary opacity values will be applied
/// right before rendering and reset right after.
/// Called when solo xform check box is checked.
/// Resets the rendering process.
/// </summary>
/// <param name="state">The state of the checkbox</param>
void Fractorium::OnSoloXformCheckBoxStateChanged(int state)
{
if (state == Qt::Checked)
{
ui.CurrentXformCombo->setProperty("soloxform", ui.CurrentXformCombo->currentIndex());
ui.SoloXformCheckBox->setText("Solo (" + QString::number(ui.CurrentXformCombo->currentIndex() + 1) + ")");
}
else if (state == Qt::Unchecked)
{
ui.CurrentXformCombo->setProperty("soloxform", -1);
ui.SoloXformCheckBox->setText("Solo");
}
m_Controller->UpdateRender();
}
/// <summary>
/// Redraw the palette ref table.
/// Called on resize.
/// </summary>
/// <param name="logicalIndex">Ignored</param>
/// <param name="oldSize">Ignored</param>
/// <param name="newSize">Ignored</param>
void Fractorium::OnXformRefPaletteResized(int logicalIndex, int oldSize, int newSize)
{
m_Controller->SetPaletteRefTable(NULL);
}
/// <summary>
/// Set the current xform color index spinner to the current xform's color index.
/// Set the color cell in the palette ref table.
/// </summary>
/// <param name="d">The index value to set, 0-1.</param>
template <typename T>
void FractoriumEmberController<T>::SetCurrentXformColorIndex(double d)
{
UpdateCurrentXform([&] (Xform<T>* xform)
{
xform->m_ColorX = Clamp<T>(d, 0, 1);
//Grab the current color from the index and assign it to the first cell of the first table.
v4T entry = m_Ember.m_Palette[Clamp<int>(d * COLORMAP_LENGTH_MINUS_1, 0, m_Ember.m_Palette.Size())];
entry.r *= 255;
entry.g *= 255;
entry.b *= 255;
QRgb rgb = (unsigned int)entry.r << 16 | (unsigned int)entry.g << 8 | (unsigned int)entry.b;
m_Fractorium->ui.XformColorIndexTable->item(0, 0)->setBackgroundColor(QColor::fromRgb(rgb));
}, false);
}
/// <summary>
/// Set the color index, speed and opacity spinners with the values of the current xform.
/// Set the cells of the palette ref table as well.
/// </summary>
/// <param name="xform">The xform whose values will be copied to the GUI</param>
template <typename T>
void FractoriumEmberController<T>::FillColorWithXform(Xform<T>* xform)
{
m_Fractorium->m_XformColorIndexSpin->SetValueStealth(xform->m_ColorX);
m_Fractorium->m_XformColorSpeedSpin->SetValueStealth(xform->m_ColorSpeed);
m_Fractorium->m_XformOpacitySpin->SetValueStealth(xform->m_Opacity);
m_Fractorium->m_XformDirectColorSpin->SetValueStealth(xform->m_DirectColor);
m_Fractorium->OnXformColorIndexChanged(xform->m_ColorX, false);//Had to call stealth before to avoid doing an update, now manually update related controls, still without doing an update.
}
/// <summary>
/// Set the palette reference table to the passed in pixmap
/// </summary>
/// <param name="pixmap">The pixmap</param>
void FractoriumEmberControllerBase::SetPaletteRefTable(QPixmap* pixmap)
{
QSize size(m_Fractorium->ui.XformPaletteRefTable->columnWidth(0), m_Fractorium->ui.XformPaletteRefTable->rowHeight(0) + 1);
if (pixmap)
{
m_Fractorium->m_PaletteRefItem->setData(Qt::DecorationRole, pixmap->scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
}
else if (!m_FinalPaletteImage.isNull())
{
QPixmap pixTemp = QPixmap::fromImage(m_FinalPaletteImage);
m_Fractorium->m_PaletteRefItem->setData(Qt::DecorationRole, pixTemp.scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
}
}

View File

@ -0,0 +1,316 @@
#include "FractoriumPch.h"
#include "Fractorium.h"
/// <summary>
/// Initialize the xforms variations UI.
/// </summary>
void Fractorium::InitXformsVariationsUI()
{
QTreeWidget* tree = ui.VariationsTree;
tree->clear();
tree->header()->setSectionsClickable(true);
connect(tree->header(), SIGNAL(sectionClicked(int)), this, SLOT(OnTreeHeaderSectionClicked(int)));
connect(ui.VariationsFilterLineEdit, SIGNAL(textChanged(const QString&)), this, SLOT(OnVariationsFilterLineEditTextChanged(const QString&)));
connect(ui.VariationsFilterLineEdit, SIGNAL(textChanged(const QString&)), this, SLOT(OnVariationsFilterLineEditTextChanged(const QString&)));
connect(ui.VariationsFilterClearButton, SIGNAL(clicked(bool)), this, SLOT(OnVariationsFilterClearButtonClicked(bool)));
//Setting dimensions in the designer with a layout is futile, so must hard code here.
tree->setColumnWidth(0, 160);
tree->setColumnWidth(1, 23);
}
/// <summary>
/// Dynamically populate the variation tree widget with VariationTreeWidgetItem and VariationTreeDoubleSpinBox
/// templated with the correct type.
/// This will clear any previous contents.
/// Called upon initialization, or controller type change.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::SetupVariationTree()
{
T fMin = TLOW;
T fMax = TMAX;
QSize hint0(75, 16);
QSize hint1(30, 16);
QTreeWidget* tree = m_Fractorium->ui.VariationsTree;
tree->clear();
tree->blockSignals(true);
for (size_t i = 0; i < m_VariationList.Size(); i++)
{
Variation<T>* var = m_VariationList.GetVariation(i);
ParametricVariation<T>* parVar = dynamic_cast<ParametricVariation<T>*>(var);
//First add the variation, with a spinner for its weight.
VariationTreeWidgetItem<T>* item = new VariationTreeWidgetItem<T>(tree);
VariationTreeDoubleSpinBox<T>* spinBox = new VariationTreeDoubleSpinBox<T>(tree, parVar ? parVar : var, "");
item->setText(0, QString::fromStdString(var->Name()));
item->setSizeHint(0, hint0);
item->setSizeHint(1, hint1);
spinBox->setRange(fMin, fMax);
spinBox->DoubleClick(true);
spinBox->DoubleClickZero(1);
spinBox->DoubleClickNonZero(0);
spinBox->SmallStep(0.001);
tree->setItemWidget(item, 1, spinBox);
m_Fractorium->connect(spinBox, SIGNAL(valueChanged(double)), SLOT(OnVariationSpinBoxValueChanged(double)), Qt::QueuedConnection);
//Check to see if the variation was parametric, and add a tree entry with a spinner for each parameter.
if (parVar)
{
ParamWithName<T>* params = parVar->Params();
for (size_t j = 0; j< parVar->ParamCount(); j++)
{
if (!params[j].IsPrecalc())
{
VariationTreeWidgetItem<T>* paramWidget = new VariationTreeWidgetItem<T>(item);
VariationTreeDoubleSpinBox<T>* varSpinBox = new VariationTreeDoubleSpinBox<T>(tree, parVar, params[j].Name());
paramWidget->setText(0, params[j].Name().c_str());
paramWidget->setSizeHint(0, hint0);
paramWidget->setSizeHint(1, hint1);
varSpinBox->setRange(params[j].Min(), params[j].Max());
varSpinBox->setValue(params[j].ParamVal());
varSpinBox->DoubleClick(true);
varSpinBox->DoubleClickZero(1);
varSpinBox->DoubleClickNonZero(0);
if (params[j].Type() == INTEGER || params[j].Type() == INTEGER_NONZERO)
{
varSpinBox->setSingleStep(1);
varSpinBox->Step(1);
varSpinBox->SmallStep(1);
}
tree->setItemWidget(paramWidget, 1, varSpinBox);
m_Fractorium->connect(varSpinBox, SIGNAL(valueChanged(double)), SLOT(OnVariationSpinBoxValueChanged(double)), Qt::QueuedConnection);
}
}
}
}
tree->blockSignals(false);
}
/// <summary>
/// Set every spinner in the variation tree, including params, to zero.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::ClearVariationsTree()
{
QTreeWidget* tree = m_Fractorium->ui.VariationsTree;
for (unsigned int i = 0; i < tree->topLevelItemCount(); i++)
{
QTreeWidgetItem* item = tree->topLevelItem(i);
VariationTreeDoubleSpinBox<T>* spinBox = dynamic_cast<VariationTreeDoubleSpinBox<T>*>(tree->itemWidget(item, 1));
spinBox->SetValueStealth(0);
for (unsigned int j = 0; j < item->childCount(); j++)//Iterate through all of the children, which will be the params.
{
if (spinBox = dynamic_cast<VariationTreeDoubleSpinBox<T>*>(tree->itemWidget(item->child(j), 1)))//Cast the child widget to the VariationTreeDoubleSpinBox type.
spinBox->SetValueStealth(0);
}
}
}
/// <summary>
/// Copy the value of a variation or param spinner to its corresponding value
/// in the currently selected xform.
/// Called when any spinner in the variations tree is changed.
/// Resets the rendering process.
/// </summary>
/// <param name="d">The spinner value</param>
template <typename T>
void FractoriumEmberController<T>::VariationSpinBoxValueChanged(double d)
{
QObject* objSender = m_Fractorium->sender();
QTreeWidget* tree = m_Fractorium->ui.VariationsTree;
VariationTreeDoubleSpinBox<T>* sender = dynamic_cast<VariationTreeDoubleSpinBox<T>*>(objSender);
Xform<T>* xform = m_Ember.GetTotalXform(m_Fractorium->ui.CurrentXformCombo->currentIndex());//Will retrieve normal xform or final if needed.
if (sender && xform)
{
Variation<T>* var = sender->GetVariation();//The variation attached to the sender, for reference only.
ParametricVariation<T>* parVar = dynamic_cast<ParametricVariation<T>*>(var);//The parametric cast of that variation.
Variation<T>* xformVar = xform->GetVariationByName(var->Name());//The corresponding variation in the currently selected xform.
QList<QTreeWidgetItem*> items = tree->findItems(QString::fromStdString(var->Name()), Qt::MatchExactly);
bool isParam = parVar && sender->IsParam();
if (isParam)
{
//Do not take action if the xform doesn't contain the variation which this param is part of.
if (ParametricVariation<T>* xformParVar = dynamic_cast<ParametricVariation<T>*>(xformVar))//The parametric cast of the xform's variation.
{
if (xformParVar->SetParamVal(sender->ParamName().c_str(), d))
{
UpdateRender();
}
}
}
else
{
//If they spun down to zero, and it wasn't a parameter item,
//and the current xform contained the variation, then remove the variation.
if (IsNearZero(d))
{
if (xformVar)
xform->DeleteVariationById(var->VariationId());
items[0]->setBackgroundColor(0, QColor(255, 255, 255));//Ensure background is always white if weight goes to zero.
}
else
{
if (xformVar)//The xform already contained this variation, which means they just went from a non-zero weight to another non-zero weight (the simple case).
{
xformVar->m_Weight = d;
}
else
{
//If the item wasn't a param and the xform did not contain this variation,
//it means they went from zero to a non-zero weight, so add a new copy of this xform.
Variation<T>* newVar = var->Copy();//Create a new one with default values.
newVar->m_Weight = d;
xform->AddVariation(newVar);
items[0]->setBackgroundColor(0, QColor(200, 200, 200));//Set background to gray when a variation has non-zero weight in this xform.
//If they've added a new parametric variation, then grab the values currently in the spinners
//for the child parameters and assign them to the newly added variation.
if (parVar)
{
ParametricVariation<T>* newParVar = dynamic_cast<ParametricVariation<T>*>(newVar);
if (!items.empty())//Get the tree widget for the parent variation.
{
for (int i = 0; i < items[0]->childCount(); i++)//Iterate through all of the children, which will be the params.
{
QTreeWidgetItem* childItem = items[0]->child(i);//Get the child.
QWidget* itemWidget = tree->itemWidget(childItem, 1);//Get the widget for the child.
if (VariationTreeDoubleSpinBox<T>* spinBox = dynamic_cast<VariationTreeDoubleSpinBox<T>*>(itemWidget))//Cast the widget to the VariationTreeDoubleSpinBox type.
{
string s = childItem->text(0).toStdString();//Use the name of the child, and the value of the spinner widget to assign the param.
newParVar->SetParamVal(s.c_str(), spinBox->value());
}
}
}
}
}
}
UpdateRender();
}
}
}
void Fractorium::OnVariationSpinBoxValueChanged(double d) { m_Controller->VariationSpinBoxValueChanged(d); }
/// <summary>
/// Fill the variation tree values from passed in xform and apply the current sorting mode.
/// Called when the currently selected xform changes.
/// </summary>
/// <param name="xform">The xform whose variation values will be used to fill the tree</param>
template <typename T>
void FractoriumEmberController<T>::FillVariationTreeWithXform(Xform<T>* xform)
{
QTreeWidget* tree = m_Fractorium->ui.VariationsTree;
for (unsigned int i = 0; i < tree->topLevelItemCount(); i++)
{
QTreeWidgetItem* item = tree->topLevelItem(i);
string varName = item->text(0).toStdString();
Variation<T>* var = xform->GetVariationByName(varName);//See if this variation in the tree was contained in the xform.
ParametricVariation<T>* parVar = dynamic_cast<ParametricVariation<T>*>(var);//Attempt cast to parametric variation for later.
ParametricVariation<T>* origParVar = dynamic_cast<ParametricVariation<T>*>(m_VariationList.GetVariation(varName));
QWidget* itemWidget = tree->itemWidget(item, 1);//Get the widget for the item.
if (VariationTreeDoubleSpinBox<T>* spinBox = dynamic_cast<VariationTreeDoubleSpinBox<T>*>(itemWidget))//Cast the widget to the VariationTreeDoubleSpinBox type.
{
spinBox->SetValueStealth(var ? var->m_Weight : 0);//If the variation was present, set the spin box to its weight, else zero.
item->setBackgroundColor(0, var ? QColor(200, 200, 200) : QColor(255, 255, 255));//Ensure background is always white if the value goes to zero, else gray if var present.
for (unsigned int j = 0; j < item->childCount(); j++)//Iterate through all of the children, which will be the params if it was a parametric variation.
{
T* param = NULL;
QTreeWidgetItem* childItem = item->child(j);//Get the child.
QWidget* childItemWidget = tree->itemWidget(childItem, 1);//Get the widget for the child.
if (VariationTreeDoubleSpinBox<T>* childSpinBox = dynamic_cast<VariationTreeDoubleSpinBox<T>*>(childItemWidget))//Cast the widget to the VariationTreeDoubleSpinBox type.
{
string s = childItem->text(0).toStdString();//Get the name of the child.
if (parVar)
{
if (param = parVar->GetParam(s.c_str()))//Retrieve pointer to the param.
childSpinBox->SetValueStealth(*param);
}
else if (origParVar)//Parametric variation was not present in this xform, so set child values to defaults.
{
if (param = origParVar->GetParam(s.c_str()))
childSpinBox->SetValueStealth(*param);
else
childSpinBox->SetValueStealth(0);//Will most likely never happen, but just to be safe.
}
}
}
}
}
m_Fractorium->OnTreeHeaderSectionClicked(m_Fractorium->m_VarSortMode);
}
/// <summary>
/// Change the sorting to be either by variation ID, or by weight.
/// If sorting by variation ID, repeated clicks will altername ascending or descending.
/// Called when user clicks the tree headers.
/// </summary>
/// <param name="logicalIndex">Column index of the header clicked. Sort by name if 0, sort by weight if 1.</param>
void Fractorium::OnTreeHeaderSectionClicked(int logicalIndex)
{
m_VarSortMode = logicalIndex;
ui.VariationsTree->sortItems(m_VarSortMode, m_VarSortMode == 0 ? Qt::AscendingOrder : Qt::DescendingOrder);
if (logicalIndex == 1)
ui.VariationsTree->scrollToTop();
}
/// <summary>
/// Apply the text in the variation filter text box to only show variations whose names
/// contain the substring.
/// Called when the user types in the variation filter text box.
/// </summary>
/// <param name="text">The text to filter on</param>
void Fractorium::OnVariationsFilterLineEditTextChanged(const QString& text)
{
QTreeWidget* tree = ui.VariationsTree;
tree->setUpdatesEnabled(false);
for (unsigned int i = 0; i < tree->topLevelItemCount(); i++)
{
QTreeWidgetItem* item = tree->topLevelItem(i);
QString varName = item->text(0);
item->setHidden(!varName.contains(text, Qt::CaseInsensitive));
}
OnTreeHeaderSectionClicked(m_VarSortMode);//Must re-sort every time the filter changes.
tree->setUpdatesEnabled(true);
}
/// <summary>
/// Clear the variation name filter, which will display all variations.
/// Called when clear variations filter button is clicked.
/// </summary>
/// <param name="checked">Ignored</param>
void Fractorium::OnVariationsFilterClearButtonClicked(bool checked)
{
ui.VariationsFilterLineEdit->clear();
}

View File

@ -0,0 +1,175 @@
#include "FractoriumPch.h"
#include "Fractorium.h"
/// <summary>
/// Initialize the xforms xaos UI.
/// </summary>
void Fractorium::InitXformsXaosUI()
{
connect(ui.XaosToRadio, SIGNAL(toggled(bool)), this, SLOT(OnXaosFromToToggled(bool)), Qt::QueuedConnection);
connect(ui.ClearXaosButton, SIGNAL(clicked(bool)), this, SLOT(OnClearXaosButtonClicked(bool)), Qt::QueuedConnection);
}
/// <summary>
/// Fill the xaos table with the values from the current xform.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::FillXaosWithCurrentXform()
{
Xform<T>* currXform = CurrentXform();
if (!IsFinal(currXform))
{
for (int i = 0; i < m_Ember.XformCount(); i++)
{
DoubleSpinBox* spinBox = dynamic_cast<DoubleSpinBox*>(m_Fractorium->ui.XaosTable->cellWidget(i, 1));
//Fill in values column.
if (m_Fractorium->ui.XaosToRadio->isChecked())//"To": Single xform, advance index.
spinBox->SetValueStealth(currXform->Xaos(i));
else//"From": Advance xforms, single index.
if (currXform = m_Ember.GetXform(i))
spinBox->SetValueStealth(currXform->Xaos(m_Fractorium->ui.CurrentXformCombo->currentIndex()));
//Fill in name column.
Xform<T>* xform = m_Ember.GetXform(i);
QTableWidgetItem* xformNameItem = m_Fractorium->ui.XaosTable->item(i, 0);
if (xform && xformNameItem)
xformNameItem->setText(MakeXaosNameString(i));
}
}
m_Fractorium->ui.XaosTable->setEnabled(!IsFinal(currXform));//Disable if final, else enable.
}
/// <summary>
/// Create and return a xaos name string.
/// </summary>
/// <param name="i">The index of the xform whose xaos will be used</param>
/// <returns>The xaos name string</returns>
template <typename T>
QString FractoriumEmberController<T>::MakeXaosNameString(unsigned int i)
{
Xform<T>* xform = m_Ember.GetXform(i);
QString name;
if (xform)
{
int i = m_Ember.GetXformIndex(xform) + 1;//GUI is 1 indexed to avoid confusing the user.
int curr = m_Fractorium->ui.CurrentXformCombo->currentIndex() + 1;
if (i != -1)
{
if (m_Fractorium->ui.XaosToRadio->isChecked())
name = QString("From ") + QString::number(curr) + QString(" To ") + QString::number(i);
else
name = QString("From ") + QString::number(i) + QString(" To ") + QString::number(curr);
//if (xform->m_Name != "")
// name = name + " (" + QString::fromStdString(xform->m_Name) + ")";
}
}
return name;
}
/// <summary>
/// Set the xaos value.
/// Called when any xaos spinner is changed.
/// Different action taken based on the state of to/from radio button.
/// Resets the rendering process.
/// </summary>
/// <param name="sender">The DoubleSpinBox that triggered this event</param>
template <typename T>
void FractoriumEmberController<T>::XaosChanged(DoubleSpinBox* sender)
{
UpdateCurrentXform([&] (Xform<T>* xform)
{
QTableWidget* xaosTable = m_Fractorium->ui.XaosTable;
if (!IsFinal(xform))//This should never get called for the final xform because the table will be disabled, but check just to be safe.
{
for (int i = 0; i < xaosTable->rowCount(); i++)//Find the spin box that triggered the event.
{
DoubleSpinBox* spinBox = dynamic_cast<DoubleSpinBox*>(xaosTable->cellWidget(i, 1));
if (spinBox == sender)
{
if (m_Fractorium->ui.XaosToRadio->isChecked())//"To": Single xform, advance index.
{
xform->SetXaos(i, spinBox->value());
}
else//"From": Advance xforms, single index.
{
if (xform = m_Ember.GetXform(i))//Single = is intentional.
xform->SetXaos(m_Fractorium->ui.CurrentXformCombo->currentIndex(), spinBox->value());
}
break;
}
}
}
});
}
void Fractorium::OnXaosChanged(double d)
{
if (DoubleSpinBox* senderSpinBox = dynamic_cast<DoubleSpinBox*>(this->sender()))
m_Controller->XaosChanged(senderSpinBox);
}
/// <summary>
/// Update xaos display to use either "to" or "from" logic.
/// Called when xaos to/from radio buttons checked.
/// </summary>
/// <param name="checked">Ignored</param>
void Fractorium::OnXaosFromToToggled(bool checked)
{
m_Controller->FillXaosWithCurrentXform();
}
/// <summary>
/// Clear xaos table, recreate all spinners based on the xaos used by the current xform in the current ember.
/// Called every time the current xform changes.
/// </summary>
void Fractorium::FillXaosTable()
{
int spinHeight = 20;
ui.XaosTable->setRowCount(m_Controller->XformCount());//This will grow or shrink the number of rows and call the destructor for previous DoubleSpinBoxes.
for (int i = 0; i < m_Controller->XformCount(); i++)
{
DoubleSpinBox* spinBox = new DoubleSpinBox(ui.XaosTable, spinHeight, 0.1);
QTableWidgetItem* xformNameItem = new QTableWidgetItem(m_Controller->MakeXaosNameString(i));
spinBox->DoubleClick(true);
spinBox->DoubleClickZero(1);
spinBox->DoubleClickNonZero(0);
ui.XaosTable->setItem(i, 0, xformNameItem);
ui.XaosTable->setCellWidget(i, 1, spinBox);
connect(spinBox, SIGNAL(valueChanged(double)), this, SLOT(OnXaosChanged(double)), Qt::QueuedConnection);
}
}
/// <summary>
/// Clear all xaos from the current ember.
/// Called when xaos to/from radio buttons checked.
/// </summary>
/// <param name="checked">Ignored</param>
template <typename T>
void FractoriumEmberController<T>::ClearXaos()
{
UpdateCurrentXform([&] (Xform<T>* xform)
{
m_Ember.ClearXaos();
});
//Can't just call FillXaosWithCurrentXform() because the current xform might the final.
for (int i = 0; i < m_Ember.XformCount(); i++)
if (DoubleSpinBox* spinBox = dynamic_cast<DoubleSpinBox*>(m_Fractorium->ui.XaosTable->cellWidget(i, 1)))
spinBox->SetValueStealth(1.0);
}
void Fractorium::OnClearXaosButtonClicked(bool checked) { m_Controller->ClearXaos(); }

View File

@ -0,0 +1,254 @@
#include "FractoriumPch.h"
#include "GLEmberController.h"
#include "FractoriumEmberController.h"
#include "Fractorium.h"
#include "GLWidget.h"
/// <summary>
/// Constructor which assigns pointers to the main window and the GLWidget.
/// </summary>
/// <param name="fractorium">Pointer to the main window</param>
/// <param name="glWidget">Pointer to the GLWidget</param>
GLEmberControllerBase::GLEmberControllerBase(Fractorium* fractorium, GLWidget* glWidget)
{
m_Fractorium = fractorium;
m_GL = glWidget;
m_AffineType = AffinePre;
m_HoverType = HoverNone;
m_DragState = DragNone;
m_DragModifier = 0;
}
/// <summary>
/// Empty destructor which does nothing.
/// </summary>
GLEmberControllerBase::~GLEmberControllerBase() { }
/// <summary>
/// Constructor which passes the pointers to the main window the GLWidget to the base,
/// then assigns the pointer to the parent controller.
/// </summary>
/// <param name="fractorium">Pointer to the main window</param>
/// <param name="glWidget">Pointer to the GLWidget</param>
/// <param name="controller">Pointer to the parent controller of the same template type</param>
template <typename T>
GLEmberController<T>::GLEmberController(Fractorium* fractorium, GLWidget* glWidget, FractoriumEmberController<T>* controller)
: GLEmberControllerBase(fractorium, glWidget)
{
GridStep = T(1.0 / 8.0);
m_FractoriumEmberController = controller;
m_HoverXform = NULL;
m_SelectedXform = NULL;
m_CenterDownX = 0;
m_CenterDownY = 0;
}
/// <summary>
/// Empty destructor which does nothing.
/// </summary>
template <typename T>
GLEmberController<T>::~GLEmberController() { }
/// <summary>
/// Check that the final output size of the current ember matches the dimensions passed in.
/// </summary>
/// <param name="w">The width to compare to</param>
/// <param name="h">The height to compare to</param>
/// <returns>True if any don't match, else false if they are both equal.</returns>
template <typename T>
bool GLEmberController<T>::CheckForSizeMismatch(int w, int h)
{
return (m_FractoriumEmberController->FinalRasW() != w || m_FractoriumEmberController->FinalRasH() != h);
}
/// <summary>
/// Calculate the scale.
/// Used when dragging the right mouse button.
/// </summary>
/// <returns>The distance dragged in pixels</returns>
template <typename T>
T GLEmberController<T>::CalcScale()
{
//Can't operate using world coords here because every time scale changes, the world bounds change.
//So must instead calculate distance traveled based on window coords, which do not change outside of resize events.
v2T windowCenter((T)m_GL->width() / T(2), (T)m_GL->height() / T(2));
v2T windowMousePosDistanceFromCenter(m_MousePos.x - windowCenter.x, m_MousePos.y - windowCenter.y);
v2T windowMouseDownDistanceFromCenter(m_MouseDownPos.x - windowCenter.x, m_MouseDownPos.y - windowCenter.y);
T lengthMousePosFromCenterInPixels = glm::length(windowMousePosDistanceFromCenter);
T lengthMouseDownFromCenterInPixels = glm::length(windowMouseDownDistanceFromCenter);
return lengthMousePosFromCenterInPixels - lengthMouseDownFromCenterInPixels;
}
/// <summary>
/// Calculate the rotation.
/// Used when dragging the right mouse button.
/// </summary>
/// <returns>The angular distance rotated from -180-180</returns>
template <typename T>
T GLEmberController<T>::CalcRotation()
{
T rotStart = NormalizeDeg180<T>(T(90) - (atan2(-m_MouseDownWorldPos.y, m_MouseDownWorldPos.x) * RAD_2_DEG_T));
T rot = NormalizeDeg180<T>(T(90) - (atan2(-m_MouseWorldPos.y, m_MouseWorldPos.x) * RAD_2_DEG_T));
return rotStart - rot;
}
/// <summary>
/// Snap the passed in world cartesian coordinate to the grid for rotation, scale or translation.
/// </summary>
/// <param name="vec">The world cartesian coordinate to be snapped</param>
/// <returns>The snapped world cartesian coordinate</returns>
template <typename T>
typename v3T GLEmberController<T>::SnapToGrid(v3T& vec)
{
v3T ret;
ret.x = glm::round(vec.x / GridStep) * GridStep;
ret.y = glm::round(vec.y / GridStep) * GridStep;
return ret;
}
/// <summary>
/// Snap the passed in world cartesian coordinate to the grid for rotation only.
/// </summary>
/// <param name="vec">The world cartesian coordinate to be snapped</param>
/// <param name="divisions">The divisions of a circle to use for snapping</param>
/// <returns>The snapped world cartesian coordinate</returns>
template <typename T>
typename v3T GLEmberController<T>::SnapToNormalizedAngle(v3T& vec, unsigned int divisions)
{
T rsq, theta;
T bestRsq = numeric_limits<T>::max();
v3T c, best;
best.x = 1;
best.y = 0;
for (unsigned int i = 0; i < divisions; i++)
{
theta = 2.0 * M_PI * (T)i / (T)divisions;
c.x = cos(theta);
c.y = sin(theta);
rsq = glm::distance(vec, c);
if (rsq < bestRsq)
{
best = c;
bestRsq = rsq;
}
}
return best;
}
/// <summary>
/// Convert raster window coordinates to world cartesian coordinates.
/// </summary>
/// <param name="v">The window coordinates to convert</param>
/// <param name="flip">True to flip vertically, else don't.</param>
/// <returns>The converted world cartesian coordinates</returns>
template <typename T>
typename v3T GLEmberController<T>::WindowToWorld(v3T& v, bool flip)
{
v3T mouse(v.x, flip ? m_Viewport[3] - v.y : v.y, 0);//Must flip y because in OpenGL, 0,0 is bottom left, but in windows, it's top left.
v3T newCoords = glm::unProject(mouse, m_Modelview, m_Projection, m_Viewport);//Perform the calculation.
newCoords.z = 0;//For some reason, unProject() always comes back with the z coordinate as something other than 0. It should be 0 at all times.
return newCoords;
}
/// <summary>
/// Template specialization for querying the viewport, modelview and projection
/// matrices as floats.
/// </summary>
template <>
void GLEmberController<float>::QueryVMP()
{
m_GL->glGetIntegerv(GL_VIEWPORT, glm::value_ptr(m_Viewport));
m_GL->glGetFloatv(GL_MODELVIEW_MATRIX, glm::value_ptr(m_Modelview));
m_GL->glGetFloatv(GL_PROJECTION_MATRIX, glm::value_ptr(m_Projection));
}
#ifdef DO_DOUBLE
/// <summary>
/// Template specialization for querying the viewport, modelview and projection
/// matrices as doubles.
/// </summary>
template <>
void GLEmberController<double>::QueryVMP()
{
m_GL->glGetIntegerv(GL_VIEWPORT, glm::value_ptr(m_Viewport));
m_GL->glGetDoublev(GL_MODELVIEW_MATRIX, glm::value_ptr(m_Modelview));
m_GL->glGetDoublev(GL_PROJECTION_MATRIX, glm::value_ptr(m_Projection));
}
#endif
/// <summary>
/// Template specialization for multiplying the current matrix
/// by an m4<float>.
/// </summary>
template <>
void GLEmberController<float>::MultMatrix(glm::detail::tmat4x4<float, glm::defaultp>& mat)
{
m_GL->glMultMatrixf(glm::value_ptr(mat));
}
#ifdef DO_DOUBLE
/// <summary>
/// Template specialization for multiplying the current matrix
/// by an m4<double>.
/// </summary>
template <>
void GLEmberController<double>::MultMatrix(glm::detail::tmat4x4<double, glm::defaultp>& mat)
{
m_GL->glMultMatrixd(glm::value_ptr(mat));
}
#endif
/// <summary>
/// Query the matrices currently being used.
/// Debugging function, unused.
/// </summary>
/// <param name="print">True to print values, else false.</param>
template <typename T>
void GLEmberController<T>::QueryMatrices(bool print)
{
RendererBase* renderer = m_FractoriumEmberController->Renderer();
if (renderer)
{
double unitX = fabs(renderer->UpperRightX(false) - renderer->LowerLeftX(false)) / 2.0;
double unitY = fabs(renderer->UpperRightY(false) - renderer->LowerLeftY(false)) / 2.0;
m_GL->glMatrixMode(GL_PROJECTION);
m_GL->glPushMatrix();
m_GL->glLoadIdentity();
m_GL->glOrtho(-unitX, unitX, -unitY, unitY, -1, 1);
m_GL->glMatrixMode(GL_MODELVIEW);
m_GL->glPushMatrix();
m_GL->glLoadIdentity();
QueryVMP();
m_GL->glMatrixMode(GL_PROJECTION);
m_GL->glPopMatrix();
m_GL->glMatrixMode(GL_MODELVIEW);
m_GL->glPopMatrix();
if (print)
{
for (int i = 0; i < 4; i++)
qDebug() << "Viewport[" << i << "] = " << m_Viewport[i] << endl;
for (int i = 0; i < 16; i++)
qDebug() << "Modelview[" << i << "] = " << glm::value_ptr(m_Modelview)[i] << endl;
for (int i = 0; i < 16; i++)
qDebug() << "Projection[" << i << "] = " << glm::value_ptr(m_Projection)[i] << endl;
}
}
}

View File

@ -0,0 +1,146 @@
#pragma once
#include "FractoriumPch.h"
/// <summary>
/// GLEmberControllerBase and GLEmberController<T> classes.
/// </summary>
/// <summary>
/// Use/draw pre or post affine transform.
/// </summary>
enum eAffineType { AffinePre, AffinePost };
/// <summary>
/// Hovering over nothing, the x axis, the y axis or the center.
/// </summary>
enum eHoverType { HoverNone, HoverXAxis, HoverYAxis, HoverTranslation };
/// <summary>
/// Dragging an affine transform or panning, rotating or scaling the image.
/// </summary>
enum eDragState { DragNone, DragPanning, DragDragging, DragRotateScale };
/// <summary>
/// Dragging with no keys pressed, shift, control or alt.
/// </summary>
enum eDragModifier { DragModNone = 0x00, DragModShift = 0x01, DragModControl = 0x02, DragModAlt = 0x04 };
/// <summary>
/// GLController, FractoriumEmberController, GLWidget and Fractorium need each other, but each can't all include the other.
/// So GLWidget includes this file, and GLWidget, FractoriumEmberController and Fractorium are declared as forward declarations here.
/// </summary>
class GLWidget;
class Fractorium;
template <typename T> class FractoriumEmberController;
/// <summary>
/// GLEmberControllerBase serves as a non-templated base class with virtual
/// functions which will be overridden in a derived class that takes a template parameter.
/// The controller serves as a way to access both the GLWidget and the underlying ember
/// objects through an interface that doesn't require template argument, but does allow
/// templated objects to be used underneath.
/// The functions not implemented in this file can be found in GLWidget.cpp near the area of code which uses them.
/// </summary>
class GLEmberControllerBase
{
public:
GLEmberControllerBase(Fractorium* fractorium, GLWidget* glWidget);
virtual ~GLEmberControllerBase();
void ClearDrag();
bool Allocate(bool force = false);
virtual void DrawImage() { }
virtual void DrawAffines(bool pre, bool post) { }
virtual void ClearWindow() { }
virtual bool KeyPress(QKeyEvent* e);
virtual bool KeyRelease(QKeyEvent* e);
virtual void MousePress(QMouseEvent* e) { }
virtual void MouseRelease(QMouseEvent* e) { }
virtual void MouseMove(QMouseEvent* e) { }
virtual void Wheel(QWheelEvent* e) { }
virtual bool SyncSizes() { return false; }
virtual bool CheckForSizeMismatch(int w, int h) { return true; }
virtual void QueryMatrices(bool print) { }
protected:
unsigned int m_DragModifier;
glm::ivec2 m_MousePos;
glm::ivec2 m_MouseDownPos;
glm::ivec4 m_Viewport;
eDragState m_DragState;
eHoverType m_HoverType;
eAffineType m_AffineType;
GLWidget* m_GL;
Fractorium* m_Fractorium;
};
/// <summary>
/// Templated derived class which implements all interaction functionality between the embers
/// of a specific template type and the GLWidget;
/// </summary>
template<typename T>
class GLEmberController : public GLEmberControllerBase
{
public:
GLEmberController(Fractorium* fractorium, GLWidget* glWidget, FractoriumEmberController<T>* controller);
virtual ~GLEmberController();
virtual void DrawImage();
virtual void DrawAffines(bool pre, bool post);
virtual void ClearWindow();
virtual void MousePress(QMouseEvent* e);
virtual void MouseRelease(QMouseEvent* e);
virtual void MouseMove(QMouseEvent* e);
virtual void Wheel(QWheelEvent* e);
virtual void QueryMatrices(bool print);
virtual bool SyncSizes();
virtual bool CheckForSizeMismatch(int w, int h);
T CalcScale();
T CalcRotation();
Affine2D<T> CalcDragXAxis();
Affine2D<T> CalcDragYAxis();
Affine2D<T> CalcDragTranslation();
void SetEmber(Ember<T>* ember);
void SetSelectedXform(Xform<T>* xform);
void DrawAffine(Xform<T>* xform, bool pre, bool selected);
int UpdateHover(v3T& glCoords);
bool CheckXformHover(Xform<T>* xform, v3T& glCoords, T& bestDist, bool pre, bool post);
private:
v3T SnapToGrid(v3T& vec);
v3T SnapToNormalizedAngle(v3T& vec, unsigned int divisions);
v3T WindowToWorld(v3T& v, bool flip);
void QueryVMP();
void MultMatrix(m4T& mat);
T m_CenterDownX;
T m_CenterDownY;
T m_RotationDown;
T m_ScaleDown;
v4T m_BoundsDown;
v3T m_MouseWorldPos;
v3T m_MouseDownWorldPos;
v3T m_DragHandlePos;
v3T m_DragHandleOffset;
v3T m_HoverHandlePos;
m4T m_Modelview;
m4T m_Projection;
Affine2D<T> m_DragSrcTransform;
Xform<T>* m_HoverXform;
Xform<T>* m_SelectedXform;
FractoriumEmberController<T>* m_FractoriumEmberController;
T GridStep;
};
template class GLEmberController<float>;
#ifdef DO_DOUBLE
template class GLEmberController<double>;
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,76 @@
#pragma once
#include "FractoriumEmberController.h"
/// <summary>
/// GLWidget class.
/// </summary>
class Fractorium;//Forward declaration since Fractorium uses this widget.
static const float GridStep = 1.0f / 8.0f;
/// <summary>
/// The main drawing area.
/// This uses the Qt wrapper around OpenGL to draw the output of the render to a texture whose
/// size matches the size of the window.
/// On top of that, the circles that represent the pre and post affine transforms for each xform are drawn.
/// Based on values specified on the GUI, it will either draw the presently selected xform, or all of them.
/// It can show/hide pre/post affine as well.
/// The currently selected xform is drawn with a circle around it, with all others only showing their axes.
/// The current xform is set by either clicking on it, or by changing the index of the xforms combo box on the main window.
/// A problem here is that all drawing is done using the legacy OpenGL fixed function pipeline which is deprecated
/// and even completely disabled on Mac OS. This will need to be replaced with shader programs for every draw operation.
/// Since this window has to know about various states of the renderer and the main window, it retains pointers to
/// the main window and several of its members.
/// This class uses a controller-based design similar to the main window.
/// </summary>
class GLWidget : public QGLWidget, protected QOpenGLFunctions_2_0//QOpenGLFunctions_3_2_Compatibility//QOpenGLFunctions_3_2_Core//, protected QOpenGLFunctions
{
Q_OBJECT
friend Fractorium;
friend FractoriumEmberController<float>;
friend FractoriumEmberController<double>;
friend GLEmberControllerBase;
friend GLEmberController<float>;
friend GLEmberController<double>;
public:
GLWidget(QWidget* parent);
~GLWidget();
void DrawQuad();
void SetMainWindow(Fractorium* f);
bool Init();
bool Drawing();
GLuint OutputTexID();
protected:
virtual void initializeGL();
virtual void paintGL();
virtual void keyPressEvent(QKeyEvent* e);
virtual void keyReleaseEvent(QKeyEvent* e);
virtual void mousePressEvent(QMouseEvent* e);
virtual void mouseReleaseEvent(QMouseEvent* e);
virtual void mouseMoveEvent(QMouseEvent* e);
virtual void wheelEvent(QWheelEvent* e);
virtual void resizeEvent(QResizeEvent* e);
private:
bool Allocate(bool force = false);
bool Deallocate();
void SetViewport();
void DrawGrid();
void DrawUnitSquare();
void DrawAffineHelper(bool selected, bool pre, bool final, bool background);
GLEmberControllerBase* GLController();
bool m_Init;
bool m_Drawing;
GLint m_TexWidth;
GLint m_TexHeight;
GLint m_ViewWidth;
GLint m_ViewHeight;
GLuint m_OutputTexID;
Fractorium* m_Fractorium;
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 911 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 482 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 460 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 538 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 531 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 584 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 563 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 532 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 514 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 459 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 512 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1016 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 722 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 661 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 643 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 451 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 498 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 932 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 614 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 739 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 634 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 647 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 634 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 675 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 459 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 438 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 677 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 582 B

View File

@ -0,0 +1,25 @@
#include "FractoriumPch.h"
#include "Fractorium.h"
#include <QtWidgets/QApplication>
/// <summary>
/// Main program entry point for Fractorium.exe.
/// </summary>
/// <param name="argc">The number of command line arguments passed</param>
/// <param name="argv">The command line arguments passed</param>
/// <returns>0 if successful, else 1.</returns>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
#ifdef TEST_CL
QMessageBox::critical(QApplication::desktop(), "Error", "Fractorium cannot be run in test mode, undefine TEST_CL first.");
return 1;
#endif
//Required for large allocs, else GPU memory usage will be severely limited to small sizes.
//This must be done in the application and not in the EmberCL DLL.
_putenv_s("GPU_MAX_ALLOC_PERCENT", "100");
Fractorium w;
w.show();
return a.exec();
}

View File

@ -0,0 +1,210 @@
#include "FractoriumPch.h"
#include "OptionsDialog.h"
#include "Fractorium.h"
/// <summary>
/// Constructor that takes a pointer to the settings object and the parent widget.
/// </summary>
/// <param name="settings">A pointer to the settings object to use</param>
/// <param name="parent">The parent widget. Default: NULL.</param>
/// <param name="f">The window flags. Default: 0.</param>
FractoriumOptionsDialog::FractoriumOptionsDialog(FractoriumSettings* settings, QWidget* parent, Qt::WindowFlags f)
: QDialog(parent, f)
{
int row = 0, spinHeight = 20;
unsigned int i;
ui.setupUi(this);
m_Settings = settings;
QTableWidget* table = ui.OptionsXmlSavingTable;
ui.ThreadCountSpin->setRange(1, Timing::ProcessorCount());
connect(ui.OpenCLCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnOpenCLCheckBoxStateChanged(int)), Qt::QueuedConnection);
connect(ui.PlatformCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(OnPlatformComboCurrentIndexChanged(int)), Qt::QueuedConnection);
SetupSpinner<SpinBox, int>(table, this, row, 1, m_XmlWidthSpin, spinHeight, 10, 100000, 50, "", "", true, 1920);
SetupSpinner<SpinBox, int>(table, this, row, 1, m_XmlHeightSpin, spinHeight, 10, 100000, 50, "", "", true, 1080);
SetupSpinner<SpinBox, int>(table, this, row, 1, m_XmlTemporalSamplesSpin, spinHeight, 1, 1000, 100, "", "", true, 1000);
SetupSpinner<SpinBox, int>(table, this, row, 1, m_XmlQualitySpin, spinHeight, 1, 200000, 50, "", "", true, 1000);
SetupSpinner<SpinBox, int>(table, this, row, 1, m_XmlSupersampleSpin, spinHeight, 1, 4, 1, "", "", true, 2);
m_IdEdit = new QLineEdit(ui.OptionsIdentityTable);
ui.OptionsIdentityTable->setCellWidget(0, 1, m_IdEdit);
m_UrlEdit = new QLineEdit(ui.OptionsIdentityTable);
ui.OptionsIdentityTable->setCellWidget(1, 1, m_UrlEdit);
m_NickEdit = new QLineEdit(ui.OptionsIdentityTable);
ui.OptionsIdentityTable->setCellWidget(2, 1, m_NickEdit);
//QWidget::setTabOrder(m_IdEdit, m_UrlEdit);
//QWidget::setTabOrder(m_UrlEdit, m_NickEdit);
//QWidget::setTabOrder(m_NickEdit, m_IdEdit);
m_IdEdit->setText(m_Settings->Id());
m_UrlEdit->setText(m_Settings->Url());
m_NickEdit->setText(m_Settings->Nick());
if (m_Wrapper.CheckOpenCL())
{
vector<string> platforms = m_Wrapper.PlatformNames();
//Populate combo boxes with available OpenCL platforms and devices.
for (i = 0; i < platforms.size(); i++)
ui.PlatformCombo->addItem(QString::fromStdString(platforms[i]));
//If init succeeds, set the selected platform and device combos to match what was saved in the settings.
if (m_Wrapper.Init(m_Settings->PlatformIndex(), m_Settings->DeviceIndex()))
{
ui.OpenCLCheckBox->setChecked( m_Settings->OpenCL());
ui.PlatformCombo->setCurrentIndex(m_Settings->PlatformIndex());
ui.DeviceCombo->setCurrentIndex( m_Settings->DeviceIndex());
}
else
{
OnPlatformComboCurrentIndexChanged(0);
ui.OpenCLCheckBox->setChecked(false);
}
}
else
{
ui.OpenCLCheckBox->setChecked(false);
ui.OpenCLCheckBox->setEnabled(false);
}
ui.EarlyClipCheckBox->setChecked( m_Settings->EarlyClip());
ui.TransparencyCheckBox->setChecked(m_Settings->Transparency());
ui.DoublePrecisionCheckBox->setChecked( m_Settings->Double());
ui.ThreadCountSpin->setValue( m_Settings->ThreadCount());
if (m_Settings->CpuDEFilter())
ui.CpuFilteringDERadioButton->setChecked(true);
else
ui.CpuFilteringLogRadioButton->setChecked(true);
if (m_Settings->OpenCLDEFilter())
ui.OpenCLFilteringDERadioButton->setChecked(true);
else
ui.OpenCLFilteringLogRadioButton->setChecked(true);
ui.CpuSubBatchSpin->setValue(m_Settings->CpuSubBatch());
ui.OpenCLSubBatchSpin->setValue(m_Settings->OpenCLSubBatch());
m_XmlWidthSpin->setValue(m_Settings->XmlWidth());
m_XmlHeightSpin->setValue(m_Settings->XmlHeight());
m_XmlTemporalSamplesSpin->setValue(m_Settings->XmlTemporalSamples());
m_XmlQualitySpin->setValue(m_Settings->XmlQuality());
m_XmlSupersampleSpin->setValue(m_Settings->XmlSupersample());
OnOpenCLCheckBoxStateChanged(ui.OpenCLCheckBox->isChecked());
}
/// <summary>
/// GUI settings wrapper functions, getters only.
/// </summary>
bool FractoriumOptionsDialog::EarlyClip() { return ui.EarlyClipCheckBox->isChecked(); }
bool FractoriumOptionsDialog::Transparency() { return ui.TransparencyCheckBox->isChecked(); }
bool FractoriumOptionsDialog::OpenCL() { return ui.OpenCLCheckBox->isChecked(); }
bool FractoriumOptionsDialog::Double() { return ui.DoublePrecisionCheckBox->isChecked(); }
unsigned int FractoriumOptionsDialog::PlatformIndex() { return ui.PlatformCombo->currentIndex(); }
unsigned int FractoriumOptionsDialog::DeviceIndex() { return ui.DeviceCombo->currentIndex(); }
unsigned int FractoriumOptionsDialog::ThreadCount() { return ui.ThreadCountSpin->value(); }
/// <summary>
/// Disable or enable the OpenCL related controls based on the state passed in.
/// Called when the state of the OpenCL checkbox is changed.
/// </summary>
/// <param name="state">The state of the checkbox</param>
void FractoriumOptionsDialog::OnOpenCLCheckBoxStateChanged(int state)
{
bool checked = state == Qt::Checked;
ui.PlatformCombo->setEnabled(checked);
ui.DeviceCombo->setEnabled(checked);
ui.ThreadCountSpin->setEnabled(!checked);
}
/// <summary>
/// Populate the the device combo box with all available
/// OpenCL devices for the selected platform.
/// Called when the platform combo box index changes.
/// </summary>
/// <param name="index">The selected index of the combo box</param>
void FractoriumOptionsDialog::OnPlatformComboCurrentIndexChanged(int index)
{
vector<string> devices = m_Wrapper.DeviceNames(index);
ui.DeviceCombo->clear();
for (size_t i = 0; i < devices.size(); i++)
ui.DeviceCombo->addItem(QString::fromStdString(devices[i]));
}
/// <summary>
/// Save all settings on the GUI to the settings object.
/// Called when the user clicks ok.
/// Not called if cancelled or closed with the X.
/// </summary>
void FractoriumOptionsDialog::accept()
{
//Interactive rendering.
m_Settings->EarlyClip(EarlyClip());
m_Settings->Transparency(Transparency());
m_Settings->OpenCL(OpenCL());
m_Settings->Double(Double());
m_Settings->PlatformIndex(PlatformIndex());
m_Settings->DeviceIndex(DeviceIndex());
m_Settings->ThreadCount(ThreadCount());
m_Settings->CpuSubBatch(ui.CpuSubBatchSpin->value());
m_Settings->OpenCLSubBatch(ui.OpenCLSubBatchSpin->value());
m_Settings->CpuDEFilter(ui.CpuFilteringDERadioButton->isChecked());
m_Settings->OpenCLDEFilter(ui.OpenCLFilteringDERadioButton->isChecked());
//Xml saving.
m_Settings->XmlWidth(m_XmlWidthSpin->value());
m_Settings->XmlHeight(m_XmlHeightSpin->value());
m_Settings->XmlTemporalSamples(m_XmlTemporalSamplesSpin->value());
m_Settings->XmlQuality(m_XmlQualitySpin->value());
m_Settings->XmlSupersample(m_XmlSupersampleSpin->value());
//Identity.
m_Settings->Id(m_IdEdit->text());
m_Settings->Url(m_UrlEdit->text());
m_Settings->Nick(m_NickEdit->text());
QDialog::accept();
}
/// <summary>
/// Restore all GUI items to what was originally in the settings object.
/// Called when the user clicks cancel or closes with the X.
/// </summary>
void FractoriumOptionsDialog::reject()
{
//Interactive rendering.
ui.EarlyClipCheckBox->setChecked(m_Settings->EarlyClip());
ui.TransparencyCheckBox->setChecked(m_Settings->Transparency());
ui.OpenCLCheckBox->setChecked(m_Settings->OpenCL());
ui.DoublePrecisionCheckBox->setChecked(m_Settings->Double());
ui.PlatformCombo->setCurrentIndex(m_Settings->PlatformIndex());
ui.DeviceCombo->setCurrentIndex(m_Settings->DeviceIndex());
ui.ThreadCountSpin->setValue(m_Settings->ThreadCount());
ui.CpuSubBatchSpin->setValue(m_Settings->CpuSubBatch());
ui.OpenCLSubBatchSpin->setValue(m_Settings->OpenCLSubBatch());
ui.CpuFilteringDERadioButton->setChecked(m_Settings->CpuDEFilter());
ui.OpenCLFilteringDERadioButton->setChecked(m_Settings->OpenCLDEFilter());
//Xml saving.
m_XmlWidthSpin->setValue(m_Settings->XmlWidth());
m_XmlHeightSpin->setValue(m_Settings->XmlHeight());
m_XmlTemporalSamplesSpin->setValue(m_Settings->XmlTemporalSamples());
m_XmlQualitySpin->setValue(m_Settings->XmlQuality());
m_XmlSupersampleSpin->setValue(m_Settings->XmlSupersample());
//Identity.
m_IdEdit->setText(m_Settings->Id());
m_UrlEdit->setText(m_Settings->Url());
m_NickEdit->setText(m_Settings->Nick());
QDialog::reject();
}

View File

@ -0,0 +1,56 @@
#pragma once
#include "ui_OptionsDialog.h"
#include "FractoriumSettings.h"
#include "SpinBox.h"
/// <summary>
/// FractoriumOptionsDialog class.
/// </summary>
class Fractorium;//Forward declaration since Fractorium uses this dialog.
/// <summary>
/// The options dialog allows the user to save various preferences
/// between program runs.
/// It has a pointer to a FractoriumSettings object which is assigned
/// in the constructor. The main window holds the object as a member and the
/// pointer to it here is just for convenience.
/// </summary>
class FractoriumOptionsDialog : public QDialog
{
Q_OBJECT
friend Fractorium;
public:
FractoriumOptionsDialog(FractoriumSettings* settings, QWidget* parent = 0, Qt::WindowFlags f = 0);
public slots:
void OnOpenCLCheckBoxStateChanged(int state);
void OnPlatformComboCurrentIndexChanged(int index);
virtual void accept();
virtual void reject();
private:
bool EarlyClip();
bool AlphaChannel();
bool Transparency();
bool OpenCL();
bool Double();
unsigned int PlatformIndex();
unsigned int DeviceIndex();
unsigned int ThreadCount();
Ui::OptionsDialog ui;
OpenCLWrapper m_Wrapper;
SpinBox* m_XmlWidthSpin;
SpinBox* m_XmlHeightSpin;
SpinBox* m_XmlTemporalSamplesSpin;
SpinBox* m_XmlQualitySpin;
SpinBox* m_XmlSupersampleSpin;
QLineEdit* m_IdEdit;
QLineEdit* m_UrlEdit;
QLineEdit* m_NickEdit;
FractoriumSettings* m_Settings;
};

View File

@ -0,0 +1,746 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>OptionsDialog</class>
<widget class="QDialog" name="OptionsDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>286</width>
<height>388</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>286</width>
<height>388</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>286</width>
<height>388</height>
</size>
</property>
<property name="windowTitle">
<string>Options</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item>
<widget class="QTabWidget" name="OptionsTabWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="tabShape">
<enum>QTabWidget::Triangular</enum>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="OptionsInteractiveRenderingTab">
<attribute name="title">
<string>Interactive Rendering</string>
</attribute>
<layout class="QFormLayout" name="formLayout">
<property name="verticalSpacing">
<number>6</number>
</property>
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="QCheckBox" name="EarlyClipCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Checked: clip colors and gamma correct after density filtering.&lt;/p&gt;&lt;p&gt;Unchecked: do it after spatial filtering.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="statusTip">
<string/>
</property>
<property name="whatsThis">
<string/>
</property>
<property name="text">
<string>Early Clip</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="TransparencyCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Use transparency in the final image.&lt;/p&gt;&lt;p&gt;This will not make a difference in the editor, but will when saving as .png and opening in other programs.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Transparency</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="OpenCLCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Use OpenCL to render if your video card supports it.&lt;/p&gt;&lt;p&gt;This is highly recommended as it will give fluid, real-time interactive editing.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Use OpenCL</string>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="QComboBox" name="PlatformCombo"/>
</item>
<item row="5" column="0" colspan="2">
<widget class="QComboBox" name="DeviceCombo"/>
</item>
<item row="6" column="0">
<widget class="QSpinBox" name="ThreadCountSpin">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The number of threads to use with CPU rendering.&lt;/p&gt;&lt;p&gt;Decrease for more responsive editing, increase for better performance.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="prefix">
<string>Threads </string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>64</number>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QGroupBox" name="InteraciveCpuFilteringGroupBox">
<property name="title">
<string>CPU Filtering</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>6</number>
</property>
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>4</number>
</property>
<property name="bottomMargin">
<number>5</number>
</property>
<item>
<widget class="QRadioButton" name="CpuFilteringLogRadioButton">
<property name="toolTip">
<string>Use log scale filtering for interactive editing on the CPU.</string>
</property>
<property name="text">
<string>Log Scale</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="CpuFilteringDERadioButton">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Use full density estimation filtering for interactive editing on the CPU.&lt;/p&gt;&lt;p&gt;This is slower, but gives better feedback.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Full DE</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="10" column="0">
<widget class="QGroupBox" name="InteraciveGpuFilteringGroupBox">
<property name="title">
<string>OpenCL Filtering</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>4</number>
</property>
<property name="bottomMargin">
<number>5</number>
</property>
<item>
<widget class="QRadioButton" name="OpenCLFilteringLogRadioButton">
<property name="toolTip">
<string>Use log scale filtering for interactive editing using OpenCL.</string>
</property>
<property name="text">
<string>Log Scale</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="OpenCLFilteringDERadioButton">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Use full density estimation filtering for interactive editing using OpenCL.&lt;/p&gt;&lt;p&gt;This is slower, but gives better feedback.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Full DE</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="7" column="0">
<widget class="QSpinBox" name="CpuSubBatchSpin">
<property name="toolTip">
<string>The number of 10,000 iteration chunks ran per thread on the CPU
in interactive mode for each mouse movement</string>
</property>
<property name="prefix">
<string>CPU Sub Batch </string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>100</number>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QSpinBox" name="OpenCLSubBatchSpin">
<property name="toolTip">
<string>The number of ~8M iteration chunks ran using OpenCL
in interactive mode for each mouse movement</string>
</property>
<property name="prefix">
<string>OpenCL Sub Batch </string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>100</number>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="DoublePrecisionCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Checked: use 64-bit double precision numbers (slower, but better image quality).&lt;/p&gt;&lt;p&gt;Unchecked: use 32-bit single precision numbers (faster, but worse image quality).&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Use Double Precision</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="OptionsXmlSavingTab">
<attribute name="title">
<string>Xml Saving</string>
</attribute>
<layout class="QFormLayout" name="formLayout_2">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0" colspan="2">
<widget class="TableWidget" name="OptionsXmlSavingTable">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>122</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>122</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="frameShape">
<enum>QFrame::Panel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="tabKeyNavigation">
<bool>false</bool>
</property>
<property name="alternatingRowColors">
<bool>false</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::NoSelection</enum>
</property>
<property name="verticalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
<property name="horizontalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
<property name="showGrid">
<bool>true</bool>
</property>
<property name="gridStyle">
<enum>Qt::SolidLine</enum>
</property>
<property name="cornerButtonEnabled">
<bool>false</bool>
</property>
<property name="columnCount">
<number>2</number>
</property>
<attribute name="horizontalHeaderVisible">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderCascadingSectionResizes">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderDefaultSectionSize">
<number>110</number>
</attribute>
<attribute name="horizontalHeaderHighlightSections">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderMinimumSectionSize">
<number>35</number>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
<attribute name="verticalHeaderDefaultSectionSize">
<number>24</number>
</attribute>
<attribute name="verticalHeaderHighlightSections">
<bool>false</bool>
</attribute>
<attribute name="verticalHeaderMinimumSectionSize">
<number>24</number>
</attribute>
<attribute name="verticalHeaderStretchLastSection">
<bool>false</bool>
</attribute>
<row>
<property name="text">
<string>Width</string>
</property>
</row>
<row>
<property name="text">
<string>Height</string>
</property>
</row>
<row>
<property name="text">
<string>Temporal Samples</string>
</property>
</row>
<row>
<property name="text">
<string>Quality</string>
</property>
</row>
<row>
<property name="text">
<string>Supersample</string>
</property>
</row>
<column>
<property name="text">
<string>Field</string>
</property>
</column>
<column>
<property name="text">
<string/>
</property>
</column>
<item row="0" column="0">
<property name="text">
<string>Width</string>
</property>
<property name="flags">
<set>ItemIsDragEnabled|ItemIsUserCheckable|ItemIsEnabled</set>
</property>
</item>
<item row="0" column="1">
<property name="text">
<string>0</string>
</property>
</item>
<item row="1" column="0">
<property name="text">
<string>Height</string>
</property>
<property name="flags">
<set>ItemIsDragEnabled|ItemIsUserCheckable|ItemIsEnabled</set>
</property>
</item>
<item row="1" column="1">
<property name="text">
<string>0</string>
</property>
</item>
<item row="2" column="0">
<property name="text">
<string>Temporal Samples</string>
</property>
<property name="flags">
<set>ItemIsDragEnabled|ItemIsUserCheckable|ItemIsEnabled</set>
</property>
</item>
<item row="2" column="1">
<property name="text">
<string>0</string>
</property>
</item>
<item row="3" column="0">
<property name="text">
<string>Quality</string>
</property>
<property name="flags">
<set>ItemIsDragEnabled|ItemIsUserCheckable|ItemIsEnabled</set>
</property>
</item>
<item row="3" column="1">
<property name="text">
<string>0</string>
</property>
</item>
<item row="4" column="0">
<property name="text">
<string>Supersample</string>
</property>
<property name="flags">
<set>ItemIsDragEnabled|ItemIsUserCheckable|ItemIsEnabled</set>
</property>
</item>
<item row="4" column="1">
<property name="text">
<string>0</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="OptionsIdentityTab">
<attribute name="title">
<string>Identity</string>
</attribute>
<layout class="QFormLayout" name="formLayout_3">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0" colspan="2">
<widget class="TableWidget" name="OptionsIdentityTable">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>120</width>
<height>74</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>74</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="frameShape">
<enum>QFrame::Panel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="tabKeyNavigation">
<bool>false</bool>
</property>
<property name="alternatingRowColors">
<bool>false</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::NoSelection</enum>
</property>
<property name="verticalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
<property name="horizontalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
<property name="showGrid">
<bool>true</bool>
</property>
<property name="gridStyle">
<enum>Qt::SolidLine</enum>
</property>
<property name="cornerButtonEnabled">
<bool>false</bool>
</property>
<property name="columnCount">
<number>2</number>
</property>
<attribute name="horizontalHeaderVisible">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderCascadingSectionResizes">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderDefaultSectionSize">
<number>110</number>
</attribute>
<attribute name="horizontalHeaderHighlightSections">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderMinimumSectionSize">
<number>35</number>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
<attribute name="verticalHeaderDefaultSectionSize">
<number>24</number>
</attribute>
<attribute name="verticalHeaderHighlightSections">
<bool>false</bool>
</attribute>
<attribute name="verticalHeaderMinimumSectionSize">
<number>24</number>
</attribute>
<attribute name="verticalHeaderStretchLastSection">
<bool>false</bool>
</attribute>
<row>
<property name="text">
<string>Id</string>
</property>
</row>
<row>
<property name="text">
<string>Url</string>
</property>
</row>
<row>
<property name="text">
<string>Nick</string>
</property>
</row>
<column>
<property name="text">
<string>Field</string>
</property>
</column>
<column>
<property name="text">
<string/>
</property>
</column>
<item row="0" column="0">
<property name="text">
<string>Id</string>
</property>
<property name="flags">
<set>ItemIsDragEnabled|ItemIsUserCheckable|ItemIsEnabled</set>
</property>
</item>
<item row="0" column="1">
<property name="text">
<string>-</string>
</property>
<property name="flags">
<set>ItemIsSelectable|ItemIsEditable|ItemIsDragEnabled|ItemIsUserCheckable|ItemIsEnabled</set>
</property>
</item>
<item row="1" column="0">
<property name="text">
<string>Url</string>
</property>
<property name="flags">
<set>ItemIsDragEnabled|ItemIsUserCheckable|ItemIsEnabled</set>
</property>
</item>
<item row="1" column="1">
<property name="text">
<string>-</string>
</property>
<property name="flags">
<set>ItemIsSelectable|ItemIsEditable|ItemIsDragEnabled|ItemIsUserCheckable|ItemIsEnabled</set>
</property>
</item>
<item row="2" column="0">
<property name="text">
<string>Nick</string>
</property>
<property name="flags">
<set>ItemIsDragEnabled|ItemIsUserCheckable|ItemIsEnabled</set>
</property>
</item>
<item row="2" column="1">
<property name="text">
<string>-</string>
</property>
<property name="flags">
<set>ItemIsSelectable|ItemIsEditable|ItemIsDragEnabled|ItemIsUserCheckable|ItemIsEnabled</set>
</property>
</item>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="OptionsButtonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
<property name="centerButtons">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>TableWidget</class>
<extends>QTableWidget</extends>
<header>TableWidget.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>OptionsButtonBox</sender>
<signal>accepted()</signal>
<receiver>OptionsDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>OptionsButtonBox</sender>
<signal>rejected()</signal>
<receiver>OptionsDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,205 @@
#include "FractoriumPch.h"
#include "SpinBox.h"
/// <summary>
/// Constructor that passes parent to the base and sets up height and step.
/// Specific focus policy is used to allow the user to hover over the control
/// and change its value using the mouse wheel without explicitly having to click
/// inside of it.
/// </summary>
/// <param name="parent">The parent widget. Default: NULL.</param>
/// <param name="height">The height of the spin box. Default: 16.</param>
/// <param name="step">The step used to increment/decrement the spin box when using the mouse wheel. Default: 1.</param>
SpinBox::SpinBox(QWidget* parent, int height, int step)
: QSpinBox(parent)
{
m_Select = false;
m_DoubleClick = false;
m_DoubleClickNonZero = 0;
m_DoubleClickZero = 1;
m_Step = step;
m_SmallStep = 1;
setSingleStep(step);
setFrame(false);
setButtonSymbols(QAbstractSpinBox::NoButtons);
setFocusPolicy(Qt::StrongFocus);
setMinimumHeight(height);//setGeometry() has no effect, so set both of these instead.
setMaximumHeight(height);
lineEdit()->installEventFilter(this);
lineEdit()->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
connect(this, SIGNAL(valueChanged(int)), this, SLOT(onSpinBoxValueChanged(int)), Qt::QueuedConnection);
}
/// <summary>
/// Set the value of the control without triggering signals.
/// </summary>
/// <param name="d">The value to set it to</param>
void SpinBox::SetValueStealth(int d)
{
blockSignals(true);
setValue(d);
blockSignals(false);
}
/// <summary>
/// Set whether to respond to double click events.
/// </summary>
/// <param name="b">True if this should respond to double click events, else false.</param>
void SpinBox::DoubleClick(bool b)
{
m_DoubleClick = b;
}
/// <summary>
/// Set the value to be used when the user double clicks the spinner while
/// it contains zero.
/// </summary>
/// <param name="val">The value to be used</param>
void SpinBox::DoubleClickZero(int val)
{
m_DoubleClickZero = val;
}
/// <summary>
/// Set the value to be used when the user double clicks the spinner while
/// it contains a non-zero value.
/// </summary>
/// <param name="val">The value to be used</param>
void SpinBox::DoubleClickNonZero(int val)
{
m_DoubleClickNonZero = val;
}
/// <summary>
/// Set the small step to be used when the user holds down shift while scrolling.
/// The default is step / 10, so use this if something else is needed.
/// </summary>
/// <param name="step">The small step to use for scrolling while the shift key is down</param>
void SpinBox::SmallStep(int step)
{
m_SmallStep = min(1, step);
}
/// <summary>
/// Expose the underlying QLineEdit control to the caller.
/// </summary>
/// <returns>A pointer to the QLineEdit</returns>
QLineEdit* SpinBox::lineEdit()
{
return QSpinBox::lineEdit();
}
/// <summary>
/// Another workaround for the persistent text selection bug in Qt.
/// </summary>
void SpinBox::onSpinBoxValueChanged(int i)
{
lineEdit()->deselect();//Gets rid of nasty "feature" that always has text selected.
}
/// <summary>
/// Event filter for taking special action on double click events.
/// </summary>
/// <param name="o">The object</param>
/// <param name="e">The eevent</param>
/// <returns>false</returns>
bool SpinBox::eventFilter(QObject* o, QEvent* e)
{
if (e->type() == QMouseEvent::MouseButtonPress && isEnabled())
{
QPoint pt;
if (QMouseEvent* me = (QMouseEvent*)e)
pt = me->localPos().toPoint();
int pos = lineEdit()->cursorPositionAt(pt);
if (lineEdit()->selectedText() != "")
{
lineEdit()->deselect();
lineEdit()->setCursorPosition(pos);
return true;
}
else if (m_Select)
{
lineEdit()->setCursorPosition(pos);
selectAll();
m_Select = false;
return true;
}
}
else if (m_DoubleClick && e->type() == QMouseEvent::MouseButtonDblClick && isEnabled())
{
if (value() == 0)
setValue(m_DoubleClickZero);
else
setValue(m_DoubleClickNonZero);
}
else
{
if (e->type() == QEvent::Wheel)
{
//Take special action for shift to reduce the scroll amount. Control already
//increases it automatically.
if (QWheelEvent* wheelEvent = dynamic_cast<QWheelEvent*>(e))
{
Qt::KeyboardModifiers mod = wheelEvent->modifiers();
if (mod.testFlag(Qt::ShiftModifier))
setSingleStep(m_SmallStep);
else
setSingleStep(m_Step);
}
}
}
return false;
}
/// <summary>
/// Called when focus enters the spinner.
/// </summary>
/// <param name="e">The event</param>
void SpinBox::focusInEvent(QFocusEvent* e)
{
lineEdit()->setReadOnly(false);
QSpinBox::focusInEvent(e);
}
/// <summary>
/// Called when focus leaves the spinner.
/// Qt has a nasty "feature" that leaves the text in a spinner selected
/// and the cursor visible, regardless of whether it has the focus.
/// Manually clear both here.
/// </summary>
/// <param name="e">The event</param>
void SpinBox::focusOutEvent(QFocusEvent* e)
{
lineEdit()->deselect();//Clear selection when leaving.
lineEdit()->setReadOnly(true);//Clever hack to clear the cursor when leaving.
QSpinBox::focusOutEvent(e);
}
/// <summary>
/// Called when focus enters the spinner.
/// Must set the focus to make sure key down messages don't erroneously go to the GLWidget.
/// </summary>
/// <param name="e">The event</param>
void SpinBox::enterEvent(QEvent* e)
{
m_Select = true;
setFocus();
QSpinBox::enterEvent(e);
}
/// <summary>
/// Called when focus leaves the spinner.
/// Must clear the focus to make sure key down messages don't erroneously go to the GLWidget.
/// </summary>
/// <param name="e">The event</param>
void SpinBox::leaveEvent(QEvent* e)
{
m_Select = false;
clearFocus();
QSpinBox::leaveEvent(e);
}

View File

@ -0,0 +1,45 @@
#pragma once
#include "FractoriumPch.h"
/// <summary>
/// SpinBox class.
/// </summary>
/// <summary>
/// A derivation to prevent the spin box from selecting its own text
/// when editing. Also to prevent multiple spin boxes from all having
/// selected text at once.
/// </summary>
class SpinBox : public QSpinBox
{
Q_OBJECT
public:
explicit SpinBox(QWidget* parent = 0, int height = 16, int step = 1);
virtual ~SpinBox() { }
void SetValueStealth(int d);
void DoubleClick(bool b);
void DoubleClickZero(int val);
void DoubleClickNonZero(int val);
void SmallStep(int step);
QLineEdit* lineEdit();
public slots:
void onSpinBoxValueChanged(int i);
protected:
bool eventFilter(QObject* o, QEvent* e);
virtual void focusInEvent(QFocusEvent* e);
virtual void focusOutEvent(QFocusEvent* e);
virtual void enterEvent(QEvent* e);
virtual void leaveEvent(QEvent* e);
private:
bool m_Select;
bool m_DoubleClick;
int m_DoubleClickNonZero;
int m_DoubleClickZero;
int m_Step;
int m_SmallStep;
};

View File

@ -0,0 +1,30 @@
#pragma once
#include "FractoriumPch.h"
/// <summary>
/// StealthComboBox class.
/// </summary>
/// <summary>
/// A thin derivation of QComboBox which allows the user
/// to set the index without triggering signals.
/// </summary>
class StealthComboBox : public QComboBox
{
Q_OBJECT
public:
explicit StealthComboBox(QWidget* parent = 0) : QComboBox(parent) { }
/// <summary>
/// Set the current index of the combo box without triggering signals.
/// </summary>
/// <param name="index">The current index to set</param>
void StealthComboBox::SetCurrentIndexStealth(int index)
{
blockSignals(true);
setCurrentIndex(index);
blockSignals(false);
}
};

View File

@ -0,0 +1,57 @@
#pragma once
#include "FractoriumPch.h"
/// <summary>
/// TableWidget class.
/// </summary>
/// <summary>
/// The entire purpose for this subclass is to overcome a glaring flaw
/// in the way Qt handles table drawing.
/// For most of the tables Fractorium uses, it draw the grid lines. Qt draws them
/// in a very naive manner, whereby it draws lines above the first row and below
/// the last row. It also draws to the left of the first column and to the right
/// of the last column. This has the effect of putting an additional border inside
/// of the specified border. This extra border becomes very noticeable when changing
/// the background color of a cell.
/// The workaround is to scrunch the size of the table up by one pixel. However,
/// since the viewable area is then smaller than the size of the table, it will scroll
/// by one pixel if the mouse is hovered over the table and the user moves the mouse wheel.
/// This subclass is done solely to filter out the mouse wheel event.
/// Note that this filtering only applies to the table as a whole, which means
/// mouse wheel events still get properly routed to spinners.
/// </summary>
class TableWidget : public QTableWidget
{
Q_OBJECT
public:
/// <summary>
/// Constructor that passes the parent to the base and installs
/// the event filter.
/// </summary>
/// <param name="parent">The parent widget</param>
explicit TableWidget(QWidget* parent = 0)
: QTableWidget(parent)
{
viewport()->installEventFilter(this);
}
protected:
/// <summary>
/// Event filter to ignore mouse wheel events.
/// </summary>
/// <param name="obj">The object sending the event</param>
/// <param name="e">The event</param>
/// <returns>True if mouse wheel, else return the result of calling the base fucntion.</returns>
bool eventFilter(QObject* obj, QEvent* e)
{
if(e->type() == QEvent::Wheel)
{
e->ignore();
return true;
}
return QTableWidget::eventFilter(obj, e);
}
};

View File

@ -0,0 +1,2 @@
#include "FractoriumPch.h"
#include "TwoButtonWidget.h"

View File

@ -0,0 +1,113 @@
#pragma once
#include "FractoriumPch.h"
#include "DoubleSpinBox.h"
/// <summary>
/// TwoButtonWidget and SpinnerButtonWidget classes.
/// </summary>
/// <summary>
/// Thin container that is both a widget and a container of two QPushButtons.
/// Used for when a layout expects a single widget, but two need to go in its place.
/// The buttons are public so the caller can easily use them individually.
/// </summary>
class TwoButtonWidget : public QWidget
{
Q_OBJECT
public:
/// <summary>
/// Constructor that passes the parent to the base, then creates two QPushButtons,
/// and sets up their captions and dimensions.
/// </summary>
/// <param name="caption1">The caption of the first button</param>
/// <param name="caption2">The caption of the second button</param>
/// <param name="w1">The width of the first button</param>
/// <param name="w2">The width of the second button</param>
/// <param name="h">The height of both buttons</param>
/// <param name="parent">The parent widget</param>
TwoButtonWidget(QString caption1, QString caption2, int w1, int w2, int h, QWidget* parent)
: QWidget(parent)
{
QHBoxLayout* layout = new QHBoxLayout(this);
m_Button1 = new QPushButton(caption1, parent);
m_Button2 = new QPushButton(caption2, parent);
if (w1 != -1)
{
m_Button1->setMinimumWidth(w1);
m_Button1->setMaximumWidth(w1);
}
if (w2 != -1)
{
m_Button2->setMinimumWidth(w2);
m_Button2->setMaximumWidth(w2);
}
m_Button1->setMinimumHeight(h);
m_Button1->setMaximumHeight(h);
m_Button2->setMinimumHeight(h);
m_Button2->setMaximumHeight(h);
layout->addWidget(m_Button1);
layout->addWidget(m_Button2);
layout->setAlignment(Qt::AlignLeft);
layout->setMargin(0);
layout->setSpacing(2);
setLayout(layout);
}
QPushButton* m_Button1;
QPushButton* m_Button2;
};
/// <summary>
/// Thin container that is both a widget and a container of one DoubleSpinBox and one QPushButton.
/// Used for when a layout expects a single widget, but two need to go in its place.
/// The widgets are public so the caller can easily use them individually.
/// </summary>
class SpinnerButtonWidget : public QWidget
{
Q_OBJECT
public:
/// <summary>
/// Constructor that passes the parent to the base, then creates a QPushButton and
/// sets up its caption and dimensions, then assigns the DoubleSpinBox.
/// </summary>
/// <param name="spinBox">The pre-created DoubleSpinBox</param>
/// <param name="buttonCaption">The caption of the button</param>
/// <param name="w">The width of the button</param>
/// <param name="h">The height of the button</param>
/// <param name="parent">The parent widget</param>
SpinnerButtonWidget(DoubleSpinBox* spinBox, QString buttonCaption, int w, int h, QWidget* parent)
: QWidget(parent)
{
QHBoxLayout* layout = new QHBoxLayout(this);
m_Button = new QPushButton(buttonCaption, parent);
m_SpinBox = spinBox;
if (w != -1)
{
m_Button->setMinimumWidth(w);
m_Button->setMaximumWidth(w);
}
m_Button->setMinimumHeight(h);
m_Button->setMaximumHeight(h);
layout->addWidget(spinBox);
layout->addWidget(m_Button);
layout->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
layout->setMargin(0);
layout->setSpacing(0);
setLayout(layout);
}
DoubleSpinBox* m_SpinBox;
QPushButton* m_Button;
};

View File

@ -0,0 +1,89 @@
#pragma once
#include "FractoriumPch.h"
#include "DoubleSpinBox.h"
/// <summary>
/// TwoButtonWidget class.
/// </summary>
/// <summary>
/// A derivation of QTreeWidgetItem which helps us with sorting.
/// This is used when the user chooses to sort the variations tree
/// by index or by weight. It supports weights less than, equal to, or
/// greater than zero.
/// </summary>
template <typename T>
class VariationTreeWidgetItem : public QTreeWidgetItem
{
public:
/// <summary>
/// Constructor that takes a pointer to a QTreeWidget as the parent
/// and passes it to the base.
/// </summary>
/// <param name="parent">The parent widget</param>
VariationTreeWidgetItem(QTreeWidget* parent = 0)
: QTreeWidgetItem(parent)
{
}
/// <summary>
/// Constructor that takes a pointer to a QTreeWidgetItem as the parent
/// and passes it to the base.
/// This is used for making sub items for parametric variation parameters.
/// </summary>
/// <param name="parent">The parent widget</param>
VariationTreeWidgetItem(QTreeWidgetItem* parent = 0)
: QTreeWidgetItem(parent)
{
}
virtual ~VariationTreeWidgetItem() { }
private:
/// <summary>
/// Less than operator used for sorting.
/// </summary>
/// <param name="other">The QTreeWidgetItem to compare against for sorting</param>
/// <returns>True if this is less than other, else false.</returns>
bool operator < (const QTreeWidgetItem& other) const
{
int column = treeWidget()->sortColumn();
eVariationId index1, index2;
double weight1 = 0, weight2 = 0;
VariationTreeWidgetItem<T>* varItemWidget;
VariationTreeDoubleSpinBox<T>* spinBox1, *spinBox2;
QWidget* itemWidget1 = treeWidget()->itemWidget(const_cast<VariationTreeWidgetItem<T>*>(this), 1);//Get the widget for the second column.
if (spinBox1 = dynamic_cast<VariationTreeDoubleSpinBox<T>*>(itemWidget1))//Cast the widget to the VariationTreeDoubleSpinBox type.
{
QWidget* itemWidget2 = treeWidget()->itemWidget(const_cast<QTreeWidgetItem*>(&other), 1);//Get the widget for the second column of the widget item passed in.
if (spinBox2 = dynamic_cast<VariationTreeDoubleSpinBox<T>*>(itemWidget2))//Cast the widget to the VariationTreeDoubleSpinBox type.
{
if (spinBox1->IsParam() || spinBox2->IsParam())//Do not sort params, their order will always remain the same.
return false;
weight1 = spinBox1->value();
weight2 = spinBox2->value();
index1 = spinBox1->GetVariation()->VariationId();
index2 = spinBox2->GetVariation()->VariationId();
if (column == 0)//First column clicked, sort by variation index.
{
return index1 < index2;
}
else if (column == 1)//Second column clicked, sort by weight.
{
if (IsNearZero(weight1) && IsNearZero(weight2))
return index1 > index2;
else
return fabs(weight1) < fabs(weight2);
}
}
}
return false;
}
};

View File

@ -0,0 +1,17 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by Fractorium.rc
//
#define IDI_ICON1 101
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NO_MFC 1
#define _APS_NEXT_RESOURCE_VALUE 102
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1000
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif