mirror of
https://bitbucket.org/mfeemster/fractorium.git
synced 2025-09-20 13:31:05 -04:00

--User changes -Allow users to set the Exp value when using the Exp temporal filter type. -Set the default temporal filter type to be Box, which does not alter the palette values at all during animation. This is done to avoid confusion when using Gaussian or Exp which can produce darkened images. --Bug fixes -Sending a sequence to the final render dialog when the keyframes had non zero rotate and center Y values would produce off center animations when rendered. -Temporal filters were being unnecessarily recreated many times when rendering or generating sequences. -Exp filter was always treated like a Box filter. --Code changes -Add a new member function SaveCurrentAsXml(QString filename = "") to the controllers which is only used for testing. -Modernize some C++ code.
400 lines
12 KiB
C++
400 lines
12 KiB
C++
#pragma once
|
|
|
|
#include "EmberDefines.h"
|
|
|
|
/// <summary>
|
|
/// TemporalFilter base, derived and factory classes.
|
|
/// </summary>
|
|
|
|
namespace EmberNs
|
|
{
|
|
/// <summary>
|
|
/// The types of temporal filters available.
|
|
/// </summary>
|
|
enum class eTemporalFilterType : et
|
|
{
|
|
BOX_TEMPORAL_FILTER,
|
|
GAUSSIAN_TEMPORAL_FILTER,
|
|
EXP_TEMPORAL_FILTER
|
|
};
|
|
|
|
/// <summary>
|
|
/// g++ needs a forward declaration here.
|
|
/// </summary>
|
|
template <typename T> class TemporalFilterCreator;
|
|
|
|
#define TEMPORALFILTERUSINGS \
|
|
using TemporalFilter<T>::m_Filter; \
|
|
using TemporalFilter<T>::m_FilterExp; \
|
|
using TemporalFilter<T>::Size; \
|
|
using TemporalFilter<T>::FinishFilter;
|
|
|
|
/// <summary>
|
|
/// Temporal filter is for doing motion blur while rendering a series of frames for animation.
|
|
/// The filter created is used as a vector of scalar values to multiply the time value by in between embers.
|
|
/// There are three possible types: Gaussian, Box and Exp.
|
|
/// Template argument expected to be float or double.
|
|
/// </summary>
|
|
template <typename T>
|
|
class EMBER_API TemporalFilter
|
|
{
|
|
public:
|
|
/// <summary>
|
|
/// Constructor to set up basic filtering parameters, allocate buffers and calculate deltas.
|
|
/// Derived class constructors will complete the final part of filter setup.
|
|
/// </summary>
|
|
/// <param name="filterType">Type of the filter.</param>
|
|
/// <param name="temporalSamples">The number of temporal samples in the ember being rendered</param>
|
|
/// <param name="filterWidth">The width of the filter.</param>
|
|
/// <param name="filterExp">The filter exponent. Unused except with ExpTemporalFilter, but needed to prevent equality tests from failing.</param>
|
|
TemporalFilter(eTemporalFilterType filterType, size_t temporalSamples, T filterWidth, T filterExp)
|
|
{
|
|
size_t i, steps = temporalSamples;
|
|
m_TemporalSamples = temporalSamples;
|
|
m_FilterWidth = filterWidth;
|
|
m_Deltas.resize(steps);
|
|
m_Filter.resize(steps);
|
|
m_FilterType = filterType;
|
|
m_FilterExp = filterExp;//Always assign this to prevent excessive recreates in Renderer::CreateTemporalFilter().
|
|
|
|
if (steps == 1)
|
|
{
|
|
m_SumFilt = 1;
|
|
m_Deltas[0] = 0;
|
|
m_Filter[0] = 1;
|
|
}
|
|
else
|
|
{
|
|
//Define the temporal deltas.
|
|
for (i = 0; i < steps; i++)
|
|
m_Deltas[i] = (static_cast<T>(i) / static_cast<T>(steps - 1) - static_cast<T>(0.5)) * filterWidth;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copy constructor.
|
|
/// </summary>
|
|
/// <param name="filter">The TemporalFilter object to copy</param>
|
|
TemporalFilter(const TemporalFilter<T>& filter)
|
|
{
|
|
*this = filter;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Virtual destructor so derived class destructors get called.
|
|
/// </summary>
|
|
virtual ~TemporalFilter()
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Assignment operator.
|
|
/// </summary>
|
|
/// <param name="filter">The TemporalFilter object to copy.</param>
|
|
/// <returns>Reference to updated self</returns>
|
|
TemporalFilter<T>& operator = (const TemporalFilter<T>& filter)
|
|
{
|
|
if (this != &filter)
|
|
{
|
|
m_TemporalSamples = filter.m_TemporalSamples;
|
|
m_FilterWidth = filter.m_FilterWidth;
|
|
m_FilterExp = filter.m_FilterExp;
|
|
m_SumFilt = filter.m_SumFilt;
|
|
m_Deltas = filter.m_Deltas;
|
|
m_Filter = filter.m_Filter;
|
|
m_FilterType = filter.m_FilterType;
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return a string representation of this filter.
|
|
/// </summary>
|
|
/// <returns>The string representation of this filter</returns>
|
|
string ToString() const
|
|
{
|
|
size_t i;
|
|
stringstream ss;
|
|
ss << "Temporal Filter:\n"
|
|
<< "\n Size: " << Size()
|
|
<< "\n Type: " << TemporalFilterCreator<T>::ToString(m_FilterType)
|
|
<< "\n Sum Filt: " << SumFilt();
|
|
ss << "\nDeltas: \n";
|
|
|
|
for (i = 0; i < m_Deltas.size(); i++)
|
|
{
|
|
ss << "Deltas[" << i << "]: " << m_Deltas[i] << "\n";
|
|
}
|
|
|
|
ss << "Filter: \n";
|
|
|
|
for (i = 0; i < m_Filter.size(); i++)
|
|
{
|
|
ss << "Filter[" << i << "]: " << m_Filter[i] << "\n";
|
|
//ss << m_Filter[i] << "\n";
|
|
}
|
|
|
|
return ss.str();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Accessors.
|
|
/// </summary>
|
|
size_t Size() const { return m_Filter.size(); }
|
|
size_t TemporalSamples() const { return m_TemporalSamples; }
|
|
T FilterWidth() const { return m_FilterWidth; }
|
|
T FilterExp() const { return m_FilterExp; }
|
|
T SumFilt() const { return m_SumFilt; }
|
|
T* Deltas() { return m_Deltas.data(); }
|
|
T* Filter() { return m_Filter.data(); }
|
|
eTemporalFilterType FilterType() const { return m_FilterType; }
|
|
|
|
protected:
|
|
/// <summary>
|
|
/// Normalize the filter and the sum filt.
|
|
/// </summary>
|
|
/// <param name="maxFilt">The maximum filter value contained in the filter vector after it was created</param>
|
|
void FinishFilter(T maxFilt)
|
|
{
|
|
m_SumFilt = 0;
|
|
|
|
for (size_t i = 0; i < Size(); i++)
|
|
{
|
|
m_Filter[i] /= maxFilt;
|
|
m_SumFilt += m_Filter[i];
|
|
}
|
|
|
|
m_SumFilt /= Size();
|
|
}
|
|
|
|
T m_SumFilt = 1;//The sum of all filter values.
|
|
T m_FilterWidth;
|
|
T m_FilterExp;
|
|
size_t m_TemporalSamples;
|
|
vector<T> m_Deltas;//Delta vector.
|
|
vector<T> m_Filter;//Filter vector.
|
|
eTemporalFilterType m_FilterType;//The type of filter this is.
|
|
};
|
|
|
|
/// <summary>
|
|
/// Derivation which implements the Exp filter.
|
|
/// </summary>
|
|
template <typename T>
|
|
class EMBER_API ExpTemporalFilter : public TemporalFilter<T>
|
|
{
|
|
TEMPORALFILTERUSINGS
|
|
public:
|
|
/// <summary>
|
|
/// Constructor to create an Exp filter.
|
|
/// </summary>
|
|
/// <param name="temporalSamples">The number of temporal samples in the ember being rendered</param>
|
|
/// <param name="filterWidth">The width of the filter.</param>
|
|
/// <param name="filterExp">The filter exponent.</param>
|
|
ExpTemporalFilter(size_t temporalSamples, T filterWidth, T filterExp = 1)
|
|
: TemporalFilter<T>(eTemporalFilterType::EXP_TEMPORAL_FILTER, temporalSamples, filterWidth, filterExp)
|
|
{
|
|
if (Size() > 1)
|
|
{
|
|
T slpx, maxFilt = 0;
|
|
|
|
for (size_t i = 0; i < Size(); i++)
|
|
{
|
|
if (filterExp >= 0)
|
|
slpx = (static_cast<T>(i) + 1) / Size();
|
|
else
|
|
slpx = static_cast<T>(Size() - i) / Size();
|
|
|
|
//Scale the color based on these values.
|
|
m_Filter[i] = std::pow(slpx, fabs(filterExp));
|
|
|
|
//Keep the max.
|
|
if (m_Filter[i] > maxFilt)
|
|
maxFilt = m_Filter[i];
|
|
}
|
|
|
|
FinishFilter(maxFilt);
|
|
}
|
|
}
|
|
};
|
|
|
|
/// <summary>
|
|
/// Derivation which implements the Gaussian filter.
|
|
/// </summary>
|
|
template <typename T>
|
|
class EMBER_API GaussianTemporalFilter : public TemporalFilter<T>
|
|
{
|
|
TEMPORALFILTERUSINGS
|
|
public:
|
|
/// <summary>
|
|
/// Constructor to create a Gaussian filter.
|
|
/// </summary>
|
|
/// <param name="temporalSamples">The number of temporal samples in the ember being rendered</param>
|
|
/// <param name="filterWidth">The width of the filter.</param>
|
|
/// <param name="filterExp">Unused, but needed to prevent equality tests from failing.</param>
|
|
GaussianTemporalFilter(size_t temporalSamples, T filterWidth, T filterExp)
|
|
: TemporalFilter<T>(eTemporalFilterType::GAUSSIAN_TEMPORAL_FILTER, temporalSamples, filterWidth, filterExp)
|
|
{
|
|
if (Size() > 1)
|
|
{
|
|
T maxFilt = 0, halfSteps = static_cast<T>(Size()) / static_cast<T>(2);
|
|
GaussianFilter<T> gaussian(1, 1);//Just pass dummy values, they are unused in this case.
|
|
|
|
for (size_t i = 0; i < Size(); i++)
|
|
{
|
|
m_Filter[i] = gaussian.Filter(gaussian.Support() * fabs(i - halfSteps) / halfSteps);
|
|
|
|
//Keep the max.
|
|
if (m_Filter[i] > maxFilt)
|
|
maxFilt = m_Filter[i];
|
|
}
|
|
|
|
FinishFilter(maxFilt);
|
|
}
|
|
}
|
|
};
|
|
|
|
/// <summary>
|
|
/// Derivation which implements the Box filter.
|
|
/// </summary>
|
|
template <typename T>
|
|
class EMBER_API BoxTemporalFilter : public TemporalFilter<T>
|
|
{
|
|
TEMPORALFILTERUSINGS
|
|
public:
|
|
/// <summary>
|
|
/// Constructor to create a Box filter.
|
|
/// </summary>
|
|
/// <param name="temporalSamples">The number of temporal samples in the ember being rendered</param>
|
|
/// <param name="filterWidth">The width of the filter.</param>
|
|
/// <param name="filterExp">Unused, but needed to prevent equality tests from failing.</param>
|
|
BoxTemporalFilter(size_t temporalSamples, T filterWidth, T filterExp)
|
|
: TemporalFilter<T>(eTemporalFilterType::BOX_TEMPORAL_FILTER, temporalSamples, filterWidth, filterExp)
|
|
{
|
|
if (Size() > 1)
|
|
{
|
|
for (size_t i = 0; i < Size(); i++)
|
|
m_Filter[i] = 1;
|
|
|
|
FinishFilter(1);
|
|
}
|
|
}
|
|
};
|
|
|
|
/// <summary>
|
|
/// Convenience class to assist in converting between filter names and the filter objects themselves.
|
|
/// </summary>
|
|
template <typename T>
|
|
class EMBER_API TemporalFilterCreator
|
|
{
|
|
public:
|
|
/// <summary>
|
|
/// Creates the specified filter type based on the filterType enum parameter.
|
|
/// </summary>
|
|
/// <param name="filterType">Type of the filter</param>
|
|
/// <param name="temporalSamples">The number of temporal samples in the ember being rendered</param>
|
|
/// <param name="filterWidth">The width of the filter</param>
|
|
/// <param name="filterExp">The filter exp, only used with Exp filter, otherwise unused but needed to prevent equality tests from failing.</param>
|
|
/// <returns>A pointer to the newly created filter object</returns>
|
|
static TemporalFilter<T>* Create(eTemporalFilterType filterType, size_t temporalSamples, T filterWidth, T filterExp)
|
|
{
|
|
TemporalFilter<T>* filter = nullptr;
|
|
|
|
switch (filterType)
|
|
{
|
|
case EmberNs::eTemporalFilterType::BOX_TEMPORAL_FILTER:
|
|
filter = new BoxTemporalFilter<T>(temporalSamples, filterWidth, filterExp);
|
|
break;
|
|
|
|
case EmberNs::eTemporalFilterType::GAUSSIAN_TEMPORAL_FILTER:
|
|
filter = new GaussianTemporalFilter<T>(temporalSamples, filterWidth, filterExp);
|
|
break;
|
|
|
|
case EmberNs::eTemporalFilterType::EXP_TEMPORAL_FILTER:
|
|
filter = new ExpTemporalFilter<T>(temporalSamples, filterWidth, filterExp);
|
|
break;
|
|
|
|
default:
|
|
filter = new BoxTemporalFilter<T>(temporalSamples, filterWidth, filterExp);//Default to box if bad enum passed in.
|
|
break;
|
|
}
|
|
|
|
return filter;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return a string vector of the available filter types.
|
|
/// </summary>
|
|
/// <returns>A vector of strings populated with the available filter types</returns>
|
|
static vector<string> FilterTypes()
|
|
{
|
|
vector<string> v;
|
|
v.reserve(3);
|
|
v.push_back("Box");
|
|
v.push_back("Gaussian");
|
|
v.push_back("Exp");
|
|
return v;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert between the filter name string and its type enum.
|
|
/// </summary>
|
|
/// <param name="filterType">The string name of the filter</param>
|
|
/// <returns>The filter type enum</returns>
|
|
static eTemporalFilterType FromString(const string& filterType)
|
|
{
|
|
if (!_stricmp(filterType.c_str(), "box"))
|
|
return eTemporalFilterType::BOX_TEMPORAL_FILTER;
|
|
else if (!_stricmp(filterType.c_str(), "gaussian"))
|
|
return eTemporalFilterType::GAUSSIAN_TEMPORAL_FILTER;
|
|
else if (!_stricmp(filterType.c_str(), "exp"))
|
|
return eTemporalFilterType::EXP_TEMPORAL_FILTER;
|
|
else
|
|
return eTemporalFilterType::BOX_TEMPORAL_FILTER;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert between the filter type enum and its name string.
|
|
/// </summary>
|
|
/// <param name="eTemporalFilterType">The filter type enum</param>
|
|
/// <returns>The string name of the filter</returns>
|
|
static string ToString(eTemporalFilterType filterType)
|
|
{
|
|
string filter;
|
|
|
|
switch (filterType)
|
|
{
|
|
case EmberNs::eTemporalFilterType::BOX_TEMPORAL_FILTER:
|
|
filter = "Box";
|
|
break;
|
|
|
|
case EmberNs::eTemporalFilterType::GAUSSIAN_TEMPORAL_FILTER:
|
|
filter = "Gaussian";
|
|
break;
|
|
|
|
case EmberNs::eTemporalFilterType::EXP_TEMPORAL_FILTER:
|
|
filter = "Exp";
|
|
break;
|
|
|
|
default:
|
|
filter = "Box";
|
|
break;
|
|
}
|
|
|
|
return filter;
|
|
}
|
|
};
|
|
|
|
/// <summary>
|
|
/// Thin wrapper around TemporalFilterCreator::ToString() to allow << operator on temporal filter type.
|
|
/// </summary>
|
|
/// <param name="stream">The stream to insert into</param>
|
|
/// <param name="t">The type whose string representation will be inserted into the stream</param>
|
|
/// <returns></returns>
|
|
static std::ostream& operator<<(std::ostream& stream, const eTemporalFilterType& t)
|
|
{
|
|
stream << TemporalFilterCreator<float>::ToString(t);
|
|
return stream;
|
|
}
|
|
}
|