#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. }; }