#pragma once
#include "EmberDefines.h"
#include "Isaac.h"
#include "VariationList.h"
#include "Renderer.h"
///
/// SheepTools class.
///
namespace EmberNs
{
///
/// Mutation mode enum.
///
enum eMutateMode
{
MUTATE_NOT_SPECIFIED = -1,
MUTATE_ALL_VARIATIONS = 0,
MUTATE_ONE_XFORM_COEFS = 1,
MUTATE_ADD_SYMMETRY = 2,
MUTATE_POST_XFORMS = 3,
MUTATE_COLOR_PALETTE = 4,
MUTATE_DELETE_XFORM = 5,
MUTATE_ALL_COEFS = 6
};
///
/// Cross mode enum.
///
enum eCrossMode
{
CROSS_NOT_SPECIFIED = -1,
CROSS_UNION = 0,
CROSS_INTERPOLATE = 1,
CROSS_ALTERNATE = 2
};
///
/// SheepTools contains miscellaneous functions for mutating, rotating
/// crossing and randomizing embers. It is named so because these functions
/// are used in the electric sheep genome mutation process.
/// Most functions in this class perform a particular action and return
/// a string describing what it did so it can be recorded in an Xml edit doc
/// to be saved with the ember when converting to Xml.
/// Since it's members can occupy significant memory space and also have
/// hefty initialization sequences, it's important to declare one instance
/// and reuse it for the duration of the program instead of creating and deleting
/// them as local variables.
/// Template argument expected to be float or double.
///
template
class EMBER_API SheepTools
{
public:
///
/// Constructor which takes a palette path and pre-constructed renderer.
/// This class will take over ownership of the passed in renderer so the
/// caller should not delete it.
///
/// The full path and filename of the palette file
/// A pre-constructed renderer to use. The caller should not delete this.
SheepTools(string palettePath, Renderer* renderer)
{
Timing t;
m_Smooth = true;
m_SheepGen = -1;
m_SheepId = -1;
m_Stagger = 0;
m_OffsetX = 0;
m_OffsetY = 0;
m_PaletteList.Init(palettePath);
m_StandardIterator = auto_ptr>(new StandardIterator());
m_XaosIterator = auto_ptr>(new XaosIterator());
m_Renderer = auto_ptr>(renderer);
m_Rand = QTIsaac(ISAAC_INT(t.Tic()), ISAAC_INT(t.Tic() * 2), ISAAC_INT(t.Tic() * 3));
}
///
/// Create the linear default ember with a random palette.
///
/// The newly constructed linear default ember
Ember CreateLinearDefault()
{
Ember ember;
Xform xform1(T(0.25), T(1), T(0.5), T(1), T(0.5), T(0), T(0), T(0.5), T(0.5), T(0.25));
Xform xform2(T(0.25), T(0.66), T(0.5), T(1), T(0.5), T(0), T(0), T(0.5), T(-0.5), T(0.25));
Xform xform3(T(0.25), T(0.33), T(0.5), T(1), T(0.5), T(0), T(0), T(0.5), T(0.0), T(-0.5));
xform1.AddVariation(new LinearVariation());
xform2.AddVariation(new LinearVariation());
xform3.AddVariation(new LinearVariation());
ember.AddXform(xform1);
ember.AddXform(xform2);
ember.AddXform(xform3);
if (m_PaletteList.Init())
ember.m_Palette = *m_PaletteList.GetPalette(-1);
return ember;
}
///
/// Ensure all xforms, including final, have no more than the specified number of variations.
/// Remove variations in order of smallest weight to largest weight.
/// Also remove all xforms whose density is less than 0.001.
///
/// The ember whose xforms will be truncated
/// The maximum number of variations each xform can have
/// A string describing what was done
string TruncateVariations(Ember& ember, unsigned int maxVars)
{
int smallest;
unsigned int i, j, numVars;
T sv = 0;
ostringstream os;
//First clear out any xforms that are not the final, and have a density of less than 0.001.
for (i = 0; i < ember.XformCount(); i++)
{
Xform* xform = ember.GetXform(i);
if (xform->m_Weight < T(0.001))
{
os << "trunc_density " << i;
ember.DeleteXform(i);
i = 0;//Size will have changed, so start over.
}
}
//Now consider all xforms, including final.
for (i = 0; i < ember.TotalXformCount(); i++)
{
Xform* xform = ember.GetTotalXform(i);
do
{
Variation* var = NULL;
Variation* smallestVar = NULL;
numVars = 0;
smallest = -1;
for (j = 0; j < xform->TotalVariationCount(); j++)
{
var = xform->GetVariation(j);
if (var && var->m_Weight != 0.0)
{
T v = var->m_Weight;
numVars++;
if (smallest == -1 || fabs(v) < sv)
{
smallest = j;
smallestVar = var;
sv = fabs(v);
}
}
}
if (numVars > maxVars)
{
os << " trunc " << i << " " << smallest;
if (smallestVar)
xform->DeleteVariationById(smallestVar->VariationId());
}
} while (numVars > maxVars);
}
return os.str();
}
///
/// Mutate the ember using the specified mode.
///
/// The ember to mutate
/// The mutation mode
/// The variations to use if the mutation mode is random
/// The type of symmetry to add if random specified. If 0, it will be added randomly.
/// The speed to multiply the pre affine transforms by if the mutate mode is MUTATE_ALL_COEFS, else ignored.
/// A string describing what was done
string Mutate(Ember& ember, eMutateMode mode, vector& useVars, int sym, T speed)
{
unsigned int i, j, k, x, done, modXform;
char ministr[32];
T randSelect;
ostringstream os;
Ember mutation;
mutation.Clear();
//If mutate_mode = -1, choose a random mutation mode.
if (mode == MUTATE_NOT_SPECIFIED)
{
randSelect = m_Rand.Frand01();
if (randSelect < T(0.1))
mode = MUTATE_ALL_VARIATIONS;
else if (randSelect < T(0.3))
mode = MUTATE_ONE_XFORM_COEFS;
else if (randSelect < T(0.5))
mode = MUTATE_ADD_SYMMETRY;
else if (randSelect < T(0.6))
mode = MUTATE_POST_XFORMS;
else if (randSelect < T(0.7))
mode = MUTATE_COLOR_PALETTE;
else if (randSelect < T(0.8))
mode = MUTATE_DELETE_XFORM;
else
mode = MUTATE_ALL_COEFS;
}
if (mode == MUTATE_ALL_VARIATIONS)
{
os << "mutate all variations";
do
{
//Create a random flame, and use the variations to replace those in the original.
Random(mutation, useVars, sym, ember.TotalXformCount());
for (i = 0; i < ember.TotalXformCount(); i++)
{
Xform* xform1 = ember.GetTotalXform(i);
Xform* xform2 = mutation.GetTotalXform(i);
if (xform1 && xform2)
{
for (j = 0; j < xform1->TotalVariationCount(); j++)
{
Variation* var1 = xform1->GetVariation(j);
Variation* var2 = xform2->GetVariationById(var1->VariationId());
if ((var1 == NULL) ^ (var2 == NULL))//If any of them are different, clear the first and copy all of the second and exit the while loop.
{
xform1->ClearAndDeleteVariations();
for (k = 0; k < xform2->TotalVariationCount(); k++)
xform1->AddVariation(xform2->GetVariation(k)->Copy());
done = 1;
}
}
}
}
} while (!done);
}
else if (mode == MUTATE_ONE_XFORM_COEFS)
{
//Generate a 2-xform random.
Random(mutation, useVars, sym, 2);
//Which xform to mutate?
modXform = m_Rand.Rand() % ember.TotalXformCount();
Xform* xform1 = ember.GetTotalXform(modXform);
Xform* xform2 = mutation.GetTotalXform(0);
os << "mutate xform " << modXform << " coefs";
//If less than 3 xforms, then change only the translation part.
if (ember.TotalXformCount() < 2)
{
xform1->m_Affine.C(xform2->m_Affine.C());
xform1->m_Affine.F(xform2->m_Affine.F());
}
else
{
for (i = 0; i < 2; i++)
for (j = 0; j < 3; j++)
xform1->m_Affine.m_Mat[i][j] = xform2->m_Affine.m_Mat[i][j];
}
}
else if (mode == MUTATE_ADD_SYMMETRY)
{
os << "mutate symmetry";
ember.AddSymmetry(0, m_Rand);
}
else if (mode == MUTATE_POST_XFORMS)
{
unsigned int b = 1 + m_Rand.Rand() % 6;
unsigned int same = m_Rand.Rand() & 3;//25% chance of using the same post for all of them.
sprintf_s(ministr, 32, "(%d%s)", b, (same > 0) ? " same" : "");
os << "mutate post xforms " << ministr;
for (i = 0; i < ember.TotalXformCount(); i++)
{
int copy = (i > 0) && same;
Xform* xform = ember.GetTotalXform(i);
if (copy)//Copy the post from the first xform to the rest of them.
{
xform->m_Post = ember.GetTotalXform(0)->m_Post;
}
else
{
//50% chance.
if (b & 1)
{
T f = T(M_PI) * m_Rand.Frand11();
T a, b, d, e;
a = (xform->m_Affine.A() * cos(f) + xform->m_Affine.B() * -sin(f));
d = (xform->m_Affine.A() * sin(f) + xform->m_Affine.D() * cos(f));
b = (xform->m_Affine.B() * cos(f) + xform->m_Affine.E() * -sin(f));
e = (xform->m_Affine.B() * sin(f) + xform->m_Affine.E() * cos(f));
xform->m_Affine.A(a);
xform->m_Affine.B(b);
xform->m_Affine.D(d);
xform->m_Affine.E(e);
f *= -1;
a = (xform->m_Post.A() * cos(f) + xform->m_Post.B() * -sin(f));
d = (xform->m_Post.A() * sin(f) + xform->m_Post.D() * cos(f));
b = (xform->m_Post.B() * cos(f) + xform->m_Post.E() * -sin(f));
e = (xform->m_Post.B() * sin(f) + xform->m_Post.E() * cos(f));
xform->m_Post.A(a);
xform->m_Post.B(b);
xform->m_Post.D(d);
xform->m_Post.E(e);
}
//33% chance.
if (b & 2)
{
T f = T(0.2) + m_Rand.Frand01();
T g = T(0.2) + m_Rand.Frand01();
if (m_Rand.RandBit())
f = 1 / f;
if (m_Rand.RandBit())
g = f;
else
if (m_Rand.RandBit())
g = 1 / g;
xform->m_Affine.A(xform->m_Affine.A() / f);
xform->m_Affine.D(xform->m_Affine.D() / f);
xform->m_Affine.B(xform->m_Affine.B() / g);
xform->m_Affine.E(xform->m_Affine.E() / g);
xform->m_Post.A(xform->m_Post.A() * f);
xform->m_Post.B(xform->m_Post.B() * f);
xform->m_Post.D(xform->m_Post.D() * g);
xform->m_Post.E(xform->m_Post.E() * g);
}
if (b & 4)//16% chance.
{
T f = m_Rand.Frand11();
T g = m_Rand.Frand11();
xform->m_Affine.C(xform->m_Affine.C() - f);
xform->m_Affine.F(xform->m_Affine.F() - g);
xform->m_Post.C(xform->m_Post.C() + f);
xform->m_Post.F(xform->m_Post.F() + g);
}
}
}
}
else if (mode == MUTATE_COLOR_PALETTE)
{
T s = m_Rand.Frand01();
if (s < T(0.4))//Randomize xform color coords.
{
ImproveColors(ember, 100, false, 10);
os << "mutate color coords";
}
else if (s < T(0.8))//Randomize xform color coords and palette.
{
ImproveColors(ember, 25, true, 10);
os << "mutate color all";
}
else//Randomize palette only.
{
Palette palette;
if (m_PaletteList.Init())
palette = *m_PaletteList.GetPalette(-1);
palette.MakeHueAdjustedPalette(ember.m_Palette, ember.m_Hue);
//If the palette retrieval fails, skip the mutation.
if (ember.m_Palette.m_Index >= 0)
{
os << "mutate color palette";
}
else
{
palette.Clear(false);
ember.m_Palette = palette;
cout << "Failure getting random palette, palette set to white\n";
}
}
}
else if (mode == MUTATE_DELETE_XFORM)
{
unsigned int nx = m_Rand.Rand() % ember.TotalXformCount();
os << "mutate delete xform " << nx;
if (ember.TotalXformCount() > 1)
ember.DeleteTotalXform(nx);
}
else if (mode == MUTATE_ALL_COEFS)
{
os << "mutate all coefs";
Random(mutation, useVars, sym, ember.TotalXformCount());
//Change all the coefs by a fraction of the random.
for (x = 0; x < ember.TotalXformCount(); x++)
{
Xform* xform1 = ember.GetTotalXform(x);
Xform* xform2 = mutation.GetTotalXform(x);
for (i = 0; i < 2; i++)
for (j = 0; j < 3; j++)
xform1->m_Affine.m_Mat[i][j] += speed * xform2->m_Affine.m_Mat[i][j];
//Eventually, mutate the parametric variation parameters here.
}
}
return os.str();
}
///
/// Crosse the two embers and place the result in emberOut.
///
/// The first ember to cross
/// The second ember to cross
/// The result ember
/// The cross mode
/// A string describing what was done
string Cross(Ember& ember0, Ember& ember1, Ember& emberOut, int crossMode)
{
unsigned int i, rb;
T t;
ostringstream os;
char ministr[32];
if (crossMode == CROSS_NOT_SPECIFIED)
{
T s = m_Rand.Frand01();
if (s < 0.1)
crossMode = CROSS_UNION;
else if (s < 0.2)
crossMode = CROSS_INTERPOLATE;
else
crossMode = CROSS_ALTERNATE;
}
if (crossMode == CROSS_UNION)
{
//Make a copy of the first ember.
emberOut = ember0;
//Copy all xforms in the second ember except the final. Default behavior keeps the final from parent0.
for (i = 0; i < ember1.XformCount(); i++)
emberOut.AddXform(*ember1.GetXform(i));
os << "cross union";
}
else if (crossMode == CROSS_INTERPOLATE)
{
//Linearly interpolate somewhere between the two.
Ember parents[2];
//t = 0.5;//If you ever need to test.
t = m_Rand.Frand01();
parents[0] = ember0;
parents[1] = ember1;
parents[0].m_Time = T(0);
parents[1].m_Time = T(1);
Interpolater::Interpolate(parents, 2, t, 0, emberOut);
for (i = 0; i < emberOut.TotalXformCount(); i++)
emberOut.GetTotalXform(i)->DeleteMotionElements();
sprintf_s(ministr, 32, "%7.5g", t);
os << "cross interpolate " << ministr;
}
else//Alternate mode.
{
int got0, got1, usedParent;
string trystr;
//Each xform comes from a random parent, possible for an entire parent to be excluded.
do
{
got0 = got1 = 0;
rb = m_Rand.RandBit();
os << rb << ":";
//Copy the parent, sorting the final xform to the end if it's present.
emberOut = rb ? ember1 : ember0;
usedParent = rb;
//Only replace non-final xforms.
for (i = 0; i < emberOut.XformCount(); i++)
{
rb = m_Rand.RandBit();
//Replace xform if bit is 1.
if (rb == 1)
{
if (usedParent == 0)
{
if (i < ember1.XformCount() && ember1.GetXform(i)->m_Weight > 0)
{
Xform* xform = emberOut.GetXform(i);
*xform = *ember1.GetXform(i);
os << " 1";
got1 = 1;
}
else
{
os << " 0";
got0 = 1;
}
}
else
{
if (i < ember0.XformCount() && ember0.GetXform(i)->m_Weight > 0)
{
Xform* xform = emberOut.GetXform(i);
*xform = *ember0.GetXform(i);
os << " 0";
got0 = 1;
}
else
{
os << " 1";
got1 = 1;
}
}
}
else
{
os << " " << usedParent;
if (usedParent)
got1 = 1;
else
got0 = 1;
}
}
if (usedParent == 0 && ember0.UseFinalXform())
got0 = 1;
else if (usedParent == 1 && ember1.UseFinalXform())
got1 = 1;
} while ((i > 1) && !(got0 && got1));
os << "cross alternate ";
os << trystr;
}
//Reset color coords.
for (i = 0; i < emberOut.TotalXformCount(); i++)
{
emberOut.GetTotalXform(i)->m_ColorX = T(i & 1);//Original pingponged between 0 and 1, which gives bad coloring but is useful for testing.
//emberOut.GetTotalXform(i)->m_ColorX = m_Rand.Frand01();//Do rand which gives better coloring but produces different results every time it's run.
//emberOut.GetTotalXform(i)->m_ColorY = ?????;//Will need to update this if 2D coordinates are ever supported.
}
//Potentially genetically cross the two palettes together.
if (m_Rand.Frand01() < T(0.4))
{
//Select the starting parent.
unsigned int ci, startParent = m_Rand.RandBit();
os << " cmap_cross " << startParent << ":";
//Loop over the entries, switching to the other parent 1% of the time.
for (ci = 0; ci < 256; ci++)//Will need to update this if 2D coordinates are ever supported.
{
if (m_Rand.Frand01() < T(.01))
{
startParent = 1 - startParent;
os << " " << ci;
}
emberOut.m_Palette.m_Entries[ci] = startParent ? ember1.m_Palette.m_Entries[ci] : ember0.m_Palette.m_Entries[ci];
}
}
return os.str();
}
///
/// Thin wrapper around Random() that passes an empty vector for useVars, a random value for symmetry and 0 for max xforms.
///
/// The newly created random ember
void Random(Ember& ember)
{
vector useVars;
Random(ember, useVars, (int)m_Rand.Frand(-2, 2), 0);
}
///
/// Create a random ember.
///
/// The newly created random ember
/// A list of variations to use. If empty, any variation can be used.
/// The symmetry type to use from -2 to 2
/// The number of xforms to use. If 0, a quasi random count is used.
void Random(Ember& ember, vector& useVars, int sym, int specXforms)
{
bool postid, addfinal = false;
int var, samed, multid, samepost;
unsigned int i, j, k, n, varCount = (unsigned int)m_VariationList.Size();
Palette palette;
static unsigned int xformDistrib[] =
{
2, 2, 2, 2,
3, 3, 3, 3,
4, 4, 4,
5, 5,
6
};
ember.Clear();
ember.m_Hue = (m_Rand.Rand() & 7) ? 0 : m_Rand.Frand01();
if (m_PaletteList.Init())
palette = *m_PaletteList.GetPalette(-1);
palette.MakeHueAdjustedPalette(ember.m_Palette, ember.m_Hue);
ember.m_Time = 0;
ember.m_Interp = EMBER_INTERP_LINEAR;
ember.m_PaletteInterp = INTERP_HSV;
//Choose the number of xforms.
if (specXforms > 0)
{
ember.AddXforms(specXforms);
}
else
{
ember.AddXforms(xformDistrib[m_Rand.Rand() % Vlen(xformDistrib)]);
addfinal = m_Rand.Frand01() < T(0.15);//Add a final xform 15% of the time.
if (addfinal)
{
Xform xform;
xform.m_Affine.A(T(1.1));//Just put something in there so it doesn't show up as being an empty final xform.
ember.SetFinalXform(xform);
}
}
//If first input variation is -1 random choose one to use or decide to use multiple.
if (useVars.empty() || useVars[0] == -1)
var = m_Rand.RandBit() ? m_Rand.Rand() % varCount : -1;
else
var = -2;
samed = m_Rand.RandBit();
multid = m_Rand.RandBit();
postid = m_Rand.Frand01() < T(0.6);
samepost = m_Rand.RandBit();
//Loop over xforms.
for (i = 0; i < ember.TotalXformCount(); i++)
{
Xform* xform = ember.GetTotalXform(i);
xform->m_Weight = T(1) / ember.TotalXformCount();
xform->m_ColorX = m_Rand.Frand01();//Original pingponged between 0 and 1, which gives bad coloring. Ember does random instead.
xform->m_ColorY = m_Rand.Frand01();//Will need to update this if 2D coordinates are ever supported.
xform->m_ColorSpeed = T(0.5);
xform->m_Animate = 1;
xform->m_Affine.A(m_Rand.Frand11());
xform->m_Affine.B(m_Rand.Frand11());
xform->m_Affine.C(m_Rand.Frand11());
xform->m_Affine.D(m_Rand.Frand11());
xform->m_Affine.E(m_Rand.Frand11());
xform->m_Affine.F(m_Rand.Frand11());
xform->m_Post.MakeID();
if (!ember.IsFinalXform(xform))
{
if (!postid)
{
for (j = 0; j < 2; j++)
{
for (k = 0; k < 3; k++)
{
if (samepost || (i == 0))
xform->m_Post.m_Mat[j][k] = m_Rand.Frand11();
else
xform->m_Post.m_Mat[j][k] = ember.GetTotalXform(0)->m_Post.m_Mat[j][k];
}
}
}
if (var > -1)
{
xform->AddVariation(m_VariationList.GetVariation(var)->Copy());//Use only one variation specified for all xforms.
}
else if (multid && var == -1)
{
xform->AddVariation(m_VariationList.GetVariation(m_Rand.Rand() % varCount)->Copy());//Choose a random var for this xform.
}
else
{
if (samed && i > 0)
{
//Copy the same variations from the previous xform.
Xform* prevXform = ember.GetXform(i - 1);
for (j = 0; j < prevXform->TotalVariationCount(); j++)
xform->AddVariation(prevXform->GetVariation(j)->Copy());
}
else
{
//Choose a random number of vars to use, at least 2
//but less than varCount. Probability leans
//towards fewer variations.
n = 2;
while (m_Rand.RandBit() && (n < varCount))
n++;
//Randomly choose n variations, and change their weights.
//A var can be selected more than once, further reducing
//the probability that multiple vars are used.
for (j = 0; j < n; j++)
{
if (var != -2)
{
//Pick a random variation and use a random weight from 0-1.
Variation* v = m_VariationList.GetVariationCopy((size_t)(m_Rand.Rand() % varCount), m_Rand.Frand(T(0.001), 1));
if (v && !xform->AddVariation(v))
delete v;//It already existed and therefore was not added.
}
else
{
//Pick a random variation from the suppled IDs and use a random weight from 0-1.
Variation* v = m_VariationList.GetVariationCopy(useVars[m_Rand.Rand() % useVars.size()], m_Rand.Frand(T(0.001), 1));
if (v && !xform->AddVariation(v))
delete v;
}
}
xform->NormalizeVariationWeights();//Normalize weights to 1.0 total.
}
}
}
else
{
//Handle final xform randomness.
n = 1;
if (m_Rand.RandBit())
n++;
//Randomly choose n variations, and change their weights.
//A var can be selected more than once, further reducing
//the probability that multiple vars are used.
for (j = 0; j < n; j++)
{
if (var != -2)
{
//Pick a random variation and use a random weight from 0-1.
xform->AddVariation(m_VariationList.GetVariationCopy((size_t)(m_Rand.Rand() % varCount), m_Rand.Frand(T(0.001), 1)));
}
else
{
//Pick a random variation from the suppled IDs and use a random weight from 0-1.
xform->AddVariation(m_VariationList.GetVariationCopy(useVars[m_Rand.Rand() % useVars.size()], m_Rand.Frand(T(0.001), 1)));
}
}
xform->NormalizeVariationWeights();//Normalize weights to 1.0 total.
}
//Randomize parametric variations.
for (j = 0; j < xform->TotalVariationCount(); j++)
xform->GetVariation(j)->Random(m_Rand);
}
//Randomly add symmetry (but not if we've already added a final xform).
if (sym || (!(m_Rand.Rand() % 4) && !addfinal))
ember.AddSymmetry(sym, m_Rand);
else
ember.m_Symmetry = 0;
}
///
/// Attempt to make colors better by doing some test renders.
///
/// The ember to render
/// The number of test renders to try before giving up
/// Change palette if true, else keep trying with the same palette.
/// The resolution of the test histogram. This value ^ 3 will be used for the total size. Common value is 10.
void ImproveColors(Ember& ember, int tries, bool changePalette, int colorResolution)
{
int i;
T best, b;
Ember bestEmber = ember;
best = TryColors(ember, colorResolution);
if (best < 0)
{
cout << "Error in TryColors(), skipping ImproveColors()" << endl;
return;
}
for (i = 0; i < tries; i++)
{
ChangeColors(ember, changePalette);
b = TryColors(ember, colorResolution);
if (b < 0)
{
cout << "Error in TryColors, aborting tries." << endl;
break;
}
if (b > best)
{
best = b;
bestEmber = ember;
}
}
ember = bestEmber;
}
///
/// Run a test render to improve the colors.
///
/// The ember to render
/// The resolution of the test histogram. This value ^ 3 will be used for the total size. Common value is 10.
/// The number of histogram cells that weren't black
T TryColors(Ember& ember, int colorResolution)
{
unsigned char* p;
unsigned int i, hits = 0, res = colorResolution;
unsigned int pixTotal, res3 = res * res * res;
T scalar;
Ember adjustedEmber = ember;
adjustedEmber.m_Quality = 1;
adjustedEmber.m_Supersample = 1;
adjustedEmber.m_MaxRadDE = 0;
//Scale the image so that the total number of pixels is ~10,000.
pixTotal = ember.m_FinalRasW * ember.m_FinalRasH;
scalar = sqrt(T(10000) / pixTotal);
adjustedEmber.m_FinalRasW = (unsigned int)(ember.m_FinalRasW * scalar);
adjustedEmber.m_FinalRasH = (unsigned int)(ember.m_FinalRasH * scalar);
adjustedEmber.m_PixelsPerUnit *= scalar;
adjustedEmber.m_Passes = 1;
adjustedEmber.m_TemporalSamples = 1;
m_Renderer->SetEmber(adjustedEmber);
m_Renderer->BytesPerChannel(1);
m_Renderer->EarlyClip(true);
m_Renderer->PixelAspectRatio(1);
m_Renderer->ThreadCount(Timing::ProcessorCount());
m_Renderer->SubBatchSize(10000);
m_Renderer->Callback(NULL);
if (m_Renderer->Run(m_FinalImage) != RENDER_OK)
{
cout << "Error rendering test image for TryColors(). Aborting." << endl;
return -1;
}
m_Hist.resize(res3);
memset(m_Hist.data(), 0, res3);
p = m_FinalImage.data();
for (i = 0; i < m_Renderer->FinalDimensions(); i++)
{
m_Hist[(p[0] * res / 256) +
(p[1] * res / 256) * res +
(p[2] * res / 256) * res * res]++;
p += m_Renderer->NumChannels();
}
for (i = 0; i < res3; i++)
{
if (m_Hist[i])
hits++;
}
return T(hits / res3);
}
///
/// Change around color coordinates. Optionall change out the entire palette.
///
/// The ember whose xform's color coordinates will be changed
/// Change palette if true, else don't
void ChangeColors(Ember& ember, bool changePalette)
{
unsigned int i;
Xform* xform0;
Xform* xform1;
if (changePalette)
{
Palette* palette;
ember.m_Hue = 0.0;
if (m_PaletteList.Init())
palette = m_PaletteList.GetPalette(-1);
if (palette)
{
palette->MakeHueAdjustedPalette(ember.m_Palette, ember.m_Hue);
}
else
{
ember.m_Palette.Clear(false);
cout << "Error retrieving random palette, setting to all white" << endl;
}
}
for (i = 0; i < ember.TotalXformCount(); i++)
{
ember.GetTotalXform(i)->m_ColorX = m_Rand.Frand01();
ember.GetTotalXform(i)->m_ColorY = m_Rand.Frand01();
}
xform0 = RandomXform(ember, -1);
xform1 = RandomXform(ember, ember.GetXformIndex(xform0));
if (xform0 && (m_Rand.Rand() & 1))
{
xform0->m_ColorX = 0;
xform0->m_ColorY = 0;
}
if (xform1 && (m_Rand.Rand() & 1))
{
xform1->m_ColorX = 1;
xform1->m_ColorY = 1;
}
}
///
/// Try to get a random xform from the ember, including final, whose density is non-zero.
/// Give up after 100 tries.
///
/// The ember to get a random xform from
/// Optionally exclude an xform. Pass -1 to include all for consideration.
/// The random xform if successful, else NULL.
Xform* RandomXform(Ember& ember, int excluded)
{
int ntries = 0;
while (ntries++ < 100)
{
int i = m_Rand.Rand() % ember.TotalXformCount();
if (i != excluded)
{
Xform* xform = ember.GetTotalXform(i);
if (xform->m_Weight > 0)
return xform;
}
}
return NULL;
}
///
/// Rotate affine transforms and optionally apply motion elements,
/// and store the result in rotated.
///
/// The ember to rotate
/// The rotated xform
/// The time percentage value which dictates how much of a percentage of 360 degrees it should be rotated and the time position for the motion elements
void Loop(Ember& ember, Ember& rotated, T blend)
{
rotated = ember;
//Insert motion magic here :
//If there are motion elements, modify the contents of
//the result xforms before rotate is called.
for (unsigned int i = 0; i < ember.TotalXformCount(); i++)
{
Xform* xform1 = ember.GetTotalXform(i);
Xform* xform2 = rotated.GetTotalXform(i);
if (!xform1->m_Motion.empty())
xform2->ApplyMotion(*xform1, blend);
xform2->DeleteMotionElements();
}
rotated.RotateAffines(-blend * 360);//Rotate the affines.
}
///
/// Interpolate two embers and place the output in result.
/// The embers parameter is expected to be a pointer to an array of at least 2 elements.
///
/// The embers to interpolate
/// The result of the interpolation
/// The interpolation time
/// True if embers points to the first or last ember in the entire sequence, else false.
void Edge(Ember* embers, Ember& result, T blend, bool seqFlag)
{
unsigned int i, si;
Ember spun[2], prealign[2];
//Insert motion magic here :
//If there are motion elements, modify the contents of
//the result xforms before rotate is called.
for (si = 0; si < 2; si++)
{
prealign[si] = embers[si];
for (i = 0; i < embers[si].TotalXformCount(); i++)
{
Xform* xform = embers[si].GetTotalXform(i);
if (!xform->m_Motion.empty())
xform->ApplyMotion(*(prealign[si].GetTotalXform(i)), blend);//Apply motion parameters to result.xform[i] using blend parameter.
}
}
//Use the un-padded original for blend=0 when creating a sequence.
//This keeps the original interpolation type intact.
if (seqFlag && blend == 0)
{
result = prealign[0];
}
else
{
//Align what's going to be interpolated.
Interpolater::Align(prealign, spun, 2);
spun[0].m_Time = 0;
spun[1].m_Time = 1;
//Call this first to establish the asymmetric reference angles.
Interpolater::AsymmetricRefAngles(spun, 2);
//Rotate the aligned xforms.
spun[0].RotateAffines(-blend * 360);
spun[1].RotateAffines(-blend * 360);
Interpolater::Interpolate(spun, 2, m_Smooth ? Interpolater::Smoother(blend) : blend, m_Stagger, result);
}
//Make sure there are no motion elements in the result.
result.DeleteMotionElements();
}
///
/// Spin the specified ember, optionally apply a template ember, and place the output in result.
/// Create auto-generated name
/// Append edits using the Nick, Url and Id members.
/// Apply subpixel jitter to center using offset members.
///
/// The ember to spin
/// The template to apply if not NULL, else ignore.
/// The result of the spin
/// The frame in the sequence to be stored in the m_Time member of result
/// The interpolation time
void Spin(Ember& parent, Ember* templ, Ember& result, int frame, T blend)
{
char temp[50];
//Spin the parent blend degrees.
Loop(parent, result, blend);
//Apply the template if necessary.
if (templ)
ApplyTemplate(result, *templ);
//Set ember parameters accordingly.
result.m_Time = T(frame);
result.m_Interp = EMBER_INTERP_LINEAR;
result.m_PaletteInterp = INTERP_HSV;
//Create the edit doc xml.
sprintf_s(temp, 50, "rotate %g", blend * 360.0);
result.ClearEdit();
result.m_Edits = m_EmberToXml.CreateNewEditdoc(&parent, NULL, temp, m_Nick, m_Url, m_Id, m_Comment, m_SheepGen, m_SheepId);
//Subpixel jitter.
Offset(result, m_OffsetX, m_OffsetY);
//Make the name of the flame the time.
sprintf_s(temp, 50, "%f", result.m_Time);
result.m_Name = string(temp);
}
///
/// Call Edge() on parents, optionally apply a template ember, and place the output in result.
/// Create auto-generated name
/// Append edits using the Nick, Url and Id members.
/// Apply subpixel jitter to center using offset members.
///
/// The embers to interpolate
/// The template to apply if not NULL, else ignore.
/// The result of the spin
/// The frame in the sequence to be stored in the m_Time member of result
/// True if embers points to the first or last ember in the entire sequence, else false.
/// The interpolation time
void SpinInter(Ember* parents, Ember* templ, Ember& result, int frame, bool seqFlag, T blend)
{
char temp[50];
//Interpolate between spun parents.
Edge(parents, result, blend, seqFlag);
//Original did an interpolated palette hack here for random palettes, but it was never used anywhere so ember omits it.//ORIG
//Apply the template if necessary.
if (templ)
ApplyTemplate(result, *templ);
//Set ember parameters accordingly.
result.m_Time = T(frame);
//Create the edit doc xml.
sprintf_s(temp, 50, "interpolate %g", blend * 360.0);
result.ClearEdit();
result.m_Edits = m_EmberToXml.CreateNewEditdoc(&parents[0], &parents[1], temp, m_Nick, m_Url, m_Id, m_Comment, m_SheepGen, m_SheepId);
//Subpixel jitter.
Offset(result, m_OffsetX, m_OffsetY);
//Make the name of the flame the time.
sprintf_s(temp, 50, "%f", result.m_Time);
result.m_Name = string(temp);
}
///
/// Apply a template to an ember.
///
/// The ember to apply the template to
/// The template to apply
void ApplyTemplate(Ember& ember, Ember& templ)
{
//Check for invalid values - only replace those with valid ones.
for (unsigned int i = 0; i < 3; i++)
if (templ.m_Background[i] >= 0)
ember.m_Background[i] = templ.m_Background[i];
if (templ.m_Zoom < 999999998)
ember.m_Zoom = templ.m_Zoom;
if (templ.m_Supersample > 0)
ember.m_Supersample = templ.m_Supersample;
if (templ.m_SpatialFilterRadius >= 0)
ember.m_SpatialFilterRadius = templ.m_SpatialFilterRadius;
if (templ.m_Quality > 0)
ember.m_Quality = templ.m_Quality;
if (templ.m_Passes > 0)
ember.m_Passes = templ.m_Passes;
if (templ.m_TemporalSamples > 0)
ember.m_TemporalSamples = templ.m_TemporalSamples;
if (templ.m_FinalRasW > 0)
{
//Preserving scale should be an option.
ember.m_PixelsPerUnit = ember.m_PixelsPerUnit * templ.m_FinalRasW / ember.m_FinalRasW;
ember.m_FinalRasW = templ.m_FinalRasW;
}
if (templ.m_FinalRasH > 0)
ember.m_FinalRasH = templ.m_FinalRasH;
if (templ.m_MaxRadDE >= 0)
ember.m_MaxRadDE = templ.m_MaxRadDE;
if (templ.m_MinRadDE >= 0)
ember.m_MinRadDE = templ.m_MinRadDE;
if (templ.m_CurveDE >= 0)
ember.m_CurveDE = templ.m_CurveDE;
if (templ.m_GammaThresh >= 0)
ember.m_GammaThresh = templ.m_GammaThresh;
if (templ.m_Passes > 0)
ember.m_Passes = templ.m_Passes;
if (templ.m_SpatialFilterType > 0)
ember.m_SpatialFilterType = templ.m_SpatialFilterType;
if (templ.m_Interp >= 0)
ember.m_Interp = templ.m_Interp;
if (templ.m_AffineInterp >= 0)
ember.m_AffineInterp = templ.m_AffineInterp;
if (templ.m_TemporalFilterType >= 0)
ember.m_TemporalFilterType = templ.m_TemporalFilterType;
if (templ.m_TemporalFilterWidth > 0)
ember.m_TemporalFilterWidth = templ.m_TemporalFilterWidth;
if (templ.m_TemporalFilterExp > -900)
ember.m_TemporalFilterExp = templ.m_TemporalFilterExp;
if (templ.m_HighlightPower >= 0)
ember.m_HighlightPower = templ.m_HighlightPower;
if (templ.m_PaletteMode >= 0)
ember.m_PaletteMode = templ.m_PaletteMode;
}
///
/// Move the center of the ember by the specified amount.
///
/// The ember to move
/// The x offset.
/// The y offset.
void Offset(Ember& ember, T offsetX, T offsetY)
{
if (!IsNearZero(offsetX))
ember.m_CenterX += offsetX / (ember.m_PixelsPerUnit * ember.m_Supersample);
if (!IsNearZero(offsetY))
ember.m_CenterY += offsetY / (ember.m_PixelsPerUnit * ember.m_Supersample);
}
///
/// Translate the first center point by the second, rotate it, translate back.
///
/// The new center x
/// The new center y
/// The old center x
/// The old center y
/// The angle to rotate by
void RotateOldCenterBy(T& newCenterX, T& newCenterY, T oldCenterX, T oldCenterY, T by)
{
T r[2];
T th = by * 2 * T(M_PI) / 360;
T c = cos(th);
T s = -sin(th);
newCenterX -= oldCenterX;
newCenterY -= oldCenterY;
r[0] = c * newCenterX - s * newCenterY;
r[1] = s * newCenterX + c * newCenterY;
newCenterX = r[0] + oldCenterX;
newCenterY = r[1] + oldCenterY;
}
///
/// Find a 2D bounding box that does not enclose eps of the fractal density in each compass direction.
/// This will run the inner loops of iteration without all of the surrounding interpolation and filtering.
///
/// The ember to iterate
/// The eps
/// The number samples to iterate
/// The bmin[0] and bmin[1] will be the minimum x and y values.
/// The bmax[0] and bmax[1] will be the maximum x and y values.
/// The number of iterations ran
unsigned __int64 EstimateBoundingBox(Ember& ember, T eps, unsigned int samples, T* bmin, T* bmax)
{
unsigned int i, lowTarget, highTarget;
T min[2], max[2];
if (ember.XaosPresent())
m_Iterator = m_XaosIterator.get();
else
m_Iterator = m_StandardIterator.get();
m_Iterator->InitDistributions(ember);
m_Samples.resize(samples);
unsigned __int64 bv = m_Iterator->Iterate(ember, samples, 20, m_Samples.data(), m_Rand);//Use a special fuse of 20, all other calls to this will use 15, or 100.
if (bv / T(samples) > eps)
eps = 3 * bv / T(samples);
if (eps > T(0.3))
eps = T(0.3);
lowTarget = (int)(samples * eps);
highTarget = samples - lowTarget;
min[0] = min[1] = 1e10;
max[0] = max[1] = -1e10;
for (i = 0; i < samples; i++)
{
if (m_Samples[i].m_X < min[0]) min[0] = m_Samples[i].m_X;
if (m_Samples[i].m_Y < min[1]) min[1] = m_Samples[i].m_Y;
if (m_Samples[i].m_X > max[0]) max[0] = m_Samples[i].m_X;
if (m_Samples[i].m_Y > max[1]) max[1] = m_Samples[i].m_Y;
}
if (lowTarget == 0)
{
bmin[0] = min[0];
bmin[1] = min[1];
bmax[0] = max[0];
bmax[1] = max[1];
return bv;
}
std::sort(m_Samples.begin(), m_Samples.end(), &SortPointByX);
bmin[0] = m_Samples[lowTarget].m_X;
bmax[0] = m_Samples[highTarget].m_X;
std::sort(m_Samples.begin(), m_Samples.end(), &SortPointByY);
bmin[1] = m_Samples[lowTarget + 1].m_Y;
bmax[1] = m_Samples[highTarget + 1].m_Y;
return bv;
}
///
/// When doing spin or edge, an edit doc is made to record what was done.
/// Doing so takes many extra parameters such as name and url. Passing these every
/// time is cumbersome and are unlikely to change for the duration of a program run, so
/// they are made to be member variables. After setting these, their values will be used
/// in all edits within this class.
///
/// Use smoothing if true, else false
/// Use stagger if > 0, else false
/// X amount of subpixel jitter to apply in Spin() and Edge()
/// Y amount of subpixel jitter to apply in Spin() and Edge()
/// The nickname of the author
/// The Url of the author
/// The id of the author
/// The comment to include
/// The sheep generation used if > 0. Default: 0.
/// The sheep id used if > 0. Default: 0.
void SetSpinParams(bool smooth, T stagger, T offsetX, T offsetY, string nick, string url, string id, string comment, int sheepGen, int sheepId)
{
m_Smooth = smooth;
m_SheepGen = sheepGen;
m_SheepId = sheepId;
m_Stagger = stagger;
m_OffsetX = offsetX;
m_OffsetY = offsetY;
m_Nick = nick;
m_Url = url;
m_Id = id;
m_Comment = comment;
}
private:
bool m_Smooth;
int m_SheepGen;
int m_SheepId;
T m_Stagger;
T m_OffsetX;
T m_OffsetY;
string m_Nick;
string m_Url;
string m_Id;
string m_Comment;
vector> m_Samples;
vector m_FinalImage;
vector m_Hist;
EmberToXml m_EmberToXml;
Iterator* m_Iterator;
auto_ptr> m_StandardIterator;
auto_ptr> m_XaosIterator;
auto_ptr> m_Renderer;
QTIsaac m_Rand;
PaletteList m_PaletteList;
VariationList m_VariationList;
};
}