mirror of
https://bitbucket.org/mfeemster/fractorium.git
synced 2025-01-21 21:20:07 -05:00
018ba26b5f
-Add support for multiple GPU devices. --These options are present in the command line and in Fractorium. -Change scheme of specifying devices from platform,device to just total device index. --Single number on the command line. --Change from combo boxes for device selection to a table of all devices in Fractorium. -Temporal samples defaults to 100 instead of 1000 which was needless overkill. --Bug fixes -EmberAnimate, EmberRender, FractoriumSettings, FinalRenderDialog: Fix wrong order of arguments to Clamp() when assigning thread priority. -VariationsDC.h: Fix NVidia OpenCL compilation error in DCTriangleVariation. -FractoriumXformsColor.cpp: Checking for null pixmap pointer is not enough, must also check if the underlying buffer is null via call to QPixmap::isNull(). --Code changes -Ember.h: Add case for FLAME_MOTION_NONE and default in ApplyFlameMotion(). -EmberMotion.h: Call base constructor. -EmberPch.h: #pragma once only on Windows. -EmberToXml.h: --Handle different types of exceptions. --Add default cases to ToString(). -Isaac.h: Remove unused variable in constructor. -Point.h: Call base constructor in Color(). -Renderer.h/cpp: --Add bool to Alloc() to only allocate memory for the histogram. Needed for multi-GPU. --Make CoordMap() return a const ref, not a pointer. -SheepTools.h: --Use 64-bit types like the rest of the code already does. --Fix some comment misspellings. -Timing.h: Make BeginTime(), EndTime(), ElapsedTime() and Format() be const functions. -Utils.h: --Add new functions Equal() and Split(). --Handle more exception types in ReadFile(). --Get rid of most legacy blending of C and C++ argument parsing. -XmlToEmber.h: --Get rid of most legacy blending of C and C++ code from flam3. --Remove some unused variables. -EmberAnimate: --Support multi-GPU processing that alternates full frames between devices. --Use OpenCLInfo instead of OpenCLWrapper for --openclinfo option. --Remove bucketT template parameter, and hard code float in its place. --If a render fails, exit since there is no point in continuing an animation with a missing frame. --Pass variables to threaded save better, which most likely fixes a very subtle bug that existed before. --Remove some unused variables. -EmberGenome, EmberRender: --Support multi-GPU processing that alternates full frames between devices. --Use OpenCLInfo instead of OpenCLWrapper for --openclinfo option. --Remove bucketT template parameter, and hard code float in its place. -EmberRender: --Support multi-GPU processing that alternates full frames between devices. --Use OpenCLInfo instead of OpenCLWrapper for --openclinfo option. --Remove bucketT template parameter, and hard code float in its place. --Only print values when not rendering with OpenCL, since they're always 0 in that case. -EmberCLPch.h: --#pragma once only on Windows. --#include <atomic>. -IterOpenCLKernelCreator.h: Add new kernel for summing two histograms. This is needed for multi-GPU. -OpenCLWrapper.h: --Move all OpenCL info related code into its own class OpenCLInfo. --Add members to cache the values of global memory size and max allocation size. -RendererCL.h/cpp: --Redesign to accomodate multi-GPU. --Constructor now takes a vector of devices. --Remove DumpErrorReport() function, it's handled in the base. --ClearBuffer(), ReadPoints(), WritePoints(), ReadHist() and WriteHist() now optionally take a device index as a parameter. --MakeDmap() override and m_DmapCL member removed because it no longer applies since the histogram is always float since the last commit. --Add new function SumDeviceHist() to sum histograms from two devices by first copying to a temporary on the host, then a temporary on the device, then summing. --m_Calls member removed, as it's now per-device. --OpenCLWrapper removed. --m_Seeds member is now a vector of vector of seeds, to accomodate a separate and different array of seeds for each device. --Added member m_Devices, a vector of unique_ptr of RendererCLDevice. -EmberCommon.h --Added Devices() function to convert from a vector of device indices to a vector of platform,device indices. --Changed CreateRenderer() to accept a vector of devices to create a single RendererCL which will split work across multiple devices. --Added CreateRenderers() function to accept a vector of devices to create multiple RendererCL, each which will render on a single device. --Add more comments to some existing functions. -EmberCommonPch.h: #pragma once only on Windows. -EmberOptions.h: --Remove --platform option, it's just sequential device number now with the --device option. --Make --out be OPT_USE_RENDER instead of OPT_RENDER_ANIM since it's an error condition when animating. It makes no sense to write all frames to a single image. --Add Devices() function to parse comma separated --device option string and return a vector of device indices. --Make int and uint types be 64-bit, so intmax_t and size_t. --Make better use of macros. -JpegUtils.h: Make string parameters to WriteJpeg() and WritePng() be const ref. -All project files: Turn off buffer security check option in Visual Studio (/Gs-) -deployment.pri: Remove the line OTHER_FILES +=, it's pointless and was causing problems. -Ember.pro, EmberCL.pro: Add CONFIG += plugin, otherwise it wouldn't link. -EmberCL.pro: Add new files for multi-GPU support. -build_all.sh: use -j4 and QMAKE=${QMAKE:/usr/bin/qmake} -shared_settings.pri: -Add version string. -Remove old DESTDIR definitions. -Add the following lines or else nothing would build: CONFIG(release, debug|release) { CONFIG += warn_off DESTDIR = ../../../Bin/release } CONFIG(debug, debug|release) { DESTDIR = ../../../Bin/debug } QMAKE_POST_LINK += $$quote(cp --update ../../../Data/flam3-palettes.xml $${DESTDIR}$$escape_expand(\n\t)) LIBS += -L/usr/lib -lpthread -AboutDialog.ui: Another futile attempt to make it look correct on Linux. -FinalRenderDialog.h/cpp: --Add support for multi-GPU. --Change from combo boxes for device selection to a table of all devices. --Ensure device selection makes sense. --Remove "FinalRender" prefix of various function names, it's implied given the context. -FinalRenderEmberController.h/cpp: --Add support for multi-GPU. --Change m_FinishedImageCount to be atomic. --Move CancelRender() from the base to FinalRenderEmberController<T>. --Refactor RenderComplete() to omit any progress related functionality or image saving since it can be potentially ran in a thread. --Consolidate setting various renderer fields into SyncGuiToRenderer(). -Fractorium.cpp: Allow for resizing of the options dialog to show the entire device table. -FractoriumCommon.h: Add various functions to handle a table showing the available OpenCL devices on the system. -FractoriumEmberController.h/cpp: Remove m_FinalImageIndex, it's no longer needed. -FractoriumRender.cpp: Scale the interactive sub batch count and quality by the number of devices used. -FractoriumSettings.h/cpp: --Temporal samples defaults to 100 instead of 1000 which was needless overkill. --Add multi-GPU support, remove old device,platform pair. -FractoriumToolbar.cpp: Disable OpenCL toolbar button if there are no devices present on the system. -FractoriumOptionsDialog.h/cpp: --Add support for multi-GPU. --Consolidate more assignments in DataToGui(). --Enable/disable CPU/OpenCL items in response to OpenCL checkbox event. -Misc: Convert almost everything to size_t for unsigned, intmax_t for signed.
859 lines
27 KiB
C++
859 lines
27 KiB
C++
#pragma once
|
|
|
|
#include "Utils.h"
|
|
#include "PaletteList.h"
|
|
#include "VariationList.h"
|
|
#include "Ember.h"
|
|
|
|
/// <summary>
|
|
/// EmberToXml class.
|
|
/// </summary>
|
|
|
|
namespace EmberNs
|
|
{
|
|
/// <summary>
|
|
/// Class for converting ember objects to Xml documents.
|
|
/// Support for saving one or more to a single file.
|
|
/// Template argument expected to be float or double.
|
|
/// </summary>
|
|
template <typename T>
|
|
class EMBER_API EmberToXml : public EmberReport
|
|
{
|
|
public:
|
|
/// <summary>
|
|
/// Empty constructor.
|
|
/// </summary>
|
|
EmberToXml()
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Save the ember to the specified file.
|
|
/// </summary>
|
|
/// <param name="filename">Full path and filename</param>
|
|
/// <param name="ember">The ember to save</param>
|
|
/// <param name="printEditDepth">How deep the edit depth goes</param>
|
|
/// <param name="doEdits">If true included edit tags, else don't.</param>
|
|
/// <param name="intPalette">If true use integers instead of floating point numbers when embedding a non-hex formatted palette, else use floating point numbers.</param>
|
|
/// <param name="hexPalette">If true, embed a hexadecimal palette instead of Xml Color tags, else use Xml color tags.</param>
|
|
/// <param name="append">If true, append to the file if it already exists, else create a new file.</param>
|
|
/// <param name="start">Whether a new file is to be started</param>
|
|
/// <param name="finish">Whether an existing file is to be ended</param>
|
|
/// <returns>True if successful, else false</returns>
|
|
bool Save(const string& filename, Ember<T>& ember, size_t printEditDepth, bool doEdits, bool intPalette, bool hexPalette, bool append = false, bool start = false, bool finish = false)
|
|
{
|
|
vector<Ember<T>> vec;
|
|
|
|
vec.push_back(ember);
|
|
return Save(filename, vec, printEditDepth, doEdits, intPalette, hexPalette, append, start, finish);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Save a vector of embers to the specified file.
|
|
/// </summary>
|
|
/// <param name="filename">Full path and filename</param>
|
|
/// <param name="embers">The vector of embers to save</param>
|
|
/// <param name="printEditDepth">How deep the edit depth goes</param>
|
|
/// <param name="doEdits">If true included edit tags, else don't.</param>
|
|
/// <param name="intPalette">If true use integers instead of floating point numbers when embedding a non-hex formatted palette, else use floating point numbers.</param>
|
|
/// <param name="hexPalette">If true, embed a hexadecimal palette instead of Xml Color tags, else use Xml color tags.</param>
|
|
/// <param name="append">If true, append to the file if it already exists, else create a new file.</param>
|
|
/// <param name="start">Whether a new file is to be started</param>
|
|
/// <param name="finish">Whether an existing file is to be ended</param>
|
|
/// <returns>True if successful, else false</returns>
|
|
bool Save(const string& filename, vector<Ember<T>>& embers, size_t printEditDepth, bool doEdits, bool intPalette, bool hexPalette, bool append = false, bool start = false, bool finish = false)
|
|
{
|
|
bool b = false;
|
|
string temp;
|
|
ofstream f;
|
|
|
|
try
|
|
{
|
|
if (append)
|
|
f.open(filename, std::ofstream::out | std::ofstream::app);//Appending allows us to write multiple embers to a single file.
|
|
else
|
|
f.open(filename);
|
|
|
|
if (f.is_open())
|
|
{
|
|
if ((append && start) || !append)
|
|
{
|
|
temp = "<flames>\n";
|
|
//temp = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n<flames>\n";
|
|
f.write(temp.c_str(), temp.size());
|
|
}
|
|
|
|
for (auto& ember : embers)
|
|
{
|
|
string s = ToString(ember, "", printEditDepth, doEdits, intPalette, hexPalette);
|
|
f.write(s.c_str(), s.size());
|
|
}
|
|
|
|
if ((append && finish) || !append)
|
|
{
|
|
temp = "</flames>\n";
|
|
f.write(temp.c_str(), temp.size());
|
|
}
|
|
|
|
f.close();
|
|
b = true;
|
|
}
|
|
else
|
|
{
|
|
cout << "Error: Writing flame " << filename << " failed." << endl;
|
|
b = false;
|
|
}
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
cout << "Error: Writing flame " << filename << " failed: " << e.what() << endl;
|
|
b = false;
|
|
}
|
|
catch (...)
|
|
{
|
|
cout << "Error: Writing flame " << filename << " failed." << endl;
|
|
b = false;
|
|
}
|
|
|
|
if (f.is_open())
|
|
f.close();
|
|
|
|
return b;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return the Xml string representation of an ember.
|
|
/// </summary>
|
|
/// <param name="ember">The ember to create the Xml with</param>
|
|
/// <param name="extraAttributes">If true, add extra attributes, else don't</param>
|
|
/// <param name="printEditDepth">How deep the edit depth goes</param>
|
|
/// <param name="doEdits">If true included edit tags, else don't.</param>
|
|
/// <param name="intPalette">If true use integers instead of floating point numbers when embedding a non-hex formatted palette, else use floating point numbers.</param>
|
|
/// <param name="hexPalette">If true, embed a hexadecimal palette instead of Xml Color tags, else use Xml color tags.</param>
|
|
/// <returns>The Xml string representation of the passed in ember</returns>
|
|
string ToString(Ember<T>& ember, const string& extraAttributes, size_t printEditDepth, bool doEdits, bool intPalette, bool hexPalette = true)
|
|
{
|
|
size_t i, j;
|
|
string s;
|
|
ostringstream os;
|
|
vector<Variation<T>*> variations;
|
|
|
|
os << "<flame version=\"EMBER-" << EmberVersion() << "\" time=\"" << ember.m_Time << "\"";
|
|
|
|
if (!ember.m_Name.empty())
|
|
os << " name=\"" << ember.m_Name << "\"";
|
|
|
|
os << " size=\"" << ember.m_FinalRasW << " " << ember.m_FinalRasH << "\"";
|
|
os << " center=\"" << ember.m_CenterX << " " << ember.m_CenterY << "\"";
|
|
os << " scale=\"" << ember.m_PixelsPerUnit << "\"";
|
|
|
|
if (ember.m_Zoom != 0)
|
|
os << " zoom=\"" << ember.m_Zoom << "\"";
|
|
|
|
os << " rotate=\"" << ember.m_Rotate << "\"";
|
|
os << " supersample=\"" << std::max<size_t>(1, ember.m_Supersample) << "\"";
|
|
os << " filter=\"" << ember.m_SpatialFilterRadius << "\"";
|
|
|
|
os << " filter_shape=\"" << ToLower(SpatialFilterCreator<T>::ToString(ember.m_SpatialFilterType)) << "\"";
|
|
os << " temporal_filter_type=\"" << ToLower(TemporalFilterCreator<T>::ToString(ember.m_TemporalFilterType)) << "\"";
|
|
|
|
if (ember.m_TemporalFilterType == EXP_TEMPORAL_FILTER)
|
|
os << " temporal_filter_exp=\"" << ember.m_TemporalFilterExp << "\"";
|
|
|
|
os << " temporal_filter_width=\"" << ember.m_TemporalFilterWidth << "\"";
|
|
os << " quality=\"" << ember.m_Quality << "\"";
|
|
os << " temporal_samples=\"" << ember.m_TemporalSamples << "\"";
|
|
os << " sub_batch_size=\"" << ember.m_SubBatchSize << "\"";
|
|
os << " fuse=\"" << ember.m_FuseCount << "\"";
|
|
os << " background=\"" << ember.m_Background.r << " " << ember.m_Background.g << " " << ember.m_Background.b << "\"";
|
|
os << " brightness=\"" << ember.m_Brightness << "\"";
|
|
os << " gamma=\"" << ember.m_Gamma << "\"";
|
|
os << " highlight_power=\"" << ember.m_HighlightPower << "\"";
|
|
os << " vibrancy=\"" << ember.m_Vibrancy << "\"";
|
|
//os << " hue=\"" << ember.m_Hue << "\"";//Oddly enough, flam3 never wrote this value out.//ORIG
|
|
os << " estimator_radius=\"" << ember.m_MaxRadDE << "\"";
|
|
os << " estimator_minimum=\"" << ember.m_MinRadDE << "\"";
|
|
os << " estimator_curve=\"" << ember.m_CurveDE << "\"";
|
|
os << " gamma_threshold=\"" << ember.m_GammaThresh << "\"";
|
|
os << " cam_zpos=\"" << ember.m_CamZPos << "\"";
|
|
os << " cam_persp=\"" << ember.m_CamPerspective << "\"";
|
|
os << " cam_yaw=\"" << ember.m_CamYaw << "\"";
|
|
os << " cam_pitch=\"" << ember.m_CamPitch << "\"";
|
|
os << " cam_dof=\"" << ember.m_CamDepthBlur << "\"";
|
|
|
|
if (ember.m_PaletteMode == PALETTE_STEP)
|
|
os << " palette_mode=\"step\"";
|
|
else if (ember.m_PaletteMode == PALETTE_LINEAR)
|
|
os << " palette_mode=\"linear\"";
|
|
|
|
if (ember.m_Interp == EMBER_INTERP_SMOOTH)
|
|
os << " interpolation=\"smooth\"";
|
|
|
|
if (ember.m_AffineInterp == INTERP_LINEAR)
|
|
os << " interpolation_type=\"linear\"";
|
|
else if (ember.m_AffineInterp == INTERP_LOG)
|
|
os << " interpolation_type=\"log\"";
|
|
else if (ember.m_AffineInterp == INTERP_COMPAT)
|
|
os << " interpolation_type=\"old\"";
|
|
else if (ember.m_AffineInterp == INTERP_OLDER)
|
|
os << " interpolation_type=\"older\"";
|
|
|
|
if (ember.m_PaletteInterp == INTERP_SWEEP)
|
|
os << " palette_interpolation=\"sweep\"";
|
|
|
|
if (!extraAttributes.empty())
|
|
os << " " << extraAttributes;
|
|
|
|
os << " plugins=\"";
|
|
ember.GetPresentVariations(variations, false);
|
|
|
|
if (!variations.empty())
|
|
for (auto var : variations) os << var->Name() << (var != variations.back() ? " " : "\"");
|
|
else
|
|
os << "\"";
|
|
|
|
os << " new_linear=\"1\"";
|
|
os << " curves=\"";
|
|
|
|
for (glm::length_t ci = 0; ci < 4; ci++)
|
|
{
|
|
for (glm::length_t cj = 0; cj < 4; cj++)
|
|
{
|
|
os << ember.m_Curves.m_Points[ci][cj].x << " ";
|
|
os << ember.m_Curves.m_Points[ci][cj].y << " ";
|
|
os << ember.m_Curves.m_Weights[ci][cj] << " ";
|
|
}
|
|
}
|
|
|
|
os << "\">\n";
|
|
|
|
for (i = 0; i < ember.m_EmberMotionElements.size(); ++i)
|
|
os << " " << ToString(ember.m_EmberMotionElements[i]);
|
|
|
|
//This is a grey area, what to do about symmetry to avoid duplicating the symmetry xforms when reading back?//TODO//BUG.
|
|
//if (ember.m_Symmetry)
|
|
// os << " <symmetry kind=\"" << ember.m_Symmetry << "\"/>\n";
|
|
|
|
for (i = 0; i < ember.XformCount(); i++)
|
|
os << ToString(*ember.GetXform(i), ember.XformCount(), false, false);//Not final, don't do motion.
|
|
|
|
if (ember.UseFinalXform())
|
|
os << ToString(*ember.NonConstFinalXform(), ember.XformCount(), true, false);//Final, don't do motion.
|
|
|
|
//Note that only embedded palettes are saved. The old style of specifying a palette index to look up in a default palette file
|
|
//is no longer supported, as it makes no sense when using multiple palette files. The only way it could work is if the index was
|
|
//always meant to refer to the default file, or if the filename was embedded as well. It's easier, more straightforward and
|
|
//less error prone to just embed the palette.
|
|
if (hexPalette)
|
|
{
|
|
os << " <palette count=\"256\" format=\"RGB\">\n";
|
|
|
|
for (i = 0; i < 32; i++)
|
|
{
|
|
os << " ";
|
|
|
|
for (j = 0; j < 8; j++)
|
|
{
|
|
size_t idx = 8 * i + j;
|
|
|
|
os << hex << setw(2) << setfill('0') << int(Rint(ember.m_Palette[idx][0] * 255));
|
|
os << hex << setw(2) << setfill('0') << int(Rint(ember.m_Palette[idx][1] * 255));
|
|
os << hex << setw(2) << setfill('0') << int(Rint(ember.m_Palette[idx][2] * 255));
|
|
}
|
|
|
|
os << endl;
|
|
}
|
|
|
|
os << " </palette>\n";
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < 256; i++)
|
|
{
|
|
double r = ember.m_Palette[i][0] * 255;
|
|
double g = ember.m_Palette[i][1] * 255;
|
|
double b = ember.m_Palette[i][2] * 255;
|
|
double a = ember.m_Palette[i][3] * 255;
|
|
|
|
os << " ";
|
|
//The original used a precision of 6 which is totally unnecessary, use 2.
|
|
if (IsClose(a, 255.0))
|
|
{
|
|
if (intPalette)
|
|
os << "<color index=\"" << i << "\" rgb=\"" << int(Rint(r)) << " " << int(Rint(g)) << " " << int(Rint(b)) << "\"/>";
|
|
else
|
|
os << "<color index=\"" << i << "\" rgb=\"" << std::fixed << std::setprecision(2) << r << " " << g << " " << b << "\"/>";
|
|
}
|
|
else
|
|
{
|
|
if (intPalette)
|
|
os << " <color index=\"" << i << "\" rgba=\"" << int(Rint(r)) << " " << int(Rint(g)) << " " << int(Rint(b)) << " " << int(Rint(a)) << "\"/>";
|
|
else
|
|
os << " <color index=\"" << i << "\" rgba=\"" << std::fixed << std::setprecision(2) << r << " " << g << " " << b << " " << a << "\"/>";
|
|
}
|
|
|
|
os << "\n";
|
|
}
|
|
}
|
|
|
|
if (doEdits && ember.m_Edits != nullptr)
|
|
os << ToString(xmlDocGetRootElement(ember.m_Edits), 1, true, printEditDepth);
|
|
|
|
os << "</flame>\n";
|
|
|
|
return os.str();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a new editdoc optionally based on parents passed in.
|
|
/// This is used when an ember is made out of some mutation or edit from one or two existing embers and
|
|
/// the user wants to capture the genetic lineage history information in the edit doc of the new ember.
|
|
/// </summary>
|
|
/// <param name="parent0">The first parent, optionally nullptr.</param>
|
|
/// <param name="parent1">The second parent, optionally nullptr.</param>
|
|
/// <param name="action">The action that was taken to create the new ember</param>
|
|
/// <param name="nick">The nickname of the author</param>
|
|
/// <param name="url">The Url of the author</param>
|
|
/// <param name="id">The id of the author</param>
|
|
/// <param name="comment">The comment to include</param>
|
|
/// <param name="sheepGen">The sheep generation used if > 0. Default: 0.</param>
|
|
/// <param name="sheepId">The sheep id used if > 0. Default: 0.</param>
|
|
/// <returns></returns>
|
|
xmlDocPtr CreateNewEditdoc(Ember<T>* parent0, Ember<T>* parent1, const string& action, const string& nick, const string& url, const string& id, const string& comment, intmax_t sheepGen = 0, intmax_t sheepId = 0)
|
|
{
|
|
char timeString[128];
|
|
time_t myTime;
|
|
string s;
|
|
xmlDocPtr commentDoc = nullptr;
|
|
xmlDocPtr doc = xmlNewDoc(XC("1.0"));
|
|
xmlNodePtr rootNode = nullptr, node = nullptr, nodeCopy = nullptr;
|
|
xmlNodePtr rootComment = nullptr;
|
|
ostringstream os;
|
|
|
|
//Create the root node, called "edit".
|
|
rootNode = xmlNewNode(nullptr, XC("edit"));
|
|
xmlDocSetRootElement(doc, rootNode);
|
|
|
|
//Add the edit attributes.
|
|
//Date.
|
|
myTime = time(nullptr);
|
|
#ifdef WIN32
|
|
tm localt;
|
|
localtime_s(&localt, &myTime);
|
|
strftime(timeString, 128, "%a %b %d %H:%M:%S %z %Y", &localt);//XXX use standard time format including timezone.
|
|
#else
|
|
tm* localt;
|
|
localt = localtime(&myTime);
|
|
strftime(timeString, 128, "%a %b %d %H:%M:%S %z %Y", localt);//XXX use standard time format including timezone.
|
|
#endif
|
|
xmlNewProp(rootNode, XC("date"), XC(timeString));
|
|
|
|
//Nick.
|
|
if (nick != "")
|
|
xmlNewProp(rootNode, XC("nick"), XC(nick.c_str()));
|
|
|
|
//Url.
|
|
if (url != "")
|
|
xmlNewProp(rootNode, XC("url"), XC(url.c_str()));
|
|
|
|
if (id != "")
|
|
xmlNewProp(rootNode, XC("id"), XC(id.c_str()));
|
|
|
|
//Action.
|
|
xmlNewProp(rootNode, XC("action"), XC(action.c_str()));
|
|
|
|
//Sheep info.
|
|
if (sheepGen > 0 && sheepId > 0)
|
|
{
|
|
//Create a child node of the root node called sheep.
|
|
node = xmlNewChild(rootNode, nullptr, XC("sheep"), nullptr);
|
|
|
|
//Create the sheep attributes.
|
|
os << sheepGen;
|
|
s = os.str();
|
|
xmlNewProp(node, XC("generation"), XC(s.c_str()));
|
|
os.str("");
|
|
|
|
os << sheepId;
|
|
s = os.str();
|
|
xmlNewProp(node, XC("id"), XC(s.c_str()));
|
|
os.str("");
|
|
}
|
|
|
|
//Check for the parents.
|
|
//If parent 0 not specified, this is a randomly generated genome.
|
|
if (parent0)
|
|
{
|
|
os << parent0->m_Index;
|
|
s = os.str();
|
|
|
|
if (parent0->m_Edits)
|
|
{
|
|
//Copy the node from the parent.
|
|
node = xmlDocGetRootElement(parent0->m_Edits);
|
|
nodeCopy = xmlCopyNode(node, 1);
|
|
AddFilenameWithoutAmpersand(nodeCopy, parent0->m_ParentFilename);
|
|
|
|
xmlNewProp(nodeCopy, XC("index"), XC(s.c_str()));
|
|
xmlAddChild(rootNode, nodeCopy);
|
|
}
|
|
else
|
|
{
|
|
//Insert a (parent has no edit) message.
|
|
nodeCopy = xmlNewChild(rootNode, nullptr, XC("edit"), nullptr);
|
|
AddFilenameWithoutAmpersand(nodeCopy, parent0->m_ParentFilename);
|
|
xmlNewProp(nodeCopy, XC("index"), XC(s.c_str()));
|
|
}
|
|
|
|
os.str("");
|
|
}
|
|
|
|
if (parent1)
|
|
{
|
|
os << parent1->m_Index;
|
|
s = os.str();
|
|
|
|
if (parent1->m_Edits)
|
|
{
|
|
//Copy the node from the parent.
|
|
node = xmlDocGetRootElement(parent1->m_Edits);
|
|
nodeCopy = xmlCopyNode(node, 1);
|
|
AddFilenameWithoutAmpersand(nodeCopy, parent1->m_ParentFilename);
|
|
xmlNewProp(nodeCopy, XC("index"), XC(s.c_str()));
|
|
xmlAddChild(rootNode, nodeCopy);
|
|
}
|
|
else
|
|
{
|
|
//Insert a (parent has no edit) message.
|
|
nodeCopy = xmlNewChild(rootNode, nullptr, XC("edit"),nullptr);
|
|
AddFilenameWithoutAmpersand(nodeCopy, parent1->m_ParentFilename);
|
|
xmlNewProp(nodeCopy, XC("index"), XC(s.c_str()));
|
|
}
|
|
|
|
os.str("");
|
|
}
|
|
|
|
//Comment string:
|
|
//This one's hard, since the comment string must be treated as
|
|
//a valid XML document. Create a new document using the comment
|
|
//string as the in-memory document, and then copy all children of
|
|
//the root node into the edit structure
|
|
//Parsing the comment string should be done once and then copied
|
|
//for each new edit doc, but that's for later.
|
|
if (comment != "")
|
|
{
|
|
os << "<comm>" << comment << "</comm>";
|
|
s = os.str();
|
|
commentDoc = xmlReadMemory(s.c_str(), int(s.length()), "comment.env", nullptr, XML_PARSE_NONET);
|
|
os.str("");
|
|
|
|
//Check for errors.
|
|
if (commentDoc != nullptr)
|
|
{
|
|
|
|
//Loop through the children of the new document and copy them into the rootNode.
|
|
rootComment = xmlDocGetRootElement(commentDoc);
|
|
|
|
for (node = rootComment->children; node; node = node->next)
|
|
{
|
|
nodeCopy = xmlCopyNode(node, 1);
|
|
xmlAddChild(rootNode, nodeCopy);
|
|
}
|
|
|
|
//Free the created document.
|
|
xmlFreeDoc(commentDoc);
|
|
}
|
|
else
|
|
{
|
|
cout << "Failed to parse comment into Xml." << endl;
|
|
}
|
|
}
|
|
|
|
//Return the Xml doc.
|
|
return doc;
|
|
}
|
|
|
|
private:
|
|
/// <summary>
|
|
/// Return the Xml string representation of an xform.
|
|
/// </summary>
|
|
/// <param name="xform">The xform to create the Xml with</param>
|
|
/// <param name="xformCount">The number of non-final xforms in the ember to which this xform belongs. Used for xaos.</param>
|
|
/// <param name="isFinal">True if the xform is the final xform in the ember, else false.</param>
|
|
/// <param name="doMotion">If true, include motion elements in the Xml string, else omit.</param>
|
|
/// <returns>The Xml string representation of the passed in xform</returns>
|
|
string ToString(Xform<T>& xform, size_t xformCount, bool isFinal, bool doMotion)
|
|
{
|
|
size_t i, j;
|
|
ostringstream os;
|
|
|
|
if (doMotion)
|
|
{
|
|
os << " <motion motion_frequency=\"" << xform.m_MotionFreq << "\" ";
|
|
|
|
if (xform.m_MotionFunc == MOTION_SIN)
|
|
os << "motion_function=\"sin\" ";
|
|
else if (xform.m_MotionFunc == MOTION_TRIANGLE)
|
|
os << "motion_function=\"triangle\" ";
|
|
else if (xform.m_MotionFunc== MOTION_HILL)
|
|
os << "motion_function=\"hill\" ";
|
|
else if (xform.m_MotionFunc== MOTION_SAW)
|
|
os << "motion_function=\"saw\" ";
|
|
|
|
if (xform.m_MotionOffset != 0)
|
|
os << "motion_offset=\"" << xform.m_MotionOffset << "\" ";
|
|
}
|
|
else
|
|
{
|
|
if (isFinal)
|
|
os << " <finalxform ";
|
|
else
|
|
os << " <xform weight=\"" << xform.m_Weight << "\" ";
|
|
}
|
|
|
|
if (!doMotion || xform.m_ColorX != EMPTYFIELD) os << "color=\"" << xform.m_ColorX << "\" ";
|
|
//if (!doMotion || xform.m_ColorY != EMPTYFIELD) os << "color=\"" << xform.m_ColorX << " " << xform.m_ColorY << "\" ";
|
|
if (!doMotion || xform.m_DirectColor != EMPTYFIELD) os << "var_color=\"" << xform.m_DirectColor << "\" ";
|
|
if (!doMotion || xform.m_ColorSpeed != EMPTYFIELD) os << "color_speed=\"" << xform.m_ColorSpeed << "\" ";
|
|
//os << "symmetry=\"" << fabs(xform.m_ColorSpeed - 1) * 2 << "\" ";//Legacy support.
|
|
|
|
if (!doMotion)
|
|
{
|
|
string s = xform.m_Name;
|
|
|
|
std::replace(s.begin(), s.end(), ' ', '_');
|
|
os << "name=\"" << s << "\" ";//Flam3 didn't do this, but Apo does.
|
|
|
|
if (!isFinal)
|
|
os << "animate=\"" << xform.m_Animate << "\" ";
|
|
}
|
|
|
|
//Variation writing order differs slightly from the original to make it a bit more readable.
|
|
//The original wrote out all of the variation names and weights. Then wrote out the parameters for
|
|
//the parametric variations. Here, write out the params immediately after each parametric variation
|
|
//so they are more closely grouped with the variation they apply to, rather than being all grouped at the end.
|
|
for (i = 0; i < xform.TotalVariationCount(); i++)
|
|
{
|
|
Variation<T>* var = xform.GetVariation(i);
|
|
ParametricVariation<T>* parVar = dynamic_cast<ParametricVariation<T>*>(var);
|
|
|
|
if (var->m_Weight != 0)
|
|
{
|
|
os << var->Name() << "=\"" << var->m_Weight << "\" ";
|
|
|
|
if (parVar)
|
|
{
|
|
auto params = parVar->Params();
|
|
|
|
for (j = 0; j < parVar->ParamCount(); j++)
|
|
{
|
|
if ((!doMotion || (doMotion && (params[j].ParamVal() != 0))) && !params[j].IsPrecalc())
|
|
os << params[j].Name() << "=\"" << params[j].ParamVal() << "\" ";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!doMotion || (doMotion && !xform.m_Affine.IsZero() && !xform.m_Affine.IsEmpty()))
|
|
{
|
|
os << "coefs=\"" << xform.m_Affine.A() << " " << xform.m_Affine.D() << " " << xform.m_Affine.B() << " "
|
|
<< xform.m_Affine.E() << " " << xform.m_Affine.C() << " " << xform.m_Affine.F() << "\"";
|
|
}
|
|
|
|
if ((!doMotion && !xform.m_Post.IsID()) || (doMotion && !xform.m_Post.IsZero() && !xform.m_Post.IsEmpty()))
|
|
{
|
|
os << " post=\"" << xform.m_Post.A() << " " << xform.m_Post.D() << " " << xform.m_Post.B() << " "
|
|
<< xform.m_Post.E() << " " << xform.m_Post.C() << " " << xform.m_Post.F() << "\"";
|
|
}
|
|
|
|
//Original only printed xaos values that were not 1. Here, print them all out if any are present.
|
|
if (!isFinal && !doMotion && xform.XaosPresent())//Applying motion to xaos not supported.
|
|
{
|
|
os << " chaos=\"";
|
|
|
|
for (i = 0; i < xformCount; i++)
|
|
os << xform.Xaos(i) << " ";
|
|
|
|
os << "\"";
|
|
}
|
|
|
|
if (!doMotion || xform.m_Opacity != EMPTYFIELD) os << " opacity=\"" << xform.m_Opacity << "\"";
|
|
|
|
if (!doMotion && !xform.m_Motion.empty())
|
|
{
|
|
os << ">\n";
|
|
|
|
for (i = 0; i < xform.m_Motion.size(); i++)
|
|
os << ToString(xform.m_Motion[i], 0, false, true);
|
|
|
|
if (isFinal)//Fixed to properly close final.//SMOULDER
|
|
os << " </finalxform>\n";
|
|
else
|
|
os << " </xform>\n";
|
|
}
|
|
else
|
|
os << "/>\n";
|
|
|
|
return os.str();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return an edit node Xml string.
|
|
/// </summary>
|
|
/// <param name="editNode">The edit node to get the string for</param>
|
|
/// <param name="tabs">How many tabs to use</param>
|
|
/// <param name="formatting">If true, include newlines and tabs, else don't.</param>
|
|
/// <param name="printEditDepth">How deep the edit depth goes</param>
|
|
/// <returns>The edit node Xml string</returns>
|
|
string ToString(xmlNodePtr editNode, size_t tabs, bool formatting, size_t printEditDepth)
|
|
{
|
|
bool indentPrinted = false;
|
|
const char* tabString = " ", *attStr;
|
|
const char* editString = "edit";
|
|
const char* sheepString = "sheep";
|
|
size_t ti;//, editOrSheep = 0;
|
|
xmlAttrPtr attPtr = nullptr, curAtt = nullptr;
|
|
xmlNodePtr childPtr = nullptr, curChild = nullptr;
|
|
ostringstream os;
|
|
|
|
if (printEditDepth > 0 && tabs > printEditDepth)
|
|
return "";
|
|
|
|
//If this node is an XML_ELEMENT_NODE, print it and its attributes.
|
|
if (editNode->type == XML_ELEMENT_NODE)
|
|
{
|
|
//Print the node at the tab specified.
|
|
if (formatting)
|
|
for (ti = 0; ti < tabs; ti++)
|
|
os << tabString;
|
|
|
|
os << "<" << editNode->name;
|
|
|
|
//This can either be an edit node or a sheep node.
|
|
//If it's an edit node, add one to the tab.
|
|
if (!Compare(editNode->name, editString))
|
|
{
|
|
//editOrSheep = 1;
|
|
tabs++;
|
|
}
|
|
else if (!Compare(editNode->name, sheepString)) { }
|
|
//editOrSheep = 2;
|
|
else { }
|
|
//editOrSheep = 0;
|
|
|
|
//Print the attributes.
|
|
attPtr = editNode->properties;
|
|
|
|
for (curAtt = attPtr; curAtt; curAtt = curAtt->next)
|
|
{
|
|
attStr = CX(xmlGetProp(editNode, curAtt->name));
|
|
os << " " << curAtt->name << "=\"" << attStr << "\"";
|
|
xmlFree(reinterpret_cast<void*>(const_cast<char*>(attStr)));
|
|
}
|
|
|
|
//Does this node have children?
|
|
if (!editNode->children || (printEditDepth > 0 && tabs > printEditDepth))
|
|
{
|
|
//Close the tag and subtract the tab.
|
|
os << "/>";
|
|
|
|
if (formatting)
|
|
os << "\n";
|
|
|
|
tabs--;
|
|
}
|
|
else
|
|
{
|
|
//Close the tag.
|
|
os << ">";
|
|
|
|
if (formatting)
|
|
os << "\n";
|
|
|
|
//Loop through the children and print them.
|
|
childPtr = editNode->children;
|
|
indentPrinted = false;
|
|
|
|
for (curChild = childPtr; curChild; curChild = curChild->next)
|
|
{
|
|
//If child is an element, indent first and then print it.
|
|
if (curChild->type == XML_ELEMENT_NODE &&
|
|
(!Compare(curChild->name, editString) || !Compare(curChild->name, sheepString)))
|
|
{
|
|
if (indentPrinted)
|
|
{
|
|
indentPrinted = false;
|
|
os << "\n";
|
|
}
|
|
|
|
os << ToString(curChild, tabs, true, printEditDepth);
|
|
}
|
|
else
|
|
{
|
|
//Child is a text node, don't want to indent more than once.
|
|
if (xmlIsBlankNode(curChild))
|
|
continue;
|
|
|
|
if (!indentPrinted && formatting)
|
|
{
|
|
for (ti = 0; ti < tabs; ti++)
|
|
os << tabString;
|
|
|
|
indentPrinted = true;
|
|
}
|
|
|
|
//Print nodes without formatting.
|
|
os << ToString(curChild, tabs, false, printEditDepth);
|
|
}
|
|
}
|
|
|
|
if (indentPrinted && formatting)
|
|
os << "\n";
|
|
|
|
tabs--;//Tab out.
|
|
|
|
if (formatting)
|
|
for (ti = 0; ti < tabs; ti++)
|
|
os << tabString;
|
|
|
|
os << "</" << editNode->name << ">";//Close the tag.
|
|
|
|
if (formatting)
|
|
os << "\n";
|
|
}
|
|
}
|
|
else if (editNode->type == XML_TEXT_NODE)
|
|
{
|
|
string s(reinterpret_cast<char*>(xmlNodeGetContent(editNode)));
|
|
os << Trim(s);
|
|
}
|
|
|
|
return os.str();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert a FlameMotion element to an xml string
|
|
/// </summary>
|
|
/// <param name="motion">The FlameMotion object to convert to XML</param>
|
|
string ToString(const EmberMotion<T>& motion)
|
|
{
|
|
ostringstream os;
|
|
os << "<flame_motion motion_frequency=\"" << motion.m_MotionFreq << "\" ";
|
|
|
|
if (motion.m_MotionOffset > 0)
|
|
os << "motion_offset=\"" << motion.m_MotionOffset << "\" ";
|
|
|
|
os << "motion_func=";
|
|
|
|
switch (motion.m_MotionFunc)
|
|
{
|
|
case MOTION_SIN:
|
|
os << "\"sin\"";
|
|
break;
|
|
case MOTION_HILL:
|
|
os << "\"hill\"";
|
|
break;
|
|
case MOTION_TRIANGLE:
|
|
os << "\"triangle\"";
|
|
break;
|
|
default:
|
|
case MOTION_SAW:
|
|
os << "\"saw\"";
|
|
break;
|
|
}
|
|
|
|
T r = 0.0;
|
|
T g = 0.0;
|
|
T b = 0.0;
|
|
T cx = 0.0;
|
|
T cy = 0.0;
|
|
|
|
for (size_t i = 0; i < motion.m_MotionParams.size(); ++i)
|
|
{
|
|
switch(motion.m_MotionParams[i].first)
|
|
{
|
|
case FLAME_MOTION_ZOOM:
|
|
os << " zoom=\"" << motion.m_MotionParams[i].second << "\"";
|
|
break;
|
|
case FLAME_MOTION_ZPOS:
|
|
os << " cam_zpos=\"" << motion.m_MotionParams[i].second << "\"";
|
|
break;
|
|
case FLAME_MOTION_PERSPECTIVE:
|
|
os << " cam_persp=\"" << motion.m_MotionParams[i].second << "\"";
|
|
break;
|
|
case FLAME_MOTION_YAW:
|
|
os << " cam_yaw=\"" << motion.m_MotionParams[i].second << "\"";
|
|
break;
|
|
case FLAME_MOTION_PITCH:
|
|
os << " cam_pitch=\"" << motion.m_MotionParams[i].second << "\"";
|
|
break;
|
|
case FLAME_MOTION_DEPTH_BLUR:
|
|
os << " cam_dof=\"" << motion.m_MotionParams[i].second << "\"";
|
|
break;
|
|
case FLAME_MOTION_CENTER_X:
|
|
cx = motion.m_MotionParams[i].second;
|
|
break;
|
|
case FLAME_MOTION_CENTER_Y:
|
|
cy = motion.m_MotionParams[i].second;
|
|
break;
|
|
case FLAME_MOTION_ROTATE:
|
|
os << " rotate=\"" << motion.m_MotionParams[i].second << "\"";
|
|
break;
|
|
case FLAME_MOTION_BRIGHTNESS:
|
|
os << " brightness=\"" << motion.m_MotionParams[i].second << "\"";
|
|
break;
|
|
case FLAME_MOTION_GAMMA:
|
|
os << " gamma=\"" << motion.m_MotionParams[i].second << "\"";
|
|
break;
|
|
case FLAME_MOTION_GAMMA_THRESH:
|
|
os << " gamma_threshold=\"" << motion.m_MotionParams[i].second << "\"";
|
|
break;
|
|
case FLAME_MOTION_HIGHLIGHT_POWER:
|
|
os << " highlight_power=\"" << motion.m_MotionParams[i].second << "\"";
|
|
break;
|
|
case FLAME_MOTION_BACKGROUND_R:
|
|
r = motion.m_MotionParams[i].second;
|
|
break;
|
|
case FLAME_MOTION_BACKGROUND_G:
|
|
g = motion.m_MotionParams[i].second;
|
|
break;
|
|
case FLAME_MOTION_BACKGROUND_B:
|
|
b = motion.m_MotionParams[i].second;
|
|
break;
|
|
case FLAME_MOTION_VIBRANCY:
|
|
os << " vibrancy=\"" << motion.m_MotionParams[i].second << "\"";
|
|
break;
|
|
case FLAME_MOTION_NONE:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (r != 0.0 || g != 0.0 || b != 0.0)
|
|
os << " background=\"" << r << " " << g << " " << b << "\"";
|
|
|
|
if (cx != 0.0 || cy != 0.0)
|
|
os << " center=\"" << cx << " " << cy << "\"";
|
|
|
|
os << "/>\n";
|
|
|
|
return os.str();
|
|
}
|
|
|
|
void AddFilenameWithoutAmpersand(xmlNodePtr node, string& filename)
|
|
{
|
|
if (filename.find_first_of('&') != std::string::npos)
|
|
{
|
|
string filenameWithoutAmpersands = filename;
|
|
|
|
FindAndReplace<string>(filenameWithoutAmpersands, "&", "&");
|
|
xmlNewProp(node, XC("filename"), XC(filenameWithoutAmpersands.c_str()));
|
|
}
|
|
else
|
|
{
|
|
xmlNewProp(node, XC("filename"), XC(filename.c_str()));
|
|
}
|
|
}
|
|
};
|
|
}
|