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

352
Source/Ember/Affine2D.cpp Normal file
View File

@ -0,0 +1,352 @@
#include "EmberPch.h"
#include "Affine2D.h"
namespace EmberNs
{
/// <summary>
/// Default constructor which sets the matrix to the identity.
/// </summary>
template <typename T>
Affine2D<T>::Affine2D()
{
MakeID();
}
/// <summary>
/// Constructor which takes each column of the affine as a separate parameter.
/// </summary>
/// <param name="x">A and D</param>
/// <param name="y">B and E</param>
/// <param name="t">C and F</param>
template <typename T>
Affine2D<T>::Affine2D(v2T& x, v2T& y, v2T& t)
{
X(x);
Y(y);
O(t);
}
/// <summary>
/// Constructor which takes all six of the affine values as parameters.
/// </summary>
/// <param name="xx">A</param>
/// <param name="xy">D</param>
/// <param name="yx">B</param>
/// <param name="yy">E</param>
/// <param name="tx">C</param>
/// <param name="ty">F</param>
template <typename T>
Affine2D<T>::Affine2D(T xx, T xy, T yx, T yy, T tx, T ty)
{
A(xx);
D(xy);
B(yx);
E(yy);
C(tx);
F(ty);
}
/// <summary>
/// Constructor which takes a 4x4 matrix and assigns the
/// corresponding values in the 2x3 affine matrix.
/// </summary>
/// <param name="mat">The 4x4 affine matrix to read from</param>
template <typename T>
Affine2D<T>::Affine2D(m4T& mat)
{
A(mat[0][0]);
B(mat[0][1]);
C(mat[0][3]);
D(mat[1][0]);
E(mat[1][1]);
F(mat[1][3]);
}
/// <summary>
/// Make this affine transform the identity matrix.
/// A and E = 1, all else 0.
/// </summary>
template <typename T>
void Affine2D<T>::MakeID()
{
A(1);
B(0);
C(0);
D(0);
E(1);
F(0);
}
/// <summary>
/// Determine whether this affine transform is the identity matrix.
/// </summary>
/// <returns>True if A and E are equal to 1 and all others are 0, else false.</returns>
template <typename T>
bool Affine2D<T>::IsID() const
{
return (IsClose<T>(A(), 1)) &&
(IsClose<T>(B(), 0)) &&
(IsClose<T>(C(), 0)) &&
(IsClose<T>(D(), 0)) &&
(IsClose<T>(E(), 1)) &&
(IsClose<T>(F(), 0));
}
/// <summary>
/// Determine whether this affine transform is all zeroes.
/// </summary>
/// <returns>True if all 6 elements equal zero, else false.</returns>
template <typename T>
bool Affine2D<T>::IsZero() const
{
return (IsClose<T>(A(), 0)) &&
(IsClose<T>(B(), 0)) &&
(IsClose<T>(C(), 0)) &&
(IsClose<T>(D(), 0)) &&
(IsClose<T>(E(), 0)) &&
(IsClose<T>(F(), 0));
}
/// <summary>
/// Rotate this affine transform around its origin by the specified angle in degrees.
/// </summary>
/// <param name="angle">The angle to rotate by</param>
template <typename T>
void Affine2D<T>::Rotate(T angle)
{
m4T origMat4 = ToMat4ColMajor(true);//Must center and use column major for glm to work.
m4T newMat4 = glm::rotate(origMat4, angle * DEG_2_RAD_T, v3T(0, 0, 1));//Assuming only rotating around z.
A(newMat4[0][0]);//Use direct assignments instead of constructor to skip assigning C and F.
B(newMat4[0][1]);
D(newMat4[1][0]);
E(newMat4[1][1]);
}
/// <summary>
/// Move by v.
/// </summary>
/// <param name="v">The vec2 describing how far to move in the x and y directions</param>
template <typename T>
void Affine2D<T>::Translate(v2T& v)
{
O(O() + v);
}
/// <summary>
/// Rotate and scale the X and Y components by a certain amount based on X.
/// </summary>
/// <param name="v">The vec2 describing how much to rotate and scale the X and Y components</param>
template <typename T>
void Affine2D<T>::RotateScaleXTo(v2T& v)
{
Affine2D<T> rs = CalcRotateScale(X(), v);
X(rs.TransformNormal(X()));
Y(rs.TransformNormal(Y()));
}
/// <summary>
/// Rotate and scale the X and Y components by a certain amount based on Y.
/// </summary>
/// <param name="v">The vec2 describing how much to rotate and scale the X and Y components</param>
template <typename T>
void Affine2D<T>::RotateScaleYTo(v2T& v)
{
Affine2D<T> rs = CalcRotateScale(Y(), v);
X(rs.TransformNormal(X()));
Y(rs.TransformNormal(Y()));
}
/// <summary>
/// Return the inverse of the 2x3 affine matrix.
/// </summary>
/// <returns>The inverse of this affine transform</returns>
template <typename T>
Affine2D<T> Affine2D<T>::Inverse() const
{
T det = A() * E() - D() * B();
return Affine2D<T>(E() / det, -D() / det,
-B() / det, A() / det,
(F() * B() - C() * E()) / det, (C() * D() - F() * A()) / det);
}
/// <summary>
/// Return a vec2 gotten from transforming this affine transform
/// by the vec2 passed in, but with a T component of 0, 0.
/// </summary>
/// <param name="v">The vec2 describing how much to transform by</param>
/// <returns>The centered, transformed vec2</returns>
template <typename T>
typename v2T Affine2D<T>::TransformNormal(const v2T& v) const
{
return v2T(A() * v.x + B() * v.y, D() * v.x + E() * v.y);
}
/// <summary>
/// Return a vec2 gotten from transforming this affine transform
/// by the vec2 passed in, and applying T translation.
/// </summary>
/// <param name="v">The vec2 describing how much to transform by</param>
/// <returns>The translated, transformed vec2</returns>
template <typename T>
typename v2T Affine2D<T>::TransformVector(const v2T& v) const
{
return v2T(A() * v.x + B() * v.y + C(), D() * v.x + E() * v.y + F());
}
/// <summary>
/// Return the X and Y components as a 2x2 matrix in column major order.
/// </summary>
/// <returns>The 2x2 matrix</returns>
template <typename T>
typename m2T Affine2D<T>::ToMat2ColMajor() const
{
return m2T(A(), B(),//Col0...
D(), E());//1
}
/// <summary>
/// Return the X and Y components as a 2x2 matrix in row major order.
/// </summary>
/// <returns>The 2x2 matrix</returns>
template <typename T>
typename m2T Affine2D<T>::ToMat2RowMajor() const
{
return m2T(A(), D(),//Col0...
B(), E());//1
}
/// <summary>
/// Return the 2x3 affine transform matrix as a 4x4 matrix in column major order.
/// </summary>
/// <param name="center">Whether to use T translation value or just 0 for center</param>
/// <returns>The 4x4 matrix</returns>
template <typename T>
typename m4T Affine2D<T>::ToMat4ColMajor(bool center) const
{
m4T mat(A(), B(), 0, center ? 0 : C(),//Col0...
D(), E(), 0, center ? 0 : F(),//1
0, 0, 1, 0,//2
0, 0, 0, 1);//3
return mat;
}
/// <summary>
/// Return the 2x3 affine transform matrix as a 4x4 matrix in row major order.
/// </summary>
/// <param name="center">Whether to use T translation value or just 0 for center</param>
/// <returns>The 4x4 matrix</returns>
template <typename T>
typename m4T Affine2D<T>::ToMat4RowMajor(bool center) const
{
m4T mat(A(), D(), 0, 0,
B(), E(), 0, 0,
0, 0, 1, 0,
center ? 0 : C(), center ? 0 : F(), 0, 1);
return mat;
}
/// <summary>
/// == operator which tests if all fields are equal with another Affine2D.
/// </summary>
/// <param name="affine">The Affine2D to compare to</param>
/// <returns>True if all fields are equal, else false</returns>
template <typename T>
bool Affine2D<T>::operator == (const Affine2D<T>& affine)
{
return IsClose(A(), affine.A()) &&
IsClose(B(), affine.B()) &&
IsClose(C(), affine.C()) &&
IsClose(D(), affine.D()) &&
IsClose(E(), affine.E()) &&
IsClose(F(), affine.F());
}
/// <summary>
/// * operator to multiply this affine transform by another and return the result.
/// </summary>
/// <param name="affine">The Affine2D to multiply by</param>
/// <returns>A new Affine2D which is the product of the multiplication</returns>
template <typename T>
Affine2D<T>& Affine2D<T>::operator * (const Affine2D<T>& affine)
{
X(TransformNormal(affine.X()));
Y(TransformNormal(affine.Y()));
O(TransformVector(affine.O()));
return *this;
}
/// <summary>
/// * operator to multiply this affine transform by a vec2 and return the result as a vec2.
/// </summary>
/// <param name="v">The vec2 to multiply by</param>
/// <returns>A new vec2 which is the product of the multiplication</returns>
template <typename T>
typename v2T Affine2D<T>::operator * (const v2T& v)
{
return TransformVector(v);
}
/// <summary>
/// Accessors.
/// </summary>
template <typename T> T Affine2D<T>::A() const { return m_Mat[0][0]; }//[0][0]//flam3
template <typename T> T Affine2D<T>::B() const { return m_Mat[0][1]; }//[1][0]
template <typename T> T Affine2D<T>::C() const { return m_Mat[0][2]; }//[2][0]
template <typename T> T Affine2D<T>::D() const { return m_Mat[1][0]; }//[0][1]
template <typename T> T Affine2D<T>::E() const { return m_Mat[1][1]; }//[1][1]
template <typename T> T Affine2D<T>::F() const { return m_Mat[1][2]; }//[2][1]
template <typename T> void Affine2D<T>::A(T a) { m_Mat[0][0] = a; }
template <typename T> void Affine2D<T>::B(T b) { m_Mat[0][1] = b; }
template <typename T> void Affine2D<T>::C(T c) { m_Mat[0][2] = c; }
template <typename T> void Affine2D<T>::D(T d) { m_Mat[1][0] = d; }
template <typename T> void Affine2D<T>::E(T e) { m_Mat[1][1] = e; }
template <typename T> void Affine2D<T>::F(T f) { m_Mat[1][2] = f; }
template <typename T> typename v2T Affine2D<T>::X() const { return v2T(A(), D()); }//X Axis.
template <typename T> typename v2T Affine2D<T>::Y() const { return v2T(B(), E()); }//Y Axis.
template <typename T> typename v2T Affine2D<T>::O() const { return v2T(C(), F()); }//Translation.
template <typename T> void Affine2D<T>::X(v2T& x) { A(x.x); D(x.y); }//X Axis.
template <typename T> void Affine2D<T>::Y(v2T& y) { B(y.x); E(y.y); }//Y Axis.
template <typename T> void Affine2D<T>::O(v2T& t) { C(t.x); F(t.y); }//Translation.
/// <summary>
/// Rotate and scale this affine transform and return as a copy. Orginal is unchanged.
/// </summary>
/// <param name="from">The starting point to rotate and scale from</param>
/// <param name="to">The ending point to rotate and scale to</param>
/// <returns>The newly rotated and scalled Affine2D</returns>
template <typename T>
Affine2D<T> Affine2D<T>::CalcRotateScale(v2T& from, v2T& to)
{
T a, c;
CalcRSAC(from, to, a, c);
return Affine2D<T>(a, c, -c, a, 0, 0);
}
/// <summary>
/// Never fully understood what this did or why it's named what it is.
/// But it seems to handle some rotating and scaling.
/// </summary>
/// <param name="from">The starting point to rotate and scale from</param>
/// <param name="to">The ending point to rotate and scale to</param>
/// <param name="a">a</param>
/// <param name="c">c</param>
template <typename T>
void Affine2D<T>::CalcRSAC(v2T& from, v2T& to, T& a, T& c)
{
T lsq = from.x * from.x + from.y * from.y;
a = (from.y * to.y + from.x * to.x) / lsq;
c = (from.x * to.y - from.y * to.x) / lsq;
}
}

142
Source/Ember/Affine2D.h Normal file
View File

@ -0,0 +1,142 @@
#pragma once
#include "Utils.h"
/// <summary>
/// Affine2D class.
/// </summary>
namespace EmberNs
{
/// <summary>
/// Uses matrix composition to handle the
/// affine matrix. Taken almost entirely from
/// Fractron, but using glm, and in C++.
/// Note that the matrix layout differs from flam3 so it's best to use
/// the A, B, C, D, E, F wrappers around the underlying matrix indices. But if the matrix must
/// be accessed directly, the two are laid out as such:
/// flam3: 3 columns of 2 rows each. Accessed col, row.
/// [a(0,0)][b(1,0)][c(2,0)]
/// [d(0,1)][e(1,1)][f(2,1)]
/// Ember: 2 columns of 3 rows each. Accessed col, row.
/// [a(0,0)][d(1,0)]
/// [b(0,1)][e(1,1)]
/// [c(0,2)][f(1,2)]
/// Template argument expected to be float or double.
/// </summary>
template <typename T>
class EMBER_API Affine2D
{
public:
Affine2D();
/// <summary>
/// Default copy constructor.
/// </summary>
/// <param name="affine">The Affine2D object to copy</param>
Affine2D(const Affine2D<T>& affine)
{
Affine2D<T>::operator=<T>(affine);
}
/// <summary>
/// Copy constructor to copy an Affine2D object of type U.
/// </summary>
/// <param name="affine">The Affine2D object to copy</param>
template <typename U>
Affine2D(const Affine2D<U>& affine)
{
Affine2D<T>::operator=<U>(affine);
}
Affine2D(v2T& x, v2T& y, v2T& t);
Affine2D(T xx, T xy, T yx, T yy, T tx, T ty);
Affine2D(m4T& mat);
/// <summary>
/// Default assignment operator.
/// </summary>
/// <param name="affine">The Affine2D object to copy</param>
Affine2D<T>& operator = (const Affine2D<T>& affine)
{
if (this != &affine)
Affine2D<T>::operator=<T>(affine);
return *this;
}
/// <summary>
/// Assignment operator to assign an Affine2D object of type U.
/// </summary>
/// <param name="affine">The Affine2D object to copy.</param>
/// <returns>Reference to updated self</returns>
template <typename U>
Affine2D<T>& operator = (const Affine2D<U>& affine)
{
A(T(affine.A()));
B(T(affine.B()));
C(T(affine.C()));
D(T(affine.D()));
E(T(affine.E()));
F(T(affine.F()));
return *this;
}
inline void MakeID();
inline bool IsID() const;
inline bool IsZero() const;
inline void Rotate(T angle);
inline void Translate(v2T& v);
inline void RotateScaleXTo(v2T& v);
inline void RotateScaleYTo(v2T& v);
inline Affine2D<T> Inverse() const;
inline v2T TransformNormal(const v2T& v) const;
inline v2T TransformVector(const v2T& v) const;
inline m2T ToMat2ColMajor() const;
inline m2T ToMat2RowMajor() const;
inline m4T ToMat4ColMajor(bool center = false) const;
inline m4T ToMat4RowMajor(bool center = false) const;
bool operator == (const Affine2D<T>& affine);
Affine2D<T>& operator * (const Affine2D<T>& affine);
v2T operator * (const v2T& v);
inline T A() const;
inline T B() const;
inline T C() const;
inline T D() const;
inline T E() const;
inline T F() const;
inline void A(T a);
inline void B(T b);
inline void C(T c);
inline void D(T d);
inline void E(T e);
inline void F(T f);
inline v2T X() const;
inline v2T Y() const;
inline v2T O() const;
inline void X(v2T& x);
inline void Y(v2T& y);
inline void O(v2T& t);
//static Affine2D Identity();//Complains about inline.
static inline Affine2D CalcRotateScale(v2T& from, v2T& to);
static inline void CalcRSAC(v2T& from, v2T& to, T& a, T& c);
m23T m_Mat;
};
//This class had to be implemented in a cpp file because the compiler was breaking.
//So the explicit instantiation must be declared here rather than in Ember.cpp where
//all of the other classes are done.
template EMBER_API class Affine2D<float>;
#ifdef DO_DOUBLE
template EMBER_API class Affine2D<double>;
#endif
}

252
Source/Ember/CarToRas.h Normal file
View File

@ -0,0 +1,252 @@
#pragma once
#include "Point.h"
/// <summary>
/// CarToRas class.
/// </summary>
namespace EmberNs
{
/// <summary>
/// When iterating, everything is positioned in terms of a carteseian plane with 0,0 in the center like so:
/// [-1,1] [1,1]
/// [-1,-1] [1,-1]
/// However, when accumulating to the histogram, the data is stored in the traditional raster coordinate system
/// of 0,0 at the top left and x,y at the bottom right. This class provides functionality to convert from one
/// to the other and is used when accumulating a sub batch of iteration results to the histogram.
/// Note the functions use reference arguments for the converted values because they are slightly faster than returning a value.
/// Template argument expected to be float or double.
/// </summary>
template <typename T>
class EMBER_API CarToRas
{
public:
/// <summary>
/// Empty constructor. This class should never be used unless it's been properly constructed with the constructor that takes arguments.
/// </summary>
CarToRas()
{
}
/// <summary>
/// Constructor that takes arguments to set up the bounds and passes them to Init().
/// </summary>
/// <param name="carLlX">The lower left x of the cartesian plane</param>
/// <param name="carLlY">The lower left y of the cartesian plane</param>
/// <param name="carUrX">The upper right x of the cartesian plane</param>
/// <param name="carUrY">The upper right y of the cartesian plane</param>
/// <param name="rasW">The width in pixels of the raster image/histogram</param>
/// <param name="rasH">The height in pixels of the raster image/histogram</param>
/// <param name="aspectRatio">The aspect ratio, generally 1</param>
CarToRas(T carLlX, T carLlY, T carUrX, T carUrY, unsigned int rasW, unsigned int rasH, T aspectRatio)
{
Init(carLlX, carLlY, carUrX, carUrY, rasW, rasH, aspectRatio);
}
/// <summary>
/// Default copy constructor.
/// </summary>
/// <param name="carToRas">The CarToRas object to copy</param>
CarToRas(const CarToRas<T>& carToRas)
{
CarToRas<T>::operator=<T>(carToRas);
}
/// <summary>
/// Copy constructor to copy a CarToRas object of type U.
/// </summary>
/// <param name="carToRas">The CarToRas object to copy</param>
template <typename U>
CarToRas(const CarToRas<U>& carToRas)
{
CarToRas<T>::operator=<U>(carToRas);
}
/// <summary>
/// Default assignment operator.
/// </summary>
/// <param name="carToRas">The CarToRas object to copy</param>
CarToRas<T>& operator = (const CarToRas<T>& carToRas)
{
if (this != &carToRas)
CarToRas<T>::operator=<T>(carToRas);
return *this;
}
/// <summary>
/// Assignment operator to assign a CarToRas object of type U.
/// </summary>
/// <param name="carToRas">The CarToRas object to copy.</param>
/// <returns>Reference to updated self</returns>
template <typename U>
CarToRas<T>& operator = (const CarToRas<U>& carToRas)
{
m_RasWidth = carToRas.RasWidth();
m_RasHeight = carToRas.RasHeight();
m_OneRow = T(carToRas.OneRow());
m_OneCol = T(carToRas.OneCol());
m_PixPerImageUnitW = T(carToRas.PixPerImageUnitW());
m_RasLlX = T(carToRas.RasLlX());
m_PixPerImageUnitH = T(carToRas.PixPerImageUnitH());
m_RasLlY = T(carToRas.RasLlY());
m_CarLlX = T(carToRas.CarLlX());
m_CarLlY = T(carToRas.CarLlY());
m_CarUrX = T(carToRas.CarUrX());
m_CarUrY = T(carToRas.CarUrY());
m_PadCarLlX = T(carToRas.PadCarLlX());
m_PadCarLlY = T(carToRas.PadCarLlY());
m_PadCarUrX = T(carToRas.PadCarUrX());
m_PadCarUrY = T(carToRas.PadCarUrY());
return *this;
}
/// <summary>
/// Initialize the dimensions with the specified bounds.
/// </summary>
/// <param name="carLlX">The lower left x of the cartesian plane</param>
/// <param name="carLlY">The lower left y of the cartesian plane</param>
/// <param name="carUrX">The upper right x of the cartesian plane</param>
/// <param name="carUrY">The upper right y of the cartesian plane</param>
/// <param name="rasW">The width in pixels of the raster image/histogram</param>
/// <param name="rasH">The height in pixels of the raster image/histogram</param>
/// <param name="aspectRatio">The aspect ratio, generally 1</param>
void Init(T carLlX, T carLlY, T carUrX, T carUrY, unsigned int rasW, unsigned int rasH, T aspectRatio)
{
m_RasWidth = rasW;
m_RasHeight = rasH;
m_CarLlX = carLlX;
m_CarLlY = carLlY;
m_CarUrX = carUrX;
m_CarUrY = carUrY;
T carW = m_CarUrX - m_CarLlX;//Right minus left.
T carH = m_CarUrY - m_CarLlY;//Top minus bottom.
T invSizeW = T(1.0) / carW;
T invSizeH = T(1.0) / carH;
m_PixPerImageUnitW = (T)rasW * invSizeW;
m_RasLlX = m_PixPerImageUnitW * carLlX;
m_PixPerImageUnitH = (T)rasH * invSizeH;
m_RasLlY = m_PixPerImageUnitH * carLlY;
T m_OneRow = abs(m_CarUrY - m_CarLlY) / m_RasHeight;
T m_OneCol = abs(m_CarUrX - m_CarLlX) / m_RasWidth;
m_PadCarLlX = m_CarLlX + m_OneCol;
m_PadCarUrX = m_CarUrX - m_OneCol;
m_PadCarLlY = m_CarLlY + m_OneRow;
m_PadCarUrY = m_CarUrY - m_OneRow;
}
/// <summary>
/// Convert a cartesian x, y coordinate to a raster x, y coordinate.
/// This will flip the Y coordinate, so points that hit the bottom of the cartesian plane will
/// be mapped to the top of the histogram and vice versa.
/// There is a very slim chance that a point will be right on the border and will technically be in bounds, passing the InBounds() test,
/// but ends up being mapped to a histogram bucket that is out of bounds due to roundoff error. Perform an additional check after this call to make sure the
/// mapped point is in bounds.
/// </summary>
/// <param name="cartX">The cartesian x</param>
/// <param name="cartY">The cartesian y</param>
/// <param name="rasX">The converted raster x</param>
/// <param name="rasY">The converted raster y</param>
inline void Convert(T cartX, T cartY, unsigned int& rasX, unsigned int& rasY)
{
rasX = (unsigned int)(m_PixPerImageUnitW * cartX - m_RasLlX);
rasY = (unsigned int)(m_RasLlY - (m_PixPerImageUnitH * cartY));
}
/// <summary>
/// Convert a cartesian x, y coordinate to a single raster buffer index.
/// This will flip the Y coordinate, so points that hit the bottom of the cartesian plane will
/// be mapped to the top of the histogram and vice versa.
/// There is a very slim chance that a point will be right on the border and will technically be in bounds, passing the InBounds() test,
/// but ends up being mapped to a histogram bucket that is out of bounds due to roundoff error. Perform an additional check after this call to make sure the
/// mapped point is in bounds.
/// </summary>
/// <param name="cartX">The cartesian x</param>
/// <param name="cartY">The cartesian y</param>
/// <param name="singleBufferIndex">The converted single raster buffer index</param>
inline void Convert(T cartX, T cartY, unsigned int& singleBufferIndex)
{
singleBufferIndex = (unsigned int)(m_PixPerImageUnitW * cartX - m_RasLlX) + (m_RasWidth * (unsigned int)(m_PixPerImageUnitH * cartY - m_RasLlY));
}
/// <summary>
/// Convert a cartesian x, y point to a single raster buffer index.
/// This will flip the Y coordinate, so points that hit the bottom of the cartesian plane will
/// be mapped to the top of the histogram and vice versa.
/// This is the most efficient possible way of converting, consisting of only
/// a multiply and subtract per coordinate element.
/// There is a very slim chance that a point will be right on the border and will technically be in bounds, passing the InBounds() test,
/// but ends up being mapped to a histogram bucket that is out of bounds due to roundoff error. Perform an additional check after this call to make sure the
/// mapped point is in bounds.
/// </summary>
/// <param name="point">The cartesian y</param>
/// <param name="singleBufferIndex">The converted single raster buffer index</param>
inline void Convert(Point<T>& point, unsigned int& singleBufferIndex)
{
singleBufferIndex = (unsigned int)(m_PixPerImageUnitW * point.m_X - m_RasLlX) + (m_RasWidth * (unsigned int)(m_PixPerImageUnitH * point.m_Y - m_RasLlY));
}
/// <summary>
/// Determine if a point in the cartesian plane can be converted to a point within the raster plane.
/// There is a very slim chance that a point will be right on the border and will technically be in bounds, passing the InBounds() test,
/// but ends up being mapped to a histogram bucket that is out of bounds due to roundoff error. Perform an additional check after this call to make sure the
/// mapped point is in bounds.
/// </summary>
/// <param name="point">The point to test</param>
/// <returns>True if within bounds, else false</returns>
inline bool InBounds(Point<T>& point)
{
//Debug check for hitting the very first pixel in the image.
//if (point.m_Y > m_CarLlY && point.m_Y <= m_PadCarLlY && //Mapped to top row...
// point.m_X > m_CarLlX && point.m_X <= m_PadCarLlX)//...first col.
//{
// cout << "First pixel hit." << endl;
//}
return point.m_X >= m_CarLlX &&
point.m_X < m_CarUrX &&
point.m_Y < m_CarUrY &&
point.m_Y >= m_CarLlY;
}
/// <summary>
/// Accessors.
/// </summary>
inline unsigned int RasWidth() const { return m_RasWidth; }
inline unsigned int RasHeight() const { return m_RasHeight; }
inline T OneRow() const { return m_OneRow; }
inline T OneCol() const { return m_OneCol; }
inline T PixPerImageUnitW() const { return m_PixPerImageUnitW; }
inline T RasLlX() const { return m_RasLlX; }
inline T PixPerImageUnitH() const { return m_PixPerImageUnitH; }
inline T RasLlY() const { return m_RasLlY; }
inline T CarLlX() const { return m_CarLlX; }
inline T CarLlY() const { return m_CarLlY; }
inline T CarUrX() const { return m_CarUrX; }
inline T CarUrY() const { return m_CarUrY; }
inline T PadCarLlX() const { return m_PadCarLlX; }
inline T PadCarLlY() const { return m_PadCarLlY; }
inline T PadCarUrX() const { return m_PadCarUrX; }
inline T PadCarUrY() const { return m_PadCarUrY; }
private:
unsigned int m_RasWidth, m_RasHeight;//The width and height of the raster image.
T m_OneRow;//The distance that one raster row represents in the cartesian plane.
T m_OneCol;//The distance that one raster column represents in the cartesian plane.
T m_PixPerImageUnitW;//The number of columns in the raster plane that a horizontal distance of 1 in the cartesian plane represents. The higher the number, the more zoomed in.
T m_RasLlX;//The lower left x of the raster image plane.
T m_PixPerImageUnitH;//The number of rows in the raster plane that a vertical distance of 1 in the cartesian plane represents. The higher the number, the more zoomed in.
T m_RasLlY;//The lower left y of the raster image plane.
T m_CarLlX, m_CarLlY, m_CarUrX, m_CarUrY;//The bounds of the cartesian plane.
T m_PadCarLlX, m_PadCarLlY, m_PadCarUrX, m_PadCarUrY;//The bounds of the cartesian plane padded by one raster row and column on each side.
};
}

View File

@ -0,0 +1,340 @@
#pragma once
#include "SpatialFilter.h"
/// <summary>
/// DensityFilter class.
/// </summary>
namespace EmberNs
{
/// <summary>
/// A base class with virtual functions to allow both templating and polymorphism to work together.
/// Derived classes will implement all of these functions.
/// </summary>
class EMBER_API DensityFilterBase
{
public:
DensityFilterBase() { }
virtual ~DensityFilterBase() { }
virtual int FilterWidth() { return 0; }
};
/// <summary>
/// The density estimation filter is used after iterating, but before final accumulation.
/// It's a variable width Gaussian filter, whose width is inversely proportional
/// to the number of hits a given histogram cell has received.
/// That means the fewer hits in a cell, the more blur is applied. The greater the hits,
/// the less blur.
/// Template argument expected to be float or double.
/// </summary>
template <typename T>
class EMBER_API DensityFilter : public DensityFilterBase
{
public:
/// <summary>
/// Constructor that assigns various fields but does not create the actual filter vector.
/// This is done because filter creation could fail, so the user must manually call it
/// after construction.
/// </summary>
/// <param name="minRad">The minimum filter radius</param>
/// <param name="maxRad">The maximum filter radius</param>
/// <param name="curve">The curve of the filter</param>
/// <param name="supersample">The supersample of the ember this filter will be used with</param>
DensityFilter(T minRad, T maxRad, T curve, unsigned int supersample)
{
m_MinRad = minRad;
m_MaxRad = maxRad;
m_Curve = curve;
m_Supersample = supersample;
m_MaxFilterIndex = 0;
//Make sure the values make sense.
if (m_Curve <= 0.0)
m_Curve = T(0.5);
if (m_MaxRad < m_MinRad)
m_MaxRad = m_MinRad + 1;
}
/// <summary>
/// Copy constructor.
/// </summary>
/// <param name="filter">The DensityFilter object to copy</param>
DensityFilter(const DensityFilter<T>& filter)
{
*this = filter;
}
/// <summary>
/// Assignment operator.
/// </summary>
/// <param name="filter">The DensityFilter object to copy.</param>
/// <returns>Reference to updated self</returns>
DensityFilter<T>& operator = (const DensityFilter<T>& filter)
{
if (this != &filter)
{
m_MinRad = filter.m_MinRad;
m_MaxRad = filter.m_MaxRad;
m_Curve = filter.m_Curve;
m_Supersample = filter.m_Supersample;
m_KernelSize = filter.m_KernelSize;
m_MaxFilterIndex = filter.m_MaxFilterIndex;
m_MaxFilteredCounts = filter.m_MaxFilteredCounts;
m_FilterWidth = filter.m_FilterWidth;
m_Coefs = filter.m_Coefs;
m_Widths = filter.m_Widths;
}
return *this;
}
/// <summary>
/// Create the filter vector of up to 10M entries.
/// If more than that are requested, it isn't created and
/// false is returned.
/// </summary>
/// <returns>True if success, else false.</returns>
bool Create()
{
int i, j, w;
int intFilterCount, maxIndex;
int rowSize;
int filterLoop;
int keepThresh = 100;
unsigned int filterCoefIndex = 0;
T decFilterCount;
T finalMinRad = m_MinRad * m_Supersample + 1;//Should scale the filter width by the oversample.
T finalMaxRad = m_MaxRad * m_Supersample + 1;//The '+1' comes from the assumed distance to the first pixel.
GaussianFilter<T> gaussianFilter(m_MaxRad, m_Supersample);
m_KernelSize = 0;
m_MaxFilterIndex = 0;
//Calculate how many filter kernels are needed based on the decay function
//
// num filters = (de_max_width / de_min_width)^(1 / estimator_curve)
//
decFilterCount = pow(finalMaxRad / finalMinRad, T(1.0) / m_Curve);
if (decFilterCount > 1e7)//Too many filters.
return false;
intFilterCount = (int)ceil(decFilterCount);
//Condense the smaller kernels to save space.
if (intFilterCount > keepThresh)
{
maxIndex = (int)ceil(DE_THRESH + pow(T(intFilterCount - DE_THRESH), m_Curve)) + 1;
m_MaxFilteredCounts = (int)pow(T(maxIndex - DE_THRESH), T(1.0) / m_Curve) + DE_THRESH;
}
else
{
maxIndex = intFilterCount;
m_MaxFilteredCounts = maxIndex;
}
//Allocate the memory for these filters and the hit/width lookup array.
rowSize = (int)(2 * ceil(finalMaxRad) - 1);
m_FilterWidth = (rowSize - 1) / 2;
m_KernelSize = (m_FilterWidth + 1) * (2 + m_FilterWidth) / 2;
m_Coefs.resize(maxIndex * m_KernelSize);
m_Widths.resize(maxIndex);
//Generate the filter coefficients.
for (filterLoop = 0; filterLoop < maxIndex; filterLoop++)
{
int dej, dek;
int coefIndex;
T filterSum = 0.0;
T filterVal;
T filterHeight;
T loopAdjust;
//Calculate the filter width for this number of hits in a bin.
if (filterLoop < keepThresh)
{
filterHeight = (finalMaxRad / pow(T(filterLoop + 1), m_Curve));
}
else
{
loopAdjust = pow(T(filterLoop - keepThresh), (T(1.0) / m_Curve)) + keepThresh;
filterHeight = (finalMaxRad / pow(loopAdjust + 1, m_Curve));
}
//Once we've reached the min radius, don't populate any more.
if (filterHeight <= finalMinRad)
{
filterHeight = finalMinRad;
m_MaxFilterIndex = filterLoop;
}
m_Widths[filterLoop] = filterHeight;
//Calculate norm of kernel separately (easier).
for (dej = -m_FilterWidth; dej <= m_FilterWidth; dej++)
{
for (dek = -m_FilterWidth; dek <= m_FilterWidth; dek++)
{
filterVal = sqrt((T)(dej * dej + dek * dek)) / filterHeight;
//Only populate the coefs within this radius.
if (filterVal <= 1.0)
filterSum += gaussianFilter.Filter(gaussianFilter.Support() * filterVal);
}
}
coefIndex = filterLoop * m_KernelSize;
//Calculate the unique entries of the kernel.
for (dej = 0; dej <= m_FilterWidth; dej++)
{
for (dek = 0; dek <= dej; dek++)
{
filterVal = sqrt(T(dej * dej + dek * dek)) / filterHeight;
//Only populate the coefs within this radius.
if (filterVal > 1.0)
m_Coefs[coefIndex] = 0.0;
else
m_Coefs[coefIndex] = gaussianFilter.Filter(gaussianFilter.Support() * filterVal) / filterSum;
coefIndex++;
}
}
if (m_MaxFilterIndex > 0)
break;
}
if (m_MaxFilterIndex == 0)
m_MaxFilterIndex = maxIndex - 1;
w = m_FilterWidth + 1;
m_CoefIndices.resize(w * w);
//This will populate one quadrant of filter indices.
//Really only need 1/8th, but that would require a sparse matrix.
for (j = 0; j <= m_FilterWidth; j++)
{
for (i = 0; i <= j; i++, filterCoefIndex++)
{
if (j == 0 && i == 0)
{
m_CoefIndices[(j * w) + i] = filterCoefIndex;
}
else if (i == 0)
{
m_CoefIndices[(0 * w) + j] = filterCoefIndex;
m_CoefIndices[(j * w) + 0] = filterCoefIndex;
}
else if (j == i)
{
m_CoefIndices[(j * w) + i] = filterCoefIndex;
}
else
{
m_CoefIndices[(i * w) + j] = filterCoefIndex;
m_CoefIndices[(j * w) + i] = filterCoefIndex;
}
}
}
return true;
}
/// <summary>
/// Return whether the requested dimensions are valid.
/// Meaning, is the requested filter size less than or equal to 10M?
/// </summary>
/// <returns>True if requested filter size is less than or equal to 10M, else false.</returns>
inline bool Valid() const
{
T finalMaxRad = m_MaxRad * m_Supersample + 1;
T finalMinRad = m_MinRad * m_Supersample + 1;
return pow(finalMaxRad / finalMinRad, T(1.0) / m_Curve) <= 1e7;
}
/// <summary>
/// Return a string representation of this density estimation filter.
/// </summary>
/// <returns>The string representation of this density estimation filter</returns>
string ToString() const
{
unsigned int i, j, coefIndex = 0, w = m_FilterWidth + 1;
stringstream ss;
ss
<< "Density Filter:" << endl
<< " Min radius: " << MinRad() << endl
<< " Max radius: " << MaxRad() << endl
<< " Curve: " << Curve() << endl
<< " Kernel size: " << KernelSize() << endl
<< " Max filter index: " << MaxFilterIndex() << endl
<< "Max Filtered counts: " << MaxFilteredCounts() << endl
<< " Filter width: " << FilterWidth() << endl;
ss << "Coefficients: " << endl;
for (i = 0; i < m_Widths.size(); i++)
{
for (coefIndex = 0; coefIndex < m_KernelSize; coefIndex++)
ss << "Kernel[" << i << "].Coefs[" << coefIndex << "]: " << m_Coefs[(i * m_KernelSize) + coefIndex] << endl;
}
ss << endl << "Widths: " << endl;
for (i = 0; i < m_Widths.size(); i++)
{
ss << "Widths[" << i << "]: " << m_Widths[i] << endl;
}
for (i = 0; i < w; i++)
{
for (j = 0; j < w; j++)
{
cout << std::setw(2) << std::setfill('0') << m_CoefIndices[i * w + j] << "\t";
}
cout << endl;
}
return ss.str();
}
/// <summary>
/// Accessors.
/// </summary>
inline T MinRad() const { return m_MinRad; }
inline T MaxRad() const { return m_MaxRad; }
inline T Curve() const { return m_Curve; }
inline unsigned int KernelSize() const { return m_KernelSize; }
inline unsigned int MaxFilterIndex() const { return m_MaxFilterIndex; }
inline unsigned int MaxFilteredCounts() const { return m_MaxFilteredCounts; }
virtual int FilterWidth() const { return m_FilterWidth; }
inline unsigned int BufferSize() const { return (unsigned int)m_Widths.size(); }
inline unsigned int CoefsSizeBytes() const { return BufferSize() * m_KernelSize * sizeof(T); }
inline unsigned int WidthsSizeBytes() const { return BufferSize() * sizeof(T); }
inline unsigned int CoefsIndicesSizeBytes() const { return unsigned int(m_CoefIndices.size() * sizeof(unsigned int)); }
inline const T* Coefs() const { return m_Coefs.data(); }
inline const T* Widths() const { return m_Widths.data(); }
inline const unsigned int* CoefIndices() const { return m_CoefIndices.data(); }
private:
T m_MinRad;
T m_MaxRad;//The original specified filter radius.
T m_Curve;
unsigned int m_Supersample;
unsigned int m_KernelSize;
unsigned int m_MaxFilterIndex;
unsigned int m_MaxFilteredCounts;
int m_FilterWidth;//The new radius after scaling for super sample and rounding. This is what's actually used.
vector<T> m_Coefs;
vector<T> m_Widths;
vector<unsigned int> m_CoefIndices;
};
}

