fractorium/Source/Ember/Xform.h
Person a0a205edd8 --User changes
-Users can now specify animation params on a per flame basis.
 --These get saved with the flame file.
 -Allow for rotating xforms around the world origin during animation.
 -Make the Clear Flame menu item be more comprehensive in how it clears a flame out.

--Bug fixes
 -Fix an extremely rare possible memory leak when using motion during animation, which is never used in Fractorium.
 -Do not skip to the current flame index, or attach a prefix in the Final Render Dialog when rendering an animation sequence.

--Code changes
 -Place all animation params in Ember.
2024-03-16 10:15:51 -06:00

1340 lines
43 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#pragma once
#include "VariationList.h"
#include "Interpolate.h"
/// <summary>
/// Xform class.
/// </summary>
namespace EmberNs
{
/// <summary>
/// Xform and Ember need each other, but each can't include the other.
/// So Ember includes this file, and Ember is declared as a forward declaration here.
/// </summary>
template <typename T> class Ember;
/// <summary>
/// If both polymorphism and templating are needed, uncomment this, fill it out and derive from it.
/// </summary>
//class EMBER_API XformBase
//{
//};
/// <summary>
/// An xform is a pre affine transform, a list of variations, and an optional final affine transform.
/// This is what gets applied to a point for each iteration.
/// Template argument expected to be float or double.
/// </summary>
template <typename T>
class EMBER_API Xform
{
public:
/// <summary>
/// Default constructor which calls Init() to set default or out of bounds values.
/// When useDefaults is true, Pre and post affine are defaulted to the identity matrix.
/// </summary>
/// <param name="useDefaults">Use reasonable default if true, else use out of bounds values.</param>
Xform(bool useDefaults = true)
{
Init(useDefaults);
}
/// <summary>
/// Constructor that takes default arguments. Mostly used for testing.
/// Post affine is defaulted to the identity matrix.
/// </summary>
/// <param name="density">The probability that this xform is chosen</param>
/// <param name="colorX">The color index</param>
/// <param name="colorSpeed">The color speed</param>
/// <param name="opacity">The opacity</param>
/// <param name="a">The a value of the pre affine transform</param>
/// <param name="d">The d value of the pre affine transform</param>
/// <param name="b">The b value of the pre affine transform</param>
/// <param name="e">The e value of the pre affine transform</param>
/// <param name="c">The c value of the pre affine transform</param>
/// <param name="f">The f value of the pre affine transform</param>
/// <param name="pa">The a value of the post affine transform. Default: 1.</param>
/// <param name="pd">The d value of the post affine transform. Default: 0.</param>
/// <param name="pb">The b value of the post affine transform. Default: 0.</param>
/// <param name="pe">The e value of the post affine transform. Default: 1.</param>
/// <param name="pc">The c value of the post affine transform. Default: 0.</param>
/// <param name="pf">The f value of the post affine transform. Default: 0.</param>
Xform(T weight, T colorX, T colorSpeed, T opacity,
T a, T d, T b, T e, T c, T f,
T pa = 1,
T pd = 0,
T pb = 0,
T pe = 1,
T pc = 0,
T pf = 0)
: Xform()
{
m_Weight = weight;
m_ColorX = colorX;
m_ColorSpeed = colorSpeed;
m_Opacity = opacity;
m_Affine.A(a);
m_Affine.B(b);
m_Affine.C(c);
m_Affine.D(d);
m_Affine.E(e);
m_Affine.F(f);
m_Post.A(pa);
m_Post.B(pb);
m_Post.C(pc);
m_Post.D(pd);
m_Post.E(pe);
m_Post.F(pf);
m_HasPre = !m_Affine.IsID();
m_HasPost = !m_Post.IsID();
m_HasPreOrRegularVars = PreVariationCount() > 0 || VariationCount() > 0;
CacheColorVals();//Init already called this, but must call again since color was assigned above.
}
/// <summary>
/// Default copy constructor.
/// </summary>
/// <param name="xform">The Xform object to copy</param>
Xform(const Xform<T>& xform)
: m_ParentEmber(nullptr)//Hack.
{
Xform<T>::operator=<T>(xform);
}
/// <summary>
/// Copy constructor to copy an Xform object of type U.
/// </summary>
/// <param name="xform">The Xform object to copy</param>
template <typename U>
Xform(const Xform<U>& xform)
: m_ParentEmber(nullptr)//Hack.
{
Xform<T>::operator=<U>(xform);
}
/// <summary>
/// Deletes each element of the variation vector and clears it.
/// </summary>
~Xform()
{
ClearAndDeleteVariations();
}
/// <summary>
/// Default assignment operator.
/// </summary>
/// <param name="xform">The Xform object to copy</param>
Xform<T>& operator = (const Xform<T>& xform)
{
if (this != &xform)
Xform<T>::operator=<T>(xform);
return *this;
}
/// <summary>
/// Assignment operator to assign a Xform object of type U.
/// This will delete all of the variations in the vector
/// and repopulate it with copes of the variation in xform's vector.
/// All other values are assigned directly.
/// </summary>
/// <param name="xform">The Xform object to copy.</param>
/// <returns>Reference to updated self</returns>
template <typename U>
Xform<T>& operator = (const Xform<U>& xform)
{
m_Affine = xform.m_Affine;
m_Post = xform.m_Post;
m_Weight = static_cast<T>(xform.m_Weight);
m_ColorX = static_cast<T>(xform.m_ColorX);
m_ColorY = static_cast<T>(xform.m_ColorY);
m_DirectColor = static_cast<T>(xform.m_DirectColor);
m_ColorSpeed = static_cast<T>(xform.m_ColorSpeed);
m_Animate = static_cast<T>(xform.m_Animate);
m_AnimateOrigin = static_cast<T>(xform.m_AnimateOrigin);
m_Opacity = static_cast<T>(xform.m_Opacity);
CacheColorVals();
m_HasPre = xform.HasPre();
m_HasPost = xform.HasPost();
m_HasPreOrRegularVars = xform.PreVariationCount() > 0 || xform.VariationCount() > 0;
m_Wind[0] = static_cast<T>(xform.m_Wind[0]);
m_Wind[1] = static_cast<T>(xform.m_Wind[1]);
m_MotionFreq = static_cast<T>(xform.m_MotionFreq);
m_MotionFunc = xform.m_MotionFunc;
m_MotionOffset = static_cast<T>(xform.m_MotionOffset);
ClearAndDeleteVariations();
//Must manually add them via the AddVariation() function so that
//the variation's m_IndexInXform member gets properly set to this.
for (size_t i = 0; i < xform.TotalVariationCount(); i++)
{
Variation<T>* var = nullptr;
if (Variation<U>* varOrig = xform.GetVariation(i))
{
varOrig->Copy(var);//Will convert from type U to type T.
AddVariation(var);//Will internally call SetPrecalcFlags().
}
}
if (TotalVariationCount() == 0)
SetPrecalcFlags();
//If this xform was already part of a different ember, then do not assign, else do.
if (!m_ParentEmber && (typeid(T) == typeid(U)))
m_ParentEmber = reinterpret_cast<Ember<T>*>(xform.ParentEmber());
CopyCont(m_Xaos, xform.XaosVec());
CopyCont(m_Motion, xform.m_Motion);
m_Name = xform.m_Name;
return *this;
}
/// <summary>
/// Init default values.
/// Non default values are used to signify an uninitialized state. This is useful for
/// doing motion interpolation where we don't want to apply motion to all fields. By setting
/// unreasonable values before parsing, then only assigning the ones the motion tags specified,
/// it is clear which fields are intended to have motion applied to them.
/// </summary>
/// <param name="useDefaults">Use reasonable default if true, else use out of bounds values.</param>
void Init(bool useDefaults = true)
{
static size_t count = 0;
if (useDefaults)
{
m_Weight = 0;
m_ColorSpeed = static_cast<T>(0.5);
m_Animate = 1;
m_AnimateOrigin = 0;
m_ColorX = static_cast<T>(count & 1);
m_ColorY = 0;
m_DirectColor = 1;
m_Opacity = 1;
m_Affine.A(1);
m_Affine.B(0);
m_Affine.C(0);
m_Affine.D(0);
m_Affine.E(1);
m_Affine.F(0);
m_Post.A(1);
m_Post.B(0);
m_Post.C(0);
m_Post.D(0);
m_Post.E(1);
m_Post.F(0);
m_Wind[0] = 0;
m_Wind[1] = 0;
m_MotionFreq = 0;
m_MotionOffset = 0;
}
else
{
m_Weight = EMPTYFIELD;
m_ColorSpeed = EMPTYFIELD;
m_Animate = EMPTYFIELD;
m_AnimateOrigin = EMPTYFIELD;
m_ColorX = EMPTYFIELD;
m_ColorY = EMPTYFIELD;
m_DirectColor = EMPTYFIELD;
m_Opacity = EMPTYFIELD;
m_Affine.A(EMPTYFIELD);
m_Affine.B(EMPTYFIELD);
m_Affine.C(EMPTYFIELD);
m_Affine.D(EMPTYFIELD);
m_Affine.E(EMPTYFIELD);
m_Affine.F(EMPTYFIELD);
m_Post.A(EMPTYFIELD);
m_Post.B(EMPTYFIELD);
m_Post.C(EMPTYFIELD);
m_Post.D(EMPTYFIELD);
m_Post.E(EMPTYFIELD);
m_Post.F(EMPTYFIELD);
m_Wind[0] = EMPTYFIELD;
m_Wind[1] = EMPTYFIELD;
m_MotionFreq = EMPTYFIELD;
m_MotionOffset = EMPTYFIELD;
}
m_MotionFunc = eMotion::MOTION_SIN;
m_Motion.clear();
m_NeedPrecalcSumSquares = false;
m_NeedPrecalcSqrtSumSquares = false;
m_NeedPrecalcAngles = false;
m_NeedPrecalcAtanXY = false;
m_NeedPrecalcAtanYX = false;
m_HasPre = false;
m_HasPost = false;
m_HasPreOrRegularVars = false;
m_ParentEmber = nullptr;
m_PreVariations.reserve(8);
m_Variations.reserve(8);
m_PostVariations.reserve(8);
CacheColorVals();
count++;
}
/// <summary>
/// Add a pointer to a variation which will be deleted on destruction so the caller should not delete.
/// It also checks if the variation is already present, in which case it doesn't add.
/// If add, set all precalcs.
/// </summary>
/// <param name="variation">Pointer to a varation to add</param>
/// <returns>True if the successful, else false.</returns>
bool AddVariation(Variation<T>* variation)
{
if (variation && (GetVariationById(variation->VariationId()) == nullptr))
{
string name = variation->Name();
bool pre = name.find("pre_") == 0;
bool post = name.find("post_") == 0;
vector<Variation<T>*>* vec;
if (pre)
vec = &m_PreVariations;
else if (post)
vec = &m_PostVariations;
else
vec = &m_Variations;
vec->push_back(variation);
//Flatten must always be last.
for (size_t i = 0; i < vec->size(); i++)
{
if ((i != vec->size() - 1) && ((*vec)[i]->Name().find("flatten") != string::npos))
{
std::swap((*vec)[i], (*vec)[vec->size() - 1]);
break;
}
}
SetPrecalcFlags();
return true;
}
return false;
}
/// <summary>
/// Insert a pointer to a variation, at the specified location, which will be deleted on destruction so the caller should not delete.
/// It also checks if the variation is already present, in which case it doesn't add.
/// If add, set all precalcs.
/// </summary>
/// <param name="variation">Pointer to a varation to add</param>
/// <param name="index">The index to insert at</param>
/// <returns>True if the successful, else false.</returns>
bool InsertVariation(Variation<T>* variation, size_t index)
{
if (variation && (GetVariationById(variation->VariationId()) == nullptr))
{
string name = variation->Name();
bool pre = name.find("pre_") == 0;
bool post = name.find("post_") == 0;
vector<Variation<T>*>* vec;
if (pre)
vec = &m_PreVariations;
else if (post)
vec = &m_PostVariations;
else
vec = &m_Variations;
vec->insert(vec->begin() + index, variation);
//Flatten must always be last.
for (size_t i = 0; i < vec->size(); i++)
{
if ((i != vec->size() - 1) && ((*vec)[i]->Name().find("flatten") != string::npos))
{
std::swap((*vec)[i], (*vec)[vec->size() - 1]);
break;
}
}
SetPrecalcFlags();
return true;
}
return false;
}
/// <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 nullptr.</returns>
Variation<T>* GetVariation(size_t index) const
{
size_t count = 0;
Variation<T>* var = nullptr;
const_cast<Xform<T>*>(this)->AllVarsFunc([&] (vector<Variation<T>*>& variations, bool & keepGoing)
{
for (size_t i = 0; i < variations.size(); i++, count++)
{
if (count == index)
{
var = variations[i];
keepGoing = false;
break;
}
}
});
return var;
}
/// <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 nullptr.</returns>
Variation<T>* GetVariationById(eVariationId id) const
{
Variation<T>* var = nullptr;
const_cast<Xform<T>*>(this)->AllVarsFunc([&] (vector<Variation<T>*>& variations, bool & keepGoing)
{
for (auto v : variations)
{
if (v && v->VariationId() == id)
{
var = v;
keepGoing = false;
break;
}
}
});
return var;
}
/// <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 nullptr.</returns>
Variation<T>* GetVariationByName(const string& name) const
{
Variation<T>* var = nullptr;
const_cast<Xform<T>*>(this)->AllVarsFunc([&] (vector<Variation<T>*>& variations, bool & keepGoing)
{
for (auto v : variations)
{
if (v && v->Name() == name)
{
var = v;
keepGoing = false;
break;
}
}
});
return var;
}
/// <summary>
/// Get the index in the list of the variation pointer.
/// Note this is searching for the exact pointer address and not the name or ID of the variation.
/// </summary>
/// <param name="var">A pointer to the variation to search for</param>
/// <returns>The index of the variation if found, else -1</returns>
intmax_t GetVariationIndex(Variation<T>* var) const
{
intmax_t count = 0, index = -1;
const_cast<Xform<T>*>(this)->AllVarsFunc([&] (vector<Variation<T>*>& variations, bool & keepGoing)
{
for (size_t i = 0; i < variations.size(); i++, count++)
{
if (variations[i] == var)
{
index = count;
keepGoing = false;
break;
}
}
});
return index;
}
/// <summary>
/// Delete the variation with the matching ID.
/// Update precalcs if deletion successful.
/// </summary>
/// <param name="id">The ID to search for</param>
/// <returns>True if deletion successful, else false.</returns>
bool DeleteVariationById(eVariationId id)
{
bool found = false;
AllVarsFunc([&] (vector<Variation<T>*>& variations, bool & keepGoing)
{
for (size_t i = 0; i < variations.size(); i++)
{
if (variations[i] && variations[i]->VariationId() == id)
{
delete variations[i];
variations.erase(variations.begin() + i);
found = true;
}
}
});
if (found)
SetPrecalcFlags();
return found;
}
/// <summary>
/// Remove the variation with the matching ID, but instead of deleting it, return it.
/// Update precalcs if deletion successful.
/// </summary>
/// <param name="id">The ID to search for</param>
/// <returns>The variation if found, else nullptr.</returns>
Variation<T>* RemoveVariationById(eVariationId id)
{
Variation<T>* var = nullptr;
AllVarsFunc([&](vector<Variation<T>*>& variations, bool & keepGoing)
{
for (size_t i = 0; i < variations.size(); i++)
{
if (variations[i] && variations[i]->VariationId() == id)
{
var = variations[i];
variations.erase(variations.begin() + i);
keepGoing = false;
break;
}
}
});
if (var)
SetPrecalcFlags();
return var;
}
/// <summary>
/// Delete the motion elements.
/// </summary>
void DeleteMotionElements()
{
m_Motion.clear();
}
/// <summary>
/// Delete all variations, clear the list and update precalc flags.
/// </summary>
void ClearAndDeleteVariations()
{
AllVarsFunc([&] (vector<Variation<T>*>& variations, bool & keepGoing) { ClearVec<Variation<T>>(variations); });
SetPrecalcFlags();
}
/// <summary>
/// Reset this xform to be totally empty by clearing all variations, resetting both affines to the
/// identity matrix, clearing xaos, color, visibility, wind, animate and setting name
/// to the empty string.
/// Note that this also sets the parent ember to nullptr, so if this xform is reused after calling Clear(),
/// the caller must reset the parent ember to whatever ember they add it to again.
/// </summary>
void Clear()
{
ClearAndDeleteVariations();
DeleteMotionElements();
m_Affine.MakeID();
m_Post.MakeID();
m_Xaos.clear();
m_ParentEmber = nullptr;
m_ColorSpeedCache = 0;
m_OneMinusColorCache = 0;
m_Opacity = 1;
m_Animate = 0;
m_AnimateOrigin = 0;
m_Wind[0] = 0;
m_Wind[1] = 0;
m_Name = "";
}
/// <summary>
/// Compute color cache values: color speed and one minus color speed.
/// </summary>
void CacheColorVals() noexcept
{
//Figure out which is right. //TODO.
//m_ColorSpeedCache = m_ColorX * (1 - m_ColorSpeed) / 2;//Apo style.
//m_OneMinusColorCache = (1 + m_ColorSpeed) / 2;
m_ColorSpeedCache = m_ColorSpeed * m_ColorX;//Flam3 style.
m_OneMinusColorCache = static_cast<T>(1) - m_ColorSpeed;
}
/// <summary>
/// Return the xaos value at the specified index.
/// If the index is out of range, return 1.
/// This has the convenient effect that xaos is not present
/// by default and only has a value if explicitly added.
/// </summary>
/// <param name="i">The xaos index to retrieve</param>
/// <returns>The value at the index if in range, else 1.</returns>
T Xaos(size_t i) const noexcept
{
return i < m_Xaos.size() ? m_Xaos[i] : 1;
}
/// <summary>
/// Set the xaos value for a given xform index.
/// If the index is out of range, a 1 value will be added
/// to the xaos vector repeatedly until it's one less than the
/// requested index in length, then finally add the specified value.
/// </summary>
/// <param name="i">The index to set</param>
/// <param name="val">The xaos value to set it to</param>
void SetXaos(size_t i, T val)
{
if (i < m_Xaos.size())
{
m_Xaos[i] = val;
}
else
{
while (m_Xaos.size() <= i)
m_Xaos.push_back(1);
m_Xaos[i] = val;
}
}
/// <summary>
/// Determine if any xaos value in the vector up to the xform count
/// of the parent ember is anything other than 1.
/// </summary>
/// <returns>True if found, else false.</returns>
bool XaosPresent() const noexcept
{
if (m_ParentEmber)
for (size_t i = 0; i < m_Xaos.size(); i++)
if (i < m_ParentEmber->XformCount())
if (!IsClose<T>(m_Xaos[i], 1))
return true;//If at least one entry is not equal to 1, then xaos is present.
return false;
}
/// <summary>
/// Truncate the xaos vector to match the xform count of the parent ember.
/// </summary>
void TruncateXaos()
{
if (m_ParentEmber)
while (m_Xaos.size() > m_ParentEmber->XformCount())
m_Xaos.pop_back();
}
/// <summary>
/// Remove all xaos from this xform.
/// </summary>
void ClearXaos()
{
m_Xaos.clear();
}
/// <summary>
/// Normalize the variation weights.
/// </summary>
void NormalizeVariationWeights()
{
AllVarsFunc([&] (vector<Variation<T>*>& variations, bool & keepGoing)
{
T norm = 0;
for (auto var : variations) norm += var->m_Weight;
for (auto var : variations) var->m_Weight /= Zeps(norm);//Ensure a divide by zero never happens.
});
}
/// <summary>
/// Applies this xform to the point passed in and saves the result in the out point.
/// It's important to understand what happens here since it's the inner core of the algorithm.
/// See the internal comments for step by step details.
/// </summary>
/// <param name="inPoint">The initial point from the previous iteration</param>
/// <param name="outPoint">The output point</param>
/// <param name="rand">The random context to use</param>
/// <returns>True if a bad value was calculated, else false.</returns>
bool Apply(Point<T>* inPoint, Point<T>* outPoint, QTIsaac<ISAAC_SIZE, ISAAC_INT>& rand)
{
size_t i;
//This must be local, rather than a member, because this function can be called
//from multiple threads. If it were a member, they'd be clobbering each others' values.
IteratorHelper<T> iterHelper;
//Calculate the color coordinate/index in the palette to look up later when accumulating the output point
//to the histogram. Calculate this value by interpolating between the index value of the
//last iteration with the one specified in this xform. Note that some cached values are used
//to reduce the amount of processing.
outPoint->m_Opacity = m_Opacity;
iterHelper.m_Color.x = outPoint->m_ColorX = m_ColorSpeedCache + (m_OneMinusColorCache * inPoint->m_ColorX);
//Compute the pre affine portion of the transform.
//These x, y values are what get passed to the variations below.
//Note that they are not changed after this, except in the case of pre_ variations.
if (m_HasPre)
{
iterHelper.m_TransX = (m_Affine.A() * inPoint->m_X) + (m_Affine.B() * inPoint->m_Y) + m_Affine.C();
iterHelper.m_TransY = (m_Affine.D() * inPoint->m_X) + (m_Affine.E() * inPoint->m_Y) + m_Affine.F();
}
else
{
iterHelper.m_TransX = inPoint->m_X;
iterHelper.m_TransY = inPoint->m_Y;
}
iterHelper.m_TransZ = inPoint->m_Z;
if (m_HasPreOrRegularVars)
{
//Apply pre_ variations, these don't affect outPoint, only iterHelper.m_TransX, Y, Z.
for (i = 0; i < PreVariationCount(); i++)
{
iterHelper.In.x = iterHelper.m_TransX;//Read must be done before every pre variation because transX/Y are changing.
iterHelper.In.y = iterHelper.m_TransY;
iterHelper.In.z = iterHelper.m_TransZ;
m_PreVariations[i]->PrePostPrecalcHelper(iterHelper);//Apply per-variation precalc, the second parameter is unused for pre variations.
m_PreVariations[i]->Func(iterHelper, *outPoint, rand);
WritePre(iterHelper, m_PreVariations[i]->AssignType());
}
if (VariationCount() > 0)
{
//The original calculates sumsq and sumsqrt every time, regardless if they're used or not.
//With Precalc(), only calculate those values if they're needed.
Precalc(iterHelper);//Only need per-xform precalc with regular variations.
iterHelper.In.x = iterHelper.m_TransX;//Only need to read once with regular variations, because transX/Y are fixed.
iterHelper.In.y = iterHelper.m_TransY;
iterHelper.In.z = iterHelper.m_TransZ;
//Since these get summed, initialize them to zero.
outPoint->m_X = outPoint->m_Y = outPoint->m_Z = 0;
//Apply variations to the transformed points, accumulating each time, and store the final value in outPoint.
//Using a virtual function is about 3% faster than using a large case statement like the original did.
//Although research says that using virtual functions is slow, experience says otherwise. They execute
//with the exact same speed as both regular and static member functions.
for (i = 0; i < VariationCount(); i++)
{
m_Variations[i]->Func(iterHelper, *outPoint, rand);
outPoint->m_X += iterHelper.Out.x;
outPoint->m_Y += iterHelper.Out.y;
outPoint->m_Z += iterHelper.Out.z;
}
}
else//Only pre variations are present, no regular ones, so assign the affine transformed points directly to the output points.
{
outPoint->m_X = iterHelper.m_TransX;
outPoint->m_Y = iterHelper.m_TransY;
outPoint->m_Z = iterHelper.m_TransZ;
}
}
//Return the affine transformed points if no variations are present.
//Note this differs from flam3, which would just return zero in that scenario.
else
{
//There are no variations, so the affine transformed points can be assigned directly to the output points.
outPoint->m_X = 0;//(m_Affine.A() * inPoint->m_X) + (m_Affine.B() * inPoint->m_Y) + m_Affine.C();
outPoint->m_Y = 0;//(m_Affine.D() * inPoint->m_X) + (m_Affine.E() * inPoint->m_Y) + m_Affine.F();
outPoint->m_Z = 0;//inPoint->m_Z;
}
//Apply post variations, these will modify outPoint.
for (i = 0; i < PostVariationCount(); i++)
{
iterHelper.In.x = outPoint->m_X;//Read must be done before every post variation because the out point is changing.
iterHelper.In.y = outPoint->m_Y;
iterHelper.In.z = outPoint->m_Z;
m_PostVariations[i]->PrePostPrecalcHelper(iterHelper);//Apply per-variation precalc.
m_PostVariations[i]->Func(iterHelper, *outPoint, rand);
WritePost(iterHelper, *outPoint, m_PostVariations[i]->AssignType());
}
//Optionally apply the post affine transform if it's present.
if (m_HasPost)
{
T postX = outPoint->m_X;
outPoint->m_X = (m_Post.A() * postX) + (m_Post.B() * outPoint->m_Y) + m_Post.C();
outPoint->m_Y = (m_Post.D() * postX) + (m_Post.E() * outPoint->m_Y) + m_Post.F();
}
outPoint->m_ColorX = iterHelper.m_Color.x + m_DirectColor * (outPoint->m_ColorX - iterHelper.m_Color.x);
if (std::isnan(outPoint->m_ColorX))
outPoint->m_ColorX = 0;
//Has the trajectory of x or y gone either to infinity, or too close to zero?
return BadVal(outPoint->m_X) || BadVal(outPoint->m_Y)/* || BadVal(outPoint->m_Z)*/;
}
//Why are we not using template with member var addr as arg here?//TODO
#define APPMOT(x) \
do \
{ \
if (currentMot.x != EMPTYFIELD) \
x += currentMot.x * Interpolater<T>::MotionFuncs(func, freq * (blend + offset)); \
} while (0)
/// <summary>
/// Apply the motion functions from the passed in xform to this xform.
/// </summary>
/// <param name="xform">The xform containing the motion functions</param>
/// <param name="blend">The time blending value 0-1</param>
void ApplyMotion(Xform<T>& xform, T blend)
{
//Loop over the motion elements and add their contribution to the original vals.
for (size_t i = 0; i < xform.m_Motion.size(); i++)
{
//Original only pulls these from the first motion xform which is a bug. Want to pull it from each one.
auto& currentMot = xform.m_Motion[i];
auto freq = currentMot.m_MotionFreq;
auto func = currentMot.m_MotionFunc;
auto offset = currentMot.m_MotionOffset;
auto cleanOffset = offset != EMPTYFIELD ? offset : 0;
//Clamp these to the appropriate range after all are applied.
APPMOT(m_Weight);
APPMOT(m_ColorX);
//APPMOT(m_ColorY);
APPMOT(m_DirectColor);
APPMOT(m_Opacity);
APPMOT(m_ColorSpeed);
APPMOT(m_Animate);
APPMOT(m_AnimateOrigin);
for (size_t j = 0; j < currentMot.TotalVariationCount(); j++)//For each variation in the motion xform.
{
Variation<T>* motVar = currentMot.GetVariation(j);//Get the variation, which may or may not be present in this xform.
ParametricVariation<T>* motParVar = dynamic_cast<ParametricVariation<T>*>(motVar);
Variation<T>* var = GetVariationById(motVar->VariationId());//See if the variation in the motion xform was present in the xform.
if (!var)//It wasn't present, so add it and set the weight.
{
Variation<T>* newVar = motVar->Copy();
newVar->m_Weight = motVar->m_Weight * Interpolater<T>::MotionFuncs(func, freq * (blend + cleanOffset));
if (AddVariation(newVar))
var = newVar;//Use this below for params.
else
delete newVar;
}
else//It was present, so apply the motion func to the weight.
{
var->m_Weight += motVar->m_Weight * Interpolater<T>::MotionFuncs(func, freq * (blend + cleanOffset));
}
//At this point, we've added if needed, or just applied the motion func to the weight.
//Now apply the motion func to the params if needed.
if (var && motParVar)
{
auto parVar = dynamic_cast<ParametricVariation<T>*>(var);
auto params = parVar->Params();
auto motParams = motParVar->Params();
for (size_t k = 0; k < motParVar->ParamCount(); k++)
{
if (!motParams[k].IsPrecalc())
*(params[k].Param()) += motParams[k].ParamVal() * Interpolater<T>::MotionFuncs(func, freq * (blend + cleanOffset));
}
}
}
for (glm::length_t j = 0; j < 2; j++)
{
for (glm::length_t k = 0; k < 3; k++)
{
APPMOT(m_Affine.m_Mat[j][k]);
APPMOT(m_Post.m_Mat[j][k]);
}
}
}
//Make sure certain params are within reasonable bounds.
ClampRef<T>(m_ColorX, 0, 1);
//ClampRef<T>(m_ColorY, 0, 1);
ClampRef<T>(m_DirectColor, 0, 1);
ClampRef<T>(m_Opacity, 0, 1);//Original didn't clamp these, but do it here for correctness.
ClampRef<T>(m_ColorSpeed, -1, 1);
ClampGte0Ref<T>(m_Weight);
}
/// <summary>
/// Accessors.
/// The precalc flags are duplicated in each variation. Each value here
/// is true if any of the variations need it precalculated.
/// </summary>
inline bool NeedPrecalcSumSquares() const noexcept { return m_NeedPrecalcSumSquares; }
inline bool NeedPrecalcSqrtSumSquares() const noexcept { return m_NeedPrecalcSqrtSumSquares; }
inline bool NeedPrecalcAngles() const noexcept { return m_NeedPrecalcAngles; }
inline bool NeedPrecalcAtanXY() const noexcept { return m_NeedPrecalcAtanXY; }
inline bool NeedPrecalcAtanYX() const noexcept { return m_NeedPrecalcAtanYX; }
inline bool NeedAnyPrecalc() const noexcept { return NeedPrecalcSumSquares() || NeedPrecalcSqrtSumSquares() || NeedPrecalcAngles() || NeedPrecalcAtanXY() || NeedPrecalcAtanYX(); }
bool HasPre() const noexcept { return m_HasPre; }
bool HasPost() const noexcept { return m_HasPost; }
size_t PreVariationCount() const noexcept { return m_PreVariations.size(); }
size_t VariationCount() const noexcept { return m_Variations.size(); }
size_t PostVariationCount() const noexcept { return m_PostVariations.size(); }
size_t TotalVariationCount() const noexcept { return PreVariationCount() + VariationCount() + PostVariationCount(); }
bool Empty() const noexcept { return TotalVariationCount() == 0 && m_Affine.IsID(); }//Use this instead of padding like the original did.
T ColorSpeedCache() const noexcept { return m_ColorSpeedCache; }
T OneMinusColorCache() const noexcept { return m_OneMinusColorCache; }
const vector<T>& XaosVec() const noexcept { return m_Xaos; }
Ember<T>* ParentEmber() const noexcept { return m_ParentEmber; }
void ParentEmber(Ember<T>* ember) { m_ParentEmber = ember; }
intmax_t IndexInParentEmber() const noexcept { return m_ParentEmber ? m_ParentEmber->GetTotalXformIndex(const_cast<Xform<T>*>(this)) : -1; }
/// <summary>
/// Set the precalc flags based on whether any variation in the vector needs them.
/// Also call Precalc() virtual function on each variation, which will setup any needed
/// precalcs in parametric variations.
/// Set the parent xform of each variation to this.
/// </summary>
void SetPrecalcFlags()
{
m_NeedPrecalcSumSquares = false;
m_NeedPrecalcSqrtSumSquares = false;
m_NeedPrecalcAngles = false;
m_NeedPrecalcAtanXY = false;
m_NeedPrecalcAtanYX = false;
m_HasPre = !m_Affine.IsID();
m_HasPost = !m_Post.IsID();
m_HasPreOrRegularVars = PreVariationCount() > 0 || VariationCount() > 0;
//Only set precalcs for regular variations, they work differently for pre and post.
for (auto var : m_Variations)
{
if (var->NeedPrecalcSumSquares())
m_NeedPrecalcSumSquares = true;
if (var->NeedPrecalcSqrtSumSquares())
m_NeedPrecalcSqrtSumSquares = true;
if (var->NeedPrecalcAngles())
m_NeedPrecalcAngles = true;
if (var->NeedPrecalcAtanXY())
m_NeedPrecalcAtanXY = true;
if (var->NeedPrecalcAtanYX())
m_NeedPrecalcAtanYX = true;
}
AllVarsFunc([&] (vector<Variation<T>*>& variations, bool & keepGoing)
{
for (auto var : variations)
{
var->ParentXform(this);
var->Precalc();
}
});
}
/// <summary>
/// Based on the precalc flags determined in SetPrecalcFlags(), do the appropriate precalcs.
/// </summary>
/// <param name="helper">The iterator helper to store the precalculated values in</param>
void Precalc(IteratorHelper<T>& helper)
{
if (m_NeedPrecalcSumSquares)
{
helper.m_PrecalcSumSquares = SQR(helper.m_TransX) + SQR(helper.m_TransY);
if (m_NeedPrecalcSqrtSumSquares)
{
helper.m_PrecalcSqrtSumSquares = std::sqrt(helper.m_PrecalcSumSquares);
if (m_NeedPrecalcAngles)
{
helper.m_PrecalcCosa = helper.m_TransX / Zeps(helper.m_PrecalcSqrtSumSquares);
helper.m_PrecalcSina = helper.m_TransY / Zeps(helper.m_PrecalcSqrtSumSquares);
}
}
}
if (m_NeedPrecalcAtanXY)
helper.m_PrecalcAtanxy = std::atan2(helper.m_TransX, helper.m_TransY);
if (m_NeedPrecalcAtanYX)
helper.m_PrecalcAtanyx = std::atan2(helper.m_TransY, helper.m_TransX);
}
/// <summary>
/// Flatten this xform by adding a flatten variation if none is present, and if none of the
/// variations or parameters in the vector are present.
/// </summary>
/// <param name="names">Vector of variation and parameter names that inhibit flattening</param>
/// <returns>True if flatten was added, false if it already was present or if at least one of the specified variations or parameters were present.</returns>
bool Flatten(vector<string>& names)
{
bool shouldFlatten = true;
auto vl = VariationList<T>::Instance();
if (GetVariationById(eVariationId::VAR_FLATTEN) == nullptr)
{
AllVarsFunc([&] (vector<Variation<T>*>& variations, bool & keepGoing)
{
for (auto var : variations)
{
if (var->m_Weight != 0)//This should never happen, but just to be safe.
{
if (FindIf(names, [&] (const string& s) -> bool { return !_stricmp(s.c_str(), var->Name().c_str()); })) //If any variation is present, don't flatten.
{
shouldFlatten = false;
keepGoing = false;
break;
}
}
//Now traverse the parameters for this variation.
if (auto parVar = dynamic_cast<ParametricVariation<T>*>(var))//If any parametric variation parameter is present and non-zero, don't flatten.
{
for (auto& s : names)
{
if (parVar->GetParamVal(s.c_str()) != 0)
{
shouldFlatten = false;
keepGoing = false;
break;
}
}
}
}
});
if (shouldFlatten)//Flatten was not present and neither was any variation name or parameter in the list.
{
auto varflatten = vl->GetVariationCopy(eVariationId::VAR_FLATTEN);
if (!AddVariation(varflatten))
{
delete varflatten;
return false;
}
return true;
}
}
return false;
}
/// <summary>
/// Generate the OpenCL string for reading input values to
/// be passed to a variation.
/// </summary>
/// <param name="varType">Type of the variation these values will be passed to.</param>
/// <returns>The OpenCL string</returns>
string ReadOpenCLString(eVariationType varType)
{
string s;
switch (varType)
{
case eVariationType::VARTYPE_REG:
case eVariationType::VARTYPE_PRE:
s =
"\tvIn.x = transX;\n"
"\tvIn.y = transY;\n"
"\tvIn.z = transZ;\n";
break;
case eVariationType::VARTYPE_POST:
default:
s =
"\tvIn.x = outPoint->m_X;\n"
"\tvIn.y = outPoint->m_Y;\n"
"\tvIn.z = outPoint->m_Z;\n";
break;
}
return s;
}
/// <summary>
/// Assing output values from the result of a pre variation.
/// </summary>
/// <param name="helper">The helper to store the output values in</param>
/// <param name="assignType">The type of assignment this variation uses, assign or sum.</param>
inline void WritePre(IteratorHelper<T>& helper, eVariationAssignType assignType)
{
switch (assignType)
{
case eVariationAssignType::ASSIGNTYPE_SET:
{
helper.m_TransX = helper.Out.x;
helper.m_TransY = helper.Out.y;
helper.m_TransZ = helper.Out.z;
break;
}
case eVariationAssignType::ASSIGNTYPE_SUM:
default:
{
helper.m_TransX += helper.Out.x;
helper.m_TransY += helper.Out.y;
helper.m_TransZ += helper.Out.z;
break;
}
}
}
/// <summary>
/// Assing output values from the result of a post variation.
/// </summary>
/// <param name="helper">The helper to store the output values in</param>
/// <param name="assignType">The type of assignment this variation uses, assign or sum.</param>
inline void WritePost(IteratorHelper<T>& helper, Point<T>& outPoint, eVariationAssignType assignType) noexcept
{
switch (assignType)
{
case eVariationAssignType::ASSIGNTYPE_SET:
{
outPoint.m_X = helper.Out.x;
outPoint.m_Y = helper.Out.y;
outPoint.m_Z = helper.Out.z;
break;
}
case eVariationAssignType::ASSIGNTYPE_SUM:
default:
{
outPoint.m_X += helper.Out.x;
outPoint.m_Y += helper.Out.y;
outPoint.m_Z += helper.Out.z;
break;
}
}
}
/// <summary>
/// Generate the OpenCL string for writing output values from a call to a variation.
/// </summary>
/// <param name="varType">The type of variation these values were calculated from, pre, reg or post.</param>
/// <param name="assignType">The type of assignment used by the variation these values were calculated from, assign or sum.</param>
/// <returns>The OpenCL string</returns>
string WriteOpenCLString(eVariationType varType, eVariationAssignType assignType)
{
string s;
switch (varType)
{
case eVariationType::VARTYPE_REG:
{
s =
"\toutPoint->m_X += vOut.x;\n"
"\toutPoint->m_Y += vOut.y;\n"
"\toutPoint->m_Z += vOut.z;\n";
break;
}
case eVariationType::VARTYPE_PRE:
{
switch (assignType)
{
case eVariationAssignType::ASSIGNTYPE_SET:
{
s =
"\ttransX = vOut.x;\n"
"\ttransY = vOut.y;\n"
"\ttransZ = vOut.z;\n";
break;
}
case eVariationAssignType::ASSIGNTYPE_SUM:
default:
{
s =
"\ttransX += vOut.x;\n"
"\ttransY += vOut.y;\n"
"\ttransZ += vOut.z;\n";
break;
}
}
break;
}
case eVariationType::VARTYPE_POST:
default:
{
switch (assignType)
{
case eVariationAssignType::ASSIGNTYPE_SET:
{
s =
"\toutPoint->m_X = vOut.x;\n"
"\toutPoint->m_Y = vOut.y;\n"
"\toutPoint->m_Z = vOut.z;\n";
break;
}
case eVariationAssignType::ASSIGNTYPE_SUM:
default:
{
s =
"\toutPoint->m_X += vOut.x;\n"
"\toutPoint->m_Y += vOut.y;\n"
"\toutPoint->m_Z += vOut.z;\n";
break;
}
}
break;
}
}
return s;
}
/// <summary>
/// Return a string representation of this xform.
/// It will include all pre affine values, and optionally post affine values if present.
/// Various variables, all variations as strings and xaos values if present.
/// </summary>
/// <returns>The string representation of this xform</returns>
string ToString() const
{
ostringstream ss;
ss << "A: " << m_Affine.A() << " "
<< "B: " << m_Affine.B() << " "
<< "C: " << m_Affine.C() << " "
<< "D: " << m_Affine.D() << " "
<< "E: " << m_Affine.E() << " "
<< "F: " << m_Affine.F() << " \n";
if (m_HasPost)
{
ss << "Post A: " << m_Post.A() << " "
<< "Post B: " << m_Post.B() << " "
<< "Post C: " << m_Post.C() << " "
<< "Post D: " << m_Post.D() << " "
<< "Post E: " << m_Post.E() << " "
<< "Post F: " << m_Post.F() << " \n";
}
ss << "Weight: " << m_Weight;
ss << "\nColorX: " << m_ColorX;
ss << "\nColorY: " << m_ColorY;
ss << "\nDirect Color: " << m_DirectColor;
ss << "\nColor Speed: " << m_ColorSpeed;
ss << "\nAnimate Local Rotation: " << m_Animate;
ss << "\nAnimate Origin Rotation: " << m_AnimateOrigin;
ss << "\nOpacity: " << m_Opacity;
ss << "\nWind: " << m_Wind[0] << ", " << m_Wind[1];
ss << "\nMotion Frequency: " << m_MotionFreq;
ss << "\nMotion Func: " << m_MotionFunc;
ss << "\nMotion Offset: " << m_MotionOffset;
const_cast<Xform<T>*>(this)->AllVarsFunc([&] (vector<Variation<T>*>& variations, bool & keepGoing)
{
for (auto var : variations)
ss << var->ToString() << "\n";
ss << "\n";
});
if (XaosPresent())
{
for (auto xaos : m_Xaos)
ss << xaos << " ";
ss << "\n";
}
return ss.str();
}
/// <summary>
/// Members are listed in the exact order they are used in Apply() to make them
/// as cache efficient as possible. Not all are public, so there is repeated public/private
/// access specifiers.
/// </summary>
private:
bool m_HasPreOrRegularVars;//Whethere there are any pre or regular variations present.
public:
//Color coordinates for this function. This is the index into the palette used to look up a color and add to the histogram for each iter.
//The original only allows for an x coord. Will eventually allow for a y coord like Fractron for 2D palettes.
T m_ColorX, m_ColorY;
private:
T m_ColorSpeedCache;//Cache of m_ColorSpeed * m_ColorX. Need to recalc cache values whenever anything relating to color is set. Made private because one affects the other.
T m_OneMinusColorCache;//Cache of 1 - m_ColorSpeedCache.
public:
//Coefficients for the affine portion of the transform.
//Discussed on page 3 of the paper:
//Fi(x, y) = (aix + biy + ci, dix + eiy + fi)
Affine2D<T> m_Affine;
private:
vector<Variation<T>*> m_PreVariations;//The list of pre variations to call when applying this xform.
vector<Variation<T>*> m_Variations;//The list of variations to call when applying this xform.
bool m_HasPre;//Whether a pre affine transform is present.
bool m_HasPost;//Whether a post affine transform is present.
public:
//Coefficients for the affine portion of the post transform.
//Discussed on page 5 of the paper:
//Pi(x, y) = (αix + βiy + γi, δix + ǫiy + ζi).
Affine2D<T> m_Post;
private:
vector<Variation<T>*> m_PostVariations;//The list of post variations to call when applying this xform.
public:
T m_DirectColor;//Used with direct color variations.
//Probability that this function is chosen. Can be greater than 1.
//Discussed on page 4 of the paper:
//Probability wi.
T m_Weight;
//Scaling factor on color added to current iteration, also known as color weight. Normally defaults to 0.5.
//Discussed on page 9 of the paper with a hard coded default value of 0.5:
//C = (C + Ci) * m_ColorSpeed.
T m_ColorSpeed;
T m_Opacity;//How much of this xform is seen. Range: 0.0 (invisible) - 1.0 (totally visible).
T m_Animate;//Whether or not this xform rotates around its center during animation. 0 means stationary, > 0 means rotate. Use T instead of bool so it can be interpolated.
T m_AnimateOrigin;//Same, but rotate around the global origin.
T m_Wind[2];
eMotion m_MotionFunc;
T m_MotionFreq;
T m_MotionOffset;
vector<Xform<T>> m_Motion;
string m_Name;
private:
/// <summary>
/// Perform an operation on all variation vectors.
/// The operation is supplied in the func parameter.
/// To stop performing the operation on vectors after the current one,
/// set the keepGoing parameter to false;
/// </summary>
/// <param name="func">The function to call for each variation vector.</param>
void AllVarsFunc(std::function<void (vector<Variation<T>*>&, bool&)> func)
{
bool keepGoing = true;
func(m_PreVariations, keepGoing);
if (keepGoing)
func(m_Variations, keepGoing);
if (keepGoing)
func(m_PostVariations, keepGoing);
}
vector<T> m_Xaos;//Xaos vector which affects the probability that this xform is chosen. Usually empty.
Ember<T>* m_ParentEmber;//The parent ember that contains this xform.
bool m_NeedPrecalcSumSquares;//Whether any variation uses the precalc sum squares value in its calculations.
bool m_NeedPrecalcSqrtSumSquares;//Whether any variation uses the sqrt precalc sum squares value in its calculations.
bool m_NeedPrecalcAngles;//Whether any variation uses the precalc sin and cos values in its calculations.
bool m_NeedPrecalcAtanXY;//Whether any variation uses the precalc atan XY value in its calculations.
bool m_NeedPrecalcAtanYX;//Whether any variation uses the precalc atan YX value in its calculations.
};
}