#pragma once #include "VariationList.h" #include "Interpolate.h" /// /// Xform class. /// namespace EmberNs { /// /// 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. /// template class Ember; /// /// If both polymorphism and templating are needed, uncomment this, fill it out and derive from it. /// //class EMBER_API XformBase //{ //}; /// /// 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. /// template class EMBER_API Xform { public: /// /// 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. /// /// Use reasonable default if true, else use out of bounds values. Xform(bool useDefaults = true) { Init(useDefaults); } /// /// Constructor that takes default arguments. Mostly used for testing. /// Post affine is defaulted to the identity matrix. /// /// The probability that this xform is chosen /// The color index /// The color speed /// The opacity /// The a value of the pre affine transform /// The d value of the pre affine transform /// The b value of the pre affine transform /// The e value of the pre affine transform /// The c value of the pre affine transform /// The f value of the pre affine transform /// The a value of the post affine transform. Default: 1. /// The d value of the post affine transform. Default: 0. /// The b value of the post affine transform. Default: 0. /// The e value of the post affine transform. Default: 1. /// The c value of the post affine transform. Default: 0. /// The f value of the post affine transform. Default: 0. 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. } /// /// Default copy constructor. /// /// The Xform object to copy Xform(const Xform& xform) : m_ParentEmber(nullptr)//Hack. { Xform::operator=(xform); } /// /// Copy constructor to copy an Xform object of type U. /// /// The Xform object to copy template Xform(const Xform& xform) : m_ParentEmber(nullptr)//Hack. { Xform::operator=(xform); } /// /// Deletes each element of the variation vector and clears it. /// ~Xform() { ClearAndDeleteVariations(); } /// /// Default assignment operator. /// /// The Xform object to copy Xform& operator = (const Xform& xform) { if (this != &xform) Xform::operator=(xform); return *this; } /// /// 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. /// /// The Xform object to copy. /// Reference to updated self template Xform& operator = (const Xform& xform) { m_Affine = xform.m_Affine; m_Post = xform.m_Post; m_Weight = static_cast(xform.m_Weight); m_ColorX = static_cast(xform.m_ColorX); m_ColorY = static_cast(xform.m_ColorY); m_DirectColor = static_cast(xform.m_DirectColor); m_ColorSpeed = static_cast(xform.m_ColorSpeed); m_Animate = static_cast(xform.m_Animate); m_Opacity = static_cast(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(xform.m_Wind[0]); m_Wind[1] = static_cast(xform.m_Wind[1]); m_MotionFreq = static_cast(xform.m_MotionFreq); m_MotionFunc = xform.m_MotionFunc; m_MotionOffset = static_cast(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* var = nullptr; if (Variation* 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*>(xform.ParentEmber()); CopyCont(m_Xaos, xform.XaosVec()); CopyCont(m_Motion, xform.m_Motion); m_Name = xform.m_Name; return *this; } /// /// 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. /// /// Use reasonable default if true, else use out of bounds values. void Init(bool useDefaults = true) { static size_t count = 0; if (useDefaults) { m_Weight = 0; m_ColorSpeed = static_cast(0.5); m_Animate = 1; m_ColorX = static_cast(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_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++; } /// /// 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. /// /// Pointer to a varation to add /// True if the successful, else false. bool AddVariation(Variation* variation) { if (variation && (GetVariationById(variation->VariationId()) == nullptr)) { string name = variation->Name(); bool pre = name.find("pre_") == 0; bool post = name.find("post_") == 0; vector*>* 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; } /// /// 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. /// /// Pointer to a varation to add /// The index to insert at /// True if the successful, else false. bool InsertVariation(Variation* 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*>* 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; } /// /// Get a pointer to the variation at the specified index. /// /// The index in the list to retrieve /// A pointer to the variation at the index if in range, else nullptr. Variation* GetVariation(size_t index) const { size_t count = 0; Variation* var = nullptr; const_cast*>(this)->AllVarsFunc([&] (vector*>& variations, bool & keepGoing) { for (size_t i = 0; i < variations.size(); i++, count++) { if (count == index) { var = variations[i]; keepGoing = false; break; } } }); return var; } /// /// Get a pointer to the variation with the specified ID. /// /// The ID to search for /// A pointer to the variation if found, else nullptr. Variation* GetVariationById(eVariationId id) const { Variation* var = nullptr; const_cast*>(this)->AllVarsFunc([&] (vector*>& variations, bool & keepGoing) { for (auto v : variations) { if (v && v->VariationId() == id) { var = v; keepGoing = false; break; } } }); return var; } /// /// Get a pointer to the variation with the specified name. /// /// The name to search for /// A pointer to the variation if found, else nullptr. Variation* GetVariationByName(const string& name) const { Variation* var = nullptr; const_cast*>(this)->AllVarsFunc([&] (vector*>& variations, bool & keepGoing) { for (auto v : variations) { if (v && v->Name() == name) { var = v; keepGoing = false; break; } } }); return var; } /// /// 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. /// /// A pointer to the variation to search for /// The index of the variation if found, else -1 intmax_t GetVariationIndex(Variation* var) const { intmax_t count = 0, index = -1; const_cast*>(this)->AllVarsFunc([&] (vector*>& variations, bool & keepGoing) { for (size_t i = 0; i < variations.size(); i++, count++) { if (variations[i] == var) { index = count; keepGoing = false; break; } } }); return index; } /// /// Delete the variation with the matching ID. /// Update precalcs if deletion successful. /// /// The ID to search for /// True if deletion successful, else false. bool DeleteVariationById(eVariationId id) { bool found = false; AllVarsFunc([&] (vector*>& 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; } /// /// Remove the variation with the matching ID, but instead of deleting it, return it. /// Update precalcs if deletion successful. /// /// The ID to search for /// The variation if found, else nullptr. Variation* RemoveVariationById(eVariationId id) { Variation* var = nullptr; AllVarsFunc([&](vector*>& 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; } /// /// Delete the motion elements. /// void DeleteMotionElements() { m_Motion.clear(); } /// /// Delete all variations, clear the list and update precalc flags. /// void ClearAndDeleteVariations() { AllVarsFunc([&] (vector*>& variations, bool & keepGoing) { ClearVec>(variations); }); SetPrecalcFlags(); } /// /// 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. /// 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_Wind[0] = 0; m_Wind[1] = 0; m_Name = ""; } /// /// Compute color cache values: color speed and one minus color speed. /// 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(1) - m_ColorSpeed; } /// /// 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. /// /// The xaos index to retrieve /// The value at the index if in range, else 1. T Xaos(size_t i) const noexcept { return i < m_Xaos.size() ? m_Xaos[i] : 1; } /// /// 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. /// /// The index to set /// The xaos value to set it to 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; } } /// /// Determine if any xaos value in the vector up to the xform count /// of the parent ember is anything other than 1. /// /// True if found, else false. bool XaosPresent() const noexcept { if (m_ParentEmber) for (size_t i = 0; i < m_Xaos.size(); i++) if (i < m_ParentEmber->XformCount()) if (!IsClose(m_Xaos[i], 1)) return true;//If at least one entry is not equal to 1, then xaos is present. return false; } /// /// Truncate the xaos vector to match the xform count of the parent ember. /// void TruncateXaos() { if (m_ParentEmber) while (m_Xaos.size() > m_ParentEmber->XformCount()) m_Xaos.pop_back(); } /// /// Remove all xaos from this xform. /// void ClearXaos() { m_Xaos.clear(); } /// /// Normalize the variation weights. /// void NormalizeVariationWeights() { AllVarsFunc([&] (vector*>& 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. }); } /// /// 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. /// /// The initial point from the previous iteration /// The output point /// The random context to use /// True if a bad value was calculated, else false. bool Apply(Point* inPoint, Point* outPoint, QTIsaac& 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 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::MotionFuncs(func, freq * (blend + offset)); \ } while (0) /// /// Apply the motion functions from the passed in xform to this xform. /// /// The xform containing the motion functions /// The time blending value 0-1 void ApplyMotion(Xform& 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); for (size_t j = 0; j < currentMot.TotalVariationCount(); j++)//For each variation in the motion xform. { Variation* motVar = currentMot.GetVariation(j);//Get the variation, which may or may not be present in this xform. ParametricVariation* motParVar = dynamic_cast*>(motVar); Variation* 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* newVar = motVar->Copy(); newVar->m_Weight = motVar->m_Weight * Interpolater::MotionFuncs(func, freq * (blend + cleanOffset)); AddVariation(newVar); var = newVar;//Use this below for params. } else//It was present, so apply the motion func to the weight. { var->m_Weight += motVar->m_Weight * Interpolater::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 (motParVar) { auto parVar = dynamic_cast*>(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::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(m_ColorX, 0, 1); //ClampRef(m_ColorY, 0, 1); ClampRef(m_DirectColor, 0, 1); ClampRef(m_Opacity, 0, 1);//Original didn't clamp these, but do it here for correctness. ClampRef(m_ColorSpeed, -1, 1); ClampGte0Ref(m_Weight); } /// /// Accessors. /// The precalc flags are duplicated in each variation. Each value here /// is true if any of the variations need it precalculated. /// 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& XaosVec() const noexcept { return m_Xaos; } Ember* ParentEmber() const noexcept { return m_ParentEmber; } void ParentEmber(Ember* ember) { m_ParentEmber = ember; } intmax_t IndexInParentEmber() const noexcept { return m_ParentEmber ? m_ParentEmber->GetTotalXformIndex(const_cast*>(this)) : -1; } /// /// 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. /// 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*>& variations, bool & keepGoing) { for (auto var : variations) { var->ParentXform(this); var->Precalc(); } }); } /// /// Based on the precalc flags determined in SetPrecalcFlags(), do the appropriate precalcs. /// /// The iterator helper to store the precalculated values in void Precalc(IteratorHelper& 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); } /// /// 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. /// /// Vector of variation and parameter names that inhibit flattening /// True if flatten was added, false if it already was present or if at least one of the specified variations or parameters were present. bool Flatten(vector& names) { bool shouldFlatten = true; auto vl = VariationList::Instance(); if (GetVariationById(eVariationId::VAR_FLATTEN) == nullptr) { AllVarsFunc([&] (vector*>& 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*>(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; } /// /// Generate the OpenCL string for reading input values to /// be passed to a variation. /// /// Type of the variation these values will be passed to. /// The OpenCL string 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; } /// /// Assing output values from the result of a pre variation. /// /// The helper to store the output values in /// The type of assignment this variation uses, assign or sum. inline void WritePre(IteratorHelper& 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; } } } /// /// Assing output values from the result of a post variation. /// /// The helper to store the output values in /// The type of assignment this variation uses, assign or sum. inline void WritePost(IteratorHelper& helper, Point& 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; } } } /// /// Generate the OpenCL string for writing output values from a call to a variation. /// /// The type of variation these values were calculated from, pre, reg or post. /// The type of assignment used by the variation these values were calculated from, assign or sum. /// The OpenCL string 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; } /// /// 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. /// /// The string representation of this xform 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: " << m_Animate; 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*>(this)->AllVarsFunc([&] (vector*>& 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(); } /// /// 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. /// 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 m_Affine; private: vector*> m_PreVariations;//The list of pre variations to call when applying this xform. vector*> 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 m_Post; private: vector*> 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 during animation. 0 means stationary, > 0 means rotate. Use T instead of bool so it can be interpolated. T m_Wind[2]; eMotion m_MotionFunc; T m_MotionFreq; T m_MotionOffset; vector> m_Motion; string m_Name; private: /// /// 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; /// /// The function to call for each variation vector. void AllVarsFunc(std::function*>&, bool&)> func) { bool keepGoing = true; func(m_PreVariations, keepGoing); if (keepGoing) func(m_Variations, keepGoing); if (keepGoing) func(m_PostVariations, keepGoing); } vector m_Xaos;//Xaos vector which affects the probability that this xform is chosen. Usually empty. Ember* 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. }; }