20
Source/Ember/DllMain.cpp Normal file
View File

@ -0,0 +1,20 @@
#include "EmberPch.h"
/// <summary>
/// Generated by Visual Studio to make the DLL run properly.
/// </summary>
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

410
Source/Ember/Ember.cpp Normal file
View File

@ -0,0 +1,410 @@
#include "EmberPch.h"
#include "EmberDefines.h"
#include "Isaac.h"
#include "Ember.h"
#include "Utils.h"
#include "Iterator.h"
#include "Palette.h"
#include "PaletteList.h"
#include "Point.h"
#include "Variation.h"
#include "Variations01.h"
#include "Variations02.h"
#include "Variations03.h"
#include "Variations04.h"
#include "Variations05.h"
#include "VariationsDC.h"
#include "VariationList.h"
#include "Affine2D.h"
#include "Xform.h"
#include "EmberToXml.h"
#include "XmlToEmber.h"
#include "SpatialFilter.h"
#include "DensityFilter.h"
#include "TemporalFilter.h"
#include "Interpolate.h"
#include "Renderer.h"
#include "Timing.h"
#include "SheepTools.h"
/// <summary>
/// Explicit instantiation of all templated classes which aren't implemented in cpp files.
/// All new templated classes, such as new variations, must be added here.
/// Additional instances of static class member variables.
/// </summary>
namespace EmberNs
{
bool Timing::m_TimingInit = false;
int Timing::m_ProcessorCount;
LARGE_INTEGER Timing::m_Freq;
auto_ptr<QTIsaac<ISAAC_SIZE, ISAAC_INT>> QTIsaac<ISAAC_SIZE, ISAAC_INT>::GlobalRand = auto_ptr<QTIsaac<ISAAC_SIZE, ISAAC_INT>>(new QTIsaac<ISAAC_SIZE, ISAAC_INT>());
#define EXPORTPREPOSTREGVAR(varName, T) \
template EMBER_API class varName##Variation<T>; \
template EMBER_API class Pre##varName##Variation<T>; \
template EMBER_API class Post##varName##Variation<T>;
#define EXPORT_SINGLE_TYPE_EMBER(T) \
template EMBER_API class Point<T>; \
template EMBER_API class Color<T>; \
template EMBER_API class Affine2D<T>; \
template EMBER_API class Palette<T>; \
template EMBER_API class PaletteList<T>; \
template EMBER_API class Iterator<T>; \
template EMBER_API class StandardIterator<T>; \
template EMBER_API class XaosIterator<T>; \
template EMBER_API class Xform<T>; \
template EMBER_API class IteratorHelper<T>; \
template EMBER_API class Variation<T>; \
template EMBER_API class ParamWithName<T>; \
template EMBER_API class ParametricVariation<T>; \
EXPORTPREPOSTREGVAR(Linear, T) \
EXPORTPREPOSTREGVAR(Sinusoidal, T) \
EXPORTPREPOSTREGVAR(Spherical, T) \
EXPORTPREPOSTREGVAR(Swirl, T) \
EXPORTPREPOSTREGVAR(Horseshoe, T) \
EXPORTPREPOSTREGVAR(Polar, T) \
EXPORTPREPOSTREGVAR(Handkerchief, T) \
EXPORTPREPOSTREGVAR(Heart, T) \
EXPORTPREPOSTREGVAR(Disc, T) \
EXPORTPREPOSTREGVAR(Spiral, T) \
EXPORTPREPOSTREGVAR(Hyperbolic, T) \
EXPORTPREPOSTREGVAR(Diamond, T) \
EXPORTPREPOSTREGVAR(Ex, T) \
EXPORTPREPOSTREGVAR(Julia, T) \
EXPORTPREPOSTREGVAR(Bent, T) \
EXPORTPREPOSTREGVAR(Waves, T) \
EXPORTPREPOSTREGVAR(Fisheye, T) \
EXPORTPREPOSTREGVAR(Popcorn, T) \
EXPORTPREPOSTREGVAR(Exponential, T) \
EXPORTPREPOSTREGVAR(Power, T) \
EXPORTPREPOSTREGVAR(Cosine, T) \
EXPORTPREPOSTREGVAR(Rings, T) \
EXPORTPREPOSTREGVAR(Fan, T) \
EXPORTPREPOSTREGVAR(Blob, T) \
EXPORTPREPOSTREGVAR(Pdj, T) \
EXPORTPREPOSTREGVAR(Fan2, T) \
EXPORTPREPOSTREGVAR(Rings2, T) \
EXPORTPREPOSTREGVAR(Eyefish, T) \
EXPORTPREPOSTREGVAR(Bubble, T) \
EXPORTPREPOSTREGVAR(Cylinder, T) \
EXPORTPREPOSTREGVAR(Perspective, T) \
EXPORTPREPOSTREGVAR(Noise, T) \
EXPORTPREPOSTREGVAR(JuliaNGeneric, T) \
EXPORTPREPOSTREGVAR(JuliaScope, T) \
EXPORTPREPOSTREGVAR(Blur, T) \
EXPORTPREPOSTREGVAR(GaussianBlur, T) \
EXPORTPREPOSTREGVAR(RadialBlur, T) \
EXPORTPREPOSTREGVAR(Pie, T) \
EXPORTPREPOSTREGVAR(Ngon, T) \
EXPORTPREPOSTREGVAR(Curl, T) \
EXPORTPREPOSTREGVAR(Rectangles, T) \
EXPORTPREPOSTREGVAR(Arch, T) \
EXPORTPREPOSTREGVAR(Tangent, T) \
EXPORTPREPOSTREGVAR(Square, T) \
EXPORTPREPOSTREGVAR(Rays, T) \
EXPORTPREPOSTREGVAR(Blade, T) \
EXPORTPREPOSTREGVAR(Secant2, T) \
EXPORTPREPOSTREGVAR(TwinTrian, T) \
EXPORTPREPOSTREGVAR(Cross, T) \
EXPORTPREPOSTREGVAR(Disc2, T) \
EXPORTPREPOSTREGVAR(SuperShape, T) \
EXPORTPREPOSTREGVAR(Flower, T) \
EXPORTPREPOSTREGVAR(Conic, T) \
EXPORTPREPOSTREGVAR(Parabola, T) \
EXPORTPREPOSTREGVAR(Bent2, T) \
EXPORTPREPOSTREGVAR(Bipolar, T) \
EXPORTPREPOSTREGVAR(Boarders, T) \
EXPORTPREPOSTREGVAR(Butterfly, T) \
EXPORTPREPOSTREGVAR(Cell, T) \
EXPORTPREPOSTREGVAR(Cpow, T) \
EXPORTPREPOSTREGVAR(Curve, T) \
EXPORTPREPOSTREGVAR(Edisc, T) \
EXPORTPREPOSTREGVAR(Elliptic, T) \
EXPORTPREPOSTREGVAR(Escher, T) \
EXPORTPREPOSTREGVAR(Foci, T) \
EXPORTPREPOSTREGVAR(LazySusan, T) \
EXPORTPREPOSTREGVAR(Loonie, T) \
EXPORTPREPOSTREGVAR(Modulus, T) \
EXPORTPREPOSTREGVAR(Oscilloscope, T) \
EXPORTPREPOSTREGVAR(Polar2, T) \
EXPORTPREPOSTREGVAR(Popcorn2, T) \
EXPORTPREPOSTREGVAR(Scry, T) \
EXPORTPREPOSTREGVAR(Separation, T) \
EXPORTPREPOSTREGVAR(Split, T) \
EXPORTPREPOSTREGVAR(Splits, T) \
EXPORTPREPOSTREGVAR(Stripes, T) \
EXPORTPREPOSTREGVAR(Wedge, T) \
EXPORTPREPOSTREGVAR(WedgeJulia, T) \
EXPORTPREPOSTREGVAR(WedgeSph, T) \
EXPORTPREPOSTREGVAR(Whorl, T) \
EXPORTPREPOSTREGVAR(Waves2, T) \
EXPORTPREPOSTREGVAR(Exp, T) \
EXPORTPREPOSTREGVAR(Log, T) \
EXPORTPREPOSTREGVAR(Sin, T) \
EXPORTPREPOSTREGVAR(Cos, T) \
EXPORTPREPOSTREGVAR(Tan, T) \
EXPORTPREPOSTREGVAR(Sec, T) \
EXPORTPREPOSTREGVAR(Csc, T) \
EXPORTPREPOSTREGVAR(Cot, T) \
EXPORTPREPOSTREGVAR(Sinh, T) \
EXPORTPREPOSTREGVAR(Cosh, T) \
EXPORTPREPOSTREGVAR(Tanh, T) \
EXPORTPREPOSTREGVAR(Sech, T) \
EXPORTPREPOSTREGVAR(Csch, T) \
EXPORTPREPOSTREGVAR(Coth, T) \
EXPORTPREPOSTREGVAR(Auger, T) \
EXPORTPREPOSTREGVAR(Flux, T) \
EXPORTPREPOSTREGVAR(Hemisphere, T) \
EXPORTPREPOSTREGVAR(Epispiral, T) \
EXPORTPREPOSTREGVAR(Bwraps, T) \
EXPORTPREPOSTREGVAR(Extrude, T) \
EXPORTPREPOSTREGVAR(BlurCircle, T) \
EXPORTPREPOSTREGVAR(BlurZoom, T) \
EXPORTPREPOSTREGVAR(BlurPixelize, T) \
EXPORTPREPOSTREGVAR(Crop, T) \
EXPORTPREPOSTREGVAR(BCircle, T) \
EXPORTPREPOSTREGVAR(BlurLinear, T) \
EXPORTPREPOSTREGVAR(BlurSquare, T) \
EXPORTPREPOSTREGVAR(Boarders2, T) \
EXPORTPREPOSTREGVAR(Cardioid, T) \
EXPORTPREPOSTREGVAR(Checks, T) \
EXPORTPREPOSTREGVAR(Circlize, T) \
EXPORTPREPOSTREGVAR(Circlize2, T) \
EXPORTPREPOSTREGVAR(CosWrap, T) \
EXPORTPREPOSTREGVAR(DeltaA, T) \
EXPORTPREPOSTREGVAR(Expo, T) \
EXPORTPREPOSTREGVAR(FDisc, T) \
EXPORTPREPOSTREGVAR(Fibonacci, T) \
EXPORTPREPOSTREGVAR(Fibonacci2, T) \
EXPORTPREPOSTREGVAR(Glynnia, T) \
EXPORTPREPOSTREGVAR(GridOut, T) \
EXPORTPREPOSTREGVAR(Hole, T) \
EXPORTPREPOSTREGVAR(Hypertile, T) \
EXPORTPREPOSTREGVAR(Hypertile1, T) \
EXPORTPREPOSTREGVAR(Hypertile2, T) \
EXPORTPREPOSTREGVAR(Hypertile3D, T) \
EXPORTPREPOSTREGVAR(Hypertile3D1, T) \
EXPORTPREPOSTREGVAR(Hypertile3D2, T) \
EXPORTPREPOSTREGVAR(IDisc, T) \
EXPORTPREPOSTREGVAR(Julian2, T) \
EXPORTPREPOSTREGVAR(JuliaQ, T) \
EXPORTPREPOSTREGVAR(Murl, T) \
EXPORTPREPOSTREGVAR(Murl2, T) \
EXPORTPREPOSTREGVAR(NPolar, T) \
EXPORTPREPOSTREGVAR(Ortho, T) \
EXPORTPREPOSTREGVAR(Poincare, T) \
EXPORTPREPOSTREGVAR(Poincare3D, T) \
EXPORTPREPOSTREGVAR(Polynomial, T) \
EXPORTPREPOSTREGVAR(PSphere, T) \
EXPORTPREPOSTREGVAR(Rational3, T) \
EXPORTPREPOSTREGVAR(Ripple, T) \
EXPORTPREPOSTREGVAR(Sigmoid, T) \
EXPORTPREPOSTREGVAR(SinusGrid, T) \
EXPORTPREPOSTREGVAR(Stwin, T) \
EXPORTPREPOSTREGVAR(TwoFace, T) \
EXPORTPREPOSTREGVAR(Unpolar, T) \
EXPORTPREPOSTREGVAR(WavesN, T) \
EXPORTPREPOSTREGVAR(XHeart, T) \
EXPORTPREPOSTREGVAR(Barycentroid, T) \
EXPORTPREPOSTREGVAR(BiSplit, T) \
EXPORTPREPOSTREGVAR(Crescents, T) \
EXPORTPREPOSTREGVAR(Mask, T) \
EXPORTPREPOSTREGVAR(Cpow2, T) \
EXPORTPREPOSTREGVAR(Curl3D, T) \
EXPORTPREPOSTREGVAR(Disc3D, T) \
EXPORTPREPOSTREGVAR(Funnel, T) \
EXPORTPREPOSTREGVAR(Linear3D, T) \
EXPORTPREPOSTREGVAR(PowBlock, T) \
EXPORTPREPOSTREGVAR(Squirrel, T) \
EXPORTPREPOSTREGVAR(Ennepers, T) \
EXPORTPREPOSTREGVAR(SphericalN, T) \
EXPORTPREPOSTREGVAR(Kaleidoscope, T) \
EXPORTPREPOSTREGVAR(GlynnSim1, T) \
EXPORTPREPOSTREGVAR(GlynnSim2, T) \
EXPORTPREPOSTREGVAR(GlynnSim3, T) \
EXPORTPREPOSTREGVAR(Starblur, T) \
EXPORTPREPOSTREGVAR(Sineblur, T) \
EXPORTPREPOSTREGVAR(Circleblur, T) \
EXPORTPREPOSTREGVAR(CropN, T) \
EXPORTPREPOSTREGVAR(ShredRad, T) \
EXPORTPREPOSTREGVAR(Blob2, T) \
EXPORTPREPOSTREGVAR(Julia3D, T) \
EXPORTPREPOSTREGVAR(Julia3Dz, T) \
EXPORTPREPOSTREGVAR(LinearT, T) \
EXPORTPREPOSTREGVAR(LinearT3D, T) \
EXPORTPREPOSTREGVAR(Ovoid, T) \
EXPORTPREPOSTREGVAR(Ovoid3D, T) \
EXPORTPREPOSTREGVAR(Spirograph, T) \
EXPORTPREPOSTREGVAR(Petal, T) \
EXPORTPREPOSTREGVAR(RoundSpher, T) \
EXPORTPREPOSTREGVAR(RoundSpher3D, T) \
EXPORTPREPOSTREGVAR(SpiralWing, T) \
EXPORTPREPOSTREGVAR(Squarize, T) \
EXPORTPREPOSTREGVAR(Sschecks, T) \
EXPORTPREPOSTREGVAR(PhoenixJulia, T) \
EXPORTPREPOSTREGVAR(Mobius, T) \
EXPORTPREPOSTREGVAR(MobiusN, T) \
EXPORTPREPOSTREGVAR(MobiusStrip, T) \
EXPORTPREPOSTREGVAR(Lissajous, T) \
EXPORTPREPOSTREGVAR(Svf, T) \
EXPORTPREPOSTREGVAR(Target, T) \
EXPORTPREPOSTREGVAR(Taurus, T) \
EXPORTPREPOSTREGVAR(Collideoscope, T) \
EXPORTPREPOSTREGVAR(BMod, T) \
EXPORTPREPOSTREGVAR(BSwirl, T) \
EXPORTPREPOSTREGVAR(BTransform, T) \
EXPORTPREPOSTREGVAR(BCollide, T) \
EXPORTPREPOSTREGVAR(Eclipse, T) \
EXPORTPREPOSTREGVAR(FlipCircle, T) \
EXPORTPREPOSTREGVAR(FlipY, T) \
EXPORTPREPOSTREGVAR(ECollide, T) \
EXPORTPREPOSTREGVAR(EJulia, T) \
EXPORTPREPOSTREGVAR(EMod, T) \
EXPORTPREPOSTREGVAR(EMotion, T) \
EXPORTPREPOSTREGVAR(EPush, T) \
EXPORTPREPOSTREGVAR(ERotate, T) \
EXPORTPREPOSTREGVAR(EScale, T) \
EXPORTPREPOSTREGVAR(ESwirl, T) \
EXPORTPREPOSTREGVAR(LazyTravis, T) \
EXPORTPREPOSTREGVAR(Squish, T) \
EXPORTPREPOSTREGVAR(Circus, T) \
EXPORTPREPOSTREGVAR(Tancos, T) \
EXPORTPREPOSTREGVAR(Rippled, T) \
EXPORTPREPOSTREGVAR(Flatten, T) \
EXPORTPREPOSTREGVAR(Zblur, T) \
EXPORTPREPOSTREGVAR(Blur3D, T) \
EXPORTPREPOSTREGVAR(ZScale, T) \
EXPORTPREPOSTREGVAR(ZTranslate, T) \
EXPORTPREPOSTREGVAR(ZCone, T) \
EXPORTPREPOSTREGVAR(Boarders2, T) \
EXPORTPREPOSTREGVAR(RotateX, T) \
EXPORTPREPOSTREGVAR(RotateY, T) \
EXPORTPREPOSTREGVAR(RotateZ, T) \
EXPORTPREPOSTREGVAR(MirrorX, T) \
EXPORTPREPOSTREGVAR(MirrorY, T) \
EXPORTPREPOSTREGVAR(MirrorZ, T) \
EXPORTPREPOSTREGVAR(Depth, T) \
EXPORTPREPOSTREGVAR(RBlur, T) \
EXPORTPREPOSTREGVAR(JuliaNab, T) \
EXPORTPREPOSTREGVAR(Sintrange, T) \
EXPORTPREPOSTREGVAR(Voron, T) \
EXPORTPREPOSTREGVAR(Waffle, T) \
EXPORTPREPOSTREGVAR(Square3D, T) \
EXPORTPREPOSTREGVAR(SuperShape3D, T) \
EXPORTPREPOSTREGVAR(Sphyp3D, T) \
EXPORTPREPOSTREGVAR(Circlecrop, T) \
EXPORTPREPOSTREGVAR(Julian3Dx, T) \
EXPORTPREPOSTREGVAR(Fourth, T) \
EXPORTPREPOSTREGVAR(Mobiq, T) \
EXPORTPREPOSTREGVAR(Spherivoid, T) \
EXPORTPREPOSTREGVAR(Farblur, T) \
EXPORTPREPOSTREGVAR(CurlSP, T) \
EXPORTPREPOSTREGVAR(Heat, T) \
EXPORTPREPOSTREGVAR(Interference2, T) \
EXPORTPREPOSTREGVAR(Sinq, T) \
EXPORTPREPOSTREGVAR(Sinhq, T) \
EXPORTPREPOSTREGVAR(Secq, T) \
EXPORTPREPOSTREGVAR(Sechq, T) \
EXPORTPREPOSTREGVAR(Tanq, T) \
EXPORTPREPOSTREGVAR(Tanhq, T) \
EXPORTPREPOSTREGVAR(Cosq, T) \
EXPORTPREPOSTREGVAR(Coshq, T) \
EXPORTPREPOSTREGVAR(Cotq, T) \
EXPORTPREPOSTREGVAR(Cothq, T) \
EXPORTPREPOSTREGVAR(Cscq, T) \
EXPORTPREPOSTREGVAR(Cschq, T) \
EXPORTPREPOSTREGVAR(Estiq, T) \
EXPORTPREPOSTREGVAR(Loq, T) \
EXPORTPREPOSTREGVAR(Curvature, T) \
EXPORTPREPOSTREGVAR(Qode, T) \
EXPORTPREPOSTREGVAR(BlurHeart, T) \
EXPORTPREPOSTREGVAR(Truchet, T) \
EXPORTPREPOSTREGVAR(Gdoffs, T) \
EXPORTPREPOSTREGVAR(Octagon, T) \
EXPORTPREPOSTREGVAR(Trade, T) \
EXPORTPREPOSTREGVAR(Juliac, T) \
EXPORTPREPOSTREGVAR(Blade3D, T) \
EXPORTPREPOSTREGVAR(Blob3D, T) \
EXPORTPREPOSTREGVAR(Blocky, T) \
EXPORTPREPOSTREGVAR(Bubble2, T) \
EXPORTPREPOSTREGVAR(CircleLinear, T) \
EXPORTPREPOSTREGVAR(CircleRand, T) \
EXPORTPREPOSTREGVAR(CircleTrans1, T) \
EXPORTPREPOSTREGVAR(Cubic3D, T) \
EXPORTPREPOSTREGVAR(CubicLattice3D, T) \
EXPORTPREPOSTREGVAR(Foci3D, T) \
EXPORTPREPOSTREGVAR(Ho, T) \
EXPORTPREPOSTREGVAR(Julia3Dq, T) \
EXPORTPREPOSTREGVAR(Line, T) \
EXPORTPREPOSTREGVAR(Loonie3D, T) \
EXPORTPREPOSTREGVAR(Mcarpet, T) \
EXPORTPREPOSTREGVAR(Waves23D, T) \
EXPORTPREPOSTREGVAR(Pie3D, T) \
EXPORTPREPOSTREGVAR(Popcorn23D, T) \
EXPORTPREPOSTREGVAR(Sinusoidal3D, T) \
EXPORTPREPOSTREGVAR(Scry3D, T) \
EXPORTPREPOSTREGVAR(Shredlin, T) \
EXPORTPREPOSTREGVAR(SplitBrdr, T) \
EXPORTPREPOSTREGVAR(Wdisc, T) \
EXPORTPREPOSTREGVAR(Falloff, T) \
EXPORTPREPOSTREGVAR(Falloff2, T) \
EXPORTPREPOSTREGVAR(Falloff3, T) \
EXPORTPREPOSTREGVAR(Xtrb, T) \
template EMBER_API class DCBubbleVariation<T>; \
EXPORTPREPOSTREGVAR(DCCarpet, T) \
EXPORTPREPOSTREGVAR(DCCube, T) \
template EMBER_API class DCCylinderVariation<T>; \
EXPORTPREPOSTREGVAR(DCGridOut, T) \
template EMBER_API class DCLinearVariation<T>; \
EXPORTPREPOSTREGVAR(DCZTransl, T) \
EXPORTPREPOSTREGVAR(DCTriangle, T) \
template EMBER_API class VariationList<T>; \
template EMBER_API class SpatialFilter<T>; \
template EMBER_API class GaussianFilter<T>; \
template EMBER_API class HermiteFilter<T>; \
template EMBER_API class BoxFilter<T>; \
template EMBER_API class TriangleFilter<T>; \
template EMBER_API class BellFilter<T>; \
template EMBER_API class BsplineFilter<T>; \
template EMBER_API class MitchellFilter<T>; \
template EMBER_API class BlackmanFilter<T>; \
template EMBER_API class CatromFilter<T>; \
template EMBER_API class HanningFilter<T>; \
template EMBER_API class HammingFilter<T>; \
template EMBER_API class Lanczos3Filter<T>; \
template EMBER_API class Lanczos2Filter<T>; \
template EMBER_API class QuadraticFilter<T>; \
template EMBER_API class DensityFilter<T>; \
template EMBER_API class TemporalFilter<T>; \
template EMBER_API class ExpTemporalFilter<T>; \
template EMBER_API class GaussianTemporalFilter<T>; \
template EMBER_API class BoxTemporalFilter<T>; \
template EMBER_API class SpatialFilterCreator<T>; \
template EMBER_API class TemporalFilterCreator<T>; \
template EMBER_API class Interpolater<T>; \
template EMBER_API class Ember<T>; \
/*template EMBER_API class RenderCallback<T>;*/ \
template EMBER_API class CarToRas<T>; \
template EMBER_API class XmlToEmber<T>; \
template EMBER_API class EmberToXml<T>; \
bool PaletteList<T>::m_Init = false; \
vector<Palette<T>> PaletteList<T>::m_Palettes = vector<Palette<T>>(); \
bool XmlToEmber<T>::m_Init = false; \
vector<pair<string, string>> XmlToEmber<T>::m_BadParamNames = vector<pair<string, string>>(); \
vector<pair<string, string>> XmlToEmber<T>::m_BadVariationNames = vector<pair<string, string>>();
EXPORT_SINGLE_TYPE_EMBER(float)
#define EXPORT_TWO_TYPE_EMBER(T, bucketT) \
template EMBER_API class Renderer<T, bucketT>; \
template EMBER_API class SheepTools<T, bucketT>;
EXPORT_TWO_TYPE_EMBER(float, float)
#ifdef DO_DOUBLE
EXPORT_SINGLE_TYPE_EMBER(double)
EXPORT_TWO_TYPE_EMBER(double, double)
#endif
}

1607
Source/Ember/Ember.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,83 @@
#pragma once
#include "EmberPch.h"
/// <summary>
/// Basic #defines used throughout the library.
/// </summary>
//MSVC specific?
#if defined(BUILDING_EMBER)
#define EMBER_API __declspec(dllexport)
#else
#define EMBER_API __declspec(dllimport)
#endif
#define RESTRICT __restrict//This might make things faster, unsure if it really does though.
//#define RESTRICT
namespace EmberNs
{
//Wrap the sincos function for Macs and PC.
#if defined(__APPLE__) || defined(_MSC_VER)
#define sincos(x, s, c) *(s)=sin(x); *(c)=cos(x);
#else
extern void sincos(double x, double *s, double *c);
#endif
#define EMBER_VERSION "0.4.0.2"
#define EPS6 T(1e-6)
#define EPS T(1e-10)//Apoplugin.h uses -20, but -10 seems to work fine.
#define ISAAC_SIZE 4
#define MEMALIGN 32
#define DE_THRESH 100
#define MAX_VARS_PER_XFORM 8
#define DEG_2_RAD (M_PI / 180)
#define RAD_2_DEG (180 / M_PI)
#define DEG_2_RAD_T (T(M_PI) / T(180))
#define RAD_2_DEG_T (T(180) / T(M_PI))
#define M_2PI (T(M_PI * 2))
#define M_3PI (T(M_PI * 3))
#define SQRT5 T(2.2360679774997896964091736687313)
#define M_PHI T(1.61803398874989484820458683436563)
#define COLORMAP_LENGTH 256//These will need to change if 2D palette support is ever added, or variable sized palettes.
#define COLORMAP_LENGTH_MINUS_1 255
#define WHITE 255
#define XC (const xmlChar*)
#define BadVal(x) (((x) != (x)) || ((x) > 1e10) || ((x) < -1e10))
#define Rint(A) floor((A) + (((A) < 0) ? T(-0.5) : T(0.5)))
#define Vlen(x) (sizeof(x) / sizeof(*x))
#define SQR(x) ((x) * (x))
#define CUBE(x) ((x) * (x) * (x))
#define TLOW std::numeric_limits<T>::lowest()
#define TMAX std::numeric_limits<T>::max()
#ifndef acosh
#define acosh(x) (log(x + sqrt(SQR(x) - 1)))//Remove this once you upgrade compilers to VS 2013 or later.//TODO
#endif
#ifndef fma
#define fma(x, y, z) ((x * y) + z)
#endif
#define DO_DOUBLE 1//Comment this out for shorter build times during development. Always uncomment for release.
#define v2T glm::detail::tvec2<T, glm::defaultp>
#define v3T glm::detail::tvec3<T, glm::defaultp>
#define v4T glm::detail::tvec4<T, glm::defaultp>
#define m2T glm::detail::tmat2x2<T, glm::defaultp>
#define m3T glm::detail::tmat3x3<T, glm::defaultp>
#define m4T glm::detail::tmat4x4<T, glm::defaultp>
#define m23T glm::detail::tmat2x3<T, glm::defaultp>
enum eInterp : unsigned int { EMBER_INTERP_LINEAR = 0, EMBER_INTERP_SMOOTH = 1 };
enum eAffineInterp : unsigned int { INTERP_LINEAR = 0, INTERP_LOG = 1, INTERP_COMPAT = 2, INTERP_OLDER = 3 };
enum ePaletteMode : unsigned int { PALETTE_STEP = 0, PALETTE_LINEAR = 1 };
enum ePaletteInterp : unsigned int { INTERP_HSV = 0, INTERP_SWEEP = 1 };
enum eMotion : unsigned int { MOTION_SIN = 1, MOTION_TRIANGLE = 2, MOTION_HILL = 3 };
enum eProcessAction : unsigned int { NOTHING = 0, ACCUM_ONLY = 1, FILTER_AND_ACCUM = 2, KEEP_ITERATING = 3, FULL_RENDER = 4 };
enum eProcessState : unsigned int { NONE = 0, ITER_STARTED = 1, ITER_DONE = 2, FILTER_DONE = 3, ACCUM_DONE = 4 };
enum eInteractiveFilter : unsigned int { FILTER_LOG = 0, FILTER_DE = 1 };
enum eScaleType : unsigned int { SCALE_NONE = 0, SCALE_WIDTH = 1, SCALE_HEIGHT = 2 };
enum eRenderStatus : unsigned int { RENDER_OK = 0, RENDER_ERROR = 1, RENDER_ABORT = 2 };
}

View File

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

62
Source/Ember/EmberPch.h Normal file
View File

@ -0,0 +1,62 @@
#pragma once
/// <summary>
/// Precompiled header file. Place all system includes here with appropriate #defines for different operating systems and compilers.
/// </summary>
#define NOMINMAX
#define _USE_MATH_DEFINES
#ifdef _WIN32
#define basename(x) _strdup(x)
#define snprintf _snprintf
#define snprintf_s _snprintf_s
#define WIN32_LEAN_AND_MEAN
#define EMBER_OS "WIN"
#include <SDKDDKVer.h>
#include <windows.h>
#elif __APPLE__
#define EMBER_OS "OSX"
#else
#include <libgen.h>
#include <unistd.h>
#define EMBER_OS "LNX"
#endif
//Standard headers.
#include <algorithm>
#include <complex>
#include <fstream>
#include <functional>
#include <iostream>
#include <iomanip>
#include <limits>
#include <malloc.h>
#include <math.h>
#include <numeric>
#include <ostream>
#include <sstream>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <vector>
//Third party headers.
#include <libxml/parser.h>
//Intel's Threading Building Blocks is what's used for all threading.
#include "tbb/task_group.h"
#include "tbb/parallel_for.h"
#include "tbb/task_scheduler_init.h"
#define GLM_FORCE_RADIANS
//glm is what's used for matrix math.
#include "glm/glm.hpp"
#include "glm/gtc/matrix_transform.hpp"
#include "glm/gtc/type_ptr.hpp"
#include "glm/gtx/string_cast.hpp"
using namespace tbb;
using namespace std;

686
Source/Ember/EmberToXml.h Normal file
View File

