--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.
This commit is contained in:
Person
2024-03-16 10:15:51 -06:00
parent 26e075def5
commit a0a205edd8
22 changed files with 864 additions and 857 deletions

View File

@ -163,6 +163,14 @@ public:
m_CurveDE = static_cast<T>(ember.m_CurveDE);
m_SpatialFilterType = ember.m_SpatialFilterType;
m_SpatialFilterRadius = static_cast<T>(ember.m_SpatialFilterRadius);
m_Stagger = ember.m_Stagger;
m_Rotations = ember.m_Rotations;
m_SecondsPerRotation = ember.m_SecondsPerRotation;
m_RotateXformsCw = ember.m_RotateXformsCw;
m_BlendSeconds = ember.m_BlendSeconds;
m_RotationsPerBlend = ember.m_RotationsPerBlend;
m_BlendRotateXformsCw = ember.m_BlendRotateXformsCw;
m_Linear = ember.m_Linear;
m_TemporalFilterType = ember.m_TemporalFilterType;
m_TemporalFilterExp = static_cast<T>(ember.m_TemporalFilterExp);
m_TemporalFilterWidth = static_cast<T>(ember.m_TemporalFilterWidth);
@ -171,6 +179,7 @@ public:
m_Name = ember.m_Name;
m_ParentFilename = ember.m_ParentFilename;
m_Index = ember.m_Index;
//m_Animations = ember.m_Animations;
m_ScaleType = ember.ScaleType();
m_Palette = ember.m_Palette;
m_Curves = ember.m_Curves;
@ -193,6 +202,7 @@ public:
{
m_FinalXform.m_Motion.clear();
m_FinalXform.m_Animate = 0;
m_FinalXform.m_AnimateOrigin = 0;
m_FinalXform.m_ColorSpeed = 0;
}
@ -303,6 +313,7 @@ public:
ember.m_FinalXform.m_Affine.MakeID();
ember.m_FinalXform.m_Post.MakeID();
ember.m_FinalXform.m_Animate = 0;
ember.m_FinalXform.m_AnimateOrigin = 0;
ember.m_FinalXform.m_ColorSpeed = 0;
ember.m_FinalXform.m_Motion.clear();
ember.m_FinalXform.ClearAndDeleteVariations();
@ -925,11 +936,12 @@ public:
}
}
InterpXform<&Xform<T>::m_Weight> (thisXform, i, embers, coefs, size);
InterpXform<&Xform<T>::m_ColorX> (thisXform, i, embers, coefs, size);
InterpXform<&Xform<T>::m_ColorSpeed>(thisXform, i, embers, coefs, size);
InterpXform<&Xform<T>::m_Opacity> (thisXform, i, embers, coefs, size);
InterpXform<&Xform<T>::m_Animate> (thisXform, i, embers, coefs, size);
InterpXform<&Xform<T>::m_Weight> (thisXform, i, embers, coefs, size);
InterpXform<&Xform<T>::m_ColorX> (thisXform, i, embers, coefs, size);
InterpXform<&Xform<T>::m_ColorSpeed> (thisXform, i, embers, coefs, size);
InterpXform<&Xform<T>::m_Opacity> (thisXform, i, embers, coefs, size);
InterpXform<&Xform<T>::m_Animate> (thisXform, i, embers, coefs, size);
InterpXform<&Xform<T>::m_AnimateOrigin> (thisXform, i, embers, coefs, size);
ClampGte0Ref(thisXform->m_Weight);
ClampRef<T>(thisXform->m_ColorX, 0, 1);
ClampRef<T>(thisXform->m_ColorSpeed, -1, 1);
@ -1077,15 +1089,17 @@ public:
while (auto xform = GetTotalXform(i++))//Flam3 only allowed animation with normal xforms. This has been changed to allow animations of final xforms.
{
//Don't rotate xforms with animate set to 0.
if (xform->m_Animate == 0)
continue;
//Assume that if there are no variations, then it's a padding xform.
if (xform->Empty() && m_AffineInterp != eAffineInterp::AFFINE_INTERP_LOG)
continue;
xform->m_Affine.Rotate(angle * DEG_2_RAD_T);
//Don't rotate xforms with animate set to 0.
if (xform->m_Animate != 0)
xform->m_Affine.Rotate(angle * DEG_2_RAD_T);
if (xform->m_AnimateOrigin != 0)
xform->m_Affine.RotateTrans(angle * DEG_2_RAD_T);
//Don't rotate post.
}
}
@ -1139,6 +1153,7 @@ public:
m_Xforms[i].m_Weight = 1;
m_Xforms[i].m_ColorSpeed = 0;
m_Xforms[i].m_Animate = 0;
m_Xforms[i].m_AnimateOrigin = 0;
m_Xforms[i].m_ColorX = 1;
m_Xforms[i].m_ColorY = 1;//Added in case 2D palette support is ever added.
m_Xforms[i].m_Affine.A(-1);
@ -1162,6 +1177,7 @@ public:
m_Xforms[i].m_Weight = 1;
m_Xforms[i].m_ColorSpeed = 0;
m_Xforms[i].m_Animate = 0;
m_Xforms[i].m_AnimateOrigin = 0;
m_Xforms[i].m_ColorX = m_Xforms[i].m_ColorY = (sym < 3) ? 0 : (static_cast<T>(k - 1) / static_cast<T>(sym - 2));//Added Y.
m_Xforms[i].m_Affine.A(Round6(std::cos(k * a)));
m_Xforms[i].m_Affine.D(Round6(std::sin(k * a)));
@ -1409,6 +1425,10 @@ public:
APP_FMP(m_BlurCurve);
break;
case eEmberMotionParam::FLAME_MOTION_SCALE:
APP_FMP(m_Zoom);
break;
case eEmberMotionParam::FLAME_MOTION_NONE:
default:
break;
@ -1445,8 +1465,8 @@ public:
m_HighlightPower = 1;
m_K2 = 0;
m_Background.Reset();
m_FinalRasW = 100;
m_FinalRasH = 100;
m_FinalRasW = 1920;
m_FinalRasH = 1080;
m_Supersample = 1;
m_SpatialFilterRadius = static_cast<T>(0.5);
m_Zoom = 0;
@ -1470,11 +1490,19 @@ public:
m_TemporalSamples = 100;
m_SpatialFilterType = eSpatialFilterType::GAUSSIAN_SPATIAL_FILTER;
m_AffineInterp = eAffineInterp::AFFINE_INTERP_LOG;
m_PaletteMode = ePaletteMode::PALETTE_LINEAR;
m_Interp = eInterp::EMBER_INTERP_SMOOTH;
m_Stagger = 0;
m_Rotations = 1;
m_SecondsPerRotation = 3;
m_RotateXformsCw = false;
m_BlendSeconds = 3;
m_RotationsPerBlend = 0;
m_BlendRotateXformsCw = false;
m_Linear = false;
m_TemporalFilterType = eTemporalFilterType::BOX_TEMPORAL_FILTER;
m_TemporalFilterWidth = 1;
m_TemporalFilterExp = 1;
m_PaletteMode = ePaletteMode::PALETTE_LINEAR;
m_Interp = eInterp::EMBER_INTERP_SMOOTH;
}
else
{
@ -1507,11 +1535,19 @@ public:
m_TemporalSamples = 0;
m_SpatialFilterType = eSpatialFilterType::GAUSSIAN_SPATIAL_FILTER;
m_AffineInterp = eAffineInterp::AFFINE_INTERP_LOG;
m_PaletteMode = ePaletteMode::PALETTE_STEP;
m_Interp = eInterp::EMBER_INTERP_LINEAR;
m_Stagger = 999999;
m_Rotations = 999999;
m_SecondsPerRotation = 999999;
m_RotateXformsCw = false;
m_BlendSeconds = 999999;
m_RotationsPerBlend = 999999;
m_BlendRotateXformsCw = false;
m_Linear = false;
m_TemporalFilterType = eTemporalFilterType::BOX_TEMPORAL_FILTER;
m_TemporalFilterWidth = -1;
m_TemporalFilterExp = -999;
m_PaletteMode = ePaletteMode::PALETTE_STEP;
m_Interp = eInterp::EMBER_INTERP_LINEAR;
}
m_Xforms.clear();
@ -1578,6 +1614,13 @@ public:
<< "DE Curve: " << m_CurveDE << "\n"
<< "Spatial Filter Type: " << m_SpatialFilterType << "\n"
<< "Spatial Filter Radius: " << m_SpatialFilterRadius << "\n"
<< "Stagger: " << m_Stagger << "\n"
<< "Rotations: " << m_Rotations << "\n"
<< "Seconds Per Rotation: " << m_SecondsPerRotation << "\n"
<< "Rotate Xforms Clockwise: " << m_RotateXformsCw << "\n"
<< "Blend Seconds: " << m_BlendSeconds << "\n"
<< "Rotations Per Blend: " << m_RotationsPerBlend << "\n"
<< "Blend Rotate Xforms Clockwise: " << m_BlendRotateXformsCw << "\n"
<< "Temporal Filter Type: " << m_TemporalFilterType << "\n"
<< "Temporal Filter Width: " << m_TemporalFilterWidth << "\n"
<< "Temporal Filter Exp: " << m_TemporalFilterExp << "\n"
@ -1754,20 +1797,6 @@ public:
//Xml field: "palette_interpolation".
ePaletteInterp m_PaletteInterp = ePaletteInterp::INTERP_HSV;
//Temporal Filter.
//Only used if temporal filter type is exp, else unused.
//Xml field: "temporal_filter_exp".
T m_TemporalFilterExp = 1;
//The width of the temporal filter.
//Xml field: "temporal_filter_width".
T m_TemporalFilterWidth = 1;
//The type of the temporal filter: Gaussian, Box or Exp.
//Xml field: "temporal_filter_type".
eTemporalFilterType m_TemporalFilterType = eTemporalFilterType::BOX_TEMPORAL_FILTER;
//Density Estimation Filter.
//The minimum radius of the DE filter.
@ -1825,6 +1854,30 @@ public:
//The 0-based position of this ember in the file it was contained in.
size_t m_Index = 0;
//The parameters for animating.
double m_Stagger = 0;
double m_Rotations = 1;
double m_SecondsPerRotation = 3;
bool m_RotateXformsCw = false;
double m_BlendSeconds = 3;
size_t m_RotationsPerBlend = 0;//Could this ever be double?
bool m_BlendRotateXformsCw = false;
bool m_Linear = false;
//Temporal Filter.
//Only used if temporal filter type is exp, else unused.
//Xml field: "temporal_filter_exp".
T m_TemporalFilterExp = 1;
//The width of the temporal filter.
//Xml field: "temporal_filter_width".
T m_TemporalFilterWidth = 1;
//The type of the temporal filter: Gaussian, Box or Exp.
//Xml field: "temporal_filter_type".
eTemporalFilterType m_TemporalFilterType = eTemporalFilterType::BOX_TEMPORAL_FILTER;
//The list of motion elements for the top-level flame params
vector<EmberMotion<T>> m_EmberMotionElements;