@ -0,0 +1,686 @@
#pragma once
#include "Utils.h"
#include "PaletteList.h"
#include "VariationList.h"
#include "Ember.h"
/// <summary>
/// EmberToXml class.
/// </summary>
namespace EmberNs
{
/// <summary>
/// Class for converting ember objects to Xml documents.
/// Support for saving one or more to a single file.
/// Template argument expected to be float or double.
/// </summary>
template <typename T>
class EMBER_API EmberToXml : public EmberReport
{
public:
/// <summary>
/// Empty constructor.
/// </summary>
EmberToXml()
{
}
/// <summary>
/// Save the ember to the specified file.
/// </summary>
/// <param name="filename">Full path and filename</param>
/// <param name="ember">The ember to save</param>
/// <param name="printEditDepth">How deep the edit depth goes</param>
/// <param name="doEdits">If true included edit tags, else don't.</param>
/// <param name="intPalette">If true use integers instead of floating point numbers when embedding a non-hex formatted palette, else use floating point numbers.</param>
/// <param name="hexPalette">If true, embed a hexadecimal palette instead of Xml Color tags, else use Xml color tags.</param>
/// <param name="append">If true, append to the file if it already exists, else create a new file.</param>
/// <param name="start">Whether a new file is to be started</param>
/// <param name="finish">Whether an existing file is to be ended</param>
/// <returns>True if successful, else false</returns>
bool Save(string filename, Ember<T>& ember, unsigned int printEditDepth, bool doEdits, bool intPalette, bool hexPalette, bool append = false, bool start = false, bool finish = false)
{
vector<Ember<T>> vec;
vec.push_back(ember);
return Save(filename, vec, printEditDepth, doEdits, intPalette, hexPalette, append, start, finish);
}
/// <summary>
/// Save a vector of embers to the specified file.
/// </summary>
/// <param name="filename">Full path and filename</param>
/// <param name="embers">The vector of embers to save</param>
/// <param name="printEditDepth">How deep the edit depth goes</param>
/// <param name="doEdits">If true included edit tags, else don't.</param>
/// <param name="intPalette">If true use integers instead of floating point numbers when embedding a non-hex formatted palette, else use floating point numbers.</param>
/// <param name="hexPalette">If true, embed a hexadecimal palette instead of Xml Color tags, else use Xml color tags.</param>
/// <param name="append">If true, append to the file if it already exists, else create a new file.</param>
/// <param name="start">Whether a new file is to be started</param>
/// <param name="finish">Whether an existing file is to be ended</param>
/// <returns>True if successful, else false</returns>
bool Save(string filename, vector<Ember<T>>& embers, unsigned int printEditDepth, bool doEdits, bool intPalette, bool hexPalette, bool append = false, bool start = false, bool finish = false)
{
bool b = false;
string temp;
ofstream f;
try
{
if (append)
f.open(filename, std::ofstream::out | std::ofstream::app);//Appending allows us to write multiple embers to a single file.
else
f.open(filename);
if (f.is_open())
{
if ((append && start) || !append)
{
temp = "<flames>\n";
f.write(temp.c_str(), temp.size());
}
for (size_t i = 0; i < embers.size(); i++)
{
string s = ToString(embers[i], "", printEditDepth, doEdits, intPalette, hexPalette);
f.write(s.c_str(), s.size());
}
if ((append && finish) || !append)
{
temp = "</flames>\n";
f.write(temp.c_str(), temp.size());
}
f.close();
b = true;
}
else
{
cout << "Error: Writing flame " << filename << " failed." << endl;
b = false;
}
}
catch (...)
{
if (f.is_open())
f.close();
b = false;
}
return b;
}
/// <summary>
/// Return the Xml string representation of an ember.
/// </summary>
/// <param name="ember">The ember to create the Xml with</param>
/// <param name="extraAttributes">If true, add extra attributes, else don't</param>
/// <param name="printEditDepth">How deep the edit depth goes</param>
/// <param name="doEdits">If true included edit tags, else don't.</param>
/// <param name="intPalette">If true use integers instead of floating point numbers when embedding a non-hex formatted palette, else use floating point numbers.</param>
/// <param name="hexPalette">If true, embed a hexadecimal palette instead of Xml Color tags, else use Xml color tags.</param>
/// <returns>The Xml string representation of the passed in ember</returns>
string ToString(Ember<T>& ember, string extraAttributes, unsigned int printEditDepth, bool doEdits, bool intPalette, bool hexPalette = true)
{
unsigned int i, j;
ostringstream os;
os << "<flame version=\"EMBER-" << EmberVersion() << "\" time=\"" << ember.m_Time << "\"";
if (!ember.m_Name.empty())
os << " name=\"" << ember.m_Name << "\"";
os << " size=\"" << ember.m_FinalRasW << " " << ember.m_FinalRasH << "\"";
os << " center=\"" << ember.m_CenterX << " " << ember.m_CenterY << "\"";
os << " scale=\"" << ember.m_PixelsPerUnit << "\"";
if (ember.m_Zoom != 0)
os << " zoom=\"" << ember.m_Zoom << "\"";
os << " rotate=\"" << ember.m_Rotate << "\"";
os << " supersample=\"" << max(1u, ember.m_Supersample) << "\"";
os << " filter=\"" << ember.m_SpatialFilterRadius << "\"";
os << " filter_shape=\"" << ToLower(SpatialFilterCreator<T>::ToString(ember.m_SpatialFilterType)) << "\"";
os << " temporal_filter_type=\"" << ToLower(TemporalFilterCreator<T>::ToString(ember.m_TemporalFilterType)) << "\"";
if (ember.m_TemporalFilterType == EXP_TEMPORAL_FILTER)
os << " temporal_filter_exp=\"" << ember.m_TemporalFilterExp << "\"";
os << " temporal_filter_width=\"" << ember.m_TemporalFilterWidth << "\"";
os << " quality=\"" << ember.m_Quality << "\"";
os << " passes=\"" << ember.m_Passes << "\"";
os << " temporal_samples=\"" << ember.m_TemporalSamples << "\"";
os << " background=\"" << ember.m_Background.r << " " << ember.m_Background.g << " " << ember.m_Background.b << "\"";
os << " brightness=\"" << ember.m_Brightness << "\"";
os << " gamma=\"" << ember.m_Gamma << "\"";
os << " highlight_power=\"" << ember.m_HighlightPower << "\"";
os << " vibrancy=\"" << ember.m_Vibrancy << "\"";
//os << " hue=\"" << ember.m_Hue << "\"";//Oddly enough, flam3 never wrote this value out.//ORIG
os << " estimator_radius=\"" << ember.m_MaxRadDE << "\"";
os << " estimator_minimum=\"" << ember.m_MinRadDE << "\"";
os << " estimator_curve=\"" << ember.m_CurveDE << "\"";
os << " gamma_threshold=\"" << ember.m_GammaThresh << "\"";
os << " cam_zpos=\"" << ember.m_CamZPos << "\"";
os << " cam_persp=\"" << ember.m_CamPerspective << "\"";
os << " cam_yaw=\"" << ember.m_CamYaw << "\"";
os << " cam_pitch=\"" << ember.m_CamPitch << "\"";
os << " cam_dof=\"" << ember.m_CamDepthBlur << "\"";
if (ember.m_PaletteMode == PALETTE_STEP)
os << " palette_mode=\"step\"";
else if (ember.m_PaletteMode == PALETTE_LINEAR)
os << " palette_mode=\"linear\"";
if (ember.m_Interp == EMBER_INTERP_SMOOTH)
os << " interpolation=\"smooth\"";
if (ember.m_AffineInterp == INTERP_LINEAR)
os << " interpolation_type=\"linear\"";
else if (ember.m_AffineInterp == INTERP_LOG)
os << " interpolation_type=\"log\"";
else if (ember.m_AffineInterp == INTERP_COMPAT)
os << " interpolation_type=\"old\"";
else if (ember.m_AffineInterp == INTERP_OLDER)
os << " interpolation_type=\"older\"";
if (ember.m_PaletteInterp == INTERP_SWEEP)
os << " palette_interpolation=\"sweep\"";
if (!extraAttributes.empty())
os << " " << extraAttributes;
os << ">\n";
//This is a grey area, what to do about symmetry to avoid duplicating the symmetry xforms when reading back?//TODO//BUG.
//if (ember.m_Symmetry)
// os << " <symmetry kind=\"" << ember.m_Symmetry << "\"/>\n";
for (i = 0; i < ember.XformCount(); i++)
os << ToString(*ember.GetXform(i), ember.XformCount(), false, false);//Not final, don't do motion.
if (ember.UseFinalXform())
os << ToString(*ember.NonConstFinalXform(), ember.XformCount(), true, false);//Final, don't do motion.
if (hexPalette)
{
os << " <palette count=\"256\" format=\"RGB\">\n";
for (i = 0; i < 32; i++)
{
os << " ";
for (j = 0; j < 8; j++)
{
int idx = 8 * i + j;
os << hex << setw(2) << setfill('0') << (int)Rint(ember.m_Palette[idx][0] * 255);
os << hex << setw(2) << setfill('0') << (int)Rint(ember.m_Palette[idx][1] * 255);
os << hex << setw(2) << setfill('0') << (int)Rint(ember.m_Palette[idx][2] * 255);
}
os << endl;
}
os << " </palette>\n";
}
else
{
for (i = 0; i < 256; i++)
{
double r = ember.m_Palette[i][0] * 255;
double g = ember.m_Palette[i][1] * 255;
double b = ember.m_Palette[i][2] * 255;
double a = ember.m_Palette[i][3] * 255;
os << " ";
//The original used a precision of 6 which is totally unnecessary, use 2.
if (IsClose(a, 255.0))
{
if (intPalette)
os << "<color index=\"" << i << "\" rgb=\"" << (int)Rint(r) << " " << (int)Rint(g) << " " << (int)Rint(b) << "\"/>";
else
os << "<color index=\"" << i << "\" rgb=\"" << std::fixed << std::setprecision(2) << r << " " << g << " " << b << "\"/>";
}
else
{
if (intPalette)
os << " <color index=\"" << i << "\" rgba=\"" << (int)Rint(r) << " " << (int)Rint(g) << " " << (int)Rint(b) << " " << (int)Rint(a) << "\"/>";
else
os << " <color index=\"" << i << "\" rgba=\"" << std::fixed << std::setprecision(2) << r << " " << g << " " << b << " " << a << "\"/>";
}
os << "\n";
}
}
if (doEdits && ember.m_Edits != NULL)
os << ToString(xmlDocGetRootElement(ember.m_Edits), 1, true, printEditDepth);
os << "</flame>\n";
return os.str();
}
/// <summary>
/// Create a new editdoc optionally based on parents passed in.
/// This is used when an ember is made out of some mutation or edit from one or two existing embers and
/// the user wants to capture the genetic lineage history information in the edit doc of the new ember.
/// </summary>
/// <param name="parent0">The first parent, optionally NULL.</param>
/// <param name="parent1">The second parent, optionally NULL.</param>
/// <param name="action">The action that was taken to create the new ember</param>
/// <param name="nick">The nickname of the author</param>
/// <param name="url">The Url of the author</param>
/// <param name="id">The id of the author</param>
/// <param name="comment">The comment to include</param>
/// <param name="sheepGen">The sheep generation used if > 0. Default: 0.</param>
/// <param name="sheepId">The sheep id used if > 0. Default: 0.</param>
/// <returns></returns>
xmlDocPtr CreateNewEditdoc(Ember<T>* parent0, Ember<T>* parent1, string action, string nick, string url, string id, string comment, int sheepGen = 0, int sheepId = 0)
{
char timeString[128];
char buffer[128];
char commentString[128];
tm localt;
time_t myTime;
xmlDocPtr commentDoc = NULL;
xmlDocPtr doc = xmlNewDoc(XC "1.0");
xmlNodePtr rootNode = NULL, node = NULL, nodeCopy = NULL;
xmlNodePtr rootComment = NULL;
//Create the root node, called "edit".
rootNode = xmlNewNode(NULL, XC "edit");
xmlDocSetRootElement(doc, rootNode);
//Add the edit attributes.
//Date.
myTime = time(NULL);
localtime_s(&localt, &myTime);
strftime(timeString, 128, "%a %b %d %H:%M:%S %z %Y", &localt);//XXX use standard time format including timezone.
xmlNewProp(rootNode, XC "date", XC timeString);
//Nick.
if (nick != "")
xmlNewProp(rootNode, XC "nick", XC nick.c_str());
//Url.
if (url != "")
xmlNewProp(rootNode, XC "url", XC url.c_str());
if (id != "")
xmlNewProp(rootNode, XC "id", XC id.c_str());
//Action.
xmlNewProp(rootNode, XC "action", XC action.c_str());
//Sheep info.
if (sheepGen > 0 && sheepId > 0)
{
//Create a child node of the root node called sheep.
node = xmlNewChild(rootNode, NULL, XC "sheep", NULL);
//Create the sheep attributes.
sprintf_s(buffer, 128, "%d", sheepGen);
xmlNewProp(node, XC "generation", XC buffer);
sprintf_s(buffer, 128, "%d", sheepId);
xmlNewProp(node, XC "id", XC buffer);
}
//Check for the parents.
//If parent 0 not specified, this is a randomly generated genome.
if (parent0)
{
if (parent0->m_Edits)
{
//Copy the node from the parent.
node = xmlDocGetRootElement(parent0->m_Edits);
nodeCopy = xmlCopyNode(node, 1);
AddFilenameWithoutAmpersand(nodeCopy, parent0->m_ParentFilename);
sprintf_s(buffer, 128, "%d", parent0->m_Index);
xmlNewProp(nodeCopy, XC "index", XC buffer);
xmlAddChild(rootNode, nodeCopy);
}
else
{
//Insert a (parent has no edit) message.
nodeCopy = xmlNewChild(rootNode, NULL, XC "edit", NULL);
AddFilenameWithoutAmpersand(nodeCopy, parent0->m_ParentFilename);
sprintf_s(buffer, 128, "%d", parent0->m_Index);
xmlNewProp(nodeCopy, XC "index", XC buffer);
}
}
if (parent1)
{
if (parent1->m_Edits)
{
//Copy the node from the parent.
node = xmlDocGetRootElement(parent1->m_Edits);
nodeCopy = xmlCopyNode(node, 1);
AddFilenameWithoutAmpersand(nodeCopy, parent1->m_ParentFilename);
sprintf_s(buffer, 128, "%d", parent1->m_Index);
xmlNewProp(nodeCopy, XC "index", XC buffer);
xmlAddChild(rootNode, nodeCopy);
}
else
{
//Insert a (parent has no edit) message.
nodeCopy = xmlNewChild(rootNode, NULL, XC "edit",NULL);
AddFilenameWithoutAmpersand(nodeCopy, parent1->m_ParentFilename);
sprintf_s(buffer, 128, "%d", parent1->m_Index);
xmlNewProp(nodeCopy, XC "index", XC buffer);
}
}
//Comment string:
//This one's hard, since the comment string must be treated as
//a valid XML document. Create a new document using the comment
//string as the in-memory document, and then copy all children of
//the root node into the edit structure
//Parsing the comment string should be done once and then copied
//for each new edit doc, but that's for later.
if (comment != "")
{
sprintf_s(commentString, 128, "<comm>%s</comm>", comment.c_str());
commentDoc = xmlReadMemory(commentString, (int)strlen(commentString), "comment.env", NULL, XML_PARSE_NONET);
//Check for errors.
if (commentDoc != NULL)
{
//Loop through the children of the new document and copy them into the rootNode.
rootComment = xmlDocGetRootElement(commentDoc);
for (node = rootComment->children; node; node = node->next)
{
nodeCopy = xmlCopyNode(node, 1);
xmlAddChild(rootNode, nodeCopy);
}
//Free the created document.
xmlFreeDoc(commentDoc);
}
else
{
cout << "Failed to parse comment into Xml." << endl;
}
}
//Return the xml doc.
return doc;
}
private:
/// <summary>
/// Return the Xml string representation of an xform.
/// </summary>
/// <param name="xform">The xform to create the Xml with</param>
/// <param name="xformCount">The number of non-final xforms in the ember to which this xform belongs. Used for xaos.</param>
/// <param name="isFinal">True if the xform is the final xform in the ember, else false.</param>
/// <param name="doMotion">If true, include motion elements in the Xml string, else omit.</param>
/// <returns>The Xml string representation of the passed in xform</returns>
string ToString(Xform<T>& xform, unsigned int xformCount, bool isFinal, bool doMotion)
{
unsigned int i, j;
ostringstream os;
if (doMotion)
{
os << " <motion motion_frequency=\"" << xform.m_MotionFreq << "\" ";
if (xform.m_MotionFunc == MOTION_SIN)
os << "motion_function=\"sin\" ";
else if (xform.m_MotionFunc == MOTION_TRIANGLE)
os << "motion_function=\"triangle\" ";
else if (xform.m_MotionFunc== MOTION_HILL)
os << "motion_function=\"hill\" ";
}
else
{
if (isFinal)
os << " <finalxform ";
else
os << " <xform weight=\"" << xform.m_Weight << "\" ";
}
if (!doMotion)
{
os << "color=\"" << xform.m_ColorX << "\" ";
//os << "color=\"" << xform.m_ColorX << " " << xform.m_ColorY << "\" ";
os << "var_color=\"" << xform.m_DirectColor << "\" ";
os << "color_speed=\"" << xform.m_ColorSpeed << "\" ";
os << "symmetry=\"" << xform.m_ColorSpeed << "\" ";//Legacy support.
string s = xform.m_Name;
std::replace(s.begin(), s.end(), ' ', '_');
os << "name=\"" << s << "\" ";//Flam3 didn't do this, but Apo does.
if (!isFinal)
os << "animate=\"" << xform.m_Animate << "\" ";
}
//Variation writing order differs slightly from the original to make it a bit more readable.
//The original wrote out all of the variation names and weights. Then wrote out the parameters for
//the parametric variations. Here, write out the params immediately after each parametric variation
//so they are more closely grouped with the variation they apply to, rather than being all grouped at the end.
for (i = 0; i < xform.TotalVariationCount(); i++)
{
Variation<T>* var = xform.GetVariation(i);
ParametricVariation<T>* parVar = dynamic_cast<ParametricVariation<T>*>(var);
if (var->m_Weight != 0)
{
os << var->Name() << "=\"" << var->m_Weight << "\" ";
if (parVar)
{
ParamWithName<T>* params = parVar->Params();
for (j = 0; j < parVar->ParamCount(); j++)
{
if ((!doMotion || (doMotion && (params[j].ParamVal() != 0))) && !params[j].IsPrecalc())
os << params[j].Name() << "=\"" << params[j].ParamVal() << "\" ";
}
}
}
}
if (!doMotion || (doMotion && !xform.m_Affine.IsZero()))
{
os << "coefs=\"" << xform.m_Affine.A() << " " << xform.m_Affine.D() << " " << xform.m_Affine.B() << " "
<< xform.m_Affine.E() << " " << xform.m_Affine.C() << " " << xform.m_Affine.F() << "\"";
}
if ((!doMotion && !xform.m_Post.IsID()) || (doMotion && !xform.m_Post.IsZero()))
{
os << " post=\"" << xform.m_Post.A() << " " << xform.m_Post.D() << " " << xform.m_Post.B() << " "
<< xform.m_Post.E() << " " << xform.m_Post.C() << " " << xform.m_Post.F() << "\"";
}
//Original only printed xaos values that were not 1. Here, print them all out if any are present.
if (!isFinal && !doMotion && xform.XaosPresent())
{
os << " chaos=\"";
for (i = 0; i < xformCount; i++)
os << xform.Xaos(i) << " ";
os << "\"";
}
if (!doMotion)
os << " opacity=\"" << xform.m_Opacity << "\"";
if (!doMotion && !xform.m_Motion.empty())
{
os << ">\n";
for (i = 0; i < xform.m_Motion.size(); i++)
os << ToString(xform.m_Motion[i], 0, false, true);
if (isFinal)//Fixed to properly close final.//SMOULDER
os << " </finalxform>\n";
else
os << " </xform>\n";
}
else
os << "/>\n";
return os.str();
}
/// <summary>
/// Return an edit node Xml string.
/// </summary>
/// <param name="editNode">The edit node to get the string for</param>
/// <param name="tabs">How many tabs to use</param>
/// <param name="formatting">If true, include newlines and tabs, else don't.</param>
/// <param name="printEditDepth">How deep the edit depth goes</param>
/// <returns>The edit node Xml string</returns>
string ToString(xmlNodePtr editNode, unsigned int tabs, bool formatting, unsigned int printEditDepth)
{
bool indentPrinted = false;
char* tabString = " ", *attStr;
unsigned int ti, editOrSheep = 0;
xmlAttrPtr attPtr = NULL, curAtt = NULL;
xmlNodePtr childPtr = NULL, curChild = NULL;
ostringstream os;
if (printEditDepth > 0 && tabs > printEditDepth)
return "";
//If this node is an XML_ELEMENT_NODE, print it and its attributes.
if (editNode->type == XML_ELEMENT_NODE)
{
//Print the node at the tab specified.
if (formatting)
for (ti = 0; ti < tabs; ti++)
os << tabString;
os << "<" << editNode->name;
//This can either be an edit node or a sheep node.
//If it's an edit node, add one to the tab.
if (!Compare(editNode->name, "edit"))
{
editOrSheep = 1;
tabs++;
}
else if (!Compare(editNode->name, "sheep"))
editOrSheep = 2;
else
editOrSheep = 0;
//Print the attributes.
attPtr = editNode->properties;
for (curAtt = attPtr; curAtt; curAtt = curAtt->next)
{
attStr = (char*)xmlGetProp(editNode, curAtt->name);
os << " " << curAtt->name << "=\"" << attStr << "\"";
xmlFree(attStr);
}
//Does this node have children?
if (!editNode->children || (printEditDepth > 0 && tabs > printEditDepth))
{
//Close the tag and subtract the tab.
os << "/>";
if (formatting)
os << "\n";
tabs--;
}
else
{
//Close the tag.
os << ">";
if (formatting)
os << "\n";
//Loop through the children and print them.
childPtr = editNode->children;
indentPrinted = false;
for (curChild = childPtr; curChild; curChild = curChild->next)
{
//If child is an element, indent first and then print it.
if (curChild->type == XML_ELEMENT_NODE &&
(!Compare(curChild->name, "edit") || !Compare(curChild->name, "sheep")))
{
if (indentPrinted)
{
indentPrinted = false;
os << "\n";
}
os << ToString(curChild, tabs, true, printEditDepth);
}
else
{
//Child is a text node, don't want to indent more than once.
if (xmlIsBlankNode(curChild))
continue;
if (!indentPrinted && formatting)
{
for (ti = 0; ti < tabs; ti++)
os << tabString;
indentPrinted = true;
}
//Print nodes without formatting.
os << ToString(curChild, tabs, false, printEditDepth);
}
}
if (indentPrinted && formatting)
os << "\n";
tabs--;//Tab out.
if (formatting)
for (ti = 0; ti < tabs; ti++)
os << tabString;
os << "</" << editNode->name << ">";//Close the tag.
if (formatting)
os << "\n";
}
}
else if (editNode->type == XML_TEXT_NODE)
{
string s((char*)xmlNodeGetContent(editNode));
os << Trim(s);
}
return os.str();
}
void AddFilenameWithoutAmpersand(xmlNodePtr node, string& filename)
{
if (filename.find_first_of('&') != std::string::npos)
{
string filenameWithoutAmpersands = filename;
FindAndReplace<string>(filenameWithoutAmpersands, "&", "&amp;");
xmlNewProp(node, XC "filename", XC filenameWithoutAmpersands.c_str());
}
else
{
xmlNewProp(node, XC "filename", XC filename.c_str());
}
}
};
}

1018
Source/Ember/Interpolate.h Normal file

File diff suppressed because it is too large Load Diff

386
Source/Ember/Isaac.h Normal file
View File

@ -0,0 +1,386 @@
#pragma once
#include "EmberDefines.h"
/// <summary>
/// C++ TEMPLATE VERSION OF Robert J. Jenkins Jr.'s
/// ISAAC Random Number Generator.
///
/// Ported from vanilla C to to template C++ class
/// by Quinn Tyler Jackson on 16-23 July 1998.
///
/// quinn@qtj.net
///
/// The function for the expected period of this
/// random number generator, according to Jenkins is:
///
/// f(a,b) = 2**((a+b*(3+2^^a)-1)
///
/// (where a is ALPHA and b is bitwidth)
///
/// So, for a bitwidth of 32 and an ALPHA of 8,
/// the expected period of ISAAC is:
///
/// 2^^(8+32*(3+2^^8)-1) = 2^^8295
///
/// Jackson has been able to run implementations
/// with an ALPHA as high as 16, or
///
/// 2^^2097263
///
/// -Modified by Matt Feemster to eliminate needless dynamic memory allocation and virtual functions and bring inline with Ember coding style.
/// </summary>
#ifndef __ISAAC64
typedef unsigned long int ISAAC_INT;
const ISAAC_INT GOLDEN_RATIO = ISAAC_INT(0x9e3779b9);
#else
typedef unsigned __int64 ISAAC_INT;
const ISAAC_INT GOLDEN_RATIO = ISAAC_INT(0x9e3779b97f4a7c13);
#endif
namespace EmberNs
{
/// <summary>
/// QTIsaac class which allows using ISAAC in an OOP manner.
/// </summary>
template <int ALPHA = 4, class T = ISAAC_INT>
class EMBER_API QTIsaac
{
public:
typedef unsigned char byte;
enum { N = (1 << ALPHA) };
/// <summary>
/// Global ISAAC RNG to be used from anywhere. This is not thread safe, so take caution to only
/// use it when no other threads are.
/// </summary>
static auto_ptr<QTIsaac<ALPHA, ISAAC_INT>> GlobalRand;
/// <summary>
/// The structure which holds all of the random information.
/// </summary>
struct EMBER_API randctx
{
T randcnt;
T randrsl[N];
T randmem[N];
T randa;
T randb;
T randc;
};
/// <summary>
/// Constructor which initialized the random context using the values passed in.
/// Leaving these as their defaults is fine, and will still give different
/// results because time is internally used if they are default.
/// However, specifying specific values is useful if you want to duplicate
/// a sequence of random numbers.
/// </summary>
/// <param name="a">First random seed. Default: 0.</param>
/// <param name="b">Second random seed. Default: 0.</param>
/// <param name="c">Third random seed. Default: 0.</param>
/// <param name="s">Pointer to a buffer of 256 random integer seeds. Default: NULL.</param>
QTIsaac(T a = 0, T b = 0, T c = 0, T* s = NULL)
{
Srand(a, b, c, s);
}
/// <summary>
/// Return the next random integer.
/// </summary>
/// <returns>The next random integer</returns>
inline T Rand()
{
return (m_Rc.randcnt++ == N ? (Isaac(&m_Rc), m_Rc.randcnt=0, m_Rc.randrsl[m_Rc.randcnt]) : m_Rc.randrsl[m_Rc.randcnt]);
}
/// <summary>
/// Return the next random integer between 0 and the value passed in.
/// </summary>
/// <returns>A value one greater than the maximum value that will be returned</returns>
inline T Rand(T upper)
{
return (upper == 0) ? Rand() : Rand() % upper;
}
/// <summary>
/// Returns a random floating point value between the specified minimum and maximum.
/// Template argument expected to be float or double.
/// </summary>
/// <param name="fMin">The minimum value allowed, inclusive.</param>
/// <param name="fMax">The maximum value allowed, inclusive.</param>
/// <returns>A new random floating point value within the specified range, inclusive.</returns>
template<typename floatType>
inline floatType Frand(floatType fMin, floatType fMax)
{
floatType f = (floatType)Rand() / (floatType)std::numeric_limits<T>::max();
return fMin + (f * (fMax - fMin));
}
/// <summary>
/// Thin wrapper around a call to Frand() with a range of 0-1.
/// Template argument expected to be float or double.
/// </summary>
/// <returns>A new random number in the range of 0-1, inclusive.</returns>
template<typename floatType>
inline floatType Frand01()
{
return Frand<floatType>(floatType(0), floatType(1));
}
/// <summary>
/// Thin wrapper around a call to Frand() with a range of -1-1.
/// Template argument expected to be float or double.
/// </summary>
/// <returns>A new random number in the range of -1-1, inclusive.</returns>
template<typename floatType>
inline floatType Frand11()
{
return Frand<floatType>(floatType(-1), floatType(1));
}
/// <summary>
/// Not sure what this does.
/// </summary>
/// <returns>Something that is golden</returns>
template<typename floatType>
inline floatType GoldenBit()
{
return RandBit() ? floatType(0.38196) : floatType(0.61804);
}
/// <summary>
/// Returns a random 0 or 1.
/// </summary>
/// <returns>A random 0 or 1</returns>
inline unsigned int RandBit()
{
return Rand() & 1;
}
/// <summary>
/// A different way of getting a floating point rand in the range -1-1.
/// Flam3 used this but it seems unnecessary now, keep around if it's ever needed.
/// </summary>
/// <returns>A new random number in the range of -1-1, inclusive.</returns>
//double drand11()
//{
// return (((int)Rand() & 0xfffffff) - 0x7ffffff) / (double) 0x7ffffff;
//}
/// <summary>
/// Initializes a random context.
/// Unsure exacly how this works, but it does.
/// </summary>
/// <param name="ctx">The random context to initialize</param>
/// <param name="useSeed">Whether to use the seeds passed in to the constructor, else zero.</param>
void RandInit(randctx* ctx, bool useSeed)
{
int i;
T a, b, c, d, e, f, g, h;
T* m = ctx->randmem;
T* r = ctx->randrsl;
a = b = c = d = e = f = g = h = GOLDEN_RATIO;
if (!useSeed)
{
ctx->randa = 0;
ctx->randb = 0;
ctx->randc = 0;
}
//Scramble it.
for (i = 0; i < 4; ++i)
{
Shuffle(a, b, c, d, e, f, g, h);
}
if (useSeed)
{
//Initialize using the contents of r[] as the seed.
for (i = 0; i < N; i += 8)
{
a += r[i ]; b += r[i + 1]; c += r[i + 2]; d += r[i + 3];
e += r[i + 4]; f += r[i + 5]; g += r[i + 6]; h += r[i + 7];
Shuffle(a, b, c, d, e, f, g, h);
m[i ] = a; m[i + 1] = b; m[i + 2] = c; m[i + 3] = d;
m[i + 4] = e; m[i + 5] = f; m[i + 6] = g; m[i + 7] = h;
}
//Do a second pass to make all of the seed affect all of m.
for (i = 0; i < N; i += 8)
{
a += m[i ]; b += m[i + 1]; c += m[i + 2]; d += m[i + 3];
e += m[i + 4]; f += m[i + 5]; g += m[i + 6]; h += m[i + 7];
Shuffle(a, b, c, d, e, f, g, h);
m[i ] = a; m[i + 1] = b; m[i + 2] = c; m[i + 3] = d;
m[i + 4] = e; m[i + 5] = f; m[i + 6] = g; m[i + 7] = h;
}
}
else
{
//Fill in mm[] with messy stuff.
Shuffle(a, b, c, d, e, f, g, h);
m[i ] = a; m[i + 1] = b; m[i + 2] = c; m[i + 3] = d;
m[i + 4] = e; m[i + 5] = f; m[i + 6] = g; m[i + 7] = h;
}
Isaac(ctx); //Fill in the first set of results.
ctx->randcnt = 0;//Prepare to use the first set of results.
}
/// <summary>
/// Initialize the seeds of the member random context using the specified seeds.
/// If s is null, time plus index up to 256 is used for the random buffer.
/// </summary>
/// <param name="a">First random seed. Default: 0.</param>
/// <param name="b">Second random seed. Default: 0.</param>
/// <param name="c">Third random seed. Default: 0.</param>
/// <param name="s">Pointer to a buffer of 256 random integer seeds. Default: NULL.</param>
void Srand(T a = 0, T b = 0, T c = 0, T* s = NULL)
{
if (s == NULL)//Default to using time plus index as the seed if s was NULL.
{
for (int i = 0; i < N; i++)
m_Rc.randrsl[i] = (T)time(0) + i;
}
else
{
for (int i = 0; i < N; i++)
m_Rc.randrsl[i] = s[i];
}
if (a == 0 && b == 0 && c == 0)
{
m_Rc.randa = (T)time(0);
m_Rc.randb = (T)time(0) * (T)time(0);
m_Rc.randc = (T)time(0) * (T)time(0) * (T)time(0);
}
else
{
m_Rc.randa = a;
m_Rc.randb = b;
m_Rc.randc = c;
}
RandInit(&m_Rc, true);
}
protected:
/// <summary>
/// Compute the next batch of random numbers for a random context.
/// </summary>
/// <param name="ctx">The context to populate.</param>
void Isaac(randctx* ctx)
{
T x,y;
T* mm = ctx->randmem;
T* r = ctx->randrsl;
T a = (ctx->randa);
T b = (ctx->randb + (++ctx->randc));
T* m = mm;
T* m2 = (m + (N / 2));
T* mend = m2;
for(; m < mend; )
{
#ifndef __ISAAC64
RngStep((a << 13), a, b, mm, m, m2, r, x, y);
RngStep((a >> 6) , a, b, mm, m, m2, r, x, y);
RngStep((a << 2) , a, b, mm, m, m2, r, x, y);
RngStep((a >> 16), a, b, mm, m, m2, r, x, y);
#else // __ISAAC64
RngStep(~(a ^ (a << 21)), a, b, mm, m, m2, r, x, y);
RngStep( a ^ (a >> 5) , a, b, mm, m, m2, r, x, y);
RngStep( a ^ (a << 12) , a, b, mm, m, m2, r, x, y);
RngStep( a ^ (a >> 33) , a, b, mm, m, m2, r, x, y);
#endif // __ISAAC64
}
m2 = mm;
for(; m2<mend;)
{
#ifndef __ISAAC64
RngStep((a << 13), a, b, mm, m, m2, r, x, y);
RngStep((a >> 6) , a, b, mm, m, m2, r, x, y);
RngStep((a << 2) , a, b, mm, m, m2, r, x, y);
RngStep((a >> 16), a, b, mm, m, m2, r, x, y);
#else // __ISAAC64
RngStep(~(a ^ (a << 21)), a, b, mm, m, m2, r, x, y);
RngStep( a ^ (a >> 5) , a, b, mm, m, m2, r, x, y);
RngStep( a ^ (a << 12) , a, b, mm, m, m2, r, x, y);
RngStep( a ^ (a >> 33) , a, b, mm, m, m2, r, x, y);
#endif // __ISAAC64
}
ctx->randb = b;
ctx->randa = a;
}
/// <summary>
/// Retrieves a value using indirection.
/// </summary>
/// <param name="mm">The buffer.</param>
/// <param name="x">The offset.</param>
/// <returns>A new value</returns>
inline T Ind(T* mm, T x)
{
#ifndef __ISAAC64
return (*(T*)((byte*)(mm) + ((x) & ((N - 1) << 2))));
#else // __ISAAC64
return (*(T*)((byte*)(mm) + ((x) & ((N - 1) << 3))));
#endif // __ISAAC64
}
/// <summary>
/// Unsure what this does.
/// </summary>
void RngStep(T mix, T& a, T& b, T*& mm, T*& m, T*& m2, T*& r, T& x, T& y)
{
x = *m;
a = (a ^ (mix)) + *(m2++);
*(m++) = y = Ind(mm, x) + a + b;
*(r++) = b = Ind(mm, y >> ALPHA) + x;
}
/// <summary>
/// Unsure what this does.
/// </summary>
void Shuffle(T& a, T& b, T& c, T& d, T& e, T& f, T& g, T& h)
{
#ifndef __ISAAC64
a ^= b << 11; d += a; b += c;
b ^= c >> 2; e += b; c += d;
c ^= d << 8; f += c; d += e;
d ^= e >> 16; g += d; e += f;
e ^= f << 10; h += e; f += g;
f ^= g >> 4; a += f; g += h;
g ^= h << 8; b += g; h += a;
h ^= a >> 9; c += h; a += b;
#else // __ISAAC64
a -= e; f ^= h >> 9; h += a;
b -= f; g ^= a << 9; a += b;
c -= g; h ^= b >> 23; b += c;
d -= h; a ^= c << 15; c += d;
e -= a; b ^= d >> 14; d += e;
f -= b; c ^= e << 20; e += f;
g -= c; d ^= f >> 17; f += g;
h -= d; e ^= g << 14; g += h;
#endif // __ISAAC64
}
private:
randctx m_Rc;//The random context which holds all of the seed and state information as well as the random number values.
};
}

541
Source/Ember/Iterator.h Normal file
View File

@ -0,0 +1,541 @@
#pragma once
#include "Ember.h"
/// <summary>
/// Iterator and derived classes.
/// </summary>
//#define CHOOSE_XFORM_GRAIN 256
#define CHOOSE_XFORM_GRAIN 10000//The size of xform random selection buffer. Multiply by the (number of non-final xforms present + 1) if xaos is used.
namespace EmberNs
{
/// <summary>
/// Iterator base class.
/// Iterating is one loop level outside of the inner xform application loop so it's still very important
/// to take every optimization possible here.
/// The original had many temporary assignments in order to feed the output of the current iteration
/// into the input of the next iteration. All unneccessary temporary assignments are eliminated by simply using i and i + 1
/// as the input and output indices on the samples array passed to Xform.Apply().
/// Note that the samples array is assigned to while fusing. Although this technically doesn't make sense
/// since values computed during fusing get thrown out, it doesn't matter because it will get overwritten
/// in the actual loop below it since the index counter is reset to zero when fusing is complete.
/// Flam3 needlessly computed the final xform on each fuse iteration only to throw it away. It's omitted here as an optimization.
/// Rather than place many conditionals inside the iteration loop, they are broken into separate classes depending
/// on what's contained in the ember's xforms.
/// The biggest difference is whether xaos is present or not it requires extra work when picking
/// the next random xform to use. Further, each of those is broken into two loops, one for embers with a final xform
/// and one without.
/// Last, the fuse loop and real loop are separated and duplicated to omit the conditional check for fuse inside the real loop.
/// Although this makes this file about four times as verbose as it would normally be, it does lead to performance improvements.
/// Template argument expected to be float or double.
/// </summary>
template <typename T>
class EMBER_API Iterator
{
public:
/// <summary>
/// Empty constructor.
/// </summary>
Iterator()
{
}
/// <summary>
/// Empty virtual destructor so proper derived class destructors get called.
/// </summary>
virtual ~Iterator()
{
}
/// <summary>
/// Accessors.
/// </summary>
const unsigned char* XformDistributions() const { return m_XformDistributions.empty() ? NULL : &m_XformDistributions[0]; }
const unsigned int XformDistributionsSize() const { return (unsigned int)m_XformDistributions.size(); }
/// <summary>
/// Virtual empty iteration function that will be overidden in derived iterator classes.
/// </summary>
/// <param name="ember">The ember whose xforms will be applied</param>
/// <param name="count">The number of iterations to do</param>
/// <param name="skip">The number of times to fuse</param>
/// <param name="samples">The buffer to store the output points</param>
/// <param name="rand">The random context to use</param>
/// <returns>The number of bad values</returns>
virtual unsigned int Iterate(Ember<T>& ember, unsigned int count, unsigned int skip, Point<T>* samples, QTIsaac<ISAAC_SIZE, ISAAC_INT>& rand) { return 0; }
/// <summary>
/// Initialize the xform selection vector by normalizing the weights of all xforms and
/// setting the corresponding percentage of elements in the vector to each xform's index in its
/// parent ember.
/// Note that this method of looking up and index in a vector is how flam3 did it and is about 10%
/// faster than using a while loop to check a random number against a normalized weight.
/// Also, the ember used to initialize this must be the same ember, unchanged, used to iterate.
/// If one is passed to this function, its parameters are changed and then it's passed to Iterate(),
/// the behavior is undefined.
/// </summary>
/// <param name="ember">The ember whose xforms will be used to populate the distribution vector</param>
/// <returns>True if success, else false.</returns>
bool InitDistributions(Ember<T>& ember)
{
unsigned int i, j = 0;
unsigned int distribCount = ember.XaosPresent() ? (unsigned int)ember.XformCount() + 1 : 1;
const Xform<T>* xforms = ember.Xforms();
if (m_XformDistributions.size() < CHOOSE_XFORM_GRAIN * distribCount)
m_XformDistributions.resize(CHOOSE_XFORM_GRAIN * distribCount);
if (m_XformDistributions.size() < CHOOSE_XFORM_GRAIN * distribCount)
return false;
for (unsigned int distrib = 0; distrib < distribCount; distrib++)
{
T totalDensity = 0;
//First find the total densities of all xforms.
for (i = 0; i < ember.XformCount(); i++)
{
T d = xforms[i].m_Weight;
if (distrib > 0)
d *= xforms[distrib - 1].Xaos(i);
//Original returned false if any xform had 0 density, it's allowed here
//because it can be useful when experimenting to test the effects of removing an
//xform by setting its probability to 0.
totalDensity += d;
}
//Original returned false if all were 0, but it's allowed here
//which will just end up setting all elements to 0 which means
//only the first xform will get used.
//Calculate how much of a fraction of a the total density each element represents.
T densityPerElement = totalDensity / CHOOSE_XFORM_GRAIN;
j = 0;
//Assign xform indices in order to each element of m_XformDistributions.
//The number of elements assigned a given index is proportional to that xform's
//density relative to the sum of all densities.
for (i = 0; i < ember.XformCount(); i++)
{
T tempDensity = 0;
T currentDensityLimit = xforms[i].m_Weight;
if (distrib > 0)
currentDensityLimit *= xforms[distrib - 1].Xaos(i);
//Populate points corresponding to this xform's weight/density.
//Also check that j is within the bounds of the distribution array just to be safe in the case of a rounding error.
while (tempDensity <= currentDensityLimit && j < CHOOSE_XFORM_GRAIN)
{
m_XformDistributions[(distrib * CHOOSE_XFORM_GRAIN) + j] = i;
tempDensity += densityPerElement;
j++;
}
}
}
return true;
}
protected:
/// <summary>
/// When iterating, if the computed location of the point is either very close to zero, or very close to infinity,
/// it's considered a bad value. In that case, a new random input point is fed into a new randomly chosen xform. This
/// process is repeated up to 5 times until a good value is computed. If after 5 tries, a good value is not found, then
/// the coordinates of the output point are just set to a random number between -1 and 1.
/// </summary>
/// <param name="xforms">The xforms array</param>
/// <param name="badVals">The counter for the total number of bad values this sub batch</param>
/// <param name="point">The point which initially had the bad values and which will store the newly computed values</param>
/// <param name="rand">The random context this iterator is using</param>
/// <returns>True if a good value was computed within 5 tries, else false</returns>
inline bool DoBadVals(Xform<T>* xforms, unsigned int& badVals, Point<T>* point, QTIsaac<ISAAC_SIZE, ISAAC_INT>& rand)
{
unsigned int xformIndex, consec = 0;
Point<T> firstBadPoint;
while (consec < 5)
{
consec++;
badVals++;
firstBadPoint.m_X = rand.Frand11<T>();//Re-randomize points, but keep the computed color and viz.
firstBadPoint.m_Y = rand.Frand11<T>();
firstBadPoint.m_Z = 0;
firstBadPoint.m_ColorX = point->m_ColorX;
firstBadPoint.m_VizAdjusted = point->m_VizAdjusted;
xformIndex = NextXformFromIndex(rand.Rand());
if (!xforms[xformIndex].Apply(&firstBadPoint, point, rand))
return true;
}
//After 5 tries, nothing worked, so just assign random values between -1 and 1.
if (consec == 5)
{
point->m_X = rand.Frand11<T>();
point->m_Y = rand.Frand11<T>();
point->m_Z = 0;
}
return false;
}
/// <summary>
/// Apply the final xform.
/// Note that as stated in the paper, the output of the final xform is not fed back into the next iteration.
/// Rather, only the value computed from the randomly chosen xform is. However, the output of the final xform
/// is still saved in the output samples buffer and accumulated to the histogram later.
/// </summary>
/// <param name="ember">The ember being iterated</param>
/// <param name="tempPoint">The input point</param>
/// <param name="sample">The output point</param>
/// <param name="rand">The random context to use.</param>
inline void DoFinalXform(Ember<T>& ember, Point<T>& tempPoint, Point<T>* sample, QTIsaac<ISAAC_SIZE, ISAAC_INT>& rand)
{
if (IsClose<T>(ember.FinalXform()->m_Opacity, 1) || rand.Frand01<T>() < ember.FinalXform()->m_Opacity)
{
T tempVizAdjusted = tempPoint.m_VizAdjusted;
ember.NonConstFinalXform()->Apply(&tempPoint, sample, rand);
sample->m_VizAdjusted = tempVizAdjusted;
}
}
/// <summary>
/// Retrieve an element in the distributions vector between 0 and CHOOSE_XFORM_GRAIN which will
/// contain the index of the next xform to use. When xaos is prsent, the offset is the index in
/// the ember of the previous xform used when.
/// </summary>
/// <param name="index">The index to retrieve</param>
/// <param name="distribOffset">When xaos is prsent, the index of the previous xform used. Default: 0 (xaos not present).</param>
/// <returns></returns>
unsigned int NextXformFromIndex(unsigned int index, unsigned int distribOffset = 0)
{
return (unsigned int)m_XformDistributions[(index % CHOOSE_XFORM_GRAIN) + (CHOOSE_XFORM_GRAIN * distribOffset)];
}
vector<unsigned char> m_XformDistributions;
};
/// <summary>
/// Derived iterator class for embers whose xforms do not use xaos.
/// </summary>
template <typename T>
class EMBER_API StandardIterator : public Iterator<T>
{
public:
/// <summary>
/// Empty constructor.
/// </summary>
StandardIterator()
{
}
/// <summary>
/// Overridden virtual function which iterates an ember a given number of times and does not use xaos.
/// </summary>
/// <param name="ember">The ember whose xforms will be applied</param>
/// <param name="count">The number of iterations to do</param>
/// <param name="skip">The number of times to fuse</param>
/// <param name="samples">The buffer to store the output points</param>
/// <param name="rand">The random context to use</param>
/// <returns>The number of bad values</returns>
virtual unsigned int Iterate(Ember<T>& ember, unsigned int count, unsigned int skip, Point<T>* samples, QTIsaac<ISAAC_SIZE, ISAAC_INT>& rand)
{
unsigned int i, badVals = 0;
Point<T> tempPoint, p1;
Xform<T>* xforms = ember.NonConstXforms();
if (ember.ProjBits())
{
if (ember.UseFinalXform())
{
p1 = samples[0];
for (i = 0; i < skip; i++)//Fuse.
{
if (xforms[NextXformFromIndex(rand.Rand())].Apply(&p1, &p1, rand))
DoBadVals(xforms, badVals, &p1, rand);
}
DoFinalXform(ember, p1, samples, rand);//Apply to last fuse point and store as the first element in samples.
ember.Proj(samples[0], rand);
for (i = 1; i < count; i++)//Real loop.
{
if (xforms[NextXformFromIndex(rand.Rand())].Apply(&p1, &p1, rand))
DoBadVals(xforms, badVals, &p1, rand);
DoFinalXform(ember, p1, samples + i, rand);
ember.Proj(samples[i], rand);
}
}
else
{
p1 = samples[0];
for (i = 0; i < skip; i++)//Fuse.
{
if (xforms[NextXformFromIndex(rand.Rand())].Apply(&p1, &p1, rand))
DoBadVals(xforms, badVals, &p1, rand);
}
samples[0] = p1;
ember.Proj(samples[0], rand);
for (i = 1; i < count; i++)//Real loop.
{
if (xforms[NextXformFromIndex(rand.Rand())].Apply(&p1, &samples[i], rand))
DoBadVals(xforms, badVals, samples + i, rand);
p1 = samples[i];
ember.Proj(samples[i], rand);
}
}
}
else
{
if (ember.UseFinalXform())
{
p1 = samples[0];
for (i = 0; i < skip; i++)//Fuse.
{
if (xforms[NextXformFromIndex(rand.Rand())].Apply(&p1, &p1, rand))
DoBadVals(xforms, badVals, &p1, rand);
}
DoFinalXform(ember, p1, samples, rand);//Apply to last fuse point and store as the first element in samples.
for (i = 1; i < count; i++)//Real loop.
{
if (xforms[NextXformFromIndex(rand.Rand())].Apply(&p1, &p1, rand))//Feed the resulting value of applying the randomly selected xform back into the next iter, and not the result of applying the final xform.
DoBadVals(xforms, badVals, &p1, rand);
DoFinalXform(ember, p1, samples + i, rand);
}
}
else
{
p1 = samples[0];
for (i = 0; i < skip; i++)//Fuse.
{
if (xforms[NextXformFromIndex(rand.Rand())].Apply(&p1, &p1, rand))
DoBadVals(xforms, badVals, &p1, rand);
}
samples[0] = p1;
for (i = 0; i < count - 1; i++)//Real loop.
if (xforms[NextXformFromIndex(rand.Rand())].Apply(samples + i, samples + i + 1, rand))
DoBadVals(xforms, badVals, samples + i + 1, rand);
}
}
return badVals;
}
};
/// <summary>
/// Derived iterator class for embers whose xforms use xaos.
/// </summary>
template <typename T>
class EMBER_API XaosIterator : public Iterator<T>
{
public:
/// <summary>
/// Empty constructor.
/// </summary>
XaosIterator()
{
}
/// <summary>
/// Handler for bad values similar to the one in the base class, except it takes the last xform used
/// as a parameter and saves the xform used back out because this iterator is meant to be used with xaos.
/// </summary>
/// <param name="xforms">The xforms array</param>
/// <param name="xformIndex">Index of the last used xform before calling this function</param>
/// <param name="lastXformUsed">The saved index of the last xform used within this function</param>
/// <param name="badVals">The counter for the total number of bad values this sub batch</param>
/// <param name="point">The point which initially had the bad values and which will store the newly computed values</param>
/// <param name="rand">The random context this iterator is using</param>
/// <returns>True if a good value was computed within 5 tries, else false</returns>
inline bool DoBadVals(Xform<T>* xforms, unsigned int& xformIndex, unsigned int lastXformUsed, unsigned int& badVals, Point<T>* point, QTIsaac<ISAAC_SIZE, ISAAC_INT>& rand)
{
unsigned int consec = 0;
Point<T> firstBadPoint;
while (consec < 5)
{
consec++;
badVals++;
firstBadPoint.m_X = rand.Frand11<T>();//Re-randomize points, but keep the computed color and viz.
firstBadPoint.m_Y = rand.Frand11<T>();
firstBadPoint.m_Z = 0;
firstBadPoint.m_ColorX = point->m_ColorX;
firstBadPoint.m_VizAdjusted = point->m_VizAdjusted;
xformIndex = NextXformFromIndex(rand.Rand(), lastXformUsed);
if (!xforms[xformIndex].Apply(&firstBadPoint, point, rand))
return true;
}
//After 5 tries, nothing worked, so just assign random.
if (consec == 5)
{
point->m_X = rand.Frand11<T>();
point->m_Y = rand.Frand11<T>();
point->m_Z = 0;
}
return false;
}
/// <summary>
/// Overridden virtual function which iterates an ember a given number of times and uses xaos.
/// </summary>
/// <param name="ember">The ember whose xforms will be applied</param>
/// <param name="count">The number of iterations to do</param>
/// <param name="skip">The number of times to fuse</param>
/// <param name="samples">The buffer to store the output points</param>
/// <param name="rand">The random context to use</param>
/// <returns>The number of bad values</returns>
virtual unsigned int Iterate(Ember<T>& ember, unsigned int count, unsigned int skip, Point<T>* samples, QTIsaac<ISAAC_SIZE, ISAAC_INT>& rand)
{
unsigned int i, xformIndex;
unsigned int lastXformUsed = 0;
unsigned int badVals = 0;
Point<T> tempPoint, p1;
Xform<T>* xforms = ember.NonConstXforms();
if (ember.ProjBits())
{
if (ember.UseFinalXform())
{
p1 = samples[0];
for (i = 0; i < skip; i++)//Fuse.
{
xformIndex = NextXformFromIndex(rand.Rand(), lastXformUsed);
if (xforms[xformIndex].Apply(&p1, &p1, rand))
DoBadVals(xforms, xformIndex, lastXformUsed, badVals, &p1, rand);
lastXformUsed = xformIndex + 1;//Store the last used transform.
}
DoFinalXform(ember, p1, samples, rand);//Apply to last fuse point and store as the first element in samples.
ember.Proj(samples[0], rand);
for (i = 1; i < count; i++)//Real loop.
{
xformIndex = NextXformFromIndex(rand.Rand(), lastXformUsed);
if (xforms[xformIndex].Apply(&p1, &p1, rand))//Feed the resulting value of applying the randomly selected xform back into the next iter, and not the result of applying the final xform.
DoBadVals(xforms, xformIndex, lastXformUsed, badVals, &p1, rand);
DoFinalXform(ember, p1, samples + i, rand);
ember.Proj(samples[i], rand);
lastXformUsed = xformIndex + 1;//Store the last used transform.
}
}
else
{
p1 = samples[0];
for (i = 0; i < skip; i++)//Fuse.
{
xformIndex = NextXformFromIndex(rand.Rand(), lastXformUsed);
if (xforms[xformIndex].Apply(&p1, &p1, rand))
DoBadVals(xforms, xformIndex, lastXformUsed, badVals, &p1, rand);
lastXformUsed = xformIndex + 1;//Store the last used transform.
}
ember.Proj(p1, rand);
samples[0] = p1;
for (i = 1; i < count; i++)//Real loop.
{
xformIndex = NextXformFromIndex(rand.Rand(), lastXformUsed);
if (xforms[xformIndex].Apply(&p1, &p1, rand))
DoBadVals(xforms, xformIndex, lastXformUsed, badVals, &p1, rand);
samples[i] = p1;
ember.Proj(samples[i], rand);
lastXformUsed = xformIndex + 1;//Store the last used transform.
}
}
}
else
{
if (ember.UseFinalXform())
{
p1 = samples[0];
for (i = 0; i < skip; i++)//Fuse.
{
xformIndex = NextXformFromIndex(rand.Rand(), lastXformUsed);
if (xforms[xformIndex].Apply(&p1, &p1, rand))
DoBadVals(xforms, xformIndex, lastXformUsed, badVals, &p1, rand);
lastXformUsed = xformIndex + 1;//Store the last used transform.
}
DoFinalXform(ember, p1, samples, rand);//Apply to last fuse point and store as the first element in samples.
for (i = 1; i < count; i++)//Real loop.
{
xformIndex = NextXformFromIndex(rand.Rand(), lastXformUsed);
if (xforms[xformIndex].Apply(&p1, &p1, rand))//Feed the resulting value of applying the randomly selected xform back into the next iter, and not the result of applying the final xform.
DoBadVals(xforms, xformIndex, lastXformUsed, badVals, &p1, rand);
DoFinalXform(ember, p1, samples + i, rand);
lastXformUsed = xformIndex + 1;//Store the last used transform.
}
}
else
{
p1 = samples[0];
for (i = 0; i < skip; i++)//Fuse.
{
xformIndex = NextXformFromIndex(rand.Rand(), lastXformUsed);
if (xforms[xformIndex].Apply(&p1, &p1, rand))
DoBadVals(xforms, xformIndex, lastXformUsed, badVals, &p1, rand);
lastXformUsed = xformIndex + 1;//Store the last used transform.
}
samples[0] = p1;
for (i = 0; i < count - 1; i++)//Real loop.
{
xformIndex = NextXformFromIndex(rand.Rand(), lastXformUsed);
if (xforms[xformIndex].Apply(samples + i, samples + i + 1, rand))
DoBadVals(xforms, xformIndex, lastXformUsed, badVals, samples + i + 1, rand);
lastXformUsed = xformIndex + 1;//Store the last used transform.
}
}
}
return badVals;
}
};
}

571
Source/Ember/Palette.h Normal file
View File

@ -0,0 +1,571 @@
#pragma once
#include "Utils.h"
#include "Isaac.h"
/// <summary>
/// Palette class.
/// </summary>
namespace EmberNs
{
/// <summary>
/// The palette stores a set of 256 colors which are what get accumulated to the histogram
/// for each iteration. The colors come from either the main palette Xml file or directly
/// from the ember parameter file. Either way, they come in as 0-255 and get normalized to 0-1.
/// In the future, 2D palette support might be added in which case this class will have to be modified.
/// Template argument expected to be float or double.
/// </summary>
template <typename T>
class EMBER_API Palette
{
public:
/// <summary>
/// Constructor which sets the palette index to random and allocates space to hold the color entries.
/// </summary>
Palette()
{
m_Name = "-";
m_Index = -1;
m_Entries.resize(COLORMAP_LENGTH);
Clear();
}
/// <summary>
/// Constructor that takes a name various parameters. If no color buffer is specified, a default is used.
/// This is a safety fallback, and it's highly recommended to always supply a buffer of color entries.
/// </summary>
/// <param name="name">The name of the palette</param>
/// <param name="index">The index in the palette file</param>
/// <param name="size">The size of the palette which should be 256</param>
/// <param name="xmlPaletteEntries">A pointer to 256 color entries</param>
Palette(string name, int index, unsigned int size, v4T* xmlPaletteEntries)
{
m_Name = name;
m_Index = index;
m_Entries.resize(size);
if (xmlPaletteEntries)
{
memcpy(&m_Entries[0], xmlPaletteEntries, Size() * sizeof(m_Entries[0]));
}
else//They passed in null, so just fill with hard coded values so they at least have something.
{
//Palette 15 used in the test ember file.
unsigned char palette15[COLORMAP_LENGTH * 4] = {
0x00, 0xda, 0xde, 0xbc, 0x00, 0xee, 0xe6, 0xc5, 0x00, 0xee, 0xf2, 0xce, 0x00, 0xee, 0xf2, 0xcf, 0x00, 0xe6, 0xee, 0xe1, 0x00, 0xea, 0xee, 0xd8, 0x00, 0xf2, 0xf1, 0xeb, 0x00, 0xf2, 0xf5, 0xd8,
0x00, 0xe6, 0xf2, 0xce, 0x00, 0xde, 0xea, 0xc5, 0x00, 0xd6, 0xda, 0xc6, 0x00, 0xce, 0xd2, 0xbc, 0x00, 0xc2, 0xca, 0xa9, 0x00, 0xbe, 0xca, 0xa0, 0x00, 0xce, 0xd6, 0xaa, 0x00, 0xde, 0xe2, 0xc5,
0x00, 0xea, 0xed, 0xce, 0x00, 0xea, 0xf2, 0xc5, 0x00, 0xde, 0xe2, 0xc5, 0x00, 0xc2, 0xca, 0xaa, 0x00, 0xae, 0xbe, 0xaa, 0x00, 0xa5, 0xb2, 0x96, 0x00, 0xa2, 0xa9, 0x8d, 0x00, 0x96, 0xa2, 0x84,
0x00, 0x8d, 0x8d, 0x7a, 0x00, 0x85, 0x89, 0x71, 0x00, 0x85, 0x8d, 0x71, 0x00, 0x85, 0x85, 0x67, 0x00, 0x79, 0x7d, 0x67, 0x00, 0x79, 0x7d, 0x67, 0x00, 0x71, 0x79, 0x5e, 0x00, 0x65, 0x6d, 0x55,
0x00, 0x4d, 0x5d, 0x42, 0x00, 0x34, 0x40, 0x25, 0x00, 0x30, 0x40, 0x25, 0x00, 0x30, 0x38, 0x1c, 0x00, 0x2c, 0x3c, 0x1c, 0x00, 0x2c, 0x34, 0x1c, 0x00, 0x24, 0x2c, 0x12, 0x00, 0x24, 0x24, 0x00,
0x00, 0x24, 0x2c, 0x09, 0x00, 0x28, 0x34, 0x09, 0x00, 0x38, 0x40, 0x12, 0x00, 0x30, 0x40, 0x1c, 0x00, 0x40, 0x50, 0x2f, 0x00, 0x55, 0x69, 0x42, 0x00, 0x65, 0x75, 0x55, 0x00, 0x6c, 0x7d, 0x5e,
0x00, 0x74, 0x8d, 0x71, 0x00, 0x74, 0x89, 0x84, 0x00, 0x74, 0x8d, 0x84, 0x00, 0x78, 0x8d, 0x84, 0x00, 0x79, 0x89, 0x7a, 0x00, 0x79, 0x85, 0x71, 0x00, 0x75, 0x7d, 0x67, 0x00, 0x71, 0x79, 0x5e,
0x00, 0x6c, 0x71, 0x5e, 0x00, 0x6d, 0x70, 0x5e, 0x00, 0x6c, 0x79, 0x5e, 0x00, 0x68, 0x75, 0x5e, 0x00, 0x69, 0x71, 0x55, 0x00, 0x6d, 0x75, 0x55, 0x00, 0x6d, 0x75, 0x55, 0x00, 0x69, 0x71, 0x55,
0x00, 0x65, 0x71, 0x55, 0x00, 0x69, 0x6d, 0x55, 0x00, 0x64, 0x71, 0x5e, 0x00, 0x68, 0x70, 0x67, 0x00, 0x68, 0x70, 0x67, 0x00, 0x68, 0x6c, 0x67, 0x00, 0x6c, 0x6c, 0x5e, 0x00, 0x71, 0x71, 0x5e,
0x00, 0x79, 0x79, 0x67, 0x00, 0x81, 0x85, 0x71, 0x00, 0x7d, 0x91, 0x71, 0x00, 0x85, 0x92, 0x7a, 0x00, 0x85, 0x92, 0x7a, 0x00, 0x7d, 0x92, 0x84, 0x00, 0x79, 0x92, 0x84, 0x00, 0x78, 0x92, 0x8d,
0x00, 0x78, 0x8d, 0x8d, 0x00, 0x74, 0x8d, 0x84, 0x00, 0x74, 0x92, 0x84, 0x00, 0x75, 0x92, 0x7a, 0x00, 0x6c, 0x85, 0x67, 0x00, 0x64, 0x79, 0x5e, 0x00, 0x59, 0x69, 0x4b, 0x00, 0xaa, 0x57, 0x00,
0x00, 0x38, 0x44, 0x1c, 0x00, 0x30, 0x3c, 0x1c, 0x00, 0x2c, 0x3c, 0x1c, 0x00, 0x34, 0x40, 0x25, 0x00, 0x50, 0x61, 0x4b, 0x00, 0x5d, 0x6d, 0x5e, 0x00, 0x64, 0x71, 0x5e, 0x00, 0x60, 0x71, 0x5e,
0x00, 0x60, 0x75, 0x5e, 0x00, 0x68, 0x75, 0x5e, 0x00, 0x6c, 0x79, 0x5e, 0x00, 0x6c, 0x79, 0x5e, 0x00, 0x71, 0x79, 0x67, 0x00, 0x70, 0x79, 0x67, 0x00, 0x6c, 0x7d, 0x67, 0x00, 0x68, 0x79, 0x67,
0x00, 0x6c, 0x79, 0x67, 0x00, 0x6c, 0x75, 0x67, 0x00, 0x71, 0x75, 0x5e, 0x00, 0x71, 0x75, 0x5e, 0x00, 0x75, 0x79, 0x5e, 0x00, 0x75, 0x7d, 0x5e, 0x00, 0x81, 0x8d, 0x5e, 0x00, 0x8d, 0x92, 0x5e,
0x00, 0x8d, 0x92, 0x67, 0x00, 0x9a, 0x9a, 0x71, 0x00, 0x9a, 0xa2, 0x7a, 0x00, 0x9a, 0xa2, 0x7a, 0x00, 0x9a, 0xa1, 0x7a, 0x00, 0x92, 0x9a, 0x71, 0x00, 0x89, 0x92, 0x67, 0x00, 0x81, 0x85, 0x5e,
0x00, 0x7d, 0x7d, 0x55, 0x00, 0x69, 0x79, 0x4b, 0x00, 0x61, 0x6d, 0x42, 0x00, 0x44, 0x4c, 0x25, 0x00, 0x38, 0x44, 0x1c, 0x00, 0x40, 0x51, 0x25, 0x00, 0x45, 0x4d, 0x25, 0x00, 0x71, 0x6d, 0x42,
0x00, 0x79, 0x7d, 0x4b, 0x00, 0x81, 0x7d, 0x55, 0x00, 0x79, 0x79, 0x55, 0x00, 0x6d, 0x75, 0x55, 0x00, 0x69, 0x7d, 0x55, 0x00, 0x6c, 0x79, 0x5e, 0x00, 0x65, 0x79, 0x54, 0x00, 0x68, 0x79, 0x5e,
0x00, 0x64, 0x79, 0x67, 0x00, 0x64, 0x79, 0x67, 0x00, 0x68, 0x75, 0x5e, 0x00, 0x64, 0x71, 0x5e, 0x00, 0x64, 0x6c, 0x5e, 0x00, 0x65, 0x6d, 0x55, 0x00, 0x4d, 0x58, 0x42, 0x00, 0x34, 0x40, 0x25,
0x00, 0x2c, 0x38, 0x1c, 0x00, 0x20, 0x28, 0x1c, 0x00, 0x1c, 0x14, 0x09, 0x00, 0x18, 0x18, 0x00, 0x00, 0x04, 0x14, 0x00, 0x00, 0x08, 0x10, 0x00, 0x00, 0x0c, 0x18, 0x00, 0x00, 0x1c, 0x28, 0x09,
0x00, 0x24, 0x30, 0x12, 0x00, 0x3c, 0x44, 0x25, 0x00, 0x5d, 0x65, 0x55, 0x00, 0x75, 0x79, 0x55, 0x00, 0x85, 0x89, 0x5e, 0x00, 0x89, 0x91, 0x71, 0x00, 0x96, 0xa2, 0x71, 0x00, 0x9a, 0xa2, 0x7a,
0x00, 0x9e, 0xaa, 0x7a, 0x00, 0x9e, 0xaa, 0x7a, 0x00, 0xaa, 0xae, 0x71, 0x00, 0xa6, 0xaa, 0x7a, 0x00, 0xa2, 0xaa, 0x7a, 0x00, 0xa1, 0xa5, 0x7a, 0x00, 0x96, 0x9e, 0x7a, 0x00, 0x85, 0x96, 0x7a,
0x00, 0x81, 0x92, 0x7a, 0x00, 0x78, 0x92, 0x7a, 0x00, 0x75, 0x92, 0x7a, 0x00, 0x75, 0x8d, 0x7a, 0x00, 0x70, 0x81, 0x67, 0x00, 0x7d, 0x7d, 0x67, 0x00, 0x89, 0x89, 0x67, 0x00, 0x92, 0x9a, 0x71,
0x00, 0x9e, 0xaa, 0x7a, 0x00, 0xaa, 0xb6, 0x84, 0x00, 0xb2, 0xb6, 0x8d, 0x00, 0xb6, 0xba, 0x97, 0x00, 0xc2, 0xca, 0x97, 0x00, 0xb2, 0xbe, 0x8d, 0x00, 0xb2, 0xb6, 0x8d, 0x00, 0xaa, 0xb2, 0x8d,
0x00, 0xa2, 0xae, 0x84, 0x00, 0x9a, 0xa6, 0x7a, 0x00, 0x92, 0x9e, 0x7a, 0x00, 0x85, 0x9a, 0x7a, 0x00, 0x7d, 0x96, 0x7a, 0x00, 0x7d, 0x92, 0x7a, 0x00, 0x7d, 0x92, 0x84, 0x00, 0x7d, 0x92, 0x84,
0x00, 0x81, 0x96, 0x84, 0x00, 0x85, 0x96, 0x84, 0x00, 0x85, 0x96, 0x84, 0x00, 0x81, 0x92, 0x84, 0x00, 0x85, 0x9a, 0x84, 0x00, 0x85, 0x9a, 0x84, 0x00, 0x8d, 0x9a, 0x84, 0x00, 0x92, 0x96, 0x84,
0x00, 0x9e, 0xa9, 0x84, 0x00, 0xae, 0xb2, 0x84, 0x00, 0xaa, 0xba, 0x84, 0x00, 0xb2, 0xbe, 0x8d, 0x00, 0xb6, 0xc2, 0xa0, 0x00, 0xc6, 0xca, 0xa0, 0x00, 0xc6, 0xce, 0xaa, 0x00, 0xd6, 0xda, 0xb3,
0x00, 0xda, 0xe2, 0xc5, 0x00, 0xd2, 0xd6, 0xbc, 0x00, 0xbe, 0xc2, 0xa0, 0x00, 0xaa, 0xb6, 0x8d, 0x00, 0x9e, 0xa6, 0x7a, 0x00, 0x92, 0x9a, 0x71, 0x00, 0x89, 0x89, 0x71, 0x00, 0x81, 0x7d, 0x67,
0x00, 0x7d, 0x7d, 0x67, 0x00, 0x81, 0x78, 0x67, 0x00, 0x7d, 0x7d, 0x5e, 0x00, 0x79, 0x79, 0x5e, 0x00, 0x79, 0x81, 0x5e, 0x00, 0x81, 0x7d, 0x67, 0x00, 0x81, 0x7d, 0x67, 0x00, 0x81, 0x81, 0x67,
0x00, 0x81, 0x89, 0x71, 0x00, 0x85, 0x91, 0x7a, 0x00, 0x89, 0x92, 0x7a, 0x00, 0x96, 0x9d, 0x7a, 0x00, 0x96, 0x9e, 0x7a, 0x00, 0x92, 0x96, 0x84, 0x00, 0x96, 0x9a, 0x8d, 0x00, 0x92, 0x92, 0x84,
0x00, 0x89, 0x91, 0x84, 0x00, 0x81, 0x92, 0x84, 0x00, 0x7d, 0x92, 0x8d, 0x00, 0x78, 0x92, 0x8d, 0x00, 0x74, 0x92, 0x8d, 0x00, 0x78, 0x92, 0x8d, 0x00, 0x78, 0x96, 0x97, 0x00, 0x81, 0x96, 0x8d,
0x00, 0x81, 0x96, 0x8d, 0x00, 0x81, 0x9a, 0x8d, 0x00, 0x85, 0x9a, 0x8d, 0x00, 0x89, 0x9e, 0x8d, 0x00, 0x89, 0x9e, 0x8d, 0x00, 0x8d, 0xa2, 0x97, 0x00, 0x95, 0xa2, 0x97, 0x00, 0x8d, 0xa2, 0x97,
0x00, 0x96, 0xa6, 0x8d, 0x00, 0x9a, 0xa1, 0x8d, 0x00, 0x9e, 0xa9, 0x84, 0x00, 0x9e, 0xa6, 0x7a, 0x00, 0xa2, 0xa5, 0x71, 0x00, 0x9e, 0xa6, 0x71, 0x00, 0x9a, 0xa6, 0x71, 0x00, 0x95, 0x9d, 0x71 };
for (unsigned int i = 0; i < size; i++)
{
m_Entries[i].a = (T)palette15[i * 4 + 0];
m_Entries[i].r = (T)palette15[i * 4 + 1];
m_Entries[i].g = (T)palette15[i * 4 + 2];
m_Entries[i].b = (T)palette15[i * 4 + 3];
}
}
}
/// <summary>
/// Default copy constructor.
/// </summary>
/// <param name="palette">The Palette object to copy</param>
Palette(const Palette<T>& palette)
{
Palette<T>::operator=<T>(palette);
}
/// <summary>
/// Copy constructor to copy a Palette object of type U.
/// </summary>
/// <param name="palette">The Palette object to copy</param>
template <typename U>
Palette(const Palette<U>& palette)
{
Palette<T>::operator=<U>(palette);
}
/// <summary>
/// Default assignment operator.
/// </summary>
/// <param name="palette">The Palette object to copy</param>
Palette<T>& operator = (const Palette<T>& palette)
{
if (this != &palette)
Palette<T>::operator=<T>(palette);
return *this;
}
/// <summary>
/// Assignment operator to assign a Palette object of type U.
/// </summary>
/// <param name="palette">The Palette object to copy</param>
/// <returns>Reference to updated self</returns>
template <typename U>
Palette<T>& operator = (const Palette<U>& palette)
{
m_Index = palette.m_Index;
m_Name = palette.m_Name;
CopyVec(m_Entries, palette.m_Entries);
return *this;
}
/// <summary>
/// Convenience [] operator to index into the color entries vector.
/// </summary>
/// <param name="i">The index to get</param>
/// <returns>The color value at the specified index</returns>
v4T& operator[] (size_t i)
{
return m_Entries[i];
}
/// <summary>
/// Convenience * operator to get a pointer to the beginning of the color entries vector.
/// </summary>
/// <returns>The address of the first element in the color entries vector</returns>
inline v4T* operator() (void)
{
return &m_Entries[0];
}
/// <summary>
/// The size of the color entries vector.
/// </summary>
/// <returns>The size of the color entries vector</returns>
size_t Size() { return m_Entries.size(); }
/// <summary>
/// Set all colors to either black or white, including the alpha channel.
/// </summary>
/// <param name="black">Set all colors to black if true, else white</param>
void Clear(bool black = true)
{
for (glm::length_t i = 0; i < Size(); i++)
{
for (glm::length_t j = 0; j < 4; j++)
{
if (black)
m_Entries[i][j] = 0;
else
m_Entries[i][j] = 1;
}
}
}
/// <summary>
/// Make a copy of this palette, adjust for hue and store in the passed in palette.
/// This is used because one way an ember Xml can specify color is with an index in the
/// palette Xml file and a hue rotation value.
/// </summary>
/// <param name="palette">The palette to store the results in</param>
/// <param name="hue">The hue rotation to apply</param>
void MakeHueAdjustedPalette(Palette<T>& palette, T hue)
{
palette.m_Index = m_Index;
palette.m_Name = m_Name;
palette.m_Entries.resize(Size());
for (unsigned int i = 0; i < Size(); i++)
{
size_t ii = (i * 256) / COLORMAP_LENGTH;
T rgb[3], hsv[3];
rgb[0] = m_Entries[ii].r;
rgb[1] = m_Entries[ii].g;
rgb[2] = m_Entries[ii].b;
RgbToHsv(rgb, hsv);
hsv[0] += hue * T(6.0);
HsvToRgb(hsv, rgb);
//Alpha serves as merely a hit counter that gets incremented by 1 each time, see Renderer::Accumulate() for its usage.
//Removing it saves no memory since it's 16 byte aligned. This also means alpha is not used.
palette[i].r = rgb[0];
palette[i].g = rgb[1];
palette[i].b = rgb[2];
palette[i].a = 1;
}
}
/// <summary>
/// More advanced adjustment than MakeHueAdjustedPalette() provides.
/// Adjustments are applied in the order:
/// Frequency, index rotation, hue rotation, saturation, brightness, contrast, blur.
/// </summary>
/// <param name="palette">The palette to store the result in</param>
/// <param name="rot">Index rotation.</param>
/// <param name="hue">Hue rotation -5 - 5</param>
/// <param name="sat">Saturation 0 - 1</param>
/// <param name="bright">Brightness 0 - 1</param>
/// <param name="cont">Contrast -1 - 2</param>
/// <param name="blur">Blur 0 - 127</param>
/// <param name="freq">Frequency 1 - 10</param>
void MakeAdjustedPalette(Palette<T>& palette, int rot, T hue, T sat, T bright, T cont, unsigned int blur, unsigned int freq)
{
T rgb[3], hsv[3];
if (freq > 1)
{
size_t n = Size() / freq;
for (size_t j = 0; j <= freq; j++)
{
for (size_t i = 0; i <= n; i++)
{
if ((i + j * n) < Size())
{
palette[i + j * n].r = m_Entries[i * freq].r;
palette[i + j * n].g = m_Entries[i * freq].g;
palette[i + j * n].b = m_Entries[i * freq].b;
}
}
}
palette.m_Name = m_Name;
}
else
{
palette = *this;
}
for (size_t i = 0; i < Size(); i++)
{
size_t ii = (i * 256) / COLORMAP_LENGTH;
rgb[0] = palette[(COLORMAP_LENGTH + ii - rot) % COLORMAP_LENGTH].r;//Rotation.
rgb[1] = palette[(COLORMAP_LENGTH + ii - rot) % COLORMAP_LENGTH].g;
rgb[2] = palette[(COLORMAP_LENGTH + ii - rot) % COLORMAP_LENGTH].b;
RgbToHsv(rgb, hsv);
hsv[0] += hue * T(6.0);//Hue.
hsv[1] = Clamp<T>(hsv[1] + sat, 0, 1);//Saturation.
HsvToRgb(hsv, rgb);
rgb[0] = Clamp<T>(rgb[0] + bright, 0, 1);//Brightness.
rgb[1] = Clamp<T>(rgb[1] + bright, 0, 1);
rgb[2] = Clamp<T>(rgb[2] + bright, 0, 1);
rgb[0] = Clamp<T>(((rgb[0] - T(0.5)) * (cont + T(1.0))) + T(0.5), 0, 1);//Contrast.
rgb[1] = Clamp<T>(((rgb[1] - T(0.5)) * (cont + T(1.0))) + T(0.5), 0, 1);
rgb[2] = Clamp<T>(((rgb[2] - T(0.5)) * (cont + T(1.0))) + T(0.5), 0, 1);
//Alpha serves as merely a hit counter that gets incremented by 1 each time, see Renderer::Accumulate() for its usage.
//Removing it saves no memory since it's 16 byte aligned.
palette[i].r = rgb[0];
palette[i].g = rgb[1];
palette[i].b = rgb[2];
palette[i].a = 1;
}
if (blur > 0)
{
Palette<T> blurPal = palette;
for (int i = 0; i < 256; i++)
{
int n = -1;
rgb[0] = 0;
rgb[1] = 0;
rgb[2] = 0;
for (int j = i - (int)blur; j <= i + (int)blur; j++)
{
n++;
int k = (256 + j) % 256;
if (k != i)
{
rgb[0] = rgb[0] + blurPal[k].r;
rgb[1] = rgb[1] + blurPal[k].g;
rgb[2] = rgb[2] + blurPal[k].b;
}
}
if (n != 0)
{
palette[i].r = rgb[0] / n;
palette[i].g = rgb[1] / n;
palette[i].b = rgb[2] / n;
}
}
}
}
/// <summary>
/// Make a copy of this palette and multiply all RGB values by a scalar.
/// </summary>
/// <param name="palette">The palette to store the result in</param>
/// <param name="colorScalar">The color scalar to multiply each RGB value by</param>
template<typename bucketT>
void MakeDmap(Palette<bucketT>& palette, T colorScalar = 1)
{
palette.m_Index = m_Index;
palette.m_Name = m_Name;
if (palette.Size() != Size())
palette.m_Entries.resize(Size());
for (unsigned int j = 0; j < palette.Size(); j++)
{
palette.m_Entries[j] = m_Entries[j] * colorScalar;
palette.m_Entries[j].a = 1;
}
}
/// <summary>
/// Make a buffer with the color values of this palette scaled to 255
/// and repeated for a number of rows.
/// Convenience function for displaying this palette on a GUI.
/// </summary>
/// <param name="height">The height of the output block</param>
/// <returns>A vector holding the color values</returns>
vector<unsigned char> MakeRgbPaletteBlock(unsigned int height)
{
size_t width = Size();
vector<unsigned char> v(height * width * 3);
if (v.size() == (height * Size() * 3))
{
for (unsigned int i = 0; i < height; i++)
{
for (unsigned int j = 0; j < width; j++)
{
v[(width * 3 * i) + (j * 3)] = (unsigned char)(m_Entries[j][0] * T(255));//Palettes are as [0..1], so convert to [0..255] here since it's for GUI display.
v[(width * 3 * i) + (j * 3) + 1] = (unsigned char)(m_Entries[j][1] * T(255));
v[(width * 3 * i) + (j * 3) + 2] = (unsigned char)(m_Entries[j][2] * T(255));
}
}
}
return v;
}
/// <summary>
/// Convert RGB to HSV.
/// </summary>
/// <param name="r">Red 0 - 1</param>
/// <param name="g">Green 0 - 1</param>
/// <param name="b">Blue 0 - 1</param>
/// <param name="h">Hue 0 - 6</param>
/// <param name="s">Saturation 0 - 1</param>
/// <param name="v">Value 0 - 1</param>
static void RgbToHsv(T r, T g, T b, T& h, T& s, T& v)
{
T max, min, del, rc, gc, bc;
max = std::max(std::max(r, g), b);//Compute maximum of r, g, b.
min = std::min(std::min(r, g), b);//Compute minimum of r, g, b.
del = max - min;
v = max;
s = (max != 0) ? (del / max) : 0;
h = 0;
if (s != 0)
{
rc = (max - r) / del;
gc = (max - g) / del;
bc = (max - b) / del;
if (r == max)
h = bc - gc;
else if (g == max)
h = 2 + rc - bc;
else if (b == max)
h = 4 + gc - rc;
if (h < 0)
h += 6;
}
}
/// <summary>
/// Wrapper around RgbToHsv() which takes buffers as parameters instead of individual components.
/// </summary>
/// <param name="rgb">The RGB buffer</param>
/// <param name="hsv">The HSV buffer</param>
static void RgbToHsv(T* rgb, T* hsv)
{
RgbToHsv(rgb[0], rgb[1], rgb[2], hsv[0], hsv[1], hsv[2]);
}
/// <summary>
/// Convert HSV to RGB.
/// </summary>
/// <param name="h">Hue 0 - 6</param>
/// <param name="s">Saturation 0 - 1</param>
/// <param name="v">Value 0 - 1</param>
/// <param name="r">Red 0 - 1</param>
/// <param name="g">Green 0 - 1</param>
/// <param name="b">Blue 0 - 1</param>
static void HsvToRgb(T h, T s, T v, T& r, T& g, T& b)
{
int j;
T f, p, q, t;
while (h >= 6)
h -= 6;
while (h < 0)
h += 6;
j = Floor<T>(h);
f = h - j;
p = v * (1 - s);
q = v * (1 - (s * f));
t = v * (1 - (s * (1 - f)));
switch (j)
{
case 0: r = v; g = t; b = p; break;
case 1: r = q; g = v; b = p; break;
case 2: r = p; g = v; b = t; break;
case 3: r = p; g = q; b = v; break;
case 4: r = t; g = p; b = v; break;
case 5: r = v; g = p; b = q; break;
default: r = v; g = t; b = p; break;
}
}
/// <summary>
/// Wrapper around HsvToRgb() which takes buffers as parameters instead of individual components.
/// </summary>
/// <param name="hsv">The HSV buffer</param>
/// <param name="rgb">The RGB buffer</param>
static void HsvToRgb(T* hsv, T* rgb)
{
HsvToRgb(hsv[0], hsv[1], hsv[2], rgb[0], rgb[1], rgb[2]);
}
/// <summary>
/// Calculates the alpha.
/// Used for gamma correction in final accumulation.
/// Not the slightest clue what this is doing.
/// </summary>
/// <param name="density">Density</param>
/// <param name="gamma">Gamma</param>
/// <param name="linrange">Linear range</param>
/// <returns>Alpha</returns>
static T CalcAlpha(T density, T gamma, T linrange)
{
T frac, alpha;
T funcval = pow(linrange, gamma);
if (density > 0)
{
if (density < linrange)
{
frac = density / linrange;
alpha = (T(1.0) - frac) * density * (funcval / linrange) + frac * pow(density, gamma);
}
else
alpha = pow(density, gamma);
}
else
alpha = 0;
return alpha;
}
/// <summary>
/// Calculates the new RGB and stores in the supplied buffer.
/// Used for gamma correction in final accumulation.
/// Not the slightest clue what this is doing.
/// </summary>
/// <param name="cBuf">The input RGB color buffer 0 - 1</param>
/// <param name="ls">Log scaling</param>
/// <param name="highPow">Highlight power, -1 - 1</param>
/// <param name="newRgb">Newly computed RGB value</param>
template<typename bucketT>
static void CalcNewRgb(bucketT* cBuf, T ls, T highPow, bucketT* newRgb)
{
int rgbi;
T newls, lsratio;
bucketT newhsv[3];
T maxa, maxc;
T adjustedHighlight;
if (ls == 0 || (cBuf[0] == 0 && cBuf[1] == 0 && cBuf[2] == 0))
{
newRgb[0] = 0;
newRgb[1] = 0;
newRgb[2] = 0;
return;
}
//Identify the most saturated channel.
maxc = max(max(cBuf[0], cBuf[1]), cBuf[2]);
maxa = ls * maxc;
//If a channel is saturated and highlight power is non-negative
//modify the color to prevent hue shift.
if (maxa > 255 && highPow >= 0)
{
newls = T(255.0) / maxc;
lsratio = pow(newls / ls, highPow);
//Calculate the max-value color (ranged 0 - 1).
for (rgbi = 0; rgbi < 3; rgbi++)
newRgb[rgbi] = (bucketT)newls * cBuf[rgbi] / bucketT(255.0);
//Reduce saturation by the lsratio.
Palette<bucketT>::RgbToHsv(newRgb, newhsv);
newhsv[1] *= (bucketT)lsratio;
Palette<bucketT>::HsvToRgb(newhsv, newRgb);
for (rgbi = 0; rgbi < 3; rgbi++)
newRgb[rgbi] *= T(255.0);
}
else
{
newls = T(255.0) / maxc;
adjustedHighlight = -highPow;
if (adjustedHighlight > 1)
adjustedHighlight = 1;
if (maxa <= 255)
adjustedHighlight = 1;
//Calculate the max-value color (ranged 0 - 1) interpolated with the old behavior.
for (rgbi = 0; rgbi < 3; rgbi++)
newRgb[rgbi] = bucketT((T(1.0) - adjustedHighlight) * newls + adjustedHighlight * ls) * cBuf[rgbi];
}
}
int m_Index;//Index in the xml palette file of this palette, use -1 for random.
string m_Name;//Name of this palette.
vector<v4T> m_Entries;//Storage for the color values.
};
}

226
Source/Ember/PaletteList.h Normal file
View File

@ -0,0 +1,226 @@
#pragma once
#include "Palette.h"
/// <summary>
/// PaletteList class.
/// </summary>
namespace EmberNs
{
/// <summary>
/// Holds a list of palettes read from an Xml file. Since the default list from flam3-palettes.xml is fairly large at 700 palettes,
/// the list member is kept as a static. This class derives from EmberReport in order to report any errors that occurred while reading the Xml.
/// Note that although the Xml color values are expected to be 0-255, they are converted and stored as normalized colors, with values from 0-1.
/// Template argument expected to be float or double.
/// </summary>
template <typename T>
class EMBER_API PaletteList : public EmberReport
{
public:
/// <summary>
/// Empty constructor which does nothing.
/// </summary>
PaletteList()
{
}
/// <summary>
/// Read an Xml palette file into memory.
/// This must be called before any palette file usage.
/// </summary>
/// <param name="filename">The full path to the file to read</param>
/// <param name="force">If true, override the initialization state and force a read, else observe the initialization state.</param>
/// <returns>The initialization state</returns>
bool Init(string filename, bool force = false)
{
if (!m_Init || force)
{
const char* loc = __FUNCTION__;
m_Init = false;
m_Palettes.clear();
m_ErrorReport.clear();
string buf;
if (ReadFile(filename.c_str(), buf))
{
xmlDocPtr doc = xmlReadMemory((const char*)buf.data(), (int)buf.size(), filename.c_str(), NULL, XML_PARSE_NONET);
if (doc != NULL)
{
xmlNode* rootNode = xmlDocGetRootElement(doc);
m_Palettes.reserve(buf.size() / 2048);//Roughly what it takes per palette.
ParsePalettes(rootNode);
xmlFreeDoc(doc);
m_Init = m_ErrorReport.empty();
}
else
{
m_ErrorReport.push_back(string(loc) + " : Couldn't load xml doc");
}
}
else
{
m_ErrorReport.push_back(string(loc) + " : Couldn't read palette file " + filename);
}
}
return m_Init;
}
/// <summary>
/// Gets the palette at a specified index.
/// </summary>
/// <param name="i">The index of the palette to read. A value of -1 indicates a random palette.</param>
/// <returns>A pointer to the requested palette if the index was in range, else NULL.</returns>
Palette<T>* GetPalette(int i)
{
if (!m_Palettes.empty())
{
if (i == -1)
return &m_Palettes[QTIsaac<ISAAC_SIZE, ISAAC_INT>::GlobalRand->Rand() % Count()];
else if (i < (int)m_Palettes.size())
return &m_Palettes[i];
}
return NULL;
}
/// <summary>
/// Gets a pointer to a palette with a specified name.
/// </summary>
/// <param name="name">The name of the palette to retrieve</param>
/// <returns>A pointer to the palette if found, else NULL</returns>
Palette<T>* GetPaletteByName(string& name)
{
for (unsigned int i = 0; i < Count(); i++)
if (m_Palettes[i].m_Name == name)
return &m_Palettes[i];
return NULL;
}
/// <summary>
/// Gets a copy of the palette at a specified index with its hue adjusted by the specified amount.
/// </summary>
/// <param name="i">The index of the palette to read. A value of -1 indicates a random palette.</param>
/// <param name="hue">The hue adjustment to apply</param>
/// <param name="palette">The palette to store the output</param>
/// <returns>True if successful, else false.</returns>
bool GetHueAdjustedPalette(int i, T hue, Palette<T>& palette)
{
bool b = false;
Palette<T>* unadjustedPal = GetPalette(i);
if (unadjustedPal)
{
unadjustedPal->MakeHueAdjustedPalette(palette, hue);
b = true;
}
return b;
}
/// <summary>
/// Clear the palette list and reset the initialization state.
/// </summary>
void Clear()
{
m_Palettes.clear();
m_Init = false;
}
/// <summary>
/// Accessors.
/// </summary>
bool Init() { return m_Init; }
unsigned int Count() { return (unsigned int)m_Palettes.size(); }
private:
/// <summary>
/// Parses an Xml node for all palettes present and stores in the palette list.
/// Note that although the Xml color values are expected to be 0-255, they are converted and
/// stored as normalized colors, with values from 0-1.
/// </summary>
/// <param name="node">The parent note of all palettes in the Xml file.</param>
void ParsePalettes(xmlNode* node)
{
bool hexError = false;
char* val;
const char* loc = __FUNCTION__;
xmlAttrPtr attr;
while (node)
{
if (node->type == XML_ELEMENT_NODE && !Compare(node->name, "palette"))
{
attr = node->properties;
Palette<T> palette;
while (attr)
{
val = (char*)xmlGetProp(node, attr->name);
if (!Compare(attr->name, "data"))
{
int colorIndex = 0;
int r, g, b;
int colorCount = 0;
hexError = false;
do
{
int ret = sscanf_s((char*)&(val[colorIndex]),"00%2x%2x%2x", &r, &g, &b);
if (ret != 3)
{
m_ErrorReport.push_back(string(loc) + " : Problem reading hexadecimal color data " + string(&val[colorIndex]));
hexError = true;
break;
}
colorIndex += 8;
while (isspace((int)val[colorIndex]))
colorIndex++;
palette[colorCount].r = T(r) / T(255);//Store as normalized colors in the range of 0-1.
palette[colorCount].g = T(g) / T(255);
palette[colorCount].b = T(b) / T(255);
colorCount++;
} while (colorCount < COLORMAP_LENGTH);
}
else if (!Compare(attr->name, "number"))
{
palette.m_Index = atoi(val);
}
else if (!Compare(attr->name, "name"))
{
palette.m_Name = string(val);
}
xmlFree(val);
attr = attr->next;
}
if (!hexError)
{
m_Palettes.push_back(palette);
}
}
else
{
ParsePalettes(node->children);
}
node = node->next;
}
}
static bool m_Init;//Initialized to false in Ember.cpp, and will be set to true upon successful reading of an Xml palette file.
static vector<Palette<T>> m_Palettes;//The vector that stores the palettes.
};
}

217
Source/Ember/Point.h Normal file
View File

@ -0,0 +1,217 @@
#pragma once
#include "EmberDefines.h"
#include "Affine2D.h"
#include "Timing.h"
/// <summary>
/// Basic point and color structures used in iteration.
/// </summary>
namespace EmberNs
{
/// <summary>
/// The point used to store the result of each iteration, which is
/// a spatial coordinate, a color index/coordinate and a visibility value.
/// Note that a Y color coordinate is not used at the moment because
/// only 1D palettes are supported like the original. However, in the future
/// 2D palettes may be supported like Fractron does.
/// Template argument expected to be float or double.
/// </summary>
template <typename T>
class EMBER_API Point
{
public:
/// <summary>
/// Constructor to initialize spatial and color coordinates to zero, with full visibility.
/// </summary>
Point()
{
Init();
}
/// <summary>
/// Default copy constructor.
/// </summary>
/// <param name="point">The Point object to copy</param>
Point(const Point<T>& point)
{
Point<T>::operator=<T>(point);
}
/// <summary>
/// Copy constructor to copy a Point object of type U.
/// </summary>
/// <param name="point">The Point object to copy</param>
template <typename U>
Point(const Point<U>& point)
{
Point<T>::operator=<U>(point);
}
/// <summary>
/// Default assignment operator.
/// </summary>
/// <param name="point">The Point object to copy</param>
Point<T>& operator = (const Point<T>& point)
{
if (this != &point)
Point<T>::operator=<T>(point);
return *this;
}
/// <summary>
/// Assignment operator to assign a Point object of type U.
/// </summary>
/// <param name="point">The Point object to copy.</param>
/// <returns>Reference to updated self</returns>
template <typename U>
Point<T>& operator = (const Point<U>& point)
{
m_X = point.m_X;
m_Y = point.m_Y;
m_Z = point.m_Z;
m_ColorX = point.m_ColorX;
//m_ColorY = point.m_ColorY;
m_VizAdjusted = point.m_VizAdjusted;
return *this;
}
/// <summary>
/// Set spatial and color coordinates to zero, with full visibility.
/// </summary>
void Init()
{
m_X = 0;
m_Y = 0;
m_Z = 0;
m_ColorX = 0;
//m_ColorY = 0;
m_VizAdjusted = 1;
}
T m_X;
T m_Y;
T m_Z;
T m_ColorX;
//T m_ColorY;
T m_VizAdjusted;
};
/// <summary>
/// Comparer used for sorting the results of iteration by their spatial x coordinates.
/// </summary>
/// <param name="a">The first point to compare</param>
/// <param name="b">The second point to compare</param>
/// <returns>1 if the first point had an x coordinate less than the second point, else 0</returns>
template <typename T>
static int SortPointByX(const Point<T>& a, const Point<T>& b)
{
return a.m_X < b.m_X;
}
/// <summary>
/// Comparer used for sorting the results of iteration by their spatial y coordinates.
/// </summary>
/// <param name="a">The first point to compare</param>
/// <param name="b">The second point to compare</param>
/// <returns>1 if the first point had an y coordinate less than the second point, else 0</returns>
template <typename T>
static int SortPointByY(const Point<T>& a, const Point<T>& b)
{
return a.m_Y < b.m_Y;
}
/// <summary>
/// Thin override of a glm::vec4 which adds a couple of functions
/// specific to color handling.
/// </summary>
template <typename T>
class EMBER_API Color : public v4T
{
public:
/// <summary>
/// Constructor to set color values to zero, with full visibility.
/// </summary>
Color()
{
Reset();
}
/// <summary>
/// Default copy constructor.
/// </summary>
/// <param name="color">The Color object to copy</param>
Color(const Color<T>& color)
{
Color<T>::operator=<T>(color);
}
/// <summary>
/// Copy constructor to copy a Color object of type U.
/// </summary>
/// <param name="color">The Color object to copy</param>
template <typename U>
Color(const Color<U>& color)
{
Color<T>::operator=<U>(color);
}
/// <summary>
/// Default assignment operator.
/// </summary>
/// <param name="color">The Color object to copy</param>
Color<T>& operator = (const Color<T>& color)
{
if (this != &color)
Color<T>::operator=<T>(color);
return *this;
}
/// <summary>
/// Assignment operator to assign a Color object of type U.
/// </summary>
/// <param name="color">The Color object to copy.</param>
/// <returns>Reference to updated self</returns>
template <typename U>
Color<T>& operator = (const Color<U>& color)
{
v4T::operator=<U>(color);
return *this;
}
/// <summary>
/// Member-wise constructor.
/// </summary>
Color(T rr, T gg, T bb, T aa)
: v4T(rr, gg, bb, aa)
{
}
/// <summary>
/// Set color values and visibility to zero.
/// </summary>
inline void Clear()
{
r = 0;
g = 0;
b = 0;
a = 0;
}
/// <summary>
/// Set color values to zero, with full visibility.
/// </summary>
/// <param name="norm">If norm is true, the color fields are expected to have a range of 0-1, else 0-255</param>
inline void Reset(bool norm = true)
{
r = 0;
g = 0;
b = 0;
a = norm ? T(1) : T(255);
}
};
}

2222
Source/Ember/Renderer.cpp Normal file

File diff suppressed because it is too large Load Diff

412
Source/Ember/Renderer.h Normal file
View File

@ -0,0 +1,412 @@
#pragma once
#include "Ember.h"
#include "Iterator.h"
#include "Utils.h"
#include "SpatialFilter.h"
#include "DensityFilter.h"
#include "TemporalFilter.h"
#include "Interpolate.h"
#include "CarToRas.h"
#include "EmberToXml.h"
/// <summary>
/// Renderer, RenderCallback and EmberStats classes.
/// </summary>
namespace EmberNs
{
/// <summary>
/// Function pointers present a major restriction when dealing
/// with member functions, and that is they can only point to
/// static ones. So instead of a straight function pointer, use
/// a callback class with a single virtual callback
/// member function.
/// Template argument expected to be float or double.
/// </summary>
class EMBER_API RenderCallback
{
public:
/// <summary>
/// Virtual destructor to ensure anything declared in derived classes gets cleaned up.
/// </summary>
virtual ~RenderCallback() { }
/// <summary>
/// Empty progress function to be implemented in derived classes to take action on progress updates.
/// </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>Override should return 0 if an abort is requested, else 1 to continue rendering</returns>
virtual int ProgressFunc(Ember<float>& ember, void* foo, double fraction, int stage, double etaMs) { return 0; }
virtual int ProgressFunc(Ember<double>& ember, void* foo, double fraction, int stage, double etaMs) { return 0; }
};
/// <summary>
/// Render statistics for the number of iterations ran,
/// number of bad values calculated during iteration, and
/// the total time for the entire render from the start of
/// iteration to the end of final accumulation.
/// </summary>
class EMBER_API EmberStats
{
public:
/// <summary>
/// Constructor which sets all values to 0.
/// </summary>
EmberStats()
{
m_Iters = 0;
m_Badvals = 0;
m_RenderSeconds = 0;
}
unsigned __int64 m_Iters, m_Badvals;
double m_RenderSeconds;
};
/// <summary>
/// The types of available renderers.
/// Add more in the future as different rendering methods are experimented with.
/// Possible values might be: CPU+OpenGL, Particle, Inverse.
/// </summary>
enum eRendererType { CPU_RENDERER, OPENCL_RENDERER };
/// <summary>
/// A base class with virtual functions to allow both templating and polymorphism to work together.
/// Derived classes will implement all of these functions.
/// Note that functions which return a decimal number use the most precise type, double.
/// </summary>
class EMBER_API RendererBase : public EmberReport
{
public:
RendererBase() { }
virtual ~RendererBase() { }
virtual void SetEmber(Ember<float>& ember, eProcessAction action = FULL_RENDER) { }
virtual void SetEmber(vector<Ember<float>>& embers) { }
virtual void SetEmber(Ember<double>& ember, eProcessAction action = FULL_RENDER) { }
virtual void SetEmber(vector<Ember<double>>& embers) { }
virtual void Callback(RenderCallback* callback) { }
virtual bool CreateSpatialFilter(bool& newAlloc) { return false; }
virtual bool CreateTemporalFilter(bool& newAlloc) { return false; }
virtual void ComputeBounds() { }
virtual bool Ok() const { return false; }
virtual void Reset() { }
virtual void EnterRender() { }
virtual void LeaveRender() { }
virtual void EnterFinalAccum() { }
virtual void LeaveFinalAccum() { }
virtual void EnterResize() { }
virtual void LeaveResize() { }
virtual void Abort() { }
virtual bool Aborted() { return false; }
virtual bool InRender() { return false; }
virtual bool InFinalAccum() { return false; }
virtual unsigned int NumChannels() const { return 0; }
virtual void NumChannels(unsigned int numChannels) { }
virtual eRendererType RendererType() const { return CPU_RENDERER; }
virtual void ReclaimOnResize(bool reclaimOnResize) { }
virtual bool EarlyClip() const { return false; }
virtual void EarlyClip(bool earlyClip) { }
virtual void ThreadCount(unsigned int threads, const char* seedString = NULL) { }
virtual void Transparency(bool transparency) { }
virtual void InteractiveFilter(eInteractiveFilter filter) { }
virtual unsigned int FinalRasW() const { return 0; }
virtual unsigned int FinalRasH() const { return 0; }
virtual unsigned int SuperRasW() const { return 0; }
virtual unsigned int SuperRasH() const { return 0; }
virtual unsigned int FinalBufferSize() const { return 0; }
virtual unsigned int GutterWidth() const { return 0; }
virtual double ScaledQuality() const { return 0; }
virtual double LowerLeftX(bool gutter = true) const { return 0; }
virtual double LowerLeftY(bool gutter = true) const { return 0; }
virtual double UpperRightX(bool gutter = true) const { return 0; }
virtual double UpperRightY(bool gutter = true) const { return 0; }
virtual unsigned __int64 MemoryRequired(bool includeFinal) { return 0; }
virtual unsigned __int64 MemoryAvailable() { return 0; }
virtual bool PrepFinalAccumVector(vector<unsigned char>& pixels) { return false; }
virtual eProcessState ProcessState() const { return NONE; }
virtual eProcessAction ProcessAction() const { return NOTHING; }
virtual EmberStats Stats() const { EmberStats stats; return stats; }
virtual eRenderStatus Run(vector<unsigned char>& finalImage, double time = 0, unsigned int subBatchCountOverride = 0, bool forceOutput = false, size_t finalOffset = 0) { return RENDER_ERROR; }
virtual EmberImageComments ImageComments(unsigned int printEditDepth = 0, bool intPalette = false, bool hexPalette = true) { EmberImageComments comments; return comments; }
virtual DensityFilterBase* GetDensityFilter() { return NULL; }
};
/// <summary>
/// Renderer is the main class where all of the execution takes place.
/// It is intended that the program have one instance of it that it
/// keeps around for its duration. After a user sets up an ember, it's passed
/// in to be rendered.
/// This class derives from EmberReport, so the caller is able
/// to retrieve a text dump of error information if any errors occur.
/// The final image output vector is also passed in because the calling code has more
/// use for it than this class does.
/// Several functions are made virtual and have a default CPU-based implementation
/// that roughly matches what flam3 did. However they can be overriden in derived classes
/// to provide alternative rendering implementations, such as using the GPU.
/// Since this is a templated class, it's supposed to be entirely implemented in this .h file.
/// However, VC++ 2010 has very crippled support for lambdas, which Renderer makes use of.
/// If too many lambdas are used in a .h file, it will crash the compiler when another library
/// tries to link to it. To work around the bug, only declarations are here and all implementations
/// are in the .cpp file. It's unclear at what point it starts/stops working. But it seems that once
/// enough code is placed in the .h file, the compiler crashes. So for the sake of consistency, everything
/// is moved to the .cpp, even simple getters. One drawback however, is that the class must be
/// explicitly exported at the bottom of the file.
/// Also, despite explicitly doing this, the compiler throws a C4661 warning
/// for every single function in this class, saying it can't find the implementation. This warning
/// can be safely ignored.
/// Template argument T expected to be float or double.
/// Template argument bucketT was originally used to experiment with different types for the histogram, however
/// the only types that work are float and double, so it's useless and should always match what T is.
/// Mismatched types between T and bucketT are undefined.
/// </summary>
template <typename T, typename bucketT>
class EMBER_API Renderer : public RendererBase
{
public:
Renderer();
virtual ~Renderer();
virtual void ComputeBounds();
void ComputeCamera();
void ChangeVal(std::function<void (void)> func, eProcessAction action);
virtual void SetEmber(Ember<T>& ember, eProcessAction action = FULL_RENDER);
virtual void SetEmber(vector<Ember<T>>& embers);
void AddEmber(Ember<T>& ember);
bool CreateTemporalFilter(bool& newAlloc);
bool AssignIterator();
virtual bool PrepFinalAccumVector(vector<unsigned char>& pixels);
virtual eRenderStatus Run(vector<unsigned char>& finalImage, double time = 0, unsigned int subBatchCountOverride = 0, bool forceOutput = false, size_t finalOffset = 0);
virtual EmberImageComments ImageComments(unsigned int printEditDepth = 0, bool intPalette = false, bool hexPalette = true);
virtual unsigned __int64 MemoryRequired(bool includeFinal);
//Virtual functions to be overriden in derived renderers that use the GPU.
virtual unsigned __int64 MemoryAvailable();
virtual void Reset();
virtual bool Ok() const;
virtual bool CreateDEFilter(bool& newAlloc);
virtual bool CreateSpatialFilter(bool& newAlloc);
virtual unsigned int SubBatchSize() const;
virtual void SubBatchSize(unsigned int sbs);
virtual unsigned int NumChannels() const;
virtual void NumChannels(unsigned int numChannels);
virtual eRendererType RendererType() const;
virtual unsigned int ThreadCount() const;
virtual void ThreadCount(unsigned int threads, const char* seedString = NULL);
virtual void Callback(RenderCallback* callback);
protected:
//Virtual functions to be overriden in derived renderers that use the GPU, but not accessed outside.
virtual void MakeDmap(T colorScalar);
virtual bool Alloc();
virtual bool ResetBuckets(bool resetHist = true, bool resetAccum = true);
virtual eRenderStatus LogScaleDensityFilter();
virtual eRenderStatus GaussianDensityFilter();
virtual eRenderStatus AccumulatorToFinalImage(vector<unsigned char>& pixels, size_t finalOffset);
virtual eRenderStatus AccumulatorToFinalImage(unsigned char* pixels, size_t finalOffset);
virtual EmberStats Iterate(unsigned __int64 iterCount, unsigned int pass, unsigned int temporalSample);
public:
//Accessors for render properties.
vector<QTIsaac<ISAAC_SIZE, ISAAC_INT>> RandVec();
bool RandVec(vector<QTIsaac<ISAAC_SIZE, ISAAC_INT>>& randVec);
inline bool LockAccum() const;
void LockAccum(bool lockAccum);
virtual bool EarlyClip() const;
virtual void EarlyClip(bool earlyClip);
inline bool InsertPalette() const;
void InsertPalette(bool insertPalette);
inline bool ReclaimOnResize() const;
virtual void ReclaimOnResize(bool reclaimOnResize);
inline bool Transparency() const;
virtual void Transparency(bool transparency);
inline unsigned int BytesPerChannel() const;
void BytesPerChannel(unsigned int bytesPerChannel);
inline T PixelAspectRatio() const;
void PixelAspectRatio(T pixelAspectRatio);
inline eInteractiveFilter InteractiveFilter() const;
virtual void InteractiveFilter(eInteractiveFilter filter);
//Threading control.
virtual void EnterRender();
virtual void LeaveRender();
virtual void EnterFinalAccum();
virtual void LeaveFinalAccum();
virtual void EnterResize();
virtual void LeaveResize();
virtual void Abort();
virtual bool Aborted();
virtual bool InRender();
virtual bool InFinalAccum();
//Renderer properties, getters only.
virtual unsigned int SuperRasW() const;
virtual unsigned int SuperRasH() const;
inline unsigned int SuperSize() const;
virtual unsigned int FinalBufferSize() const;
inline unsigned int FinalRowSize() const;
inline unsigned int FinalDimensions() const;
inline unsigned int PixelSize() const;
virtual unsigned int GutterWidth() const;
inline unsigned int DensityFilterOffset() const;
virtual double ScaledQuality() const;
inline T Scale() const;
inline T PixelsPerUnitX() const;
inline T PixelsPerUnitY() const;
virtual double LowerLeftX(bool gutter = true) const;
virtual double LowerLeftY(bool gutter = true) const;
virtual double UpperRightX(bool gutter = true) const;
virtual double UpperRightY(bool gutter = true) const;
inline T K1() const;
inline T K2() const;
inline unsigned __int64 TotalIterCount() const;
inline unsigned __int64 ItersPerTemporalSample() const;
virtual eProcessState ProcessState() const;
virtual eProcessAction ProcessAction() const;
virtual EmberStats Stats() const;
inline const CarToRas<T>* CoordMap() const;
inline glm::detail::tvec4<bucketT, glm::defaultp>* HistBuckets();
inline glm::detail::tvec4<bucketT, glm::defaultp>* AccumulatorBuckets();
inline SpatialFilter<T>* GetSpatialFilter();
inline TemporalFilter<T>* GetTemporalFilter();
virtual DensityFilter<T>* GetDensityFilter();
//Ember wrappers, getters only.
inline bool XaosPresent();
virtual inline unsigned int FinalRasW() const;
virtual inline unsigned int FinalRasH() const;
inline unsigned int Supersample() const;
inline unsigned int Passes() const;
inline unsigned int TemporalSamples() const;
inline unsigned int PaletteIndex() const;
inline T Time() const;
inline T Quality() const;
inline T SpatialFilterRadius() const;
inline T PixelsPerUnit() const;
inline T Zoom() const;
inline T CenterX() const;
inline T CenterY() const;
inline T Rotate() const;
inline T Hue() const;
inline T Brightness() const;
inline T Contrast() const;
inline T Gamma() const;
inline T Vibrancy() const;
inline T GammaThresh() const;
inline T HighlightPower() const;
inline Color<T> Background() const;
inline const Xform<T>* Xforms() const;
inline Xform<T>* NonConstXforms();
inline unsigned int XformCount() const;
inline const Xform<T>* FinalXform() const;
inline Xform<T>* NonConstFinalXform();
inline bool UseFinalXform() const;
inline const Palette<T>* GetPalette() const;
inline ePaletteMode PaletteMode() const;
//Iterator wrappers.
const unsigned char* XformDistributions() const;
const unsigned int XformDistributionsSize() const;
Point<T>* Samples(unsigned int threadIndex) const;
void* m_ProgressParameter;
protected:
//Non-virtual functions that might be needed by a derived class.
void PrepFinalAccumVals(Color<T>& background, T& g, T& linRange, T& vibrancy);
private:
//Miscellaneous functions used only in this class.
void Accumulate(Point<T>* samples, unsigned int sampleCount, const Palette<bucketT>* palette);
inline void AddToAccum(const glm::detail::tvec4<bucketT, glm::defaultp>& bucket, int i, int ii, int j, int jj);
template <typename accumT> void GammaCorrection(glm::detail::tvec4<bucketT, glm::defaultp>& bucket, Color<T>& background, T g, T linRange, T vibrancy, bool doAlpha, bool scale, accumT* correctedChannels);
protected:
bool m_EarlyClip;
bool m_Transparency;
unsigned int m_SuperRasW;
unsigned int m_SuperRasH;
unsigned int m_SuperSize;
unsigned int m_GutterWidth;
unsigned int m_DensityFilterOffset;
unsigned int m_NumChannels;
unsigned int m_BytesPerChannel;
unsigned int m_SubBatchSize;
unsigned int m_ThreadsToUse;
T m_ScaledQuality;
T m_Scale;
T m_PixelsPerUnitX;
T m_PixelsPerUnitY;
T m_PixelAspectRatio;
T m_LowerLeftX;
T m_LowerLeftY;
T m_UpperRightX;
T m_UpperRightY;
T m_K1;
T m_K2;
T m_Vibrancy;//Accumulate these after each temporal sample.
T m_Gamma;
Color<T> m_Background;
Affine2D<T> m_RotMat;
volatile bool m_Abort;
bool m_LockAccum;
bool m_InRender;
bool m_InFinalAccum;
bool m_InsertPalette;
bool m_ReclaimOnResize;
unsigned int m_VibGamCount;
unsigned int m_LastPass;
unsigned int m_LastTemporalSample;
unsigned __int64 m_LastIter;
double m_LastIterPercent;
eProcessAction m_ProcessAction;
eProcessState m_ProcessState;
eInteractiveFilter m_InteractiveFilter;
EmberStats m_Stats;
Ember<T> m_Ember;
Ember<T> m_TempEmber;
Ember<T> m_LastEmber;
vector<Ember<T>> m_Embers;
CarToRas<T> m_CarToRas;
RenderCallback* m_Callback;
Iterator<T>* m_Iterator;
auto_ptr<StandardIterator<T>> m_StandardIterator;
auto_ptr<XaosIterator<T>> m_XaosIterator;
Palette<bucketT> m_Dmap;
vector<glm::detail::tvec4<bucketT, glm::defaultp>> m_HistBuckets;
vector<glm::detail::tvec4<bucketT, glm::defaultp>> m_AccumulatorBuckets;
auto_ptr<SpatialFilter<T>> m_SpatialFilter;
auto_ptr<TemporalFilter<T>> m_TemporalFilter;
auto_ptr<DensityFilter<T>> m_DensityFilter;
vector<vector<Point<T>>> m_Samples;
vector<unsigned __int64> m_SubBatch;
vector<unsigned __int64> m_BadVals;
vector<QTIsaac<ISAAC_SIZE, ISAAC_INT>> m_Rand;
tbb::task_group m_TaskGroup;
CriticalSection m_RenderingCs, m_AccumCs, m_FinalAccumCs, m_ResizeCs;
Timing m_RenderTimer, m_ProgressTimer;
EmberToXml<T> m_EmberToXml;
};
//This class had to be implemented in a cpp file because the compiler was breaking.
//So the explicit instantiation must be declared here rather than in Ember.cpp where
//all of the other classes are done.
template EMBER_API class Renderer<float, float>;
#ifdef DO_DOUBLE
template EMBER_API class Renderer<double, double>;
#endif
}

1394
Source/Ember/SheepTools.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,909 @@
#pragma once
#include "EmberDefines.h"
/// <summary>
/// SpatialFilter base, derived and factory classes.
/// </summary>
namespace EmberNs
{
/// <summary>
/// The types of spatial filters available.
/// </summary>
enum eSpatialFilterType
{
GAUSSIAN_SPATIAL_FILTER = 0,
HERMITE_SPATIAL_FILTER = 1,
BOX_SPATIAL_FILTER = 2,
TRIANGLE_SPATIAL_FILTER = 3,
BELL_SPATIAL_FILTER = 4,
BSPLINE_SPATIAL_FILTER = 5,
LANCZOS3_SPATIAL_FILTER = 6,
LANCZOS2_SPATIAL_FILTER = 7,
MITCHELL_SPATIAL_FILTER = 8,
BLACKMAN_SPATIAL_FILTER = 9,
CATROM_SPATIAL_FILTER = 10,
HAMMING_SPATIAL_FILTER = 11,
HANNING_SPATIAL_FILTER = 12,
QUADRATIC_SPATIAL_FILTER = 13
};
/// <summary>
/// Spatial filtering is done in the final accumulation stage to add some additional
/// bluring to smooth out noisy areas.
/// The bulk of the work is done in this base class Create() function.
/// Because it calls the virtual Filter() function, it cannot be automatically called in the constructor.
/// So the caller must manually call it after constructing the filter object.
/// Each derived class will implement an override of Filter() which
/// contains the specific filter calculation for the algorithm whose name the class matches.
/// Template argument expected to be float or double.
/// </summary>
template <typename T>
class EMBER_API SpatialFilter
{
public:
/// <summary>
/// Assign basic parameters for creating a spatial filter. The caller must still call Create().
/// </summary>
/// <param name="filterType">Type of filter to create</param>
/// <param name="support">Miscellaneous value</param>
/// <param name="filterRadius">The filter radius</param>
/// <param name="superSample">The supersample of the ember being rendered</param>
/// <param name="pixelAspectRatio">The pixel aspect ratio being used to render. Default: 1.</param>
SpatialFilter(eSpatialFilterType filterType, T support, T filterRadius, unsigned int superSample, T pixelAspectRatio = T(1.0))
{
m_FilterType = filterType;
m_Support = support;
m_FilterRadius = filterRadius;
m_Supersample = superSample;
m_PixelAspectRatio = pixelAspectRatio;
//Sadly, cannot call create here because it calls the Filter() virtual function and unlike C#, the vtables
//are not yet set up in C++ constructors. The code that instantiates this object must explicitly call Create().
}
/// <summary>
/// Copy constructor.
/// </summary>
/// <param name="filter">The SpatialFilter object to copy</param>
SpatialFilter(const SpatialFilter<T>& filter)
{
*this = filter;
}
/// <summary>
/// Virtual destructor so derived class destructors get called.
/// </summary>
virtual ~SpatialFilter()
{
}
/// <summary>
/// Assignment operator.
/// </summary>
/// <param name="filter">The SpatialFilter object to copy.</param>
/// <returns>Reference to updated self</returns>
SpatialFilter<T>& operator = (const SpatialFilter<T>& filter)
{
if (this != &filter)
{
m_FinalFilterWidth = filter.m_FinalFilterWidth;
m_Supersample = filter.m_Supersample;
m_Support = filter.m_Support;
m_FilterRadius = filter.m_FilterRadius;
m_PixelAspectRatio = filter.m_PixelAspectRatio;
m_FilterType = filter.m_FilterType;
m_Filter = filter.m_Filter;
}
return *this;
}
/// <summary>
/// Allocates and populates the filter buffer with virtual calls to derived Filter() functions.
/// The caller must manually call this after construction.
/// </summary>
void Create()
{
T fw = T(2.0) * m_Support * m_Supersample * m_FilterRadius / m_PixelAspectRatio;
T adjust, ii, jj;
int fwidth = ((int)fw) + 1;
int i, j;
//Make sure the filter kernel has same parity as oversample.
if ((fwidth ^ m_Supersample) & 1)
fwidth++;
//Calculate the coordinate scaling factor for the kernel values.
if (fw > 0.0)
adjust = m_Support * fwidth / fw;
else
adjust = T(1.0);
m_Filter.resize(fwidth * fwidth);
//Fill in the coefs.
for (i = 0; i < fwidth; i++)
{
for (j = 0; j < fwidth; j++)
{
//Calculate the function inputs for the kernel function.
ii = ((T(2.0) * i + T(1.0)) / T(fwidth) - T(1.0)) * adjust;
jj = ((T(2.0) * j + T(1.0)) / T(fwidth) - T(1.0)) * adjust;
//Adjust for aspect ratio.
jj /= m_PixelAspectRatio;
m_Filter[i + j * fwidth] = Filter(ii) * Filter(jj);//Call virtual Filter(), implemented in specific derived filter classes.
}
}
//Normalize, and return a bad value if the values were too small.
if (!Normalize())
m_FinalFilterWidth = -1;
else
m_FinalFilterWidth = fwidth;
}
/// <summary>
/// Return a string representation of this filter.
/// </summary>
/// <returns>The string representation of this filter</returns>
string ToString() const
{
unsigned int i;
stringstream ss;
ss
<< "Spatial Filter:" << endl
<< " Support: " << m_Support << endl
<< " Filter radius: " << m_FilterRadius << endl
<< " Supersample: " << m_Supersample << endl
<< "Pixel aspect ratio: " << m_PixelAspectRatio << endl
<< "Final filter width: " << m_FinalFilterWidth << endl
<< "Filter buffer size: " << m_Filter.size() << endl;
ss << "Filter: " << endl;
for (i = 0; i < m_Filter.size(); i++)
{
ss << "Filter[" << i << "]: " << m_Filter[i] << endl;
}
return ss.str();
}
/// <summary>
/// Accessors.
/// </summary>
void Apply() { }
inline int FinalFilterWidth() const { return m_FinalFilterWidth; }
inline unsigned int Supersample() const { return m_Supersample; }
inline unsigned int BufferSize() const { return (unsigned int)m_Filter.size(); }
inline unsigned int BufferSizeBytes() const { return BufferSize() * sizeof(T); }
inline T Support() const { return m_Support; }
inline T FilterRadius() const { return m_FilterRadius; }
inline T PixelAspectRatio() const { return m_PixelAspectRatio; }
inline eSpatialFilterType FilterType() const { return m_FilterType; }
inline T* Filter() { return m_Filter.data(); }
inline const T& operator[] (size_t index) const { return m_Filter[index]; }
virtual T Filter(T t) const = 0;
protected:
/// <summary>
/// Calculation function used in Lanczos filters.
/// </summary>
/// <param name="x">The x</param>
/// <returns>The calculated value</returns>
static T Sinc(T x)
{
x *= T(M_PI);
if (x != 0)
return sin(x) / x;
return 1.0;
}
private:
/// <summary>
/// Normalize all filter values.
/// </summary>
/// <returns>True if any value was non-zero, else false if all were zero.</returns>
bool Normalize()
{
size_t i;
T t = T(0.0);
for (i = 0; i < m_Filter.size(); i++)
t += m_Filter[i];
if (t == 0.0)
return false;
t = T(1.0) / t;
for (i = 0; i < m_Filter.size(); i++)
m_Filter[i] *= t;
return true;
}
int m_FinalFilterWidth;//The final width that the filter ends up being.
unsigned int m_Supersample;//The supersample value of the ember using this filter to render.
T m_Support;//Extra value.
T m_FilterRadius;//The requested filter radius.
T m_PixelAspectRatio;//The aspect ratio of the ember using this filter to render, usually 1.
eSpatialFilterType m_FilterType;//The type of filter this is.
vector<T> m_Filter;//The vector holding the calculated filter values.
};
/// <summary>
/// Derivation for Gaussian filter.
/// </summary>
template <typename T>
class EMBER_API GaussianFilter : public SpatialFilter<T>
{
public:
/// <summary>
/// Constructor which does nothing but pass values to the base class. The caller must still call Create().
/// Support = 1.5.
/// </summary>
/// <param name="filterRadius">The filter radius</param>
/// <param name="superSample">The supersample of the ember being rendered</param>
/// <param name="pixelAspectRatio">The pixel aspect ratio being used to render. Default: 1.</param>
GaussianFilter(T filterRadius, unsigned int superSample, T pixelAspectRatio = T(1.0))
: SpatialFilter<T>(GAUSSIAN_SPATIAL_FILTER, T(1.5), filterRadius, superSample, pixelAspectRatio) { }
/// <summary>
/// Apply Gaussian filter to t parameter and return.
/// </summary>
/// <param name="t">The value to apply the filter to</param>
/// <returns>The filtered value</returns>
virtual T Filter(T t) const
{
return exp(-2 * t * t) * sqrt(2 / T(M_PI));
}
};
/// <summary>
/// Derivation for Hermite filter.
/// </summary>
template <typename T>
class EMBER_API HermiteFilter : public SpatialFilter<T>
{
public:
/// <summary>
/// Constructor which does nothing but pass values to the base class. The caller must still call Create().
/// Support = 1.
/// </summary>
/// <param name="filterRadius">The filter radius</param>
/// <param name="superSample">The supersample of the ember being rendered</param>
/// <param name="pixelAspectRatio">The pixel aspect ratio being used to render. Default: 1.</param>
HermiteFilter(T filterRadius, unsigned int superSample, T pixelAspectRatio = T(1.0))
: SpatialFilter<T>(HERMITE_SPATIAL_FILTER, T(1.0), filterRadius, superSample, pixelAspectRatio) { }
/// <summary>
/// Apply Hermite filter to t parameter and return.
/// f(t) = 2|t|^3 - 3|t|^2 + 1, -1 <= t <= 1.
/// </summary>
/// <param name="t">The value to apply the filter to</param>
/// <returns>The filtered value</returns>
virtual T Filter(T t) const
{
if (t < 0)
t = -t;
if (t < 1)
return ((T(2.0) * t - T(3.0)) * t * t + T(1.0));
return 0;
}
};
/// <summary>
/// Derivation for Box filter.
/// </summary>
template <typename T>
class EMBER_API BoxFilter : public SpatialFilter<T>
{
public:
/// <summary>
/// Constructor which does nothing but pass values to the base class. The caller must still call Create().
/// Support = 0.5.
/// </summary>
/// <param name="filterRadius">The filter radius</param>
/// <param name="superSample">The supersample of the ember being rendered</param>
/// <param name="pixelAspectRatio">The pixel aspect ratio being used to render. Default: 1.</param>
BoxFilter(T filterRadius, unsigned int superSample, T pixelAspectRatio = T(1.0))
: SpatialFilter<T>(BOX_SPATIAL_FILTER, T(0.5), filterRadius, superSample, pixelAspectRatio) { }
/// <summary>
/// Apply Box filter to t parameter and return.
/// </summary>
/// <param name="t">The value to apply the filter to</param>
/// <returns>The filtered value</returns>
virtual T Filter(T t) const
{
if ((t > T(-0.5)) && (t <= T(0.5)))
return 1;
return 0;
}
};
/// <summary>
/// Derivation for Triangle filter.
/// </summary>
template <typename T>
class EMBER_API TriangleFilter : public SpatialFilter<T>
{
public:
/// <summary>
/// Constructor which does nothing but pass values to the base class. The caller must still call Create().
/// Support = 1.
/// </summary>
/// <param name="filterRadius">The filter radius</param>
/// <param name="superSample">The supersample of the ember being rendered</param>
/// <param name="pixelAspectRatio">The pixel aspect ratio being used to render. Default: 1.</param>
TriangleFilter(T filterRadius, unsigned int superSample, T pixelAspectRatio = T(1.0))
: SpatialFilter<T>(TRIANGLE_SPATIAL_FILTER, T(1.0), filterRadius, superSample, pixelAspectRatio) { }
/// <summary>
/// Apply Triangle filter to t parameter and return.
/// </summary>
/// <param name="t">The value to apply the filter to</param>
/// <returns>The filtered value</returns>
virtual T Filter(T t) const
{
if (t < 0)
t = -t;
if (t < 1)
return 1 - t;
return 0;
}
};
/// <summary>
/// Derivation for Bell filter.
/// </summary>
template <typename T>
class EMBER_API BellFilter : public SpatialFilter<T>
{
public:
/// <summary>
/// Constructor which does nothing but pass values to the base class. The caller must still call Create().
/// Support = 1.5.
/// </summary>
/// <param name="filterRadius">The filter radius</param>
/// <param name="superSample">The supersample of the ember being rendered</param>
/// <param name="pixelAspectRatio">The pixel aspect ratio being used to render. Default: 1.</param>
BellFilter(T filterRadius, unsigned int superSample, T pixelAspectRatio = T(1.0))
: SpatialFilter<T>(BELL_SPATIAL_FILTER, T(1.5), filterRadius, superSample, pixelAspectRatio) { }
/// <summary>
/// Apply Bell filter to t parameter and return.
/// </summary>
/// <param name="t">The value to apply the filter to</param>
/// <returns>The filtered value</returns>
virtual T Filter(T t) const
{
//box (*) box (*) box.
if (t < 0)
t = -t;
if (t < T(0.5))
return (T(0.75) - (t * t));
if (t < T(1.5))
{
t = (t - T(1.5));
return (T(0.5) * (t * t));
}
return 0;
}
};
/// <summary>
/// Derivation for B Spline filter.
/// </summary>
template <typename T>
class EMBER_API BsplineFilter : public SpatialFilter<T>
{
public:
/// <summary>
/// Constructor which does nothing but pass values to the base class. The caller must still call Create().
/// Support = 2.
/// </summary>
/// <param name="filterRadius">The filter radius</param>
/// <param name="superSample">The supersample of the ember being rendered</param>
/// <param name="pixelAspectRatio">The pixel aspect ratio being used to render. Default: 1.</param>
BsplineFilter(T filterRadius, unsigned int superSample, T pixelAspectRatio = T(1.0))
: SpatialFilter<T>(BSPLINE_SPATIAL_FILTER, T(2.0), filterRadius, superSample, pixelAspectRatio) { }
/// <summary>
/// Apply B Spline filter to t parameter and return.
/// </summary>
/// <param name="t">The value to apply the filter to</param>
/// <returns>The filtered value</returns>
virtual T Filter(T t) const
{
//box (*) box (*) box (*) box.
T tt;
if (t < 0)
t = -t;
if (t < 1)
{
tt = t * t;
return ((T(0.5) * tt * t) - tt + (T(2.0) / T(3.0)));
}
else if (t < 2)
{
t = 2 - t;
return ((T(1.0) / T(6.0)) * (t * t * t));
}
return 0;
}
};
/// <summary>
/// Derivation for Lanczos 3 filter.
/// </summary>
template <typename T>
class EMBER_API Lanczos3Filter : public SpatialFilter<T>
{
public:
/// <summary>
/// Constructor which does nothing but pass values to the base class. The caller must still call Create().
/// Support = 3.
/// </summary>
/// <param name="filterRadius">The filter radius</param>
/// <param name="superSample">The supersample of the ember being rendered</param>
/// <param name="pixelAspectRatio">The pixel aspect ratio being used to render. Default: 1.</param>
Lanczos3Filter(T filterRadius, unsigned int superSample, T pixelAspectRatio = T(1.0))
: SpatialFilter<T>(LANCZOS3_SPATIAL_FILTER, T(3.0), filterRadius, superSample, pixelAspectRatio) { }
/// <summary>
/// Apply Lanczos 3 filter to t parameter and return.
/// </summary>
/// <param name="t">The value to apply the filter to</param>
/// <returns>The filtered value</returns>
virtual T Filter(T t) const
{
if (t < 0)
t = -t;
if (t < 3)
return Sinc(t) * Sinc(t / 3);
return 0;
}
};
/// <summary>
/// Derivation for Lanczos 2 filter.
/// </summary>
template <typename T>
class EMBER_API Lanczos2Filter : public SpatialFilter<T>
{
public:
/// <summary>
/// Constructor which does nothing but pass values to the base class. The caller must still call Create().
/// Support = 2.
/// </summary>
/// <param name="filterRadius">The filter radius</param>
/// <param name="superSample">The supersample of the ember being rendered</param>
/// <param name="pixelAspectRatio">The pixel aspect ratio being used to render. Default: 1.</param>
Lanczos2Filter(T filterRadius, unsigned int superSample, T pixelAspectRatio = T(1.0))
: SpatialFilter<T>(LANCZOS2_SPATIAL_FILTER, T(2.0), filterRadius, superSample, pixelAspectRatio) { }
/// <summary>
/// Apply Lanczos 2 filter to t parameter and return.
/// </summary>
/// <param name="t">The value to apply the filter to</param>
/// <returns>The filtered value</returns>
virtual T Filter(T t) const
{
if (t < 0)
t = -t;
if (t < 2)
return Sinc(t) * Sinc(t / 2);
return 0;
}
};
/// <summary>
/// Derivation for Mitchell filter.
/// </summary>
template <typename T>
class EMBER_API MitchellFilter : public SpatialFilter<T>
{
public:
/// <summary>
/// Constructor which does nothing but pass values to the base class. The caller must still call Create().
/// Support = 2.
/// </summary>
/// <param name="filterRadius">The filter radius</param>
/// <param name="superSample">The supersample of the ember being rendered</param>
/// <param name="pixelAspectRatio">The pixel aspect ratio being used to render. Default: 1.</param>
MitchellFilter(T filterRadius, unsigned int superSample, T pixelAspectRatio = T(1.0))
: SpatialFilter<T>(MITCHELL_SPATIAL_FILTER, T(2.0), filterRadius, superSample, pixelAspectRatio) { }
/// <summary>
/// Apply Mitchell filter to t parameter and return.
/// </summary>
/// <param name="t">The value to apply the filter to</param>
/// <returns>The filtered value</returns>
virtual T Filter(T t) const
{
T tt = t * t;
const T b = T(1) / T(3);
const T c = T(1) / T(3);
if (t < 0)
t = -t;
if (t < 1)
{
t = (((T(12.0) - T(9.0) * b - T(6.0) * c) * (t * tt))
+ ((T(-18.0) + T(12.0) * b + T(6.0) * c) * tt)
+ (T(6.0) - 2 * b));
return t / 6;
}
else if (t < 2)
{
t = (((T(-1.0) * b - T(6.0) * c) * (t * tt))
+ ((T(6.0) * b + T(30.0) * c) * tt)
+ ((T(-12.0) * b - T(48.0) * c) * t)
+ (T(8.0) * b + 24 * c));
return t / 6;
}
return 0;
}
};
/// <summary>
/// Derivation for Blackman filter.
/// </summary>
template <typename T>
class EMBER_API BlackmanFilter : public SpatialFilter<T>
{
public:
/// <summary>
/// Constructor which does nothing but pass values to the base class. The caller must still call Create().
/// Support = 1.
/// </summary>
/// <param name="filterRadius">The filter radius</param>
/// <param name="superSample">The supersample of the ember being rendered</param>
/// <param name="pixelAspectRatio">The pixel aspect ratio being used to render. Default: 1.</param>
BlackmanFilter(T filterRadius, unsigned int superSample, T pixelAspectRatio = T(1.0))
: SpatialFilter<T>(BLACKMAN_SPATIAL_FILTER, T(1.0), filterRadius, superSample, pixelAspectRatio) { }
/// <summary>
/// Apply Blackman filter to t parameter and return.
/// </summary>
/// <param name="t">The value to apply the filter to</param>
/// <returns>The filtered value</returns>
virtual T Filter(T t) const
{
return (T(0.42) + T(0.5) * cos(T(M_PI) * t) + T(0.08) * cos(2 * T(M_PI) * t));
}
};
/// <summary>
/// Derivation for Catmull-Rom filter.
/// </summary>
template <typename T>
class EMBER_API CatromFilter : public SpatialFilter<T>
{
public:
/// <summary>
/// Constructor which does nothing but pass values to the base class. The caller must still call Create().
/// Support = 2.
/// </summary>
/// <param name="filterRadius">The filter radius</param>
/// <param name="superSample">The supersample of the ember being rendered</param>
/// <param name="pixelAspectRatio">The pixel aspect ratio being used to render. Default: 1.</param>
CatromFilter(T filterRadius, unsigned int superSample, T pixelAspectRatio = T(1.0))
: SpatialFilter<T>(CATROM_SPATIAL_FILTER, T(2.0), filterRadius, superSample, pixelAspectRatio) { }
/// <summary>
/// Apply Catmull-Rom filter to t parameter and return.
/// </summary>
/// <param name="t">The value to apply the filter to</param>
/// <returns>The filtered value</returns>
virtual T Filter(T t) const
{
if (t < 0)
return 0;
if (t < -1)
return T(0.5) * (4 + t * (8 + t * (5 + t)));
if (t < 0)
return T(0.5) * (2 + t * t * (-5 - 3 * t));
if (t < 1)
return T(0.5) * (2 + t * t * (-5 + 3 * t));
if (t < 2)
return T(0.5) * (4 + t * (-8 + t * (5 - t)));
return 0;
}
};
/// <summary>
/// Derivation for Hamming filter.
/// </summary>
template <typename T>
class EMBER_API HammingFilter : public SpatialFilter<T>
{
public:
/// <summary>
/// Constructor which does nothing but pass values to the base class. The caller must still call Create().
/// Support = 1.
/// </summary>
/// <param name="filterRadius">The filter radius</param>
/// <param name="superSample">The supersample of the ember being rendered</param>
/// <param name="pixelAspectRatio">The pixel aspect ratio being used to render. Default: 1.</param>
HammingFilter(T filterRadius, unsigned int superSample, T pixelAspectRatio = T(1.0))
: SpatialFilter<T>(HAMMING_SPATIAL_FILTER, T(1.0), filterRadius, superSample, pixelAspectRatio) { }
/// <summary>
/// Apply Hamming filter to t parameter and return.
/// </summary>
/// <param name="t">The value to apply the filter to</param>
/// <returns>The filtered value</returns>
virtual T Filter(T t) const
{
return T(0.54) + T(0.46) * cos(T(M_PI) * t);
}
};
/// <summary>
/// Derivation for Hanning filter.
/// </summary>
template <typename T>
class EMBER_API HanningFilter : public SpatialFilter<T>
{
public:
/// <summary>
/// Constructor which does nothing but pass values to the base class. The caller must still call Create().
/// Support = 1.
/// </summary>
/// <param name="filterRadius">The filter radius</param>
/// <param name="superSample">The supersample of the ember being rendered</param>
/// <param name="pixelAspectRatio">The pixel aspect ratio being used to render. Default: 1.</param>
HanningFilter(T filterRadius, unsigned int superSample, T pixelAspectRatio = T(1.0))
: SpatialFilter<T>(HANNING_SPATIAL_FILTER, T(1.0), filterRadius, superSample, pixelAspectRatio) { }
/// <summary>
/// Apply Hanning filter to t parameter and return.
/// </summary>
/// <param name="t">The value to apply the filter to</param>
/// <returns>The filtered value</returns>
virtual T Filter(T t) const
{
return T(0.5) + T(0.5) * cos(T(M_PI) * t);
}
};
/// <summary>
/// Derivation for Quadratic filter.
/// </summary>
template <typename T>
class EMBER_API QuadraticFilter : public SpatialFilter<T>
{
public:
/// <summary>
/// Constructor which does nothing but pass values to the base class. The caller must still call Create().
/// Support = 1.5.
/// </summary>
/// <param name="filterRadius">The filter radius</param>
/// <param name="superSample">The supersample of the ember being rendered</param>
/// <param name="pixelAspectRatio">The pixel aspect ratio being used to render. Default: 1.</param>
QuadraticFilter(T filterRadius, unsigned int superSample, T pixelAspectRatio = T(1.0))
: SpatialFilter<T>(QUADRATIC_SPATIAL_FILTER, T(1.5), filterRadius, superSample, pixelAspectRatio) { }
/// <summary>
/// Apply Quadratic filter to t parameter and return.
/// </summary>
/// <param name="t">The value to apply the filter to</param>
/// <returns>The filtered value</returns>
virtual T Filter(T t) const
{
if (t < -1.5)
return 0.0;
if (t < -0.5)
return T(0.5) * (t + T(1.5)) * (t + T(1.5));
if (t < 0.5)
return T(0.75) - (t * t);
if (t < 1.5)
return T(0.5) * (t - T(1.5)) * (t - T(1.5));
return 0;
}
};
/// <summary>
/// Convenience class to assist in converting between filter names and the filter objects themselves.
/// </summary>
template <typename T>
class EMBER_API SpatialFilterCreator
{
public:
/// <summary>
/// Creates the specified filter type based on the filterType enum parameter.
/// </summary>
/// <param name="filterType">Type of the filter</param>
/// <param name="filterRadius">The filter radius</param>
/// <param name="superSample">The supersample value of the ember using this filter to render</param>
/// <param name="pixelAspectRatio">The aspect ratio of the ember using this filter to render. Default: 1.</param>
/// <returns>A pointer to the newly created filter object</returns>
static SpatialFilter<T>* Create(eSpatialFilterType filterType, T filterRadius, unsigned int superSample, T pixelAspectRatio = 1)
{
SpatialFilter<T>* filter = NULL;
if (filterType == GAUSSIAN_SPATIAL_FILTER)
filter = new GaussianFilter<T>(filterRadius, superSample, pixelAspectRatio);
else if (filterType == HERMITE_SPATIAL_FILTER)
filter = new HermiteFilter<T>(filterRadius, superSample, pixelAspectRatio);
else if (filterType == BOX_SPATIAL_FILTER)
filter = new BoxFilter<T>(filterRadius, superSample, pixelAspectRatio);
else if (filterType == TRIANGLE_SPATIAL_FILTER)
filter = new TriangleFilter<T>(filterRadius, superSample, pixelAspectRatio);
else if (filterType == BELL_SPATIAL_FILTER)
filter = new BellFilter<T>(filterRadius, superSample, pixelAspectRatio);
else if (filterType == BSPLINE_SPATIAL_FILTER)
filter = new BsplineFilter<T>(filterRadius, superSample, pixelAspectRatio);
else if (filterType == LANCZOS3_SPATIAL_FILTER)
filter = new Lanczos3Filter<T>(filterRadius, superSample, pixelAspectRatio);
else if (filterType == LANCZOS2_SPATIAL_FILTER)
filter = new Lanczos2Filter<T>(filterRadius, superSample, pixelAspectRatio);
else if (filterType == MITCHELL_SPATIAL_FILTER)
filter = new MitchellFilter<T>(filterRadius, superSample, pixelAspectRatio);
else if (filterType == BLACKMAN_SPATIAL_FILTER)
filter = new BlackmanFilter<T>(filterRadius, superSample, pixelAspectRatio);
else if (filterType == CATROM_SPATIAL_FILTER)
filter = new CatromFilter<T>(filterRadius, superSample, pixelAspectRatio);
else if (filterType == HAMMING_SPATIAL_FILTER)
filter = new HammingFilter<T>(filterRadius, superSample, pixelAspectRatio);
else if (filterType == HANNING_SPATIAL_FILTER)
filter = new HanningFilter<T>(filterRadius, superSample, pixelAspectRatio);
else if (filterType == QUADRATIC_SPATIAL_FILTER)
filter = new QuadraticFilter<T>(filterRadius, superSample, pixelAspectRatio);
else
filter = new GaussianFilter<T>(filterRadius, superSample, pixelAspectRatio);
if (filter)
filter->Create();
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(14);
v.push_back("Gaussian");
v.push_back("Hermite");
v.push_back("Box");
v.push_back("Triangle");
v.push_back("Bell");
v.push_back("Bspline");
v.push_back("Lanczos3");
v.push_back("Lanczos2");
v.push_back("Mitchell");
v.push_back("Blackman");
v.push_back("Catrom");
v.push_back("Hamming");
v.push_back("Hanning");
v.push_back("Quadratic");
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 eSpatialFilterType FromString(string filterType)
{
if (!_stricmp(filterType.c_str(), "Gaussian"))
return GAUSSIAN_SPATIAL_FILTER;
else if (!_stricmp(filterType.c_str(), "Hermite"))
return HERMITE_SPATIAL_FILTER;
else if (!_stricmp(filterType.c_str(), "Box"))
return BOX_SPATIAL_FILTER;
else if (!_stricmp(filterType.c_str(), "Triangle"))
return TRIANGLE_SPATIAL_FILTER;
else if (!_stricmp(filterType.c_str(), "Bell"))
return BELL_SPATIAL_FILTER;
else if (!_stricmp(filterType.c_str(), "Bspline"))
return BSPLINE_SPATIAL_FILTER;
else if (!_stricmp(filterType.c_str(), "Lanczos3"))
return LANCZOS3_SPATIAL_FILTER;
else if (!_stricmp(filterType.c_str(), "Lanczos2"))
return LANCZOS2_SPATIAL_FILTER;
else if (!_stricmp(filterType.c_str(), "Mitchell"))
return MITCHELL_SPATIAL_FILTER;
else if (!_stricmp(filterType.c_str(), "Blackman"))
return BLACKMAN_SPATIAL_FILTER;
else if (!_stricmp(filterType.c_str(), "Catrom"))
return CATROM_SPATIAL_FILTER;
else if (!_stricmp(filterType.c_str(), "Hamming"))
return HAMMING_SPATIAL_FILTER;
else if (!_stricmp(filterType.c_str(), "Hanning"))
return HANNING_SPATIAL_FILTER;
else if (!_stricmp(filterType.c_str(), "Quadratic"))
return QUADRATIC_SPATIAL_FILTER;
else
return GAUSSIAN_SPATIAL_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(eSpatialFilterType filterType)
{
string filter;
if (filterType == GAUSSIAN_SPATIAL_FILTER)
filter = "Gaussian";
else if (filterType == HERMITE_SPATIAL_FILTER)
filter = "Hermite";
else if (filterType == BOX_SPATIAL_FILTER)
filter = "Box";
else if (filterType == TRIANGLE_SPATIAL_FILTER)
filter = "Triangle";
else if (filterType == BELL_SPATIAL_FILTER)
filter = "Bell";
else if (filterType == BSPLINE_SPATIAL_FILTER)
filter = "Bspline";
else if (filterType == LANCZOS3_SPATIAL_FILTER)
filter = "Lanczos3";
else if (filterType == LANCZOS2_SPATIAL_FILTER)
filter = "Lanczos2";
else if (filterType == MITCHELL_SPATIAL_FILTER)
filter = "Mitchell";
else if (filterType == BLACKMAN_SPATIAL_FILTER)
filter = "Blackman";
else if (filterType == CATROM_SPATIAL_FILTER)
filter = "Catrom";
else if (filterType == HAMMING_SPATIAL_FILTER)
filter = "Hamming";
else if (filterType == HANNING_SPATIAL_FILTER)
filter = "Hanning";
else if (filterType == QUADRATIC_SPATIAL_FILTER)
filter = "Quadratic";
else
filter = "Gaussian";
return filter;
}
};
}

View File

@ -0,0 +1,347 @@
#pragma once
#include "EmberDefines.h"
/// <summary>
/// TemporalFilter base, derived and factory classes.
/// </summary>
namespace EmberNs
{
/// <summary>
/// The types of temporal filters available.
/// </summary>
enum eTemporalFilterType
{
BOX_TEMPORAL_FILTER = 0,
GAUSSIAN_TEMPORAL_FILTER = 1,
EXP_TEMPORAL_FILTER = 2
};
/// <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="passes">The number of passes used in the ember being rendered</param>
/// <param name="temporalSamples">The number of temporal samples in the ember being rendered</param>
/// <param name="filterWidth">The width of the filter.</param>
TemporalFilter(eTemporalFilterType filterType, unsigned int passes, unsigned int temporalSamples, T filterWidth)
{
unsigned int i, steps = passes * temporalSamples;
m_Deltas.resize(steps);
m_Filter.resize(steps);
m_FilterType = filterType;
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] = (T(i) / T(steps - 1) - 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_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
{
unsigned int i;
stringstream ss;
ss << "Temporal Filter:" << endl
<< " Size: " << Size() << endl
<< " Type: " << TemporalFilterCreator<T>::ToString(m_FilterType) << endl
<< " Sum Filt: " << SumFilt() << endl;
ss << "Deltas: " << endl;
for (i = 0; i < m_Deltas.size(); i++)
{
ss << "Deltas[" << i << "]: " << m_Deltas[i] << endl;
}
ss << "Filter: " << endl;
for (i = 0; i < m_Filter.size(); i++)
{
ss << "Filter[" << i << "]: " << m_Filter[i] << endl;
}
return ss.str();
}
/// <summary>
/// Accessors.
/// </summary>
size_t Size() const { return m_Filter.size(); }
T SumFilt() const { return m_SumFilt; }
T* Deltas() { return &m_Deltas[0]; }
T* Filter() { return &m_Filter[0]; }
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 (unsigned int i = 0; i < Size(); i++)
{
m_Filter[i] /= maxFilt;
m_SumFilt += m_Filter[i];
}
m_SumFilt /= Size();
}
T m_SumFilt;//The sum of all filter values.
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>
{
public:
/// <summary>
/// Constructor to create an Exp filter.
/// </summary>
/// <param name="passes">The number of passes used in the ember being rendered</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.</param>
ExpTemporalFilter(unsigned int passes, unsigned int temporalSamples, T filterWidth, T filterExp)
: TemporalFilter<T>(BOX_TEMPORAL_FILTER, passes, temporalSamples, filterWidth)
{
if (Size() > 1)
{
T slpx, maxFilt = 0;
for (unsigned int i = 0; i < Size(); i++)
{
if (filterExp >= 0)
slpx = (T(i) + 1) / Size();
else
slpx = T(Size() - i) / Size();
//Scale the color based on these values.
m_Filter[i] = 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>
{
public:
/// <summary>
/// Constructor to create a Gaussian filter.
/// </summary>
/// <param name="passes">The number of passes used in the ember being rendered</param>
/// <param name="temporalSamples">The number of temporal samples in the ember being rendered</param>
/// <param name="filterWidth">The width of the filter.</param>
GaussianTemporalFilter(unsigned int passes, unsigned int temporalSamples, T filterWidth)
: TemporalFilter<T>(GAUSSIAN_TEMPORAL_FILTER, passes, temporalSamples, filterWidth)
{
if (Size() > 1)
{
T maxFilt = 0, halfSteps = T(Size()) / T(2);
GaussianFilter<T> gaussian(1, 1);//Just pass dummy values, they are unused in this case.
for (unsigned int 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>
{
public:
/// <summary>
/// Constructor to create a Box filter.
/// </summary>
/// <param name="passes">The number of passes used in the ember being rendered</param>
/// <param name="temporalSamples">The number of temporal samples in the ember being rendered</param>
/// <param name="filterWidth">The width of the filter.</param>
BoxTemporalFilter(unsigned int passes, unsigned int temporalSamples, T filterWidth)
: TemporalFilter<T>(BOX_TEMPORAL_FILTER, passes, temporalSamples, filterWidth)
{
if (Size() > 1)
{
for (unsigned int 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="passes">The number of passes used in the ember being rendered</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 ignored.</param>
/// <returns>A pointer to the newly created filter object</returns>
static TemporalFilter<T>* Create(eTemporalFilterType filterType, unsigned int passes, unsigned int temporalSamples, T filterWidth, T filterExp = 1)
{
TemporalFilter<T>* filter = NULL;
if (filterType == BOX_TEMPORAL_FILTER)
filter = new BoxTemporalFilter<T>(passes, temporalSamples, filterWidth);
else if (filterType == GAUSSIAN_TEMPORAL_FILTER)
filter = new GaussianTemporalFilter<T>(passes, temporalSamples, filterWidth);
else if (filterType == EXP_TEMPORAL_FILTER)
filter = new ExpTemporalFilter<T>(passes, temporalSamples, filterWidth, filterExp);
else
filter = new BoxTemporalFilter<T>(passes, temporalSamples, filterWidth);//Default to box if bad enum passed in.
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(string filterType)
{
if (!_stricmp(filterType.c_str(), "box"))
return BOX_TEMPORAL_FILTER;
else if (!_stricmp(filterType.c_str(), "gaussian"))
return GAUSSIAN_TEMPORAL_FILTER;
else if (!_stricmp(filterType.c_str(), "exp"))
return EXP_TEMPORAL_FILTER;
else
return 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;
if (filterType == BOX_TEMPORAL_FILTER)
filter = "Box";
else if (filterType == GAUSSIAN_TEMPORAL_FILTER)
filter = "Gaussian";
else if (filterType == EXP_TEMPORAL_FILTER)
filter = "Exp";
else
filter = "Box";
return filter;
}
};
}

7
Source/Ember/Timing.cpp Normal file
View File

@ -0,0 +1,7 @@
#include "stdafx.h"
#include "Timing.h"
namespace Flam3
{
}

231
Source/Ember/Timing.h Normal file
View File

@ -0,0 +1,231 @@
#pragma once
#include "EmberDefines.h"
/// <summary>
/// Timing and CriticalSection classes.
/// </summary>
namespace EmberNs
{
/// <summary>
/// Since the algorithm is so computationally intensive, timing and benchmarking are an integral portion
/// of both the development process and the execution results. This class provides an easy way to time
/// things by simply calling its Tic() and Toc() member functions. It also assists with formatting the
/// elapsed time as a string.
/// </summary>
class EMBER_API Timing
{
public:
/// <summary>
/// Constructor that takes an optional precision argument which specifies how many digits after the decimal place should be printed for seconds.
/// As a convenience, the Tic() function is called automatically.
/// </summary>
/// <param name="precision">The precision of the seconds field of the elapsed time. Default: 2.</param>
Timing(int precision = 2)
{
m_Precision = precision;
Init();
Tic();
}
/// <summary>
/// Set the begin time.
/// </summary>
/// <returns>The quad part of the begin time cast to a double</returns>
double Tic()
{
QueryPerformanceCounter(&m_BeginTime);
return BeginTime();
}
/// <summary>
/// Set the end time and optionally output a string showing the elapsed time.
/// </summary>
/// <param name="str">The string to output. Default: NULL.</param>
/// <param name="fullString">If true, output the string verbatim, else output the text " processing time: " in between str and the formatted time.</param>
/// <returns>The elapsed time in milliseconds as a double</returns>
double Toc(const char* str = NULL, bool fullString = false)
{
QueryPerformanceCounter(&m_EndTime);
double ms = ElapsedTime();
if (str != NULL)
{
cout << string(str) << (fullString ? "" : " processing time: ") << Format(ms) << endl;
}
return ms;
}
/// <summary>
/// Return the quad part of the begin time as a double.
/// </summary>
/// <returns></returns>
double BeginTime() { return (double)m_BeginTime.QuadPart; }
/// <summary>
/// Return the quad part of the end time as a double.
/// </summary>
/// <returns></returns>
double EndTime() { return (double)m_EndTime.QuadPart; }
/// <summary>
/// Return the elapsed time in milliseconds.
/// </summary>
/// <returns>The elapsed time in milliseconds as a double</returns>
double ElapsedTime() { return double(m_EndTime.QuadPart - m_BeginTime.QuadPart) * 1000.0 / double(m_Freq.QuadPart); }
/// <summary>
/// Formats a specified milliseconds value as a string.
/// This uses some intelligence to determine what to return depending on how much time has elapsed.
/// Days, hours and minutes are only included if 1 or more of them has elapsed. Seconds are always
/// included as a decimal value with the precision the user specified in the constructor.
/// </summary>
/// <param name="ms">The ms</param>
/// <returns>The formatted string</returns>
string Format(double ms)
{
stringstream ss;
double x = ms / 1000;
double secs = fmod(x, 60);
x /= 60;
double mins = fmod(x, 60);
x /= 60;
double hours = fmod(x, 24);
x /= 24;
double days = x;
if (days >= 1)
ss << (int)days << "d ";
if (hours >= 1)
ss << (int)hours << "h ";
if (mins >= 1)
ss << (int)mins << "m ";
ss << std::fixed << std::setprecision(m_Precision) << secs << "s";
return ss.str();
}
/// <summary>
/// Return the frequency of the clock as a double.
/// </summary>
/// <returns></returns>
static double Freq()
{
Init();
return (double)m_Freq.QuadPart;
}
/// <summary>
/// Return the number of cores in the system.
/// </summary>
/// <returns>The number of cores in the system</returns>
static int ProcessorCount()
{
Init();
return m_ProcessorCount;
}
private:
/// <summary>
/// Query and store the performance info of the system.
/// Since it will never change it only needs to be queried once.
/// This is achieved by keeping static state and performance variables.
/// </summary>
static void Init()
{
if (!m_TimingInit)
{
SYSTEM_INFO sysinfo;
QueryPerformanceFrequency(&m_Freq);
GetSystemInfo(&sysinfo);
m_ProcessorCount = sysinfo.dwNumberOfProcessors;
m_TimingInit = true;
}
}
int m_Precision;//How many digits after the decimal place to print for seconds.
LARGE_INTEGER m_BeginTime;//The start of the timing, set with Tic().
LARGE_INTEGER m_EndTime;//The end of the timing, set with Toc().
static bool m_TimingInit;//Whether the performance info has bee queried.
static int m_ProcessorCount;//The number of cores on the system, set in Init().
static LARGE_INTEGER m_Freq;//The clock frequency, set in Init().
};
/// <summary>
/// Cross platform critical section class which can be used for thread locking.
/// </summary>
class EMBER_API CriticalSection
{
#ifdef _WIN32
public:
/// <summary>
/// Constructor which initialized the underlying CRITICAL_SECTION object.
/// </summary>
CriticalSection() { InitializeCriticalSection(&m_CriticalSection); }
/// <summary>
/// Constructor which initialized the underlying CRITICAL_SECTION object
/// with the specified spin count value.
/// </summary>
/// <param name="spinCount">The spin count.</param>
CriticalSection(DWORD spinCount) { InitializeCriticalSectionAndSpinCount(&m_CriticalSection, spinCount); }
/// <summary>
/// Deletes the underlying CRITICAL_SECTION object.
/// </summary>
~CriticalSection() { DeleteCriticalSection(&m_CriticalSection); }
/// <summary>
/// Lock the critical section.
/// </summary>
void Enter() { EnterCriticalSection(&m_CriticalSection); }
/// <summary>
/// Unlock the critical section.
/// </summary>
void Leave() { LeaveCriticalSection(&m_CriticalSection); }
private:
CRITICAL_SECTION m_CriticalSection;//The Windows specific critical section object.
#else
/// <summary>
/// Constructor which initialized the underlying pthread_mutex_t object.
/// </summary>
CriticalSection()
{
pthread_mutexattr_t attr;
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
pthread_mutex_init(&m_CriticalSection, &attr);
pthread_mutexattr_destroy(&attr);
}
/// <summary>
/// Deletes the underlying pthread_mutex_t object.
/// </summary>
~CriticalSection() { pthread_mutex_destroy(&m_CriticalSection); }
/// <summary>
/// Lock the critical section.
/// </summary>
void Enter() { pthread_mutex_lock(&m_CriticalSection); }
/// <summary>
/// Unlock the critical section.
/// </summary>
void Leave() { pthread_mutex_unlock(&m_CriticalSection); }
private:
pthread_mutex_t m_CriticalSection;//The *nix/pthread specific critical section object.
#endif
};
}

887
Source/Ember/Utils.h Normal file
View File

@ -0,0 +1,887 @@
#pragma once
#include "Isaac.h"
/// <summary>
/// Global utility classes and functions that don't really fit anywhere else, but are
/// too small to justify being in their own file.
/// </summary>
namespace EmberNs
{
/// <summary>
/// After a run completes, information about what was run can be saved as strings to the comments
/// section of a jpg or png file. This class is just a container for those values.
/// </summary>
class EMBER_API EmberImageComments
{
public:
/// <summary>
/// Set all values to the empty string.
/// </summary>
void Clear()
{
m_Genome = "";
m_Badvals = "";
m_NumIters = "";
m_Runtime = "";
}
string m_Genome;
string m_Badvals;
string m_NumIters;
string m_Runtime;
};
/// <summary>
/// Since running is an incredibly complex process with multiple points of possible failure,
/// it's important that as much information as possible is captured if something goes wrong.
/// Classes wishing to capture this failure information will derive from this class and populate
/// the vector of strings with any useful error information. Note that a small complication can occur
/// when a class derives from this class, yet also has one or more members which do too. In that case, they should
/// override the methods to aggregate the error information from themselves, as well as their members.
/// </summary>
class EMBER_API EmberReport
{
public:
/// <summary>
/// Write the entire error report as a single string to the console.
/// Derived classes with members that also derive from EmberReport should override this to capture
/// their error information as well as that of their members.
/// </summary>
virtual void DumpErrorReport() { cout << ErrorReportString(); }
/// <summary>
/// Clear the error report string vector.
/// Derived classes with members that also derive from EmberReport should override this to clear
/// their error information as well as that of their members.
/// </summary>
virtual void ClearErrorReport() { m_ErrorReport.clear(); }
/// <summary>
/// Return the entire error report as a single string.
/// Derived classes with members that also derive from EmberReport should override this to capture
/// their error information as well as that of their members.
/// </summary>
/// <returns>The entire error report as a single string. Empty if no errors.</returns>
virtual string ErrorReportString() { return StaticErrorReportString(m_ErrorReport); }
/// <summary>
/// Return the entire error report as a vector of strings.
/// Derived classes with members that also derive from EmberReport should override this to capture
/// their error information as well as that of their members.
/// </summary>
/// <returns>The entire error report as a vector of strings. Empty if no errors.</returns>
virtual vector<string> ErrorReport() { return m_ErrorReport; }
/// <summary>
/// Add string to report.
/// </summary>
/// <param name="s">The string to add</param>
virtual void AddToReport(string s) { m_ErrorReport.push_back(s); }
/// <summary>
/// Add a vector of strings to report.
/// </summary>
/// <param name="vec">The vector of strings to add</param>
virtual void AddToReport(vector<string>& vec) { m_ErrorReport.insert(m_ErrorReport.end(), vec.begin(), vec.end()); }
/// <summary>
/// Static function to dump a vector of strings passed in.
/// </summary>
/// <param name="errorReport">The vector of strings to dump</param>
static void StaticDumpErrorReport(vector<string>& errorReport) { cout << StaticErrorReportString(errorReport); }
/// <summary>
/// Static function to return the entire error report passed in as a single string.
/// </summary>
/// <param name="errorReport">The vector of strings to concatenate</param>
/// <returns>A string containing all strings in the vector passed in separated by newlines</returns>
static string StaticErrorReportString(vector<string>& errorReport)
{
stringstream ss;
std::for_each(errorReport.begin() , errorReport.end() , [&](string s) { ss << s << endl; });
return ss.str();
}
protected:
vector<string> m_ErrorReport;
};
/// <summary>
/// Open a file in binary mode and read its entire contents into a vector of unsigned chars. Optionally null terminate.
/// </summary>
/// <param name="filename">The full path to the file to read</param>
/// <param name="buf">The vector which will be populated with the file's contents</param>
/// <param name="nullTerminate">Whether to append a NULL character as the last element of the vector. Needed when reading text files. Default: true.</param>
/// <returns>True if successfully read and populated, else false</returns>
static bool ReadFile(const char* filename, string& buf, bool nullTerminate = true)
{
bool b = false;
FILE* f;
try
{
fopen_s(&f, filename, "rb");//Open in binary mode.
if (f != NULL)
{
struct _stat statBuf;
int statResult = _fstat(f->_file, &statBuf);//Get data associated with file.
if (statResult == 0)//Check if statistics are valid.
{
buf.resize(statBuf.st_size + (nullTerminate ? 1 : 0));//Allocate vector to be the size of the entire file, with an optional additional character for NULL.
if (buf.size() == statBuf.st_size + 1)//Ensure allocation succeeded.
{
size_t bytesRead = fread(&buf[0], 1, statBuf.st_size, f);//Read the entire file at once.
if (bytesRead == statBuf.st_size)//Ensure the number of bytes read matched what was requested.
{
if (nullTerminate)//Optionally NULL terminate if they want to treat it as a string.
buf[buf.size() - 1] = NULL;
b = true;//Success.
}
}
}
fclose(f);
}
}
catch (...)
{
if (f != NULL)
fclose(f);
b = false;
}
return b;
}
/// <summary>
/// Clear dest and copy all of the elements of vector source with elements of type U to the vector
/// dest with elements of type T.
/// </summary>
/// <param name="dest">The vector of type T to copy to</param>
/// <param name="source">The vector of type U to copy from</param>
template <typename T, typename U>
void CopyVec(vector<T>& dest, const vector<U>& source)
{
dest.clear();
dest.resize(source.size());
for (size_t i = 0; i < source.size(); i++)
dest[i] = T(source[i]);//Valid assignment operator between T and U types must be defined somewhere.
}
/// <summary>
/// Clear a vector of pointers to any type by checking each element for NULL and calling delete on it, then clearing the entire vector.
/// Optionally call array delete if the elements themselves are pointers to dynamically allocated arrays.
/// </summary>
/// <param name="vec">The vector to be cleared</param>
/// <param name="arrayDelete">Whether to call delete or delete []. Default: false.</param>
template <typename T>
static void ClearVec(vector<T*>& vec, bool arrayDelete = false)
{
for (unsigned int i = 0; i < vec.size(); i++)
{
if (vec[i] != NULL)
{
if (arrayDelete)
delete [] vec[i];
else
delete vec[i];
}
vec[i] = NULL;
}
vec.clear();
}
/// <summary>
/// Convert an RGBA buffer to an RGB buffer.
/// </summary>
/// <param name="rgba">The RGBA buffer</param>
/// <param name="rgb">The RGB buffer</param>
/// <param name="width">The width of the image in pixels</param>
/// <param name="height">The height of the image in pixels</param>
static void RgbaToRgb(vector<unsigned char>& rgba, vector<unsigned char>& rgb, unsigned int width, unsigned int height)
{
rgb.resize(width * height * 3);
for (unsigned int i = 0, j = 0; i < (width * height * 4); i += 4, j += 3)
{
rgb[j] = rgba[i];
rgb[j + 1] = rgba[i + 1];
rgb[j + 2] = rgba[i + 2];
}
}
/// <summary>
/// Clamp and return a value to be greater than or equal to a specified minimum and less than
/// or equal to a specified maximum.
/// </summary>
/// <param name="val">The value to be clamped</param>
/// <param name="min">A value which the clamped value must be greater than or equal to</param>
/// <param name="max">A value which the clamped value must be less than or equal to</param>
/// <returns>The clamped value</returns>
template <typename T>
static inline T Clamp(T val, T min, T max)
{
if (val < min)
return min;
else if (val > max)
return max;
else
return val;
}
/// <summary>
/// Clamp and return a value to be greater than or equal to a specified minimum and less than
/// or equal to a specified maximum. If lesser, the value is fmod(val - min, max - min). If greater,
/// the value is max - fmod(max - val, max - min).
/// </summary>
/// <param name="val">The value to be clamped</param>
/// <param name="min">A value which the clamped value must be greater than or equal to</param>
/// <param name="max">A value which the clamped value must be less than or equal to</param>
/// <returns>The clamped and modded value</returns>
template <typename T>
static inline T ClampMod(T val, T min, T max)
{
if (val < min)
return min + fmod(val - min, max - min);
else if (val > max)
return max - fmod(max - val, max - min);
else
return val;
}
/// <summary>
/// Similar to Clamp(), but clamps a reference value in place rather than returning.
/// </summary>
/// <param name="val">The reference value to be clamped in place</param>
/// <param name="min">A value which the clamped value must be greater than or equal to</param>
/// <param name="max">A value which the clamped value must be less than or equal to</param>
template <typename T>
static inline void ClampRef(T& val, T min, T max)
{
if (val < min)
val = min;
else if (val > max)
val = max;
}
/// <summary>
/// Similar to Clamp(), but clamps a reference value in place rather than returning.
/// </summary>
/// <param name="val">The reference value to be clamped in place</param>
/// <param name="gte">A value which the clamped value must be less than or equal to</param>
template <typename T>
static inline void ClampLteRef(T& val, T lte)
{
if (val > lte)
val = lte;
}
/// <summary>
/// Clamp and return a value to be greater than or equal to a specified value.
/// Useful for ensuring something is not less than zero.
/// </summary>
/// <param name="val">The value to be clamped</param>
/// <param name="gte">A value which the clamped value must be greater than or equal to</param>
/// <returns>The clamped value</returns>
template <typename T>
static inline T ClampGte(T val, T gte)
{
return (val < gte) ? gte : val;
}
/// <summary>
/// Similar to Clamp(), but clamps a reference value in place rather than returning.
/// </summary>
/// <param name="val">The reference value to be clamped in place</param>
/// <param name="gte">A value which the clamped value must be greater than or equal to</param>
template <typename T>
static inline void ClampGteRef(T& val, T gte)
{
if (val < gte)
val = gte;
}
/// <summary>
/// Thin wrapper around a call to ClampGte() with a gte value of zero.
/// </summary>
/// <param name="val">The value to be clamped</param>
/// <returns>The clamped value</returns>
template <typename T>
static inline T ClampGte0(T val)
{
return ClampGte<T>(val, 0);
}
/// <summary>
/// Thin wrapper around a call to ClampGteRef() with a gte value of zero.
/// </summary>
/// <param name="val">The reference value to be clamped in place</param>
template <typename T>
static inline void ClampGte0Ref(T& val)
{
ClampGteRef<T>(val, 0);
}
/// <summary>
/// Return a value rounded up or down. Works for positive and negative numbers.
/// </summary>
/// <param name="r">The value to round</param>
/// <returns>The rounded value</returns>
template <typename T>
T Round(T r)
{
return (r > 0) ? (T)Floor<T>(r + T(0.5)) : ceil(r - T(0.5));
}
/// <summary>
/// Special rounding for certain variations, gotten from Apophysis.
/// </summary>
/// <param name="x">The value to round</param>
/// <returns>The rounded value</returns>
inline float LRint(float x)
{
int temp = (x >= 0 ? (int)(x + 0.5f) : (int)(x - 0.5f));
return (float)temp;
}
/// <summary>
/// Special rounding for certain variations, gotten from Apophysis.
/// </summary>
/// <param name="x">The value to round</param>
/// <returns>The rounded value</returns>
inline double LRint(double x)
{
__int64 temp = (x >= 0 ? (__int64)(x + 0.5) : (__int64)(x - 0.5));
return (double)temp;
}
/// <summary>
/// System floor() extremely slow because it accounts for various error conditions.
/// This is a much faster version that works on data that is not NaN.
/// </summary>
/// <param name="x">The value to return the floor of</param>
/// <returns>The floored value</returns>
template <typename T>
static inline int Floor(T val)
{
if (val >= 0)
{
return (int)val;
}
else
{
int i = (int)val;//Truncate.
return i - (i > val);//Convert trunc to floor.
}
}
/// <summary>
/// Never really understood what this did.
/// </summary>
/// <param name="r">The value to round</param>
/// <returns>The rounded value</returns>
template <typename T>
static inline T Round6(T r)
{
r *= 1e6;
if (r < 0)
r -= 1;
return T(1e-6 * (int)(r + T(0.5)));
}
/// <summary>
/// Return -1 if the value is less than 0, 1 if it's greater and
/// 0 if it's equal to 0.
/// </summary>
/// <param name="v">The value to inspect</param>
/// <returns>-1, 0 or 1</returns>
template <typename T>
static inline T Sign(T v)
{
return (v < 0) ? T(-1) : (v > 0) ? T(1) : T(0);
}
/// <summary>
/// Return -1 if the value is less than 0, 1 if it's greater.
/// This differs from Sign() in that it doesn't return 0.
/// </summary>
/// <param name="v">The value to inspect</param>
/// <returns>-1 or 1</returns>
template <typename T>
static inline T SignNz(T v)
{
return (v < 0) ? T(-1) : T(1);
}
/// <summary>
/// Return the square of the passed in value.
/// This is useful when the value is a result of a computation
/// rather than a fixed number. Otherwise, use the SQR macro.
/// </summary>
/// <param name="v">The value to square</param>
/// <returns>The squared value</returns>
template <typename T>
static inline T Sqr(T t)
{
return t * t;
}
/// <summary>
/// Taking the square root of numbers close to zero is dangerous. If x is negative
/// due to floating point errors, it can return NaN results.
/// </summary>
template <typename T>
static inline T SafeSqrt(T x)
{
if (x <= 0)
return 0;
return sqrt(x);
}
/// <summary>
/// Return the cube of the passed in value.
/// This is useful when the value is a result of a computation
/// rather than a fixed number. Otherwise, use the CUBE macro.
/// </summary>
/// <param name="v">The value to cube</param>
/// <returns>The cubed value</returns>
template <typename T>
static inline T Cube(T t)
{
return t * t * t;
}
/// <summary>
/// Return the hypotenuse of the passed in values.
/// </summary>
/// <param name="x">The x distance</param>
/// <param name="y">The y distance</param>
/// <returns>The hypotenuse</returns>
template <typename T>
static inline T Hypot(T x, T y)
{
return sqrt(SQR(x) + SQR(y));
}
/// <summary>
/// Spread the values.
/// </summary>
/// <param name="x">The x distance</param>
/// <param name="y">The y distance</param>
/// <returns>The spread</returns>
template <typename T>
static inline T Spread(T x, T y)
{
return Hypot<T>(x, y) * ((x) > 0 ? 1 : -1);
}
/// <summary>
/// Unsure.
/// </summary>
/// <param name="x">The x distance</param>
/// <param name="y">The y distance</param>
/// <returns>The powq4</returns>
template <typename T>
static inline T Powq4(T x, T y)
{
return pow(fabs(x), y) * SignNz(x);
}
/// <summary>
/// Unsure.
/// </summary>
/// <param name="x">The x distance</param>
/// <param name="y">The y distance</param>
/// <returns>The powq4c</returns>
template <typename T>
static inline T Powq4c(T x, T y)
{
return y == 1 ? x : Powq4(x, y);
}
/// <summary>
/// Return EPS6 if the passed in value was zero, else return the value.
/// </summary>
/// <param name="x">The value</param>
/// <param name="y">The y distance</param>
/// <returns>EPS6 or the value if it was non-zero</returns>
template <typename T>
static inline T Zeps(T x)
{
return x == 0 ? EPS6 : x;
}
/// <summary>
/// Interpolate a given percentage between two values.
/// </summary>
/// <param name="a">The first value to interpolate between.</param>
/// <param name="b">The secod value to interpolate between.</param>
/// <param name="p">The percentage between the two values to calculate.</param>
/// <returns>The interpolated value.</returns>
template <typename T>
static inline T Lerp(T a, T b, T p)
{
return a + (b - a) * p;
}
/// <summary>
/// Thin wrapper around a call to modf that discards the integer portion
/// and returns the signed fractional portion.
/// </summary>
/// <param name="v">The value to retrieve the signed fractional portion of.</param>
/// <returns>The signed fractional portion of v.</returns>
template <typename T>
static inline T Fabsmod(T v)
{
T dummy;
return modf(v, &dummy);
}
/// <summary>
/// Unsure.
/// </summary>
/// <param name="p">Unsure.</param>
/// <param name="amp">Unsure.</param>
/// <param name="ph">Unsure.</param>
/// <returns>Unsure.</returns>
template <typename T>
static inline T Fosc(T p, T amp, T ph)
{
return T(0.5) - cos(p * amp + ph) * T(0.5);
}
/// <summary>
/// Unsure.
/// </summary>
/// <param name="p">Unsure.</param>
/// <param name="ph">Unsure.</param>
/// <returns>Unsure.</returns>
template <typename T>
static inline T Foscn(T p, T ph)
{
return T(0.5) - cos(p + ph) * T(0.5);
}
/// <summary>
/// Log scale from Apophysis.
/// </summary>
/// <param name="x">The value to log scale</param>
/// <returns>The log scaled value</returns>
template <typename T>
static inline T LogScale(T x)
{
return x == 0 ? 0 : log((fabs(x) + 1) * T(M_E)) * SignNz(x) / T(M_E);
}
/// <summary>
/// Log map from Apophysis.
/// </summary>
/// <param name="x">The value to log map</param>
/// <returns>The log mapped value</returns>
template <typename T>
static inline T LogMap(T x)
{
return x == 0 ? 0 : (T(M_E) + log(x * T(M_E))) * T(0.25) * SignNz(x);
}
/// <summary>
/// Thin wrapper around calling xmlStrcmp() on an Xml tag to tell
/// if its name is a given value.
/// </summary>
/// <param name="name">The name of the tag of the to inspect</param>
/// <param name="val">The value compare against</param>
/// <returns>True if the comparison matched, else false</returns>
static inline bool Compare(const xmlChar* name, char* val)
{
return xmlStrcmp(name, XC val) != 0;
}
/// <summary>
/// Determine whether the specified value is very close to zero.
/// This is useful for determining equality of float/double types.
/// </summary>
/// <param name="val">The value to compare against</param>
/// <param name="tolerance">The tolerance. Default: 1e-6.</param>
/// <returns>True if the value was very close to zero, else false</returns>
template <typename T>
static inline bool IsNearZero(T val, T tolerance = 1e-6)
{
return (val > -tolerance && val < tolerance);
}
/// <summary>
/// Determine whether a specified value is very close to another value.
/// This is useful for determining equality of float/double types.
/// </summary>
/// <param name="val1">The first value.</param>
/// <param name="val2">The second value.</param>
/// <param name="tolerance">The tolerance. Default: 1e-6.</param>
/// <returns>True if the values were very close to each other, else false</returns>
template <typename T>
static bool IsClose(T val1, T val2, T tolerance = 1e-6)
{
return IsNearZero(val1 - val2, tolerance);
}
/// <summary>
/// Put an angular measurement in degrees into the range of -180 - 180.
/// </summary>
/// <param name="angle">The angle to normalize</param>
/// <returns>The normalized angle in a range of -180 - 180</returns>
template <typename T>
static inline T NormalizeDeg180(T angle)
{
angle = fmod(angle, 360);
if (angle > 180)
{
angle -= 360;
}
else if (angle < -180)
{
angle += 360;
}
return angle;
}
/// <summary>
/// Put an angular measurement in degrees into the range of 0 - 360.
/// </summary>
/// <param name="angle">The angle to normalize</param>
/// <returns>The normalized angle in a range of 0 - 360</returns>
template <typename T>
static inline T NormalizeDeg360(T angle)
{
if (angle > 360 || angle < -360)
angle = fmod(angle, 360);
if (angle < 0)
angle += 360;
return angle;
}
/// <summary>
/// Return a lower case copy of a string.
/// </summary>
/// <param name="str">The string to copy and make lower case</param>
/// <returns>The lower case string</returns>
static inline string ToLower(string& str)
{
string lower;
lower.resize(str.size());//Allocate the destination space.
std::transform(str.begin(), str.end(), lower.begin(), ::tolower);//Convert the source string to lower case storing the result in the destination string.
return lower;
}
/// <summary>
/// Return an upper case copy of a string.
/// </summary>
/// <param name="str">The string to copy and make upper case</param>
/// <returns>The upper case string</returns>
static inline string ToUpper(string& str)
{
string upper;
upper.resize(str.size());//Allocate the destination space.
std::transform(str.begin(), str.end(), upper.begin(), ::toupper);//Convert the source string to lower case storing the result in the destination string.
return upper;
}
/// <summary>
/// Return a copy of a string with leading and trailing occurrences of a specified character removed.
/// The default character is a space.
/// </summary>
/// <param name="str">The string to trim</param>
/// <param name="ch">The character to trim. Default: space.</param>
/// <returns>The trimmed string</returns>
static inline string Trim(string& str, char ch = ' ')
{
string ret;
if (str != "")
{
size_t firstChar = str.find_first_not_of(ch);
size_t lastChar = str.find_last_not_of(ch);
if (firstChar == string::npos)
firstChar = 0;
if (lastChar == string::npos)
lastChar = str.size();
ret = str.substr(firstChar, lastChar - firstChar + 1);
}
return ret;
}
/// <summary>
/// Placeholder for a templated function to query the value of a specified system environment variable
/// of a specific type. This function does nothing as the functions for specific types implement the behavior
/// via template specialization.
/// </summary>
/// <param name="name">The name of the environment variable to query</param>
/// <param name="def">The default value to return if the environment variable was not present</param>
/// <returns>The value of the specified environment variable if found, else default</returns>
template <typename T>
static T Arg(char* name, T def)
{
T t;
return t;
}
/// <summary>
/// Template specialization for Arg<>() with a type of int.
/// </summary>
/// <param name="name">The name of the environment variable to query</param>
/// <param name="def">The default value to return if the environment variable was not present</param>
/// <returns>The value of the specified environment variable if found, else default</returns>
template <>
static int Arg<int>(char* name, int def)
{
char* ch;
int returnVal;
size_t len;
errno_t err = _dupenv_s(&ch, &len, name);
if (err || !ch)
returnVal = def;
else
returnVal = atoi(ch);
free(ch);
return returnVal;
}
/// <summary>
/// Template specialization for Arg<>() with a type of unsigned int.
/// </summary>
/// <param name="name">The name of the environment variable to query</param>
/// <param name="def">The default value to return if the environment variable was not present</param>
/// <returns>The value of the specified environment variable if found, else default</returns>
template <>
static unsigned int Arg<unsigned int>(char* name, unsigned int def)
{
return Arg<int>(name, (int)def);
}
/// <summary>
/// Template specialization for Arg<>() with a type of bool.
/// </summary>
/// <param name="name">The name of the environment variable to query</param>
/// <param name="def">The default value to return if the environment variable was not present</param>
/// <returns>The value of the specified environment variable if found, else default</returns>
template <>
static bool Arg<bool>(char* name, bool def)
{
return (Arg<int>(name, -999) != -999) ? true : def;
}
/// <summary>
/// Template specialization for Arg<>() with a type of double.
/// </summary>
/// <param name="name">The name of the environment variable to query</param>
/// <param name="def">The default value to return if the environment variable was not present</param>
/// <returns>The value of the specified environment variable if found, else default</returns>
template <>
static double Arg<double>(char* name, double def)
{
char* ch;
double returnVal;
size_t len;
errno_t err = _dupenv_s(&ch, &len, name);
if (err || !ch)
returnVal = def;
else
returnVal = atof(ch);
free(ch);
return returnVal;
}
/// <summary>
/// Template specialization for Arg<>() with a type of string.
/// </summary>
/// <param name="name">The name of the environment variable to query</param>
/// <param name="def">The default value to return if the environment variable was not present</param>
/// <returns>The value of the specified environment variable if found, else default</returns>
template <>
static string Arg<string>(char* name, string def)
{
char* ch;
string returnVal;
size_t len;
errno_t err = _dupenv_s(&ch, &len, name);
if (err || !ch)
{
if (def != "")
returnVal = def;
}
else
returnVal = string(ch);
free(ch);
return returnVal;
}
/// <summary>
/// Replaces all instances of a value within a collection, with the specified value.
/// Taken from a StackOverflow.com post.
/// Modified to account for the scenario where the find and replace strings each start with
/// the same character.
/// Template argument should be any STL container.
/// </summary>
/// <param name="source">Collection to replace values in</param>
/// <param name="find">The value to replace</param>
/// <param name="replace">The value to replace with</param>
/// <returns>The number of instances replaced</returns>
template<typename T>
unsigned int inline FindAndReplace(T& source, const T& find, const T& replace)
{
unsigned int replaceCount = 0;
typename T::size_type fLen = find.size();
typename T::size_type rLen = replace.size();
for (typename T::size_type pos = 0; (pos = source.find(find, pos)) != T::npos; pos += rLen)
{
typename T::size_type pos2 = source.find(replace, pos);
if (pos != pos2)
{
replaceCount++;
source.replace(pos, fLen, replace);
}
}
return replaceCount;
}
/// <summary>
/// Return a character pointer to a version string composed of the EMBER_OS and EMBER_VERSION values.
/// </summary>
static char* EmberVersion()
{
return EMBER_OS "-" EMBER_VERSION;
}
}

2168
Source/Ember/Variation.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,537 @@
#pragma once
#include "Variations01.h"
#include "Variations02.h"
#include "Variations03.h"
#include "Variations04.h"
#include "Variations05.h"
#include "VariationsDC.h"
/// <summary>
/// VariationList class.
/// </summary>
namespace EmberNs
{
/// <summary>
/// Since the list of variations is numerous, it's convenient to be able to make copies
/// of specific ones. This class holds a list of pointers to variation objects for every
/// variation available. Similar to the PaletteList class, a caller can look up a variation
/// by name or ID and retrieve a copy of it.
/// All variations are deleted upon destruction.
/// Template argument expected to be float or double.
/// </summary>
template <typename T>
class EMBER_API VariationList
{
#define ADDPREPOSTREGVAR(varName) \
m_Variations.push_back(new varName##Variation<T>()); \
m_Variations.push_back(new Pre##varName##Variation<T>()); \
m_Variations.push_back(new Post##varName##Variation<T>());
public:
/// <summary>
/// Constructor which initializes all of the variation objects and stores them in the list.
/// </summary>
VariationList()
{
m_Variations.reserve(900);//Change this as the list grows.
ADDPREPOSTREGVAR(Linear)
ADDPREPOSTREGVAR(Sinusoidal)
ADDPREPOSTREGVAR(Spherical)
ADDPREPOSTREGVAR(Swirl)
ADDPREPOSTREGVAR(Horseshoe)
ADDPREPOSTREGVAR(Polar)
ADDPREPOSTREGVAR(Handkerchief)
ADDPREPOSTREGVAR(Heart)
ADDPREPOSTREGVAR(Disc)
ADDPREPOSTREGVAR(Spiral)
ADDPREPOSTREGVAR(Hyperbolic)
ADDPREPOSTREGVAR(Diamond)
ADDPREPOSTREGVAR(Ex)
ADDPREPOSTREGVAR(Julia)
ADDPREPOSTREGVAR(Bent)
ADDPREPOSTREGVAR(Waves)
ADDPREPOSTREGVAR(Fisheye)
ADDPREPOSTREGVAR(Popcorn)
ADDPREPOSTREGVAR(Exponential)
ADDPREPOSTREGVAR(Power)
ADDPREPOSTREGVAR(Cosine)
ADDPREPOSTREGVAR(Rings)
ADDPREPOSTREGVAR(Fan)
ADDPREPOSTREGVAR(Blob)
ADDPREPOSTREGVAR(Pdj)
ADDPREPOSTREGVAR(Fan2)
ADDPREPOSTREGVAR(Rings2)
ADDPREPOSTREGVAR(Eyefish)
ADDPREPOSTREGVAR(Bubble)
ADDPREPOSTREGVAR(Cylinder)
ADDPREPOSTREGVAR(Perspective)
ADDPREPOSTREGVAR(Noise)
ADDPREPOSTREGVAR(JuliaNGeneric)
ADDPREPOSTREGVAR(JuliaScope)
ADDPREPOSTREGVAR(Blur)
ADDPREPOSTREGVAR(GaussianBlur)
ADDPREPOSTREGVAR(RadialBlur)
ADDPREPOSTREGVAR(Pie)
ADDPREPOSTREGVAR(Ngon)
ADDPREPOSTREGVAR(Curl)
ADDPREPOSTREGVAR(Rectangles)
ADDPREPOSTREGVAR(Arch)
ADDPREPOSTREGVAR(Tangent)
ADDPREPOSTREGVAR(Square)
ADDPREPOSTREGVAR(Rays)
ADDPREPOSTREGVAR(Blade)
ADDPREPOSTREGVAR(Secant2)
ADDPREPOSTREGVAR(TwinTrian)
ADDPREPOSTREGVAR(Cross)
ADDPREPOSTREGVAR(Disc2)
ADDPREPOSTREGVAR(SuperShape)
ADDPREPOSTREGVAR(Flower)
ADDPREPOSTREGVAR(Conic)
ADDPREPOSTREGVAR(Parabola)
ADDPREPOSTREGVAR(Bent2)
ADDPREPOSTREGVAR(Bipolar)
ADDPREPOSTREGVAR(Boarders)
ADDPREPOSTREGVAR(Butterfly)
ADDPREPOSTREGVAR(Cell)
ADDPREPOSTREGVAR(Cpow)
ADDPREPOSTREGVAR(Curve)
ADDPREPOSTREGVAR(Edisc)
ADDPREPOSTREGVAR(Elliptic)
ADDPREPOSTREGVAR(Escher)
ADDPREPOSTREGVAR(Foci)
ADDPREPOSTREGVAR(LazySusan)
ADDPREPOSTREGVAR(Loonie)
ADDPREPOSTREGVAR(Modulus)
ADDPREPOSTREGVAR(Oscilloscope)
ADDPREPOSTREGVAR(Polar2)
ADDPREPOSTREGVAR(Popcorn2)
ADDPREPOSTREGVAR(Scry)
ADDPREPOSTREGVAR(Separation)
ADDPREPOSTREGVAR(Split)
ADDPREPOSTREGVAR(Splits)
ADDPREPOSTREGVAR(Stripes)
ADDPREPOSTREGVAR(Wedge)
ADDPREPOSTREGVAR(WedgeJulia)
ADDPREPOSTREGVAR(WedgeSph)
ADDPREPOSTREGVAR(Whorl)
ADDPREPOSTREGVAR(Waves2)
ADDPREPOSTREGVAR(Exp)
ADDPREPOSTREGVAR(Log)
ADDPREPOSTREGVAR(Sin)
ADDPREPOSTREGVAR(Cos)
ADDPREPOSTREGVAR(Tan)
ADDPREPOSTREGVAR(Sec)
ADDPREPOSTREGVAR(Csc)
ADDPREPOSTREGVAR(Cot)
ADDPREPOSTREGVAR(Sinh)
ADDPREPOSTREGVAR(Cosh)
ADDPREPOSTREGVAR(Tanh)
ADDPREPOSTREGVAR(Sech)
ADDPREPOSTREGVAR(Csch)
ADDPREPOSTREGVAR(Coth)
ADDPREPOSTREGVAR(Auger)
ADDPREPOSTREGVAR(Flux)
ADDPREPOSTREGVAR(Hemisphere)
ADDPREPOSTREGVAR(Epispiral)
ADDPREPOSTREGVAR(Bwraps)
ADDPREPOSTREGVAR(BlurCircle)
ADDPREPOSTREGVAR(BlurZoom)
ADDPREPOSTREGVAR(BlurPixelize)
ADDPREPOSTREGVAR(Crop)
ADDPREPOSTREGVAR(BCircle)
ADDPREPOSTREGVAR(BlurLinear)
ADDPREPOSTREGVAR(BlurSquare)
ADDPREPOSTREGVAR(Boarders2)
ADDPREPOSTREGVAR(Cardioid)
ADDPREPOSTREGVAR(Checks)
ADDPREPOSTREGVAR(Circlize)
ADDPREPOSTREGVAR(Circlize2)
ADDPREPOSTREGVAR(CosWrap)
ADDPREPOSTREGVAR(DeltaA)
ADDPREPOSTREGVAR(Expo)
ADDPREPOSTREGVAR(Extrude)
ADDPREPOSTREGVAR(FDisc)
ADDPREPOSTREGVAR(Fibonacci)
ADDPREPOSTREGVAR(Fibonacci2)
ADDPREPOSTREGVAR(Glynnia)
ADDPREPOSTREGVAR(GridOut)
ADDPREPOSTREGVAR(Hole)
ADDPREPOSTREGVAR(Hypertile)
ADDPREPOSTREGVAR(Hypertile1)
ADDPREPOSTREGVAR(Hypertile2)
ADDPREPOSTREGVAR(Hypertile3D)
ADDPREPOSTREGVAR(Hypertile3D1)
ADDPREPOSTREGVAR(Hypertile3D2)
ADDPREPOSTREGVAR(IDisc)
ADDPREPOSTREGVAR(Julian2)
ADDPREPOSTREGVAR(JuliaQ)
ADDPREPOSTREGVAR(Murl)
ADDPREPOSTREGVAR(Murl2)
ADDPREPOSTREGVAR(NPolar)
ADDPREPOSTREGVAR(Ortho)
ADDPREPOSTREGVAR(Poincare)
ADDPREPOSTREGVAR(Poincare3D)
ADDPREPOSTREGVAR(Polynomial)
ADDPREPOSTREGVAR(PSphere)
ADDPREPOSTREGVAR(Rational3)
ADDPREPOSTREGVAR(Ripple)
ADDPREPOSTREGVAR(Sigmoid)
ADDPREPOSTREGVAR(SinusGrid)
ADDPREPOSTREGVAR(Stwin)
ADDPREPOSTREGVAR(TwoFace)
ADDPREPOSTREGVAR(Unpolar)
ADDPREPOSTREGVAR(WavesN)
ADDPREPOSTREGVAR(XHeart)
ADDPREPOSTREGVAR(Barycentroid)
ADDPREPOSTREGVAR(BiSplit)
ADDPREPOSTREGVAR(Crescents)
ADDPREPOSTREGVAR(Mask)
ADDPREPOSTREGVAR(Cpow2)
ADDPREPOSTREGVAR(Curl3D)
ADDPREPOSTREGVAR(Disc3D)
ADDPREPOSTREGVAR(Funnel)
ADDPREPOSTREGVAR(Linear3D)
ADDPREPOSTREGVAR(PowBlock)
ADDPREPOSTREGVAR(Squirrel)
ADDPREPOSTREGVAR(Ennepers)
ADDPREPOSTREGVAR(SphericalN)
ADDPREPOSTREGVAR(Kaleidoscope)
ADDPREPOSTREGVAR(GlynnSim1)
ADDPREPOSTREGVAR(GlynnSim2)
ADDPREPOSTREGVAR(GlynnSim3)
ADDPREPOSTREGVAR(Starblur)
ADDPREPOSTREGVAR(Sineblur)
ADDPREPOSTREGVAR(Circleblur)
ADDPREPOSTREGVAR(CropN)
ADDPREPOSTREGVAR(ShredRad)
ADDPREPOSTREGVAR(Blob2)
ADDPREPOSTREGVAR(Julia3D)
ADDPREPOSTREGVAR(Julia3Dz)
ADDPREPOSTREGVAR(LinearT)
ADDPREPOSTREGVAR(LinearT3D)
ADDPREPOSTREGVAR(Ovoid)
ADDPREPOSTREGVAR(Ovoid3D)
ADDPREPOSTREGVAR(Spirograph)
ADDPREPOSTREGVAR(Petal)
ADDPREPOSTREGVAR(RoundSpher)
ADDPREPOSTREGVAR(RoundSpher3D)
ADDPREPOSTREGVAR(SpiralWing)
ADDPREPOSTREGVAR(Squarize)
ADDPREPOSTREGVAR(Sschecks)
ADDPREPOSTREGVAR(PhoenixJulia)
ADDPREPOSTREGVAR(Mobius)
ADDPREPOSTREGVAR(MobiusN)
ADDPREPOSTREGVAR(MobiusStrip)
ADDPREPOSTREGVAR(Lissajous)
ADDPREPOSTREGVAR(Svf)
ADDPREPOSTREGVAR(Target)
ADDPREPOSTREGVAR(Taurus)
ADDPREPOSTREGVAR(Collideoscope)
ADDPREPOSTREGVAR(BMod)
ADDPREPOSTREGVAR(BSwirl)
ADDPREPOSTREGVAR(BTransform)
ADDPREPOSTREGVAR(BCollide)
ADDPREPOSTREGVAR(Eclipse)
ADDPREPOSTREGVAR(FlipCircle)
ADDPREPOSTREGVAR(FlipY)
ADDPREPOSTREGVAR(ECollide)
ADDPREPOSTREGVAR(EJulia)
ADDPREPOSTREGVAR(EMod)
ADDPREPOSTREGVAR(EMotion)
ADDPREPOSTREGVAR(EPush)
ADDPREPOSTREGVAR(ERotate)
ADDPREPOSTREGVAR(EScale)
ADDPREPOSTREGVAR(ESwirl)
ADDPREPOSTREGVAR(LazyTravis)
ADDPREPOSTREGVAR(Squish)
ADDPREPOSTREGVAR(Circus)
ADDPREPOSTREGVAR(Tancos)
ADDPREPOSTREGVAR(Rippled)
ADDPREPOSTREGVAR(RotateX)
ADDPREPOSTREGVAR(RotateY)
ADDPREPOSTREGVAR(RotateZ)
ADDPREPOSTREGVAR(Flatten)
ADDPREPOSTREGVAR(Zblur)
ADDPREPOSTREGVAR(Blur3D)
ADDPREPOSTREGVAR(ZScale)
ADDPREPOSTREGVAR(ZTranslate)
ADDPREPOSTREGVAR(ZCone)
ADDPREPOSTREGVAR(MirrorX)
ADDPREPOSTREGVAR(MirrorY)
ADDPREPOSTREGVAR(MirrorZ)
ADDPREPOSTREGVAR(Depth)
ADDPREPOSTREGVAR(Spherical3D)
ADDPREPOSTREGVAR(RBlur)
ADDPREPOSTREGVAR(JuliaNab)
ADDPREPOSTREGVAR(Sintrange)
ADDPREPOSTREGVAR(Voron)
ADDPREPOSTREGVAR(Waffle)
ADDPREPOSTREGVAR(Square3D)
ADDPREPOSTREGVAR(SuperShape3D)
ADDPREPOSTREGVAR(Sphyp3D)
ADDPREPOSTREGVAR(Circlecrop)
ADDPREPOSTREGVAR(Julian3Dx)
ADDPREPOSTREGVAR(Fourth)
ADDPREPOSTREGVAR(Mobiq)
ADDPREPOSTREGVAR(Spherivoid)
ADDPREPOSTREGVAR(Farblur)
ADDPREPOSTREGVAR(CurlSP)
ADDPREPOSTREGVAR(Heat)
ADDPREPOSTREGVAR(Interference2)
ADDPREPOSTREGVAR(Sinq)
ADDPREPOSTREGVAR(Sinhq)
ADDPREPOSTREGVAR(Secq)
ADDPREPOSTREGVAR(Sechq)
ADDPREPOSTREGVAR(Tanq)
ADDPREPOSTREGVAR(Tanhq)
ADDPREPOSTREGVAR(Cosq)
ADDPREPOSTREGVAR(Coshq)
ADDPREPOSTREGVAR(Cotq)
ADDPREPOSTREGVAR(Cothq)
ADDPREPOSTREGVAR(Cscq)
ADDPREPOSTREGVAR(Cschq)
ADDPREPOSTREGVAR(Estiq)
ADDPREPOSTREGVAR(Loq)
ADDPREPOSTREGVAR(Curvature)
ADDPREPOSTREGVAR(Qode)
ADDPREPOSTREGVAR(BlurHeart)
ADDPREPOSTREGVAR(Truchet)
ADDPREPOSTREGVAR(Gdoffs)
ADDPREPOSTREGVAR(Octagon)
ADDPREPOSTREGVAR(Trade)
ADDPREPOSTREGVAR(Juliac)
ADDPREPOSTREGVAR(Blade3D)
ADDPREPOSTREGVAR(Blob3D)
ADDPREPOSTREGVAR(Blocky)
ADDPREPOSTREGVAR(Bubble2)
ADDPREPOSTREGVAR(CircleLinear)
ADDPREPOSTREGVAR(CircleRand)
ADDPREPOSTREGVAR(CircleTrans1)
ADDPREPOSTREGVAR(Cubic3D)
ADDPREPOSTREGVAR(CubicLattice3D)
ADDPREPOSTREGVAR(Foci3D)
ADDPREPOSTREGVAR(Ho)
ADDPREPOSTREGVAR(Julia3Dq)
ADDPREPOSTREGVAR(Line)
ADDPREPOSTREGVAR(Loonie3D)
ADDPREPOSTREGVAR(Mcarpet)
ADDPREPOSTREGVAR(Waves23D)
ADDPREPOSTREGVAR(Pie3D)
ADDPREPOSTREGVAR(Popcorn23D)
ADDPREPOSTREGVAR(Sinusoidal3D)
ADDPREPOSTREGVAR(Scry3D)
ADDPREPOSTREGVAR(Shredlin)
ADDPREPOSTREGVAR(SplitBrdr)
ADDPREPOSTREGVAR(Wdisc)
ADDPREPOSTREGVAR(Falloff)
ADDPREPOSTREGVAR(Falloff2)
ADDPREPOSTREGVAR(Falloff3)
ADDPREPOSTREGVAR(Xtrb)
//ADDPREPOSTREGVAR(LinearXZ)
//ADDPREPOSTREGVAR(LinearYZ)
//DC are special.
m_Variations.push_back(new DCBubbleVariation<T>());
ADDPREPOSTREGVAR(DCCarpet)
ADDPREPOSTREGVAR(DCCube)
m_Variations.push_back(new DCCylinderVariation<T>());
ADDPREPOSTREGVAR(DCGridOut)
m_Variations.push_back(new DCLinearVariation<T>());
ADDPREPOSTREGVAR(DCTriangle)
ADDPREPOSTREGVAR(DCZTransl)
std::for_each(m_Variations.begin(), m_Variations.end(), [&](Variation<T>* var) { var->Precalc(); });
std::sort(m_Variations.begin(), m_Variations.end(), [&](const Variation<T>* var1, const Variation<T>* var2) { return var1->VariationId() < var2->VariationId(); });
m_RegVariations.reserve(m_Variations.size() / 3);
m_PreVariations.reserve(m_Variations.size() / 3);
m_PostVariations.reserve(m_Variations.size() / 3);
std::for_each(m_Variations.begin(), m_Variations.end(), [&](Variation<T>* var) { if (var->VarType() == VARTYPE_REG) m_RegVariations.push_back(var); });
std::for_each(m_Variations.begin(), m_Variations.end(), [&](Variation<T>* var) { if (var->VarType() == VARTYPE_PRE) m_PreVariations.push_back(var); });
std::for_each(m_Variations.begin(), m_Variations.end(), [&](Variation<T>* var) { if (var->VarType() == VARTYPE_POST) m_PostVariations.push_back(var); });
//Keep a list of which variations derive from ParametricVariation.
//Note that these are not new copies, rather just pointers to the original instances in m_Variations.
for (unsigned int i = 0; i < m_Variations.size(); i++)
{
if (ParametricVariation<T>* parVar = dynamic_cast<ParametricVariation<T>*>(m_Variations[i]))
m_ParametricVariations.push_back(parVar);
}
}
/// <summary>
/// Delete each element of the list.
/// </summary>
~VariationList()
{
ClearVec(m_Variations);//No need to delete parametric because they point to the entries in original vector.
}
/// <summary>
/// Get a pointer to the variation at the specified index.
/// </summary>
/// <param name="index">The index in the list to retrieve</param>
/// <returns>A pointer to the variation at the index if in range, else NULL.</returns>
Variation<T>* GetVariation(size_t index) { return index < m_Variations.size() ? m_Variations[index] : NULL; }
/// <summary>
/// Get a pointer to the variation of a specified type at the specified index.
/// </summary>
/// <param name="index">The index in the list to retrieve</param>
/// <param name="varType">The type of variation to retrieve</param>
/// <returns>A pointer to the variation of the specified type at the index if in range, else NULL.</returns>
Variation<T>* GetVariation(size_t index, eVariationType varType)
{
if (varType == VARTYPE_REG)
return index < m_RegVariations.size() ? m_RegVariations[index] : NULL;
else if (varType == VARTYPE_PRE)
return index < m_PreVariations.size() ? m_PreVariations[index] : NULL;
else if (varType == VARTYPE_POST)
return index < m_PostVariations.size() ? m_PostVariations[index] : NULL;
else
return NULL;
}
/// <summary>
/// Gets a pointer to a copy of the variation at the specified index.
/// Optionally specify a weight to assign the new copy.
/// </summary>
/// <param name="index">The index in the list to make a copy of</param>
/// <param name="weight">The weight to assign the new copy. Default: 1</param>
/// <returns>A pointer to the variation at the index if in range, else NULL.</returns>
Variation<T>* GetVariationCopy(size_t index, T weight = 1) { return MakeCopyWithWeight(GetVariation(index), weight); }
Variation<T>* GetVariationCopy(size_t index, eVariationType varType, T weight = 1) { return MakeCopyWithWeight(GetVariation(index, varType), weight); }
/// <summary>
/// Get a pointer to the variation with the specified ID.
/// </summary>
/// <param name="id">The ID to search for</param>
/// <returns>A pointer to the variation if found, else NULL.</returns>
Variation<T>* GetVariation(eVariationId id)
{
for (unsigned int i = 0; i < m_Variations.size() && m_Variations[i] != NULL; i++)
if (id == m_Variations[i]->VariationId())
return m_Variations[i];
return NULL;
}
/// <summary>
/// Gets a pointer to a copy of the variation with the specified ID.
/// Optionally specify a weight to assign the new copy.
/// </summary>
/// <param name="id">The id of the variation in the list to make a copy of</param>
/// <param name="weight">The weight to assign the new copy. Default: 1</param>
/// <returns>A pointer to the variation with a matching ID, else NULL.</returns>
Variation<T>* GetVariationCopy(eVariationId id, T weight = 1) { return MakeCopyWithWeight(GetVariation(id), weight); }
/// <summary>
/// Get a pointer to the variation with the specified name.
/// </summary>
/// <param name="name">The name to search for</param>
/// <returns>A pointer to the variation if found, else NULL.</returns>
Variation<T>* GetVariation(string name)
{
for (unsigned int i = 0; i < m_Variations.size() && m_Variations[i] != NULL; i++)
if (name == m_Variations[i]->Name())
return m_Variations[i];
return NULL;
}
/// <summary>
/// Gets a pointer to a copy of the variation with the specified name.
/// Optionally specify a weight to assign the new copy.
/// </summary>
/// <param name="name">The name of the variation in the list to make a copy of</param>
/// <param name="weight">The weight to assign the new copy. Default: 1</param>
/// <returns>A pointer to the variation with a matching name, else NULL.</returns>
Variation<T>* GetVariationCopy(string name, T weight = 1) { return MakeCopyWithWeight(GetVariation(name), weight); }
/// <summary>
/// Get a parametric variation at the specified index.
/// Note this is the index in the parametric variations list, not in the master list.
/// </summary>
/// <param name="index">The index in the parametric variations list to retrieve</param>
/// <returns>The parametric variation at the index specified if in range, else NULL.</returns>
ParametricVariation<T>* GetParametricVariation(size_t index) { return index < m_ParametricVariations.size() ? m_ParametricVariations[index] : NULL; }
/// <summary>
/// Get a parametric variation with the specified name.
/// </summary>
/// <param name="name">The name of the variation in the parametric variations list to retrieve</param>
/// <returns>The parametric variation with a matching name, else NULL.</returns>
ParametricVariation<T>* GetParametricVariation(string name)
{
for (unsigned int i = 0; i < m_ParametricVariations.size() && m_ParametricVariations[i] != NULL; i++)
if (name == m_ParametricVariations[i]->Name())
return m_ParametricVariations[i];
return NULL;
}
/// <summary>
/// Get the index of the variation with the specified name.
/// </summary>
/// <param name="name">The name of the variation whose index is returned</param>
/// <returns>The index of the variation with the matching name, else -1</returns>
int GetVariationIndex(string name)
{
for (unsigned int i = 0; i < m_Variations.size() && m_Variations[i] != NULL; i++)
if (name == m_Variations[i]->Name())
return i;
return -1;
}
/// <summary>
/// Accessors.
/// </summary>
size_t Size() { return m_Variations.size(); }
size_t RegSize() { return m_RegVariations.size(); }
size_t PreSize() { return m_PreVariations.size(); }
size_t PostSize() { return m_PostVariations.size(); }
size_t ParametricSize() { return m_ParametricVariations.size(); }
private:
/// <summary>
/// Make a dyncamically allocated copy of a variation and assign it a specified weight.
/// Return a pointer to the new copy.
/// </summary>
/// <param name="var">The variation to copy</param>
/// <param name="weight">The weight to assign it</param>
/// <returns>A pointer to the new variation copy if success, else NULL.</returns>
Variation<T>* MakeCopyWithWeight(Variation<T>* var, T weight)
{
if (var)
{
Variation<T>* var2 = var->Copy();
var2->m_Weight = weight;
return var2;
}
return NULL;
}
/// <summary>
/// Assignment operator which does nothing since these are non-copyable.
/// Do not provide a copy constructor and ensure the assignment operator does nothing.
/// </summary>
/// <param name="varList">The VariationList object which won't be copied</param>
/// <returns>Reference to unchanged self</returns>
VariationList<T>& operator = (const VariationList<T>& varList)
{
return *this;
}
vector<Variation<T>*> m_Variations;//A list of pointers to dynamically allocated variation objects.
vector<Variation<T>*> m_RegVariations;
vector<Variation<T>*> m_PreVariations;
vector<Variation<T>*> m_PostVariations;
vector<ParametricVariation<T>*> m_ParametricVariations;//A list of pointers to elements in m_Variations which are derived from ParametricVariation.
};
}

6296
Source/Ember/Variations01.h Normal file

File diff suppressed because it is too large Load Diff

5756
Source/Ember/Variations02.h Normal file

File diff suppressed because it is too large Load Diff

4596
Source/Ember/Variations03.h Normal file

File diff suppressed because it is too large Load Diff

5233
Source/Ember/Variations04.h Normal file

File diff suppressed because it is too large Load Diff

3253
Source/Ember/Variations05.h Normal file

File diff suppressed because it is too large Load Diff

1039
Source/Ember/VariationsDC.h Normal file

File diff suppressed because it is too large Load Diff

1173
Source/Ember/Xform.h Normal file

File diff suppressed because it is too large Load Diff

1350
Source/Ember/XmlToEmber.h Normal file

File diff suppressed because it is too large Load Diff