View File

@ -169,7 +169,8 @@ enum class eEmberMotionParam : et//These must remain in this order forever.
FLAME_MOTION_BACKGROUND_G,
FLAME_MOTION_BACKGROUND_B,
FLAME_MOTION_VIBRANCY,
FLAME_MOTION_BLUR_CURVE
FLAME_MOTION_BLUR_CURVE,
FLAME_MOTION_SCALE
};
/// <summary>

View File

@ -158,6 +158,14 @@ string EmberToXml<T>::ToString(Ember<T>& ember, const string& extraAttributes, s
os << " cam_pitch=\"" << ember.m_CamPitch << "\"";
os << " cam_dof=\"" << ember.m_CamDepthBlur << "\"";
os << " blur_curve=\"" << ember.m_BlurCurve << "\"";
os << " stagger=\"" << ember.m_Stagger << "\"";
os << " rotations=\"" << ember.m_Rotations << "\"";
os << " seconds_per_rotation=\"" << ember.m_SecondsPerRotation << "\"";
os << " rotate_xforms_cw=\"" << ember.m_RotateXformsCw << "\"";
os << " blend_seconds=\"" << ember.m_BlendSeconds << "\"";
os << " rotations_per_blend=\"" << ember.m_RotationsPerBlend << "\"";
os << " blend_rotate_xforms_cw=\"" << ember.m_BlendRotateXformsCw << "\"";
os << " linear_blend=\"" << ember.m_Linear << "\"";
if (ember.m_PaletteMode == ePaletteMode::PALETTE_STEP)
os << " palette_mode=\"step\"";
@ -544,6 +552,7 @@ string EmberToXml<T>::ToString(Xform<T>& xform, size_t xformCount, bool isFinal,
std::replace(s.begin(), s.end(), ' ', '_');
os << "name=\"" << s << "\" ";//Flam3 didn't do this, but Apo does.
os << "animate=\"" << xform.m_Animate << "\" ";//Flam3 only did this for non-final. Ember supports animating final.
os << "animate_origin=\"" << xform.m_AnimateOrigin << "\" ";
}
//Variation writing order differs slightly from the original to make it a bit more readable.
@ -872,6 +881,10 @@ string EmberToXml<T>::ToString(const EmberMotion<T>& motion)
os << " vibrancy=\"" << motion.m_MotionParams[i].second << "\"";
break;
case eEmberMotionParam::FLAME_MOTION_SCALE:
os << " scale=\"" << motion.m_MotionParams[i].second << "\"";
break;
case eEmberMotionParam::FLAME_MOTION_NONE:
default:
break;

View File

@ -832,8 +832,10 @@ public:
//If this is an asymmetric case, store the NON-symmetric angle
//Check them pairwise and store the reference angle in the second
//to avoid overwriting if asymmetric on both sides.
sym0 = (embers[k - 1].GetXform(xfi)->m_Animate == 0 || (embers[k - 1].GetXform(xfi)->Empty() && padSymFlag));
sym1 = (embers[k ].GetXform(xfi)->m_Animate == 0 || (embers[k ].GetXform(xfi)->Empty() && padSymFlag));
auto xfk = embers[k].GetXform(xfi);
auto xfkm1 = embers[k - 1].GetXform(xfi);
sym0 = ((xfkm1->m_Animate == 0 && xfkm1->m_AnimateOrigin == 0) || (xfkm1->Empty() && padSymFlag));
sym1 = ((xfk->m_Animate == 0 && xfk->m_AnimateOrigin == 0) || (xfk->Empty() && padSymFlag));
if (sym1 && !sym0)
embers[k].GetXform(xfi)->m_Wind[col] = cxang[k - 1][col] + 2 * T(M_PI);

View File

@ -656,6 +656,7 @@ public:
xform->m_ColorY = m_Rand.Frand01<T>();//Will need to update this if 2D coordinates are ever supported.
xform->m_ColorSpeed = T(0.5);
xform->m_Animate = 1;
xform->m_AnimateOrigin = T(m_Rand.RandBit());
xform->m_Affine.A(m_Rand.Frand11<T>());
xform->m_Affine.B(m_Rand.Frand11<T>());
xform->m_Affine.C(m_Rand.Frand11<T>());
@ -865,8 +866,8 @@ public:
{
auto& p = m_FinalImage[i];
m_Hist[static_cast<size_t>((p.r * res) +
(p.g * res) * res +
(p.b * res) * res * res)]++;//A specific histogram index representing the sum of R,G,B values.
(p.g * res) * res +
(p.b * res) * res * res)]++;//A specific histogram index representing the sum of R,G,B values.
}
for (i = 0; i < res3; i++)

View File

@ -154,6 +154,7 @@ public:
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();
@ -209,6 +210,7 @@ public:
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;
@ -235,6 +237,7 @@ public:
m_Weight = EMPTYFIELD;
m_ColorSpeed = EMPTYFIELD;
m_Animate = EMPTYFIELD;
m_AnimateOrigin = EMPTYFIELD;
m_ColorX = EMPTYFIELD;
m_ColorY = EMPTYFIELD;
m_DirectColor = EMPTYFIELD;
@ -547,6 +550,7 @@ public:
m_OneMinusColorCache = 0;
m_Opacity = 1;
m_Animate = 0;
m_AnimateOrigin = 0;
m_Wind[0] = 0;
m_Wind[1] = 0;
m_Name = "";
@ -800,6 +804,7 @@ public:
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.
{
@ -811,8 +816,11 @@ public:
{
Variation<T>* newVar = motVar->Copy();
newVar->m_Weight = motVar->m_Weight * Interpolater<T>::MotionFuncs(func, freq * (blend + cleanOffset));
AddVariation(newVar);
var = newVar;//Use this below for params.
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.
{
@ -821,7 +829,7 @@ public:
//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)
if (var && motParVar)
{
auto parVar = dynamic_cast<ParametricVariation<T>*>(var);
auto params = parVar->Params();
@ -1213,7 +1221,8 @@ public:
ss << "\nColorY: " << m_ColorY;
ss << "\nDirect Color: " << m_DirectColor;
ss << "\nColor Speed: " << m_ColorSpeed;
ss << "\nAnimate: " << m_Animate;
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;
@ -1290,7 +1299,8 @@ public:
//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_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;

View File

@ -1389,6 +1389,7 @@ bool XmlToEmber<T>::ParseEmberElementFromChaos(xmlNode* emberNode, Ember<T>& cur
{
finalXform.m_Weight = 0;
finalXform.m_Animate = 0;//Do not animate final by default.
finalXform.m_AnimateOrigin = 0;
finalXform.m_ColorX = finalXform.m_ColorY = 0;//Chaotica does not support any kind of coloring for final xforms, opacity remains 1 though.
finalXform.m_ColorSpeed = 0;
currentEmber.SetFinalXform(finalXform);
@ -1615,6 +1616,14 @@ bool XmlToEmber<T>::ParseEmberElement(xmlNode* emberNode, Ember<T>& currentEmber
else if (ParseAndAssign(curAtt->name, attStr, "rand_range", currentEmber.m_RandPointRange, ret)) {}
else if (ParseAndAssign(curAtt->name, attStr, "soloxform", soloXform, ret)) {}
else if (ParseAndAssign(curAtt->name, attStr, "new_linear", newLinear, ret)) {}
else if (ParseAndAssign(curAtt->name, attStr, "stagger", currentEmber.m_Stagger, ret)) {}
else if (ParseAndAssign(curAtt->name, attStr, "rotations", currentEmber.m_Rotations, ret)) {}
else if (ParseAndAssign(curAtt->name, attStr, "seconds_per_rotation", currentEmber.m_SecondsPerRotation, ret)) {}
else if (ParseAndAssign(curAtt->name, attStr, "rotate_xforms_cw", currentEmber.m_RotateXformsCw, ret)) {}
else if (ParseAndAssign(curAtt->name, attStr, "blend_seconds", currentEmber.m_BlendSeconds, ret)) {}
else if (ParseAndAssign(curAtt->name, attStr, "rotations_per_blend", currentEmber.m_RotationsPerBlend, ret)) {}
else if (ParseAndAssign(curAtt->name, attStr, "blend_rotate_xforms_cw", currentEmber.m_BlendRotateXformsCw, ret)) {}
else if (ParseAndAssign(curAtt->name, attStr, "linear_blend", currentEmber.m_Linear, ret)) {}
//Parse more complicated reads that have multiple possible values.
else if (!Compare(curAtt->name, "interpolation"))
{
@ -1818,6 +1827,9 @@ bool XmlToEmber<T>::ParseEmberElement(xmlNode* emberNode, Ember<T>& currentEmber
currentEmber.m_Curves.m_Points[3] = vals;
}
else if (!Compare(curAtt->name, "animations"))
{
}
xmlFree(attStr);
}
@ -2023,6 +2035,7 @@ bool XmlToEmber<T>::ParseEmberElement(xmlNode* emberNode, Ember<T>& currentEmber
{
Xform<T> finalXform;
finalXform.m_Animate = 0;//Do not animate final by default.
finalXform.m_AnimateOrigin = 0;
if (!ParseXform(childNode, finalXform, false, fromEmber))
{
@ -2148,6 +2161,8 @@ bool XmlToEmber<T>::ParseEmberElement(xmlNode* emberNode, Ember<T>& currentEmber
ret = ret && AttToEmberMotionFloat(att, attStr, eEmberMotionParam::FLAME_MOTION_RAND_RANGE, motion);
else if (!Compare(curAtt->name, "vibrancy"))
ret = ret && AttToEmberMotionFloat(att, attStr, eEmberMotionParam::FLAME_MOTION_VIBRANCY, motion);
else if (!Compare(curAtt->name, "scale"))
ret = ret && AttToEmberMotionFloat(att, attStr, eEmberMotionParam::FLAME_MOTION_SCALE, motion);
else if (!Compare(curAtt->name, "background"))
{
double r = 0, g = 0, b = 0;
@ -2258,6 +2273,7 @@ bool XmlToEmber<T>::ParseXform(xmlNode* childNode, Xform<T>& xform, bool motion,
if (ParseAndAssign(curAtt->name, attStr, "weight", xform.m_Weight, success)) {}
else if (ParseAndAssign(curAtt->name, attStr, "color_speed", xform.m_ColorSpeed, success)) {}
else if (ParseAndAssign(curAtt->name, attStr, "animate", xform.m_Animate, success)) {}
else if (ParseAndAssign(curAtt->name, attStr, "animate_origin", xform.m_AnimateOrigin, success)) {}
else if (ParseAndAssign(curAtt->name, attStr, "opacity", xform.m_Opacity, success)) {}
else if (ParseAndAssign(curAtt->name, attStr, "var_color", xform.m_DirectColor, success)) {}
else if (ParseAndAssign(curAtt->name, attStr, "motion_frequency", xform.m_MotionFreq, success)) {}
@ -2275,6 +2291,7 @@ bool XmlToEmber<T>::ParseXform(xmlNode* childNode, Xform<T>& xform, bool motion,
Aton(attStr, temp);
xform.m_ColorSpeed = (1 - temp) / 2;
xform.m_Animate = T(temp > 0 ? 0 : 1);
//Ignore m_AnimateOrigin because it's new and symmetry is a legacy setting.
}
else if (!Compare(curAtt->name, "motion_function"))
{