mirror of
https://bitbucket.org/mfeemster/fractorium.git
synced 2025-01-21 21:20:07 -05:00
47b6614c8a
-Fix improper usage of rand() in cpow2, cpow3, hypertile1, hypertile3D1, hypertile3D2, juliac, juliaq. -Fix program crashing during density filtering on some Nvidia cards. -hypertile3D1 was wrong. -Parsing phoenix_julia when coming from Apophysis was wrong. -Density filtering was freezing on certain Nvidia cards. --Code changes -Optimize juliac, npolar. -Add a new function Crand() which behaves like the legacy C rand() which returns an integer between 0 and 32766, inclusive. -Use RandBit() in some places. -Remove Zeps() from vignette, it's not needed. -Restructure OpenCL code for density filtering such that it does not hang after being compiled on some Nvidia cards, such as the gtx 1660. Remove barriers from conditionals where possible.
2651 lines
92 KiB
C++
2651 lines
92 KiB
C++
#include "EmberPch.h"
|
|
#include "XmlToEmber.h"
|
|
|
|
namespace EmberNs
|
|
{
|
|
/// <summary>
|
|
/// Constructor which saves the state of the current locale and
|
|
/// sets the new one based on the parameters passed in.
|
|
/// </summary>
|
|
/// <param name="category">The locale category. Default: LC_NUMERIC.</param>
|
|
/// <param name="loc">The locale. Default: "C".</param>
|
|
Locale::Locale(int category, const char* loc)
|
|
{
|
|
m_Category = category;
|
|
m_NewLocale = string(loc);
|
|
m_OriginalLocale = setlocale(category, nullptr);//Query.
|
|
|
|
if (m_OriginalLocale.empty())
|
|
cout << "Couldn't get original locale.\n";
|
|
|
|
if (setlocale(category, loc) == nullptr)//Set.
|
|
cout << "Couldn't set new locale " << category << ", " << loc << ".\n";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reset the locale to the value stored during construction.
|
|
/// </summary>
|
|
Locale::~Locale()
|
|
{
|
|
if (!m_OriginalLocale.empty())
|
|
if (setlocale(m_Category, m_OriginalLocale.c_str()) == nullptr)//Restore.
|
|
cout << "Couldn't restore original locale " << m_Category << ", " << m_OriginalLocale << ".\n";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructor that initializes the random context.
|
|
/// </summary>
|
|
template <typename T>
|
|
XmlToEmber<T>::XmlToEmber()
|
|
: m_VariationList(VariationList<T>::Instance()),
|
|
m_PaletteList(PaletteList<float>::Instance())
|
|
{
|
|
Timing t;
|
|
|
|
if (!m_Init)
|
|
{
|
|
//This list is for variation params which are incorrect, but their parent variation name may or may not be correct.
|
|
//This has some overlap with the list below since some of these have parent variation names that are also incorrect.
|
|
m_BadParamNames = unordered_map<string, string>
|
|
{
|
|
{ "swtin_distort", "stwin_distort" }, //stwin.
|
|
{ "pow_numerator", "pow_block_numerator" }, //pow_block.
|
|
{ "pow_denominator", "pow_block_denominator" },
|
|
{ "pow_root", "pow_block_root" },
|
|
{ "pow_correctn", "pow_block_correctn" },
|
|
{ "pow_correctd", "pow_block_correctd" },
|
|
{ "pow_power", "pow_block_power" },
|
|
{ "lt", "linearT_powX" }, //linearT.
|
|
{ "lt", "linearT_powY" },
|
|
{ "re_a", "Mobius_Re_A" }, //Mobius.
|
|
{ "im_a", "Mobius_Im_A" },
|
|
{ "re_b", "Mobius_Re_B" },
|
|
{ "im_b", "Mobius_Im_B" },
|
|
{ "re_c", "Mobius_Re_C" },
|
|
{ "im_c", "Mobius_Im_C" },
|
|
{ "re_d", "Mobius_Re_D" },
|
|
{ "im_d", "Mobius_Im_D" },
|
|
{ "rx_sin", "rotate_x_sin" }, //rotate_x.
|
|
{ "rx_cos", "rotate_x_cos" },
|
|
{ "ry_sin", "rotate_y_sin" }, //rotate_y.
|
|
{ "ry_cos", "rotate_y_cos" },
|
|
{ "intrfr2_a1", "interference2_a1" }, //interference2.
|
|
{ "intrfr2_b1", "interference2_b1" },
|
|
{ "intrfr2_c1", "interference2_c1" },
|
|
{ "intrfr2_p1", "interference2_p1" },
|
|
{ "intrfr2_t1", "interference2_t1" },
|
|
{ "intrfr2_a2", "interference2_a2" },
|
|
{ "intrfr2_b2", "interference2_b2" },
|
|
{ "intrfr2_c2", "interference2_c2" },
|
|
{ "intrfr2_p2", "interference2_p2" },
|
|
{ "intrfr2_t2", "interference2_t2" },
|
|
{ "octa_x", "octagon_x" }, //octagon.
|
|
{ "octa_y", "octagon_y" },
|
|
{ "octa_z", "octagon_z" },
|
|
{ "bubble_x", "bubble2_x" }, //bubble2.
|
|
{ "bubble_y", "bubble2_y" },
|
|
{ "bubble_z", "bubble2_z" },
|
|
{ "cubic3d_xpand", "cubicLattice_3D_xpand" }, //cubicLattice_3D.
|
|
{ "cubic3d_style", "cubicLattice_3D_style" },
|
|
{ "splitb_x", "SplitBrdr_x" }, //SplitBrdr.
|
|
{ "splitb_y", "SplitBrdr_y" },
|
|
{ "splitb_px", "SplitBrdr_px" },
|
|
{ "splitb_py", "SplitBrdr_py" },
|
|
{ "dc_cyl_offset", "dc_cylinder_offset" }, //dc_cylinder.
|
|
{ "dc_cyl_angle", "dc_cylinder_angle" },
|
|
{ "dc_cyl_scale", "dc_cylinder_scale" },
|
|
{ "cyl_x", "dc_cylinder_x" },
|
|
{ "cyl_y", "dc_cylinder_y" },
|
|
{ "cyl_blur", "dc_cylinder_blur" },
|
|
{ "mobius_radius", "mobius_strip_radius" }, //mobius_strip.
|
|
{ "mobius_width", "mobius_strip_width" },
|
|
{ "mobius_rect_x", "mobius_strip_rect_x" },
|
|
{ "mobius_rect_y", "mobius_strip_rect_y" },
|
|
{ "mobius_rotate_x", "mobius_strip_rotate_x" },
|
|
{ "mobius_rotate_y", "mobius_strip_rotate_y" },
|
|
{ "bwraps2_cellsize", "bwraps_cellsize" }, //bwraps2.
|
|
{ "bwraps2_space", "bwraps_space" },
|
|
{ "bwraps2_gain", "bwraps_gain" },
|
|
{ "bwraps2_inner_twist", "bwraps_inner_twist" },
|
|
{ "bwraps2_outer_twist", "bwraps_outer_twist" },
|
|
{ "bwraps7_cellsize", "bwraps_cellsize" }, //bwraps7.
|
|
{ "bwraps7_space", "bwraps_space" },
|
|
{ "bwraps7_gain", "bwraps_gain" },
|
|
{ "bwraps7_inner_twist", "bwraps_inner_twist" },
|
|
{ "bwraps7_outer_twist", "bwraps_outer_twist" },
|
|
{ "pre_bwraps2_cellsize", "pre_bwraps_cellsize" }, //bwraps2.
|
|
{ "pre_bwraps2_space", "pre_bwraps_space" },
|
|
{ "pre_bwraps2_gain", "pre_bwraps_gain" },
|
|
{ "pre_bwraps2_inner_twist", "pre_bwraps_inner_twist" },
|
|
{ "pre_bwraps2_outer_twist", "pre_bwraps_outer_twist" },
|
|
{ "post_bwraps2_cellsize", "post_bwraps_cellsize" },
|
|
{ "post_bwraps2_space", "post_bwraps_space" },
|
|
{ "post_bwraps2_gain", "post_bwraps_gain" },
|
|
{ "post_bwraps2_inner_twist", "post_bwraps_inner_twist" },
|
|
{ "post_bwraps2_outer_twist", "post_bwraps_outer_twist" },
|
|
{ "hexa3d_majp", "hexaplay3D_majp" }, //hexaplay3D.
|
|
{ "hexa3d_scale", "hexaplay3D_scale" },
|
|
{ "hexa3d_zlift", "hexaplay3D_zlift" },
|
|
{ "nb_numedges", "nBlur_numEdges" }, //nBlur.
|
|
{ "nb_numstripes", "nBlur_numStripes" },
|
|
{ "nb_ratiostripes", "nBlur_ratioStripes" },
|
|
{ "nb_ratiohole", "nBlur_ratioHole" },
|
|
{ "nb_circumcircle", "nBlur_circumCircle" },
|
|
{ "nb_adjusttolinear", "nBlur_adjustToLinear" },
|
|
{ "nb_equalblur", "nBlur_equalBlur" },
|
|
{ "nb_exactcalc", "nBlur_exactCalc" },
|
|
{ "nb_highlightedges", "nBlur_highlightEdges" },
|
|
{ "octapol_r", "octapol_radius" }, //octapol.
|
|
{ "number_of_stripes", "bubbleT3D_number_of_stripes" }, //bubbleT3D.
|
|
{ "ratio_of_stripes", "bubbleT3D_ratio_of_stripes" },
|
|
{ "angle_of_hole", "bubbleT3D_angle_of_hole" },
|
|
{ "exponentZ", "bubbleT3D_exponentZ" },
|
|
{ "_symmetryZ", "bubbleT3D_symmetryZ" },
|
|
{ "_modusBlur", "bubbleT3D_modusBlur" },
|
|
{ "post_scrop_power", "post_smartcrop_power" }, //post_smartcrop.
|
|
{ "post_scrop_radius", "post_smartcrop_radius" },
|
|
{ "post_scrop_roundstr", "post_smartcrop_roundstr" },
|
|
{ "post_scrop_roundwidth", "post_smartcrop_roundwidth" },
|
|
{ "post_scrop_distortion", "post_smartcrop_distortion" },
|
|
{ "post_scrop_edge", "post_smartcrop_edge" },
|
|
{ "post_scrop_scatter", "post_smartcrop_scatter" },
|
|
{ "post_scrop_offset", "post_smartcrop_offset" },
|
|
{ "post_scrop_rotation", "post_smartcrop_rotation" },
|
|
{ "post_scrop_cropmode", "post_smartcrop_cropmode" },
|
|
{ "post_scrop_static", "post_smartcrop_static" },
|
|
{ "cs_radius", "circlesplit_radius" },
|
|
{ "cs_split", "circlesplit_split" },
|
|
{ "w2r_freqx", "waves2_radial_freqx" },
|
|
{ "w2r_scalex", "waves2_radial_scalex" },
|
|
{ "w2r_freqy", "waves2_radial_freqy" },
|
|
{ "w2r_scaley", "waves2_radial_scaley" },
|
|
{ "w2r_null", "waves2_radial_null" },
|
|
{ "w2r_distance", "waves2_radial_distance" },
|
|
{ "tf_exponent", "Truchet_fill_exponent" },
|
|
{ "tf_arc_width", "Truchet_fill_arc_width" },
|
|
{ "tf_seed", "Truchet_fill_seed" },
|
|
{ "blockSize", "randCubes_blockSize" },
|
|
{ "blockHeight", "randCubes_blockHeight" },
|
|
{ "spread", "randCubes_spread" },
|
|
{ "seed", "randCubes_seed" },
|
|
{ "density", "randCubes_density" },
|
|
{ "radius", "concentric_radius" },
|
|
//{ "density", "concentric_density" },//Can't have two, which means you can never properly paste from Apophysis with both of these in one xform.
|
|
{ "r_blur", "concentric_R_blur" },
|
|
{ "z_blur", "concentric_Z_blur" },
|
|
{ "angle", "pixel_flow_angle" },
|
|
{ "len", "pixel_flow_len" },
|
|
{ "width", "pixel_flow_width" },
|
|
//{ "seed", "pixel_flow_seed" },//randCubes above already uses "seed", but it's just for randomness, so it shouldn't matter.
|
|
{ "enable_dc", "pixel_flow_enable_dc" },
|
|
{ "radial_gaussian_angle", "radial_blur_angle" },
|
|
{ "pr_a", "projective_A" },
|
|
{ "pr_b", "projective_B" },
|
|
{ "pr_c", "projective_C" },
|
|
{ "pr_a1", "projective_A1" },
|
|
{ "pr_b1", "projective_B1" },
|
|
{ "pr_c1", "projective_C1" },
|
|
{ "pr_a2", "projective_A2" },
|
|
{ "pr_b2", "projective_B2" },
|
|
{ "pr_c2", "projective_C2" },
|
|
{ "db_power", "depth_blur_power" },
|
|
{ "db_range", "depth_blur_range" },
|
|
{ "db_blur", "depth_blur_blur" },
|
|
{ "db_radius", "depth_blur_radius" },
|
|
{ "osco2_separation", "oscilloscope2_separation" },
|
|
{ "osco2_frequencyx", "oscilloscope2_frequencyx" },
|
|
{ "osco2_frequencyy", "oscilloscope2_frequencyy" },
|
|
{ "osco2_amplitude", "oscilloscope2_amplitude" },
|
|
{ "osco2_perturbation", "oscilloscope2_perturbation" },
|
|
{ "osco2_damping", "oscilloscope2_damping" },
|
|
{ "oscope_separation", "oscilloscope_separation" },
|
|
{ "oscope_frequency", "oscilloscope_frequency" },
|
|
{ "oscope_amplitude", "oscilloscope_amplitude" },
|
|
{ "oscope_damping", "oscilloscope_damping" },
|
|
{ "power", "scry2_power" },
|
|
{ "faber_w_angle", "w_angle" },
|
|
{ "faber_w_hypergon", "w_hypergon" },
|
|
{ "faber_w_hypergon_n", "w_hypergon_n" },
|
|
{ "faber_w_hypergon_r", "w_hypergon_r" },
|
|
{ "faber_w_star", "w_star" },
|
|
{ "faber_w_star_n", "w_star_n" },
|
|
{ "faber_w_star_slope", "w_star_slope" },
|
|
{ "faber_w_lituus", "w_lituus" },
|
|
{ "faber_w_lituus_a", "w_lituus_a" },
|
|
{ "faber_w_super", "w_super" },
|
|
{ "faber_w_super_m", "w_super_m" },
|
|
{ "faber_w_super_n1", "w_super_n1" },
|
|
{ "faber_w_super_n2", "w_super_n2" },
|
|
{ "faber_w_super_n3", "w_super_n3" },
|
|
{ "faber_x_hypergon", "x_hypergon" },
|
|
{ "faber_x_hypergon_n", "x_hypergon_n" },
|
|
{ "faber_x_hypergon_r", "x_hypergon_r" },
|
|
{ "faber_x_star", "x_star" },
|
|
{ "faber_x_star_n", "x_star_n" },
|
|
{ "faber_x_star_slope", "x_star_slope" },
|
|
{ "faber_x_lituus", "x_lituus" },
|
|
{ "faber_x_lituus_a", "x_lituus_a" },
|
|
{ "faber_x_super", "x_super" },
|
|
{ "faber_x_super_m", "x_super_m" },
|
|
{ "faber_x_super_n1", "x_super_n1" },
|
|
{ "faber_x_super_n2", "x_super_n2" },
|
|
{ "faber_x_super_n3", "x_super_n3" },
|
|
{ "sshape_power", "smartshape_power" },
|
|
{ "sshape_roundstr", "smartshape_roundstr" },
|
|
{ "sshape_roundwidth", "smartshape_roundwidth" },
|
|
{ "sshape_distortion", "smartshape_distortion" },
|
|
{ "sshape_compensation", "smartshape_compensation" },
|
|
{ "post_sshape_power", "post_smartshape_power" },
|
|
{ "post_sshape_roundstr", "post_smartshape_roundstr" },
|
|
{ "post_sshape_roundwidth", "post_smartshape_roundwidth" },
|
|
{ "post_sshape_distortion", "post_smartshape_distortion" },
|
|
{ "post_sshape_compensation", "post_smartshape_compensation" },
|
|
{ "mult_x", "unicorngaloshen_mult_x" },
|
|
{ "mult_y", "unicorngaloshen_mult_y" },
|
|
{ "sine", "unicorngaloshen_sine" },
|
|
{ "sin_x_amplitude", "unicorngaloshen_sin_x_amplitude" },
|
|
{ "sin_x_freq", "unicorngaloshen_sin_x_freq" },
|
|
{ "sin_y_amplitude", "unicorngaloshen_sin_y_amplitude" },
|
|
{ "sin_y_freq", "unicorngaloshen_sin_y_freq" },
|
|
{ "mode", "unicorngaloshen_mode" },
|
|
{ "d_spher_weight", "d_spherical_weight" },
|
|
{ "poincare_p", "poincare2_p" },
|
|
{ "poincare_q", "poincare2_q" },
|
|
{ "phoenix_power", "phoenix_julia_power"},
|
|
{ "phoenix_dist", "phoenix_julia_dist" },
|
|
{ "x_distort", "phoenix_julia_x_distort"},
|
|
{ "y_distort", "phoenix_julia_y_distort" }
|
|
};
|
|
m_FlattenNames =
|
|
{
|
|
"pre_crop",
|
|
"pre_falloff2",
|
|
"pre_rotate_x",
|
|
"pre_rotate_y",
|
|
"pre_ztranslate",
|
|
"blur3D",
|
|
"bubble",
|
|
"bwraps",
|
|
"bwraps2",
|
|
"crop",
|
|
"cylinder",
|
|
"falloff2",
|
|
"hemisphere",
|
|
"julia3D",
|
|
"julia3Dz",
|
|
"linear3D",
|
|
"zblur",
|
|
"zcone",
|
|
"ztranslate",
|
|
"post_crop",
|
|
"post_falloff2",
|
|
"post_rotate_x",
|
|
"post_rotate_y",
|
|
"curl3D_cz",
|
|
"flatten"//Of course don't flatten if it's already present.
|
|
};
|
|
//This is a vector of the incorrect variation names and their param names as they are in the legacy, badly named flam3/Apophysis code.
|
|
vector<string> badParams =
|
|
{
|
|
"bwraps7_cellsize",
|
|
"bwraps7_space",
|
|
"bwraps7_gain",
|
|
"bwraps7_inner_twist",
|
|
"bwraps7_outer_twist"
|
|
};
|
|
m_BadVariationNames.push_back(make_pair(make_pair(string("bwraps7"), string("bwraps")), badParams));//bwraps7 is the same as bwraps.
|
|
badParams =
|
|
{
|
|
"bwraps2_cellsize",
|
|
"bwraps2_space",
|
|
"bwraps2_gain",
|
|
"bwraps2_inner_twist",
|
|
"bwraps2_outer_twist"
|
|
};
|
|
m_BadVariationNames.push_back(make_pair(make_pair(string("bwraps2"), string("bwraps")), badParams));//bwraps2 is the same as bwraps.
|
|
badParams =
|
|
{
|
|
"pre_bwraps2_cellsize",
|
|
"pre_bwraps2_space",
|
|
"pre_bwraps2_gain",
|
|
"pre_bwraps2_inner_twist",
|
|
"pre_bwraps2_outer_twist"
|
|
};
|
|
m_BadVariationNames.push_back(make_pair(make_pair(string("pre_bwraps2"), string("pre_bwraps")), badParams));
|
|
badParams =
|
|
{
|
|
"post_bwraps2_cellsize",
|
|
"post_bwraps2_space",
|
|
"post_bwraps2_gain",
|
|
"post_bwraps2_inner_twist",
|
|
"post_bwraps2_outer_twist"
|
|
};
|
|
m_BadVariationNames.push_back(make_pair(make_pair(string("post_bwraps2"), string("post_bwraps")), badParams));
|
|
badParams =
|
|
{
|
|
"mobius_radius",
|
|
"mobius_width",
|
|
"mobius_rect_x",
|
|
"mobius_rect_y",
|
|
"mobius_rotate_x",
|
|
"mobius_rotate_y"
|
|
};
|
|
m_BadVariationNames.push_back(make_pair(make_pair(string("mobius"), string("mobius_strip")), badParams));//mobius_strip clashes with Mobius.
|
|
badParams =
|
|
{
|
|
"post_dcztransl_x0",
|
|
"post_dcztransl_x1",
|
|
"post_dcztransl_factor",
|
|
"post_dcztransl_overwrite",
|
|
"post_dcztransl_clamp"
|
|
};
|
|
m_BadVariationNames.push_back(make_pair(make_pair(string("post_dcztransl"), string("post_dc_ztransl")), badParams));
|
|
badParams =
|
|
{
|
|
"post_scrop_power",
|
|
"post_scrop_radius",
|
|
"post_scrop_roundstr",
|
|
"post_scrop_roundwidth",
|
|
"post_scrop_distortion",
|
|
"post_scrop_edge",
|
|
"post_scrop_scatter",
|
|
"post_scrop_offset",
|
|
"post_scrop_rotation",
|
|
"post_scrop_cropmode",
|
|
"post_scrop_static"
|
|
};
|
|
m_BadVariationNames.push_back(make_pair(make_pair(string("post_scrop"), string("post_smartcrop")), badParams));
|
|
badParams =
|
|
{
|
|
"sshape_power",
|
|
"sshape_roundstr",
|
|
"sshape_roundwidth",
|
|
"sshape_distortion",
|
|
"sshape_compensation"
|
|
};
|
|
m_BadVariationNames.push_back(make_pair(make_pair(string("sshape"), string("smartshape")), badParams));
|
|
badParams =
|
|
{
|
|
"post_sshape_power",
|
|
"post_sshape_roundstr",
|
|
"post_sshape_roundwidth",
|
|
"post_sshape_distortion",
|
|
"post_sshape_compensation"
|
|
};
|
|
m_BadVariationNames.push_back(make_pair(make_pair(string("post_sshape"), string("post_smartshape")), badParams));
|
|
badParams =
|
|
{
|
|
"radial_gaussian_angle"
|
|
};
|
|
m_BadVariationNames.push_back(make_pair(make_pair(string("radial_gaussian"), string("radial_blur")), badParams));
|
|
badParams =
|
|
{
|
|
"faber_w_angle",
|
|
"faber_w_hypergon",
|
|
"faber_w_hypergon_n",
|
|
"faber_w_hypergon_r",
|
|
"faber_w_star",
|
|
"faber_w_star_n",
|
|
"faber_w_star_slope",
|
|
"faber_w_lituus",
|
|
"faber_w_lituus_a",
|
|
"faber_w_super",
|
|
"faber_w_super_m",
|
|
"faber_w_super_n1",
|
|
"faber_w_super_n2",
|
|
"faber_w_super_n3"
|
|
};
|
|
m_BadVariationNames.push_back(make_pair(make_pair(string("faber_w"), string("w")), badParams));
|
|
badParams =
|
|
{
|
|
"faber_x_angle",
|
|
"faber_x_hypergon",
|
|
"faber_x_hypergon_n",
|
|
"faber_x_hypergon_r",
|
|
"faber_x_star",
|
|
"faber_x_star_n",
|
|
"faber_x_star_slope",
|
|
"faber_x_lituus",
|
|
"faber_x_lituus_a",
|
|
"faber_x_super",
|
|
"faber_x_super_m",
|
|
"faber_x_super_n1",
|
|
"faber_x_super_n2",
|
|
"faber_x_super_n3"
|
|
};
|
|
m_BadVariationNames.push_back(make_pair(make_pair(string("faber_x"), string("x")), badParams));
|
|
//Note that splits3D can't be done here because its param names are also used by splits.
|
|
badParams.clear();
|
|
m_BadVariationNames.push_back(make_pair(make_pair(string("pre_blur"), string("pre_gaussian_blur")), badParams));//No other special params for these.
|
|
m_BadVariationNames.push_back(make_pair(make_pair(string("pre_spin_z"), string("pre_rotate_z")), badParams));
|
|
m_BadVariationNames.push_back(make_pair(make_pair(string("post_spin_z"), string("post_rotate_z")), badParams));
|
|
m_Init = true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse the specified buffer and place the results in the container of embers passed in.
|
|
/// </summary>
|
|
/// <param name="buf">The buffer to parse</param>
|
|
/// <param name="filename">Full path and filename, optionally empty</param>
|
|
/// <param name="embers">The newly constructed embers based on what was parsed</param>
|
|
/// <param name="useDefaults">True to use defaults if they are not present in the file, else false to use invalid values as placeholders to indicate the values were not present. Default: true.</param>
|
|
/// <returns>True if there were no errors, else false.</returns>
|
|
template <typename T>
|
|
template <typename Alloc, template <typename, typename> class C>
|
|
bool XmlToEmber<T>::Parse(byte* buf, const char* filename, C<Ember<T>, Alloc>& embers, bool useDefaults)
|
|
{
|
|
const char* xmlPtr;
|
|
const char* loc = __FUNCTION__;
|
|
size_t emberSize;
|
|
size_t bufSize;
|
|
xmlDocPtr doc;//Parsed XML document tree.
|
|
xmlNodePtr rootnode;
|
|
Locale locale;//Sets and restores on exit.
|
|
//Timing t;
|
|
ClearErrorReport();
|
|
//Parse XML string into internal document.
|
|
xmlPtr = CX(&buf[0]);
|
|
bufSize = strlen(xmlPtr);
|
|
//embers.reserve(bufSize / 2500);//The Xml text for an ember is around 2500 bytes, but can be much more. Pre-allocate to aovid unnecessary resizing.
|
|
int flags = XML_PARSE_HUGE;// | XML_PARSE_RECOVER;
|
|
doc = xmlReadMemory(xmlPtr, int(bufSize), filename, "ISO-8859-1", flags);
|
|
//t.Toc("xmlReadMemory");
|
|
|
|
if (doc == nullptr)
|
|
{
|
|
doc = xmlReadMemory(xmlPtr, int(bufSize), filename, "UTF-8", flags);
|
|
|
|
if (doc == nullptr)
|
|
{
|
|
doc = xmlReadMemory(xmlPtr, int(bufSize), filename, "UTF-16", flags);
|
|
}
|
|
}
|
|
|
|
if (doc == nullptr)
|
|
{
|
|
AddToReport(string(loc) + " : Error parsing xml file " + string(filename));
|
|
return false;
|
|
}
|
|
|
|
//What is the root node of the document?
|
|
rootnode = xmlDocGetRootElement(doc);
|
|
|
|
//Scan for <flame> nodes, starting with this node.
|
|
//t.Tic();
|
|
if (string(filename).empty())
|
|
{
|
|
ScanForEmberNodes(rootnode, filename, embers, useDefaults);
|
|
|
|
if (embers.empty())
|
|
ScanForChaosNodes(rootnode, filename, embers, useDefaults);
|
|
}
|
|
else if (EndsWith(filename, ".chaos"))
|
|
ScanForChaosNodes(rootnode, filename, embers, useDefaults);
|
|
else
|
|
ScanForEmberNodes(rootnode, filename, embers, useDefaults);
|
|
|
|
xmlFreeDoc(doc);
|
|
emberSize = embers.size();
|
|
auto first = embers.begin();
|
|
|
|
//t.Toc("ScanForEmberNodes");
|
|
|
|
//Check to see if the first control point or the second-to-last
|
|
//control point has interpolation="smooth". This is invalid
|
|
//and should be reset to linear (with a warning).
|
|
if (emberSize > 0)
|
|
{
|
|
if (first->m_Interp == eInterp::EMBER_INTERP_SMOOTH)
|
|
first->m_Interp = eInterp::EMBER_INTERP_LINEAR;
|
|
|
|
if (emberSize >= 2)
|
|
{
|
|
auto secondToLast = Advance(embers.begin(), emberSize - 2);
|
|
|
|
if (secondToLast->m_Interp == eInterp::EMBER_INTERP_SMOOTH)
|
|
secondToLast->m_Interp = eInterp::EMBER_INTERP_LINEAR;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AddToReport(string(loc) + " : Error parsing xml file " + string(filename) + ", no flames present.");
|
|
return false;
|
|
}
|
|
|
|
//Finally, ensure that consecutive 'rotate' parameters never exceed
|
|
//a difference of more than 180 degrees (+/-) for interpolation.
|
|
//An adjustment of +/- 360 degrees is made until this is true.
|
|
if (emberSize > 1)
|
|
{
|
|
auto prev = embers.begin();
|
|
auto second = Advance(embers.begin(), 1);
|
|
|
|
for (auto it = second; it != embers.end(); ++it)
|
|
{
|
|
//Only do this adjustment if not in compat mode.
|
|
if (prev->m_AffineInterp != eAffineInterp::AFFINE_INTERP_COMPAT && prev->m_AffineInterp != eAffineInterp::AFFINE_INTERP_OLDER)
|
|
{
|
|
while (it->m_Rotate < prev->m_Rotate - 180)
|
|
it->m_Rotate += 360;
|
|
|
|
while (it->m_Rotate > prev->m_Rotate + 180)
|
|
it->m_Rotate -= 360;
|
|
}
|
|
|
|
prev = it;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
/// <summary>
|
|
/// Parse the specified file and place the results in the container of embers passed in.
|
|
/// This will strip out ampersands because the Xml parser can't handle them.
|
|
/// </summary>
|
|
/// <param name="filename">Full path and filename</param>
|
|
/// <param name="embers">The newly constructed embers based on what was parsed</param>
|
|
/// <param name="useDefaults">True to use defaults if they are not present in the file, else false to use invalid values as placeholders to indicate the values were not present. Default: true.</param>
|
|
/// <returns>True if there were no errors, else false.</returns>
|
|
template <typename T>
|
|
template <typename Alloc, template <typename, typename> class C>
|
|
bool XmlToEmber<T>::Parse(const char* filename, C<Ember<T>, Alloc>& embers, bool useDefaults)
|
|
{
|
|
const char* loc = __FUNCTION__;
|
|
string buf;
|
|
|
|
//Ensure palette list is setup first.
|
|
if (!m_PaletteList->Size())
|
|
{
|
|
AddToReport(string(loc) + " : Palette list must be initialized before parsing embers.");
|
|
return false;
|
|
}
|
|
|
|
if (ReadFile(filename, buf))
|
|
{
|
|
std::replace(buf.begin(), buf.end(), '&', '+');
|
|
return Parse(reinterpret_cast<byte*>(const_cast<char*>(buf.data())), filename, embers, useDefaults);
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
/// <summary>
|
|
/// Thin wrapper around converting the string to a numeric value and return a bool indicating success.
|
|
/// See error report for errors.
|
|
/// </summary>
|
|
/// <param name="str">The string to convert</param>
|
|
/// <param name="val">The converted value</param>
|
|
/// <returns>True if success, else false.</returns>
|
|
template <typename T>
|
|
template <typename valT>
|
|
bool XmlToEmber<T>::Aton(const char* str, valT& val)
|
|
{
|
|
bool b = true;
|
|
const char* loc = __FUNCTION__;
|
|
std::istringstream istr(str);
|
|
istr >> val;
|
|
|
|
if (istr.bad() || istr.fail())
|
|
{
|
|
AddToReport(string(loc) + " : Error converting " + string(str));
|
|
b = false;
|
|
}
|
|
|
|
return b;
|
|
}
|
|
/// <summary>
|
|
/// Scan the file for ember nodes, and parse them out into the container of embers.
|
|
/// </summary>
|
|
/// <param name="curNode">The current node to parse</param>
|
|
/// <param name="parentFile">The full path and filename</param>
|
|
/// <param name="embers">The newly constructed embers based on what was parsed</param>
|
|
/// <param name="useDefaults">True to use defaults if they are not present in the file, else false to use invalid values as placeholders to indicate the values were not present.</param>
|
|
template <typename T>
|
|
template <typename Alloc, template <typename, typename> class C>
|
|
void XmlToEmber<T>::ScanForEmberNodes(xmlNode* curNode, const char* parentFile, C<Ember<T>, Alloc>& embers, bool useDefaults)
|
|
{
|
|
bool parseEmberSuccess;
|
|
xmlNodePtr thisNode = nullptr;
|
|
const char* loc = __FUNCTION__;
|
|
string parentFileString = string(parentFile);
|
|
|
|
//Original memset to 0, but the constructors should handle that.
|
|
//Loop over this level of elements.
|
|
for (thisNode = curNode; thisNode; thisNode = thisNode->next)
|
|
{
|
|
//Check to see if this element is a <ember> element.
|
|
if (thisNode->type == XML_ELEMENT_NODE && !Compare(thisNode->name, "flame"))
|
|
{
|
|
Ember<T> currentEmber;//Place this inside here so its constructor is called each time.
|
|
|
|
//Useful for parsing templates when not every member should be set.
|
|
if (!useDefaults)
|
|
currentEmber.Clear(false);
|
|
|
|
parseEmberSuccess = ParseEmberElement(thisNode, currentEmber);
|
|
|
|
if (!parseEmberSuccess)
|
|
{
|
|
//Original leaked memory here, ours doesn't.
|
|
AddToReport(string(loc) + " : Error parsing ember element");
|
|
return;
|
|
}
|
|
|
|
if (currentEmber.PaletteIndex() != -1)
|
|
{
|
|
if (auto pal = m_PaletteList->GetPaletteByFilename(m_PaletteList->m_DefaultFilename, currentEmber.PaletteIndex()))
|
|
currentEmber.m_Palette = *pal;
|
|
else
|
|
AddToReport(string(loc) + " : Error assigning palette with index " + std::to_string(currentEmber.PaletteIndex()));
|
|
}
|
|
|
|
if (!currentEmber.XformCount())//Ensure there is always at least one xform or else the renderer will crash when trying to render.
|
|
{
|
|
Xform<T> xform;
|
|
currentEmber.AddXform(xform);
|
|
}
|
|
|
|
currentEmber.CacheXforms();
|
|
currentEmber.m_Index = embers.size();
|
|
currentEmber.m_ParentFilename = parentFileString;
|
|
embers.push_back(currentEmber);
|
|
}
|
|
else
|
|
{
|
|
//Check all of the children of this element.
|
|
ScanForEmberNodes(thisNode->children, parentFile, embers, useDefaults);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper function to verify that the name field of a node
|
|
/// matches the one passed in.
|
|
/// </summary>
|
|
/// <param name="node">The node whose name field will be inspected</param>
|
|
/// <param name="name">The name string to compare against</param>
|
|
/// <returns>The name value if they matched, else nullptr.</returns>
|
|
static const char* CheckNameVal(xmlNode* node, const char* name)
|
|
{
|
|
if (auto att = node->properties)
|
|
{
|
|
if (!Compare(att->name, "name"))
|
|
{
|
|
if (auto attStr = XC(xmlGetProp(node, att->name)))
|
|
{
|
|
if (!Compare(attStr, name))
|
|
{
|
|
return CCX(attStr);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
};
|
|
|
|
/// <summary>
|
|
/// Helper function to verify that the name of a node
|
|
/// matches the one passed in.
|
|
/// </summary>
|
|
/// <param name="node">The node whose name will be inspected</param>
|
|
/// <param name="name">The name string to compare against</param>
|
|
/// <returns>The node if they matched, else nullptr.</returns>
|
|
static xmlNode* CheckNodeName(xmlNode* node, const char* name)
|
|
{
|
|
if (!Compare(node->name, name))
|
|
return node;
|
|
|
|
return nullptr;
|
|
};
|
|
|
|
/// <summary>
|
|
/// Helper function to get the value of the name field of a node.
|
|
/// </summary>
|
|
/// <param name="node">The node whose name field will be returned</param>
|
|
/// <param name="name">The name of the field whose value will be compared against. Default: "name".</param>
|
|
/// <returns>The value of the name field if found, else nullptr.</returns>
|
|
static const char* GetNameVal(xmlNode* node, const char* name = "name")
|
|
{
|
|
if (auto att = node->properties)
|
|
{
|
|
if (!Compare(att->name, name ? name : "name"))
|
|
{
|
|
if (auto attStr = XC(xmlGetProp(node, att->name)))
|
|
{
|
|
return CCX(attStr);
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
};
|
|
|
|
/// <summary>
|
|
/// Helper function to get the child of a node based on the value of its name field.
|
|
/// </summary>
|
|
/// <param name="node">The node whose children will be searched</param>
|
|
/// <param name="name">The name string to compare the child nodes' name fields against</param>
|
|
/// <returns>The child node if found, else nullptr.</returns>
|
|
static xmlNode* GetChildNode(xmlNode* node, const char* name)
|
|
{
|
|
for (auto childNode = node->children; childNode; childNode = childNode->next)
|
|
{
|
|
if (childNode->type == XML_ELEMENT_NODE)
|
|
{
|
|
if (CheckNameVal(childNode, name))
|
|
{
|
|
return childNode;
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
};
|
|
|
|
/// <summary>
|
|
/// Helper function to get the child of a node based on the name of the child node.
|
|
/// </summary>
|
|
/// <param name="node">The node whose children will be searched</param>
|
|
/// <param name="name">The name string to compare the child nodes' name against</param>
|
|
/// <returns>The child node if found, else nullptr.</returns>
|
|
static xmlNode* GetChildNodeByNodeName(xmlNode* node, const char* name)
|
|
{
|
|
for (auto childNode = node->children; childNode; childNode = childNode->next)
|
|
{
|
|
if (childNode->type == XML_ELEMENT_NODE)
|
|
{
|
|
if (auto node = CheckNodeName(childNode, name))
|
|
{
|
|
return node;
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
};
|
|
|
|
/// <summary>
|
|
/// Helper function to parse the content of a field of a node and convert the string into a value of type T and store in the passed in val parameter.
|
|
/// </summary>
|
|
/// <param name="node">The node whose property fieldname will be parsed</param>
|
|
/// <param name="fieldname">The name of the node's field to parse</param>
|
|
/// <param name="fieldnameval">The name of the property within the field</param>
|
|
/// <param name="val">The storage location to store the parse and converted value in</param>
|
|
/// <returns>True if the field and property were successfully found and parsed, else false.</returns>
|
|
template <typename T>
|
|
template <typename valT>
|
|
bool XmlToEmber<T>::ParseAndAssignContent(xmlNode* node, const char* fieldname, const char* fieldnameval, valT& val)
|
|
{
|
|
bool ret = false;
|
|
|
|
if (auto att = node->properties)
|
|
{
|
|
if (!Compare(att->name, fieldname))
|
|
{
|
|
if (auto attStr = XC(xmlGetProp(node, att->name)))
|
|
{
|
|
if (!fieldnameval || !Compare(attStr, fieldnameval))
|
|
{
|
|
if (auto cont = xmlNodeGetContent(node))
|
|
{
|
|
istringstream istr(CCX(cont));
|
|
istr >> val;
|
|
ret = !istr.bad() && !istr.fail();//Means the Compare() was right, and the conversion succeeded.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Special overload for string.
|
|
/// </summary>
|
|
/// <param name="node">The node whose property fieldname will be parsed</param>
|
|
/// <param name="fieldname">The name of the node's field to parse</param>
|
|
/// <param name="fieldnameval">The name of the property within the field</param>
|
|
/// <param name="val">The storage location to store the parse and converted string in</param>
|
|
/// <returns>True if the field and property were successfully found and parsed, else false.</returns>
|
|
template <typename T>
|
|
bool XmlToEmber<T>::ParseAndAssignContent(xmlNode* node, const char* fieldname, const char* fieldnameval, std::string& val)
|
|
{
|
|
bool ret = false;
|
|
|
|
if (auto att = node->properties)
|
|
{
|
|
if (!Compare(att->name, fieldname))
|
|
{
|
|
if (auto attStr = XC(xmlGetProp(node, att->name)))
|
|
{
|
|
if (!fieldnameval || !Compare(attStr, fieldnameval))
|
|
{
|
|
if (auto cont = xmlNodeGetContent(node))
|
|
{
|
|
val = CX(cont);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Scan the file for chaos nodes from a .chaos file from Chaotica, and parse them out into the container of embers.
|
|
/// </summary>
|
|
/// <param name="curNode">The current node to parse</param>
|
|
/// <param name="parentFile">The full path and filename</param>
|
|
/// <param name="embers">The newly constructed embers based on what was parsed</param>
|
|
/// <param name="useDefaults">True to use defaults if they are not present in the file, else false to use invalid values as placeholders to indicate the values were not present.</param>
|
|
template <typename T>
|
|
template <typename Alloc, template <typename, typename> class C>
|
|
void XmlToEmber<T>::ScanForChaosNodes(xmlNode* curNode, const char* parentFile, C<Ember<T>, Alloc>& embers, bool useDefaults)
|
|
{
|
|
bool parseEmberSuccess;
|
|
xmlNodePtr thisNode = nullptr;
|
|
const char* loc = __FUNCTION__;
|
|
string parentFileString = string(parentFile);
|
|
|
|
//Loop over this level of elements.
|
|
for (thisNode = curNode; thisNode; thisNode = thisNode->next)
|
|
{
|
|
//Check to see if this element is a <ember> element.
|
|
if (thisNode->type == XML_ELEMENT_NODE && !Compare(thisNode->name, "IFS"))
|
|
{
|
|
Ember<T> currentEmber;//Place this inside here so its constructor is called each time.
|
|
|
|
//Useful for parsing templates when not every member should be set.
|
|
if (!useDefaults)
|
|
currentEmber.Clear(false);
|
|
|
|
if (auto embername = GetNameVal(thisNode, "name"))
|
|
currentEmber.m_Name = embername;
|
|
|
|
auto childNode = thisNode;
|
|
bool ret = true;
|
|
parseEmberSuccess = ParseEmberElementFromChaos(childNode, currentEmber);
|
|
|
|
if (!parseEmberSuccess)
|
|
{
|
|
AddToReport(string(loc) + " : Error parsing ember element");
|
|
return;
|
|
}
|
|
|
|
if (!currentEmber.XformCount())//Ensure there is always at least one xform or else the renderer will crash when trying to render.
|
|
{
|
|
Xform<T> xform;
|
|
currentEmber.AddXform(xform);
|
|
}
|
|
|
|
currentEmber.CacheXforms();
|
|
currentEmber.m_MinRadDE = currentEmber.m_MaxRadDE = 0;//Chaotica doesn't support density filtering.
|
|
currentEmber.m_Index = embers.size();
|
|
currentEmber.m_FuseCount = 30;//Chaotica doesn't publish its fuse count, but the images appear like they were using a fuse count of at least 30.
|
|
currentEmber.m_ParentFilename = parentFileString;
|
|
embers.push_back(currentEmber);
|
|
}
|
|
else
|
|
{
|
|
//Check all of the children of this element.
|
|
ScanForChaosNodes(thisNode->children, parentFile, embers, useDefaults);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse an ember element from a .chaos file.
|
|
/// </summary>
|
|
/// <param name="emberNode">The current node to parse</param>
|
|
/// <param name="currentEmber">The newly constructed ember based on what was parsed</param>
|
|
/// <returns>True if there were no errors, else false.</returns>
|
|
template <typename T>
|
|
bool XmlToEmber<T>::ParseEmberElementFromChaos(xmlNode* emberNode, Ember<T>& currentEmber)
|
|
{
|
|
bool fromEmber = false, ret = true;
|
|
const char* loc = __FUNCTION__;
|
|
int soloXform = -1;
|
|
size_t count = 0, index = 0;
|
|
T sensorWidth = 2;
|
|
xmlAttrPtr att;
|
|
currentEmber.m_Palette.Clear();//Wipe out the current palette.
|
|
att = emberNode->properties;//The top level element is a ember element, read the attributes of it and store them.
|
|
auto variationsfunc = [&](const string & prefix, const char* nodename, xmlNode * node, Xform<T>& xf, std::vector<std::string>& alliterweights)
|
|
{
|
|
if (auto transformsChildNode = GetChildNode(node, nodename))
|
|
{
|
|
for (auto variationNode = transformsChildNode->children; variationNode; variationNode = variationNode->next)
|
|
{
|
|
if (variationNode->type == XML_ELEMENT_NODE && (!Compare(variationNode->name, "flam3_variation") || !Compare(variationNode->name, "transform")))
|
|
{
|
|
auto variationNameNode = GetChildNode(variationNode, "variation_name");
|
|
|
|
if (!variationNameNode)
|
|
variationNameNode = GetChildNode(variationNode, "type_name");
|
|
|
|
if (variationNameNode)
|
|
{
|
|
std::string varname;
|
|
|
|
if (ParseAndAssignContent(variationNameNode, "name", "variation_name", varname)) {}
|
|
else ParseAndAssignContent(variationNameNode, "name", "type_name", varname);
|
|
|
|
if (!varname.empty())
|
|
{
|
|
T weight = 1;
|
|
string corrvarname = GetCorrectedVariationName(m_BadVariationNames, varname);
|
|
auto corrwprefix = !StartsWith(corrvarname, prefix) ? prefix + corrvarname : corrvarname;
|
|
|
|
if (auto var = m_VariationList->GetVariation(corrwprefix))
|
|
{
|
|
auto vc = std::unique_ptr<Variation<T>>(var->Copy());
|
|
vc->m_Weight = 1;
|
|
auto varCopy = vc.get();
|
|
|
|
if (xf.AddVariation(varCopy))
|
|
{
|
|
vc.release();
|
|
|
|
if (auto paramsNode = GetChildNodeByNodeName(variationNode, "params"))
|
|
{
|
|
auto parvar = dynamic_cast<ParametricVariation<T>*>(varCopy);
|
|
|
|
for (auto paramsChildNode = paramsNode->children; paramsChildNode; paramsChildNode = paramsChildNode->next)
|
|
{
|
|
if (paramsChildNode->type == XML_ELEMENT_NODE)
|
|
{
|
|
if (auto paramname = GetNameVal(paramsChildNode))
|
|
{
|
|
T val = 1;
|
|
|
|
if (auto paramCurveChildNode = GetChildNodeByNodeName(paramsChildNode, "curve"))
|
|
{
|
|
if (auto paramCurveValuesChildNode = GetChildNode(paramCurveChildNode, "values"))
|
|
{
|
|
if (auto paramCurveValuesContentChildNode = GetChildNodeByNodeName(paramCurveValuesChildNode, "values"))
|
|
{
|
|
if (paramCurveValuesContentChildNode->children)
|
|
{
|
|
string valstr = CCX(paramCurveValuesContentChildNode->children->content);
|
|
istringstream istr(valstr);
|
|
istr >> val;
|
|
varCopy->m_Weight = val;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (ParseAndAssignContent(paramsChildNode, "name", paramname, val))
|
|
{
|
|
auto paramstr = GetCorrectedParamName(m_BadParamNames, ToLower(paramname).c_str());
|
|
|
|
if (paramname == varname)//Compare non corrected names.
|
|
{
|
|
varCopy->m_Weight = val;
|
|
}
|
|
else if (parvar)
|
|
{
|
|
if (!StartsWith(paramstr, prefix))
|
|
paramstr = prefix + paramstr;
|
|
|
|
//Need some special corrections here because Chaotica allows values that don't make sense.
|
|
if (varname == "falloff2")
|
|
{
|
|
if (paramstr == "falloff2_mul_c")
|
|
val = 0;
|
|
else if (paramstr == "falloff2_type")
|
|
val = std::ceil(val);
|
|
}
|
|
else if (varname == "falloff3")
|
|
{
|
|
if (paramstr == "falloff3_mul_c")
|
|
val = 0;
|
|
else if (paramstr == "falloff3_blur_type")
|
|
val = std::ceil(val);
|
|
}
|
|
|
|
parvar->SetParamVal(paramstr.c_str(), val);
|
|
//if (!parvar->SetParamVal(paramstr.c_str(), val))
|
|
// AddToReport(string(loc) + " : Failed to set parametric variation parameter " + paramstr);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
auto xformfunc = [&](xmlNode * node, Xform<T>& xf, std::vector<std::string>& alliterweights) -> bool
|
|
{
|
|
bool found = false;
|
|
|
|
if (auto transformChildNode = GetChildNodeByNodeName(node, "flam3_transform"))
|
|
{
|
|
found = true;
|
|
auto itername = GetNameVal(node, "name");
|
|
xf.m_Name = itername;
|
|
|
|
if (auto affineChildNode = GetChildNode(transformChildNode, "Pre affine"))
|
|
{
|
|
std::string offsetstr;
|
|
double xangle = 0, xlength = 1, yangle = 90, ylength = 1, xoffset = 0, yoffset = 0;
|
|
|
|
if (auto xangleChildNode = GetChildNode(affineChildNode, "x_axis_angle"))
|
|
if (auto paramCurveChildNode = GetChildNodeByNodeName(xangleChildNode, "curve"))
|
|
{
|
|
if (auto paramCurveValuesChildNode = GetChildNode(paramCurveChildNode, "values"))
|
|
{
|
|
if (auto paramCurveValuesContentChildNode = GetChildNodeByNodeName(paramCurveValuesChildNode, "values"))
|
|
{
|
|
if (paramCurveValuesContentChildNode->children)
|
|
{
|
|
string valstr = CCX(paramCurveValuesContentChildNode->children->content);
|
|
istringstream istr(valstr);
|
|
istr >> xangle;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
ParseAndAssignContent(xangleChildNode, "name", "x_axis_angle", xangle);
|
|
|
|
if (auto xlengthChildNode = GetChildNode(affineChildNode, "x_axis_length"))
|
|
if (ParseAndAssignContent(xlengthChildNode, "name", "x_axis_length", xlength)) {}
|
|
|
|
if (auto yangleChildNode = GetChildNode(affineChildNode, "y_axis_angle"))
|
|
if (auto paramCurveChildNode = GetChildNodeByNodeName(yangleChildNode, "curve"))
|
|
{
|
|
if (auto paramCurveValuesChildNode = GetChildNode(paramCurveChildNode, "values"))
|
|
{
|
|
if (auto paramCurveValuesContentChildNode = GetChildNodeByNodeName(paramCurveValuesChildNode, "values"))
|
|
{
|
|
if (paramCurveValuesContentChildNode->children)
|
|
{
|
|
string valstr = CCX(paramCurveValuesContentChildNode->children->content);
|
|
istringstream istr(valstr);
|
|
istr >> yangle;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
ParseAndAssignContent(yangleChildNode, "name", "y_axis_angle", yangle);
|
|
|
|
if (auto ylengthChildNode = GetChildNode(affineChildNode, "y_axis_length"))
|
|
if (ParseAndAssignContent(ylengthChildNode, "name", "y_axis_length", ylength)) {}
|
|
|
|
if (auto offsetChildNode = GetChildNode(affineChildNode, "offset"))
|
|
if (ParseAndAssignContent(offsetChildNode, "name", "offset", offsetstr))
|
|
{
|
|
istringstream istr(offsetstr);
|
|
istr >> xoffset >> yoffset;
|
|
}
|
|
|
|
T x1 = T(xlength * std::cos(xangle * DEG_2_RAD));
|
|
T y1 = T(xlength * std::sin(xangle * DEG_2_RAD));
|
|
T x2 = T(ylength * std::cos(yangle * DEG_2_RAD));
|
|
T y2 = T(ylength * std::sin(yangle * DEG_2_RAD));
|
|
T o1 = T(xoffset);
|
|
T o2 = T(yoffset);
|
|
xf.m_Affine.A(x1);
|
|
xf.m_Affine.B(x2);
|
|
xf.m_Affine.C(o1);
|
|
xf.m_Affine.D(y1);
|
|
xf.m_Affine.E(y2);
|
|
xf.m_Affine.F(o2);
|
|
}
|
|
|
|
if (auto affineChildNode = GetChildNode(transformChildNode, "Post affine"))
|
|
{
|
|
std::string offsetstr;
|
|
double xangle = 0, xlength = 1, yangle = 90, ylength = 1, xoffset = 0, yoffset = 0;
|
|
|
|
if (auto xangleChildNode = GetChildNode(affineChildNode, "x_axis_angle"))
|
|
if (!ParseAndAssignContent(xangleChildNode, "name", "x_axis_angle", xangle))
|
|
if (auto paramCurveChildNode = GetChildNodeByNodeName(affineChildNode, "curve"))
|
|
{
|
|
if (auto paramCurveValuesChildNode = GetChildNode(paramCurveChildNode, "values"))
|
|
{
|
|
if (auto paramCurveValuesContentChildNode = GetChildNodeByNodeName(paramCurveValuesChildNode, "values"))
|
|
{
|
|
if (paramCurveValuesContentChildNode->children)
|
|
{
|
|
string valstr = CCX(paramCurveValuesContentChildNode->children->content);
|
|
istringstream istr(valstr);
|
|
istr >> xangle;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (auto xlengthChildNode = GetChildNode(affineChildNode, "x_axis_length"))
|
|
if (ParseAndAssignContent(xlengthChildNode, "name", "x_axis_length", xlength)) {}
|
|
|
|
if (auto yangleChildNode = GetChildNode(affineChildNode, "y_axis_angle"))
|
|
if (auto paramCurveChildNode = GetChildNodeByNodeName(yangleChildNode, "curve"))
|
|
{
|
|
if (auto paramCurveValuesChildNode = GetChildNode(paramCurveChildNode, "values"))
|
|
{
|
|
if (auto paramCurveValuesContentChildNode = GetChildNodeByNodeName(paramCurveValuesChildNode, "values"))
|
|
{
|
|
if (paramCurveValuesContentChildNode->children)
|
|
{
|
|
string valstr = CCX(paramCurveValuesContentChildNode->children->content);
|
|
istringstream istr(valstr);
|
|
istr >> yangle;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
ParseAndAssignContent(yangleChildNode, "name", "y_axis_angle", yangle);
|
|
|
|
if (auto ylengthChildNode = GetChildNode(affineChildNode, "y_axis_length"))
|
|
if (ParseAndAssignContent(ylengthChildNode, "name", "y_axis_length", ylength)) {}
|
|
|
|
if (auto offsetChildNode = GetChildNode(affineChildNode, "offset"))
|
|
if (ParseAndAssignContent(offsetChildNode, "name", "offset", offsetstr))
|
|
{
|
|
istringstream istr(offsetstr);
|
|
istr >> xoffset >> yoffset;
|
|
}
|
|
|
|
T x1 = T(xlength * std::cos(xangle * DEG_2_RAD));
|
|
T y1 = T(xlength * std::sin(xangle * DEG_2_RAD));
|
|
T x2 = T(ylength * std::cos(yangle * DEG_2_RAD));
|
|
T y2 = T(ylength * std::sin(yangle * DEG_2_RAD));
|
|
T o1 = T(xoffset);
|
|
T o2 = T(yoffset);
|
|
xf.m_Post.A(x1);
|
|
xf.m_Post.B(x2);
|
|
xf.m_Post.C(o1);
|
|
xf.m_Post.D(y1);
|
|
xf.m_Post.E(y2);
|
|
xf.m_Post.F(o2);
|
|
}
|
|
|
|
string prefix;
|
|
variationsfunc(prefix, "transforms", transformChildNode, xf, alliterweights);
|
|
prefix = "pre_";
|
|
variationsfunc(prefix, "pre_transforms", transformChildNode, xf, alliterweights);
|
|
prefix = "post_";
|
|
variationsfunc(prefix, "post_transforms", transformChildNode, xf, alliterweights);
|
|
}
|
|
|
|
if (auto shaderChildNode = GetChildNodeByNodeName(node, "flam3_shader"))
|
|
{
|
|
T paletteIndex = 0, colorSpeed = 0.5, opacity = 1;
|
|
|
|
if (auto paletteIndexChildNode = GetChildNode(shaderChildNode, "palette_index"))
|
|
if (ParseAndAssignContent(paletteIndexChildNode, "name", "palette_index", paletteIndex)) { xf.m_ColorX = xf.m_ColorY = paletteIndex; }
|
|
|
|
if (auto colrSpeedChildNode = GetChildNode(shaderChildNode, "blend_speed"))
|
|
if (ParseAndAssignContent(colrSpeedChildNode, "name", "blend_speed", colorSpeed)) { xf.m_ColorSpeed = colorSpeed; }
|
|
|
|
if (auto opacityChildNode = GetChildNode(shaderChildNode, "opacity"))
|
|
if (ParseAndAssignContent(opacityChildNode, "name", "opacity", opacity)) { xf.m_Opacity = opacity; }
|
|
}
|
|
|
|
|
|
if (auto weightsChildNode = GetChildNodeByNodeName(node, "weights_selector"))
|
|
{
|
|
T weight = 0;
|
|
std::string periterweights;
|
|
|
|
if (auto baseWeightChildNode = GetChildNode(weightsChildNode, "base_weight"))
|
|
{
|
|
if (ParseAndAssignContent(baseWeightChildNode, "name", "base_weight", weight))
|
|
xf.m_Weight = weight;
|
|
}
|
|
else if (auto baseWeightChildNode = GetChildNode(weightsChildNode, "Base weight"))
|
|
{
|
|
if (ParseAndAssignContent(baseWeightChildNode, "name", "Base weight", weight))
|
|
xf.m_Weight = weight;
|
|
}
|
|
|
|
if (auto periterweightsChildNode = GetChildNode(weightsChildNode, "per_iterator_weights"))
|
|
{
|
|
for (auto iterweightChildNode = periterweightsChildNode->children; iterweightChildNode; iterweightChildNode = iterweightChildNode->next)
|
|
{
|
|
if (iterweightChildNode->type == XML_ELEMENT_NODE)
|
|
{
|
|
std::string iterweight;
|
|
|
|
if (ParseAndAssignContent(iterweightChildNode, "name", nullptr, iterweight))//All of these subsequent nodes are for iter specific weights (xaos).
|
|
periterweights += iterweight + ' ';
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!periterweights.empty())
|
|
alliterweights.push_back(Trim(periterweights));
|
|
}
|
|
|
|
return found;
|
|
};
|
|
|
|
if (att == nullptr)
|
|
{
|
|
AddToReport(string(loc) + " : <IFS> element has no attributes");
|
|
return false;
|
|
}
|
|
|
|
auto childNode = emberNode->children;
|
|
|
|
for (; childNode; childNode = childNode->next)
|
|
{
|
|
if (childNode->type == XML_ELEMENT_NODE)
|
|
{
|
|
if (!Compare(childNode->name, "imaging"))
|
|
{
|
|
std::string bgstr, useHighlightPower;
|
|
|
|
for (auto imgChildNode = childNode->children; imgChildNode; imgChildNode = imgChildNode->next)
|
|
{
|
|
if (imgChildNode->type == XML_ELEMENT_NODE)
|
|
{
|
|
if (ParseAndAssignContent(imgChildNode, "name", "image_width", currentEmber.m_FinalRasW)) {}
|
|
else if (ParseAndAssignContent(imgChildNode, "name", "image_height", currentEmber.m_FinalRasH)) {}
|
|
else if (ParseAndAssignContent(imgChildNode, "name", "image_aa_level", currentEmber.m_Supersample)) { ClampRef(currentEmber.m_Supersample, size_t(1), size_t(4)); }
|
|
else if (ParseAndAssignContent(imgChildNode, "name", "image_quality", currentEmber.m_Quality)) { currentEmber.m_Quality = std::max(currentEmber.m_Quality, T(1)); }
|
|
else if (ParseAndAssignContent(imgChildNode, "name", "brightness", currentEmber.m_Brightness)) {}
|
|
else if (ParseAndAssignContent(imgChildNode, "name", "flam3_gamma", currentEmber.m_Gamma)) {}
|
|
else if (ParseAndAssignContent(imgChildNode, "name", "flam3_vibrancy", currentEmber.m_Vibrancy)) {}
|
|
else if (ParseAndAssignContent(imgChildNode, "name", "flam3_use_highlight_power", useHighlightPower)) {}
|
|
else if (ParseAndAssignContent(imgChildNode, "name", "flam3_highlight_power", currentEmber.m_HighlightPower)) {}
|
|
else if (ParseAndAssignContent(imgChildNode, "name", "flam3_gamma_linear_threshold", currentEmber.m_GammaThresh)) {}
|
|
else if (ParseAndAssignContent(imgChildNode, "name", "background_colour", bgstr))
|
|
{
|
|
istringstream is(bgstr);
|
|
is >> currentEmber.m_Background[0]//[0..1]
|
|
>> currentEmber.m_Background[1]
|
|
>> currentEmber.m_Background[2]
|
|
>> currentEmber.m_Background[3];
|
|
}
|
|
}
|
|
}
|
|
|
|
// there is no warranty that flam3_use_highlight_power will be read before flam3_highlight_power. So, better to be here.
|
|
bool bVal; istringstream istr(useHighlightPower); istr >> std::boolalpha >> bVal;
|
|
|
|
if (!bVal && !istr.bad() && !istr.fail())
|
|
currentEmber.m_HighlightPower = T(-1);
|
|
|
|
if (auto curvesnode = GetChildNodeByNodeName(childNode, "curves"))
|
|
{
|
|
T val = 0;
|
|
auto curvenodesfunc = [&](xmlNode * node, int index)
|
|
{
|
|
float x, y;
|
|
string knots, values;
|
|
vector<v2F> vals;
|
|
|
|
if (auto knotsnode = GetChildNode(node, "knots"))
|
|
{
|
|
if (auto knotvalsnode = GetChildNodeByNodeName(knotsnode, "values"))
|
|
if (knotvalsnode->children)
|
|
knots = CCX(knotvalsnode->children->content);
|
|
}
|
|
|
|
if (auto valuesnode = GetChildNode(node, "values"))
|
|
{
|
|
if (auto valvalsnode = GetChildNodeByNodeName(valuesnode, "values"))
|
|
if (valvalsnode->children)
|
|
values = CCX(valvalsnode->children->content);
|
|
}
|
|
|
|
if (knots.empty() && values.empty())
|
|
{
|
|
bool haveknots = false, havevals = false;
|
|
|
|
for (auto childNode = node->children; childNode; childNode = childNode->next)
|
|
{
|
|
if (childNode->type == XML_ELEMENT_NODE)
|
|
{
|
|
if (auto node = CheckNodeName(childNode, "table"))
|
|
{
|
|
if (!haveknots)
|
|
{
|
|
if (auto knotvalsnode = GetChildNodeByNodeName(node, "values"))
|
|
{
|
|
if (knotvalsnode->children)
|
|
{
|
|
knots = CCX(knotvalsnode->children->content);
|
|
haveknots = true;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
}
|
|
else if (!havevals)
|
|
{
|
|
if (auto valvalsnode = GetChildNodeByNodeName(node, "values"))
|
|
{
|
|
if (valvalsnode->children)
|
|
{
|
|
values = CCX(valvalsnode->children->content);
|
|
havevals = true;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
istringstream kistr(knots);
|
|
istringstream vistr(values);
|
|
|
|
while (kistr >> x && vistr >> y)
|
|
vals.push_back({ x, y });
|
|
|
|
if (index < currentEmber.m_Curves.m_Points.size() && vals.size() >= 2)
|
|
{
|
|
std::sort(vals.begin(), vals.end(), [&](auto & lhs, auto & rhs) { return lhs.x < rhs.x; });
|
|
currentEmber.m_Curves.m_Points[index].clear();
|
|
currentEmber.m_Curves.m_Points[index].push_back(vals[0]);
|
|
currentEmber.m_Curves.m_Points[index].push_back(vals[1]);
|
|
|
|
for (size_t i = 2; i < vals.size(); i++)
|
|
if (vals[i] != currentEmber.m_Curves.m_Points[index][i - 1])//An attempt to remove duplicates.
|
|
currentEmber.m_Curves.m_Points[index].push_back(vals[i]);
|
|
}
|
|
};
|
|
|
|
if (auto overallnode = GetChildNode(curvesnode, "overall"))
|
|
curvenodesfunc(overallnode, 0);
|
|
|
|
if (auto rednode = GetChildNode(curvesnode, "0"))
|
|
curvenodesfunc(rednode, 1);
|
|
|
|
if (auto greennode = GetChildNode(curvesnode, "5"))
|
|
curvenodesfunc(greennode, 2);
|
|
|
|
if (auto bluenode = GetChildNode(curvesnode, "10"))
|
|
curvenodesfunc(bluenode, 3);
|
|
}
|
|
}
|
|
else if (!Compare(childNode->name, "camera"))
|
|
{
|
|
std::string pos;
|
|
|
|
for (auto camChildNode = childNode->children; camChildNode; camChildNode = camChildNode->next)
|
|
{
|
|
if (camChildNode->type == XML_ELEMENT_NODE)
|
|
{
|
|
if (ParseAndAssignContent(camChildNode, "name", "rotate", currentEmber.m_Rotate)) { currentEmber.m_Rotate = NormalizeDeg180<T>(currentEmber.m_Rotate); }
|
|
else if (ParseAndAssignContent(camChildNode, "name", "sensor_width", sensorWidth)) { }
|
|
else if (ParseAndAssignContent(camChildNode, "name", "pos", pos))
|
|
{
|
|
istringstream istr(pos);
|
|
istr >> currentEmber.m_CenterX >> currentEmber.m_CenterY;
|
|
currentEmber.m_CenterY *= -1;
|
|
currentEmber.m_RotCenterY = currentEmber.m_CenterY;
|
|
}
|
|
else
|
|
{
|
|
Xform<T> finalXform;
|
|
std::vector<std::string> alliterweights;
|
|
|
|
if (xformfunc(childNode, finalXform, alliterweights))//Iter weights are unused in the final xform.
|
|
{
|
|
finalXform.m_Weight = 0;
|
|
finalXform.m_Animate = 0;//Do not animate final by default.
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (!Compare(childNode->name, "colouring"))
|
|
{
|
|
if (auto palettenode = GetChildNode(childNode, "flam3_palette"))
|
|
{
|
|
if (auto palettevalsnode = GetChildNodeByNodeName(palettenode, "values"))
|
|
{
|
|
float r = 0, g = 0, b = 0;
|
|
auto colors = CCX(palettevalsnode->children->content);
|
|
istringstream istr(colors);
|
|
currentEmber.m_Palette.m_Entries.clear();
|
|
std::vector<v4F> tempv;
|
|
tempv.reserve(256);
|
|
|
|
while (istr >> r && istr >> g && istr >> b)
|
|
tempv.push_back(v4F(r, g, b, 1));
|
|
|
|
if (!tempv.empty())
|
|
currentEmber.m_Palette.m_Entries = std::move(tempv);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
std::string huek, huev, satk, satv, valk, valv;
|
|
|
|
if (auto huenode = GetChildNode(childNode, "hue"))
|
|
{
|
|
if (auto knotsnode = GetChildNode(huenode, "knots"))
|
|
if (auto knotvalsnode = GetChildNodeByNodeName(knotsnode, "values"))
|
|
if (knotvalsnode->children)
|
|
huek = CCX(knotvalsnode->children->content);
|
|
|
|
if (auto valuesnode = GetChildNode(huenode, "values"))
|
|
if (auto valvalsnode = GetChildNodeByNodeName(valuesnode, "values"))
|
|
if (valvalsnode->children)
|
|
huev = CCX(valvalsnode->children->content);
|
|
}
|
|
|
|
if (auto satnode = GetChildNode(childNode, "saturation"))
|
|
{
|
|
if (auto knotsnode = GetChildNode(satnode, "knots"))
|
|
if (auto knotvalsnode = GetChildNodeByNodeName(knotsnode, "values"))
|
|
if (knotvalsnode->children)
|
|
satk = CCX(knotvalsnode->children->content);
|
|
|
|
if (auto valuesnode = GetChildNode(satnode, "values"))
|
|
if (auto valvalsnode = GetChildNodeByNodeName(valuesnode, "values"))
|
|
if (valvalsnode->children)
|
|
satv = CCX(valvalsnode->children->content);
|
|
}
|
|
|
|
if (auto valnode = GetChildNode(childNode, "value"))
|
|
{
|
|
if (auto knotsnode = GetChildNode(valnode, "knots"))
|
|
if (auto knotvalsnode = GetChildNodeByNodeName(knotsnode, "values"))
|
|
if (knotvalsnode->children)
|
|
valk = CCX(knotvalsnode->children->content);
|
|
|
|
if (auto valuesnode = GetChildNode(valnode, "values"))
|
|
if (auto valvalsnode = GetChildNodeByNodeName(valuesnode, "values"))
|
|
if (valvalsnode->children)
|
|
valv = CCX(valvalsnode->children->content);
|
|
}
|
|
|
|
auto parsehsvfunc = [&](const std::string & knots, const std::string & vals, vector<v2F>& vec)
|
|
{
|
|
istringstream kstr(knots);
|
|
istringstream vstr(vals);
|
|
float k, v;
|
|
vec.clear();
|
|
vec.reserve(8);
|
|
|
|
while (kstr >> k && vstr >> v)
|
|
{
|
|
vec.push_back({k, v});
|
|
}
|
|
};
|
|
vector<v2F> hvec, svec, vvec;
|
|
parsehsvfunc(huek, huev, hvec);
|
|
parsehsvfunc(satk, satv, svec);
|
|
parsehsvfunc(valk, valv, vvec);
|
|
|
|
if (huek.size() >= 2 && huev.size() >= 2 &&
|
|
satk.size() >= 2 && satv.size() >= 2 &&
|
|
valk.size() >= 2 && valv.size() >= 2)
|
|
{
|
|
Spline<float> hspline(hvec);
|
|
Spline<float> sspline(svec);
|
|
Spline<float> vspline(vvec);
|
|
currentEmber.m_Palette.m_Entries.resize(COLORMAP_LENGTH);
|
|
auto stepsize = (1.0f / (currentEmber.m_Palette.Size() - 1));
|
|
|
|
for (auto palindex = 0; palindex < currentEmber.m_Palette.Size(); palindex++)
|
|
{
|
|
float t = palindex * stepsize;
|
|
auto h = hspline.Interpolate(t);
|
|
auto s = sspline.Interpolate(t);
|
|
auto v = vspline.Interpolate(t);
|
|
float r, g, b;
|
|
Palette<float>::HsvToRgb(float(h * 2 * M_PI), s, v, r, g, b);
|
|
currentEmber.m_Palette.m_Entries[palindex][0] = r;
|
|
currentEmber.m_Palette.m_Entries[palindex][1] = g;
|
|
currentEmber.m_Palette.m_Entries[palindex][2] = b;
|
|
currentEmber.m_Palette.m_Entries[palindex][3] = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (!Compare(childNode->name, "node"))
|
|
{
|
|
if (auto nodename = CheckNameVal(childNode, "iterators"))
|
|
{
|
|
std::vector<std::string> alliterweights;
|
|
|
|
for (auto iterChildNode = childNode->children; iterChildNode; iterChildNode = iterChildNode->next)
|
|
{
|
|
if (iterChildNode->type == XML_ELEMENT_NODE && !Compare(iterChildNode->name, "iterator"))
|
|
{
|
|
Xform<T> xf;
|
|
xformfunc(iterChildNode, xf, alliterweights);
|
|
currentEmber.AddXform(xf);
|
|
}
|
|
}
|
|
|
|
if (!alliterweights.empty())
|
|
{
|
|
size_t i = 0;
|
|
|
|
while (auto xform = currentEmber.GetXform(i))
|
|
{
|
|
if (i < alliterweights.size() && !alliterweights[i].empty())
|
|
{
|
|
istringstream istr(alliterweights[i]);
|
|
T xaoselement = 0;
|
|
size_t j = 0;
|
|
|
|
while (istr >> xaoselement)
|
|
xform->SetXaos(j++, xaoselement);
|
|
}
|
|
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
currentEmber.m_OrigPixPerUnit = currentEmber.m_PixelsPerUnit = currentEmber.m_FinalRasW / Zeps(sensorWidth);
|
|
return currentEmber.XformCount() > 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse an ember element.
|
|
/// </summary>
|
|
/// <param name="emberNode">The current node to parse</param>
|
|
/// <param name="currentEmber">The newly constructed ember based on what was parsed</param>
|
|
/// <returns>True if there were no errors, else false.</returns>
|
|
template <typename T>
|
|
bool XmlToEmber<T>::ParseEmberElement(xmlNode* emberNode, Ember<T>& currentEmber)
|
|
{
|
|
bool ret = true;
|
|
bool fromEmber = false;
|
|
size_t newLinear = 0;
|
|
char* attStr;
|
|
const char* loc = __FUNCTION__;
|
|
int soloXform = -1;
|
|
size_t i, count = 0, index = 0;
|
|
xmlAttrPtr att, curAtt;
|
|
xmlNodePtr editNode, childNode, motionNode;
|
|
currentEmber.m_Palette.Clear();//Wipe out the current palette.
|
|
att = emberNode->properties;//The top level element is a ember element, read the attributes of it and store them.
|
|
|
|
if (att == nullptr)
|
|
{
|
|
AddToReport(string(loc) + " : <flame> element has no attributes");
|
|
return false;
|
|
}
|
|
|
|
for (curAtt = att; curAtt; curAtt = curAtt->next)
|
|
{
|
|
attStr = reinterpret_cast<char*>(xmlGetProp(emberNode, curAtt->name));
|
|
|
|
//First parse out simple float reads.
|
|
if (ParseAndAssign(curAtt->name, attStr, "time", currentEmber.m_Time, ret)) {}
|
|
else if (ParseAndAssign(curAtt->name, attStr, "scale", currentEmber.m_PixelsPerUnit, ret)) { currentEmber.m_OrigPixPerUnit = currentEmber.m_PixelsPerUnit; }
|
|
else if (ParseAndAssign(curAtt->name, attStr, "rotate", currentEmber.m_Rotate, ret)) { currentEmber.m_Rotate = NormalizeDeg180<T>(currentEmber.m_Rotate); }
|
|
else if (ParseAndAssign(curAtt->name, attStr, "zoom", currentEmber.m_Zoom, ret)) { ClampGteRef<T>(currentEmber.m_Zoom, 0); }
|
|
else if (ParseAndAssign(curAtt->name, attStr, "cam_zoom", currentEmber.m_Zoom, ret)) { ClampGteRef<T>(currentEmber.m_Zoom, 0); }//JWildfire uses cam_zoom.
|
|
else if (ParseAndAssign(curAtt->name, attStr, "filter", currentEmber.m_SpatialFilterRadius, ret)) {}
|
|
else if (ParseAndAssign(curAtt->name, attStr, "temporal_filter_width", currentEmber.m_TemporalFilterWidth, ret)) {}
|
|
else if (ParseAndAssign(curAtt->name, attStr, "temporal_filter_exp", currentEmber.m_TemporalFilterExp, ret)) {}
|
|
else if (ParseAndAssign(curAtt->name, attStr, "quality", currentEmber.m_Quality, ret)) {}
|
|
else if (ParseAndAssign(curAtt->name, attStr, "brightness", currentEmber.m_Brightness, ret)) {}
|
|
else if (ParseAndAssign(curAtt->name, attStr, "gamma", currentEmber.m_Gamma, ret)) {}
|
|
else if (ParseAndAssign(curAtt->name, attStr, "highlight_power", currentEmber.m_HighlightPower, ret)) {}
|
|
else if (ParseAndAssign(curAtt->name, attStr, "logscale_k2", currentEmber.m_K2, ret)) {}
|
|
else if (ParseAndAssign(curAtt->name, attStr, "vibrancy", currentEmber.m_Vibrancy, ret)) {}
|
|
else if (ParseAndAssign(curAtt->name, attStr, "estimator_radius", currentEmber.m_MaxRadDE, ret)) {}
|
|
else if (ParseAndAssign(curAtt->name, attStr, "estimator_minimum", currentEmber.m_MinRadDE, ret)) {}
|
|
else if (ParseAndAssign(curAtt->name, attStr, "estimator_curve", currentEmber.m_CurveDE, ret)) {}
|
|
else if (ParseAndAssign(curAtt->name, attStr, "gamma_threshold", currentEmber.m_GammaThresh, ret)) {}
|
|
else if (ParseAndAssign(curAtt->name, attStr, "cam_zpos", currentEmber.m_CamZPos, ret)) {}
|
|
else if (ParseAndAssign(curAtt->name, attStr, "cam_persp", currentEmber.m_CamPerspective, ret)) {}
|
|
else if (ParseAndAssign(curAtt->name, attStr, "cam_perspective", currentEmber.m_CamPerspective, ret)) {}//Apo bug.
|
|
else if (ParseAndAssign(curAtt->name, attStr, "cam_yaw", currentEmber.m_CamYaw, ret)) {}
|
|
else if (ParseAndAssign(curAtt->name, attStr, "cam_pitch", currentEmber.m_CamPitch, ret)) {}
|
|
else if (ParseAndAssign(curAtt->name, attStr, "cam_dof", currentEmber.m_CamDepthBlur, ret)) {}
|
|
else if (ParseAndAssign(curAtt->name, attStr, "blur_curve", currentEmber.m_BlurCurve, ret)) {}
|
|
//Parse simple int reads.
|
|
else if (ParseAndAssign(curAtt->name, attStr, "palette", currentEmber.m_Palette.m_Index, ret)) {}
|
|
else if (ParseAndAssign(curAtt->name, attStr, "oversample", currentEmber.m_Supersample, ret)) {}
|
|
else if (ParseAndAssign(curAtt->name, attStr, "supersample", currentEmber.m_Supersample, ret)) {}
|
|
else if (ParseAndAssign(curAtt->name, attStr, "temporal_samples", currentEmber.m_TemporalSamples, ret)) {}
|
|
else if (ParseAndAssign(curAtt->name, attStr, "sub_batch_size", currentEmber.m_SubBatchSize, ret)) {}
|
|
else if (ParseAndAssign(curAtt->name, attStr, "fuse", currentEmber.m_FuseCount, ret)) {}
|
|
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)) {}
|
|
//Parse more complicated reads that have multiple possible values.
|
|
else if (!Compare(curAtt->name, "interpolation"))
|
|
{
|
|
if (!_stricmp("linear", attStr))
|
|
currentEmber.m_Interp = eInterp::EMBER_INTERP_LINEAR;
|
|
else if (!_stricmp("smooth", attStr))
|
|
currentEmber.m_Interp = eInterp::EMBER_INTERP_SMOOTH;
|
|
else
|
|
AddToReport(string(loc) + " : Unrecognized interpolation type " + string(attStr));
|
|
}
|
|
else if (!Compare(curAtt->name, "palette_interpolation"))
|
|
{
|
|
if (!_stricmp("hsv", attStr))
|
|
currentEmber.m_PaletteInterp = ePaletteInterp::INTERP_HSV;
|
|
else if (!_stricmp("sweep", attStr))
|
|
currentEmber.m_PaletteInterp = ePaletteInterp::INTERP_SWEEP;
|
|
else
|
|
AddToReport(string(loc) + " : Unrecognized palette interpolation type " + string(attStr));
|
|
}
|
|
else if (!Compare(curAtt->name, "interpolation_space") || !Compare(curAtt->name, "interpolation_type"))
|
|
{
|
|
if (!_stricmp("linear", attStr))
|
|
currentEmber.m_AffineInterp = eAffineInterp::AFFINE_INTERP_LINEAR;
|
|
else if (!_stricmp("log", attStr))
|
|
currentEmber.m_AffineInterp = eAffineInterp::AFFINE_INTERP_LOG;
|
|
else if (!_stricmp("old", attStr))
|
|
currentEmber.m_AffineInterp = eAffineInterp::AFFINE_INTERP_COMPAT;
|
|
else if (!_stricmp("older", attStr))
|
|
currentEmber.m_AffineInterp = eAffineInterp::AFFINE_INTERP_OLDER;
|
|
else
|
|
AddToReport(string(loc) + " : Unrecognized interpolation type " + string(attStr));
|
|
}
|
|
else if (!Compare(curAtt->name, "name"))
|
|
{
|
|
currentEmber.m_Name = string(attStr);
|
|
std::replace(currentEmber.m_Name.begin(), currentEmber.m_Name.end(), ' ', '_');
|
|
}
|
|
else if (!Compare(curAtt->name, "version"))
|
|
{
|
|
if (ToLower(string(attStr)).find("ember") != string::npos)
|
|
fromEmber = true;
|
|
}
|
|
else if (!Compare(curAtt->name, "size"))
|
|
{
|
|
istringstream is(attStr);
|
|
is >> currentEmber.m_FinalRasW >> currentEmber.m_FinalRasH;
|
|
currentEmber.m_OrigFinalRasW = currentEmber.m_FinalRasW;
|
|
currentEmber.m_OrigFinalRasH = currentEmber.m_FinalRasH;
|
|
}
|
|
else if (!Compare(curAtt->name, "center"))
|
|
{
|
|
istringstream is(attStr);
|
|
is >> currentEmber.m_CenterX >> currentEmber.m_CenterY;
|
|
currentEmber.m_RotCenterY = currentEmber.m_CenterY;
|
|
}
|
|
else if (!Compare(curAtt->name, "filter_shape"))
|
|
{
|
|
currentEmber.m_SpatialFilterType = SpatialFilterCreator<T>::FromString(string(attStr));
|
|
}
|
|
else if (!Compare(curAtt->name, "temporal_filter_type"))
|
|
{
|
|
currentEmber.m_TemporalFilterType = TemporalFilterCreator<T>::FromString(string(attStr));
|
|
}
|
|
else if (!Compare(curAtt->name, "palette_mode"))
|
|
{
|
|
if (!_stricmp("step", attStr))
|
|
currentEmber.m_PaletteMode = ePaletteMode::PALETTE_STEP;
|
|
else if (!_stricmp("linear", attStr))
|
|
currentEmber.m_PaletteMode = ePaletteMode::PALETTE_LINEAR;
|
|
else
|
|
{
|
|
currentEmber.m_PaletteMode = ePaletteMode::PALETTE_STEP;
|
|
AddToReport(string(loc) + " : Unrecognized palette mode " + string(attStr) + ", using step");
|
|
}
|
|
}
|
|
else if (!Compare(curAtt->name, "background"))
|
|
{
|
|
istringstream is(attStr);
|
|
is >> currentEmber.m_Background[0]//[0..1]
|
|
>> currentEmber.m_Background[1]
|
|
>> currentEmber.m_Background[2];
|
|
}
|
|
else if (!Compare(curAtt->name, "curves"))
|
|
{
|
|
auto splits = Split(attStr, ' ');
|
|
istringstream is(attStr);
|
|
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
vector<v2F> vals;
|
|
|
|
for (glm::length_t j = 0; j < 4; j++)
|
|
{
|
|
T w, x = 0, y = 0;
|
|
is >> x
|
|
>> y
|
|
>> w;//Weights appear to be unused.
|
|
vals.push_back({ Clamp<T>(x, 0, 1), Clamp<T>(y, 0, 1) });
|
|
}
|
|
|
|
std::sort(vals.begin(), vals.end(), [&](auto & lhs, auto & rhs) { return lhs.x < rhs.x; });
|
|
|
|
if (vals[0] == v2F(0) && vals[1] == v2F(0))
|
|
vals[1] = v2F(0.25);
|
|
|
|
if (vals[2] == v2F(1) && vals[3] == v2F(1))
|
|
vals[3] = v2F(0.75);
|
|
|
|
currentEmber.m_Curves.m_Points[i] = vals;
|
|
}
|
|
}
|
|
else if (!Compare(curAtt->name, "overall_curve"))
|
|
{
|
|
//cout << "found overall curves\n";
|
|
auto splits = Split(attStr, ' ');
|
|
istringstream is(attStr);
|
|
vector<v2F> vals;
|
|
T x = 0, y = 0;
|
|
|
|
while (is >> x && is >> y)//No weights when using this format.
|
|
{
|
|
vals.push_back({ Clamp<T>(x, 0, 1), Clamp<T>(y, 0, 1) });
|
|
}
|
|
|
|
std::sort(vals.begin(), vals.end(), [&](auto & lhs, auto & rhs) { return lhs.x < rhs.x; });
|
|
|
|
if (vals[0] == v2F(0) && vals[1] == v2F(0))
|
|
vals[1] = v2F(0.25);
|
|
|
|
if (vals[2] == v2F(1) && vals[3] == v2F(1))
|
|
vals[3] = v2F(0.75);
|
|
|
|
currentEmber.m_Curves.m_Points[0] = vals;
|
|
}
|
|
else if (!Compare(curAtt->name, "red_curve"))
|
|
{
|
|
//cout << "found red curves\n";
|
|
auto splits = Split(attStr, ' ');
|
|
istringstream is(attStr);
|
|
vector<v2F> vals;
|
|
T x = 0, y = 0;
|
|
|
|
while (is >> x && is >> y)//No weights when using this format.
|
|
{
|
|
vals.push_back({ Clamp<T>(x, 0, 1), Clamp<T>(y, 0, 1) });
|
|
}
|
|
|
|
std::sort(vals.begin(), vals.end(), [&](auto & lhs, auto & rhs) { return lhs.x < rhs.x; });
|
|
|
|
if (vals[0] == v2F(0) && vals[1] == v2F(0))
|
|
vals[1] = v2F(0.25);
|
|
|
|
if (vals[2] == v2F(1) && vals[3] == v2F(1))
|
|
vals[3] = v2F(0.75);
|
|
|
|
currentEmber.m_Curves.m_Points[1] = vals;
|
|
}
|
|
else if (!Compare(curAtt->name, "green_curve"))
|
|
{
|
|
//cout << "found green curves\n";
|
|
auto splits = Split(attStr, ' ');
|
|
istringstream is(attStr);
|
|
vector<v2F> vals;
|
|
T x = 0, y = 0;
|
|
|
|
while (is >> x && is >> y)//No weights when using this format.
|
|
{
|
|
vals.push_back({ Clamp<T>(x, 0, 1), Clamp<T>(y, 0, 1) });
|
|
}
|
|
|
|
std::sort(vals.begin(), vals.end(), [&](auto & lhs, auto & rhs) { return lhs.x < rhs.x; });
|
|
|
|
if (vals[0] == v2F(0) && vals[1] == v2F(0))
|
|
vals[1] = v2F(0.25);
|
|
|
|
if (vals[2] == v2F(1) && vals[3] == v2F(1))
|
|
vals[3] = v2F(0.75);
|
|
|
|
currentEmber.m_Curves.m_Points[2] = vals;
|
|
}
|
|
else if (!Compare(curAtt->name, "blue_curve"))
|
|
{
|
|
//cout << "found blue curves\n";
|
|
auto splits = Split(attStr, ' ');
|
|
istringstream is(attStr);
|
|
vector<v2F> vals;
|
|
T x = 0, y = 0;
|
|
|
|
while (is >> x && is >> y)//No weights when using this format.
|
|
{
|
|
vals.push_back({ Clamp<T>(x, 0, 1), Clamp<T>(y, 0, 1) });
|
|
}
|
|
|
|
std::sort(vals.begin(), vals.end(), [&](auto & lhs, auto & rhs) { return lhs.x < rhs.x; });
|
|
|
|
if (vals[0] == v2F(0) && vals[1] == v2F(0))
|
|
vals[1] = v2F(0.25);
|
|
|
|
if (vals[2] == v2F(1) && vals[3] == v2F(1))
|
|
vals[3] = v2F(0.75);
|
|
|
|
currentEmber.m_Curves.m_Points[3] = vals;
|
|
}
|
|
|
|
xmlFree(attStr);
|
|
}
|
|
|
|
//Finished with ember attributes. Now look at the children of the ember element.
|
|
for (childNode = emberNode->children; childNode; childNode = childNode->next)
|
|
{
|
|
if (!Compare(childNode->name, "color"))
|
|
{
|
|
index = -1;
|
|
float r = 0, g = 0, b = 0, a = 0;
|
|
//Loop through the attributes of the color element.
|
|
att = childNode->properties;
|
|
|
|
if (att == nullptr)
|
|
{
|
|
AddToReport(string(loc) + " : No attributes for color element");
|
|
continue;
|
|
}
|
|
|
|
for (curAtt = att; curAtt; curAtt = curAtt->next)
|
|
{
|
|
a = 255;
|
|
attStr = reinterpret_cast<char*>(xmlGetProp(childNode, curAtt->name));
|
|
istringstream is(attStr);
|
|
//This signifies that a palette is not being retrieved from the palette file, rather it's being parsed directly out of the ember xml.
|
|
//This also means the palette has already been hue adjusted and it doesn't need to be done again, which would be necessary if it were
|
|
//coming from the palette file.
|
|
currentEmber.m_Palette.m_Index = -1;
|
|
|
|
if (!Compare(curAtt->name, "index"))
|
|
Aton(attStr, index);
|
|
else if (!Compare(curAtt->name, "rgb"))
|
|
is >> r >> g >> b;
|
|
else if (!Compare(curAtt->name, "rgba"))
|
|
is >> r >> g >> b >> a;
|
|
else if (!Compare(curAtt->name, "a"))
|
|
is >> a;
|
|
else
|
|
AddToReport(string(loc) + " : Unknown color attribute " + string(CCX(curAtt->name)));
|
|
|
|
xmlFree(attStr);
|
|
}
|
|
|
|
while (index >= currentEmber.m_Palette.Size())
|
|
currentEmber.m_Palette.m_Entries.push_back(v4F());
|
|
|
|
float alphaPercent = a / 255.0f;//Aplha percentage in the range of 0 to 1.
|
|
//Premultiply the palette.
|
|
currentEmber.m_Palette.m_Entries[index].r = alphaPercent * (r / 255.0f);//Palette colors are [0..255], convert to [0..1].
|
|
currentEmber.m_Palette.m_Entries[index].g = alphaPercent * (g / 255.0f);
|
|
currentEmber.m_Palette.m_Entries[index].b = alphaPercent * (b / 255.0f);
|
|
currentEmber.m_Palette.m_Entries[index].a = a / 255.0f;//Will be one for RGB, and other than one if RGBA with A != 255.
|
|
}
|
|
else if (!Compare(childNode->name, "colors"))
|
|
{
|
|
//Loop through the attributes of the color element.
|
|
att = childNode->properties;
|
|
|
|
if (att == nullptr)
|
|
{
|
|
AddToReport(string(loc) + " : No attributes for colors element");
|
|
continue;
|
|
}
|
|
|
|
for (curAtt = att; curAtt; curAtt = curAtt->next)
|
|
{
|
|
attStr = reinterpret_cast<char*>(xmlGetProp(childNode, curAtt->name));
|
|
|
|
if (!Compare(curAtt->name, "count"))
|
|
{
|
|
Aton(attStr, count);
|
|
}
|
|
else if (!Compare(curAtt->name, "data"))
|
|
{
|
|
if (!ParseHexColors(attStr, currentEmber, count, -4))
|
|
{
|
|
AddToReport(string(loc) + " : Error parsing hexformatted colors, some may be set to zero");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AddToReport(string(loc) + " : Unknown color attribute " + string(CCX(curAtt->name)));
|
|
}
|
|
|
|
xmlFree(attStr);
|
|
}
|
|
}
|
|
else if (!Compare(childNode->name, "palette"))
|
|
{
|
|
//This could be either the old form of palette or the new form.
|
|
//Make sure BOTH are not specified, otherwise either are ok.
|
|
int numColors = 0;
|
|
int numBytes = 0;
|
|
//Loop through the attributes of the palette element.
|
|
att = childNode->properties;
|
|
|
|
if (att == nullptr)
|
|
{
|
|
AddToReport(string(loc) + " : No attributes for palette element");
|
|
continue;
|
|
}
|
|
|
|
for (curAtt = att; curAtt; curAtt = curAtt->next)
|
|
{
|
|
attStr = reinterpret_cast<char*>(xmlGetProp(childNode, curAtt->name));
|
|
|
|
if (!Compare(curAtt->name, "count"))
|
|
{
|
|
Aton(attStr, numColors);
|
|
}
|
|
else if (!Compare(curAtt->name, "format"))
|
|
{
|
|
if (!_stricmp(attStr, "RGB"))
|
|
numBytes = 3;
|
|
else if (!_stricmp(attStr, "RGBA"))
|
|
numBytes = 4;
|
|
else
|
|
{
|
|
AddToReport(string(loc) + " : Unrecognized palette format string " + string(attStr) + ", defaulting to RGB");
|
|
numBytes = 3;
|
|
}
|
|
}
|
|
else if (!Compare(curAtt->name, "source_colors"))
|
|
{
|
|
string s(attStr);
|
|
auto vec1 = Split(s, ' ');
|
|
|
|
for (auto& v : vec1)
|
|
{
|
|
auto vec2 = Split(v, ',');
|
|
|
|
if (vec2.size() == 4)
|
|
{
|
|
float d1 = Clamp(std::stof(vec2[0]), 0.0f, 1.0f);
|
|
currentEmber.m_Palette.m_SourceColors[d1] = v4F(
|
|
Clamp(std::stof(vec2[1]), 0.0f, 1.0f),
|
|
Clamp(std::stof(vec2[2]), 0.0f, 1.0f),
|
|
Clamp(std::stof(vec2[3]), 0.0f, 1.0f), 1.0f);
|
|
}
|
|
else
|
|
AddToReport(string(loc) + " : Bad palette color source value: " + v);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AddToReport(string(loc) + " : Unknown palette attribute " + string(CCX(curAtt->name)));
|
|
}
|
|
|
|
xmlFree(attStr);
|
|
}
|
|
|
|
//Removing support for whatever "old format" was in flam3.
|
|
//Read formatted string from contents of tag.
|
|
char* palStr = CX(xmlNodeGetContent(childNode));
|
|
|
|
if (!ParseHexColors(palStr, currentEmber, numColors, numBytes))
|
|
{
|
|
AddToReport(string(loc) + " : Problem reading hexadecimal color data in palette");
|
|
}
|
|
|
|
xmlFree(palStr);
|
|
}
|
|
else if (!Compare(childNode->name, "symmetry"))
|
|
{
|
|
int symKind = INT_MAX;
|
|
//Loop through the attributes of the palette element.
|
|
att = childNode->properties;
|
|
|
|
if (att == nullptr)
|
|
{
|
|
AddToReport(string(loc) + " : No attributes for palette element");
|
|
continue;
|
|
}
|
|
|
|
for (curAtt = att; curAtt; curAtt = curAtt->next)
|
|
{
|
|
attStr = reinterpret_cast<char*>(xmlGetProp(childNode, curAtt->name));
|
|
|
|
if (!Compare(curAtt->name, "kind"))
|
|
{
|
|
Aton(attStr, symKind);
|
|
}
|
|
else
|
|
{
|
|
AddToReport(string(loc) + " : Unknown symmetry attribute " + string(attStr));
|
|
continue;
|
|
}
|
|
|
|
xmlFree(attStr);
|
|
}
|
|
|
|
//if (symKind != INT_MAX)//What to do about this? Should sym not be saved? Or perhaps better intelligence when adding?//TODO//BUG.
|
|
//{
|
|
// currentEmber.AddSymmetry(symKind, *(GlobalRand.get()));//Determine what to do here.
|
|
//}
|
|
}
|
|
else if (!Compare(childNode->name, "xform") || !Compare(childNode->name, "finalxform"))
|
|
{
|
|
Xform<T>* theXform = nullptr;
|
|
|
|
if (!Compare(childNode->name, "finalxform"))
|
|
{
|
|
Xform<T> finalXform;
|
|
finalXform.m_Animate = 0;//Do not animate final by default.
|
|
|
|
if (!ParseXform(childNode, finalXform, false, fromEmber))
|
|
{
|
|
AddToReport(string(loc) + " : Error parsing final xform");
|
|
}
|
|
else
|
|
{
|
|
if (finalXform.m_Weight != 0)
|
|
{
|
|
finalXform.m_Weight = 0;
|
|
AddToReport(string(loc) + " : Final xforms should not have weight specified, setting to zero");
|
|
}
|
|
|
|
currentEmber.SetFinalXform(finalXform);
|
|
theXform = currentEmber.NonConstFinalXform();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Xform<T> xform;
|
|
|
|
if (!ParseXform(childNode, xform, false, fromEmber))
|
|
{
|
|
AddToReport(string(loc) + " : Error parsing xform");
|
|
}
|
|
else
|
|
{
|
|
currentEmber.AddXform(xform);
|
|
theXform = currentEmber.GetXform(currentEmber.XformCount() - 1);
|
|
}
|
|
}
|
|
|
|
if (theXform)
|
|
{
|
|
//Check for non-zero motion params.
|
|
if (fabs(theXform->m_MotionFreq) > 0.0)//Original checked for motion func being non-zero, but it was set to MOTION_SIN (1) in Xform::Init(), so don't check for 0 here.
|
|
{
|
|
AddToReport(string(loc) + " : Motion parameters should not be specified in regular, non-motion xforms");
|
|
}
|
|
|
|
//Motion Language: Check the xform element for children - should be named 'motion'.
|
|
for (motionNode = childNode->children; motionNode; motionNode = motionNode->next)
|
|
{
|
|
if (!Compare(motionNode->name, "motion"))
|
|
{
|
|
Xform<T> xform(false);//Will only have valid values in fields parsed for motion, all others will be EMPTYFIELD.
|
|
|
|
if (!ParseXform(motionNode, xform, true, fromEmber))
|
|
AddToReport(string(loc) + " : Error parsing motion xform");
|
|
else
|
|
theXform->m_Motion.push_back(xform);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (!Compare(childNode->name, "edit"))
|
|
{
|
|
//Create a new XML document with this edit node as the root node.
|
|
currentEmber.m_Edits = xmlNewDoc(XC("1.0"));
|
|
editNode = xmlCopyNode(childNode, 1);
|
|
xmlDocSetRootElement(currentEmber.m_Edits, editNode);
|
|
}
|
|
else if (!Compare(childNode->name, "flame_motion"))
|
|
{
|
|
EmberMotion<T> motion;
|
|
att = childNode->properties;
|
|
|
|
if (att == nullptr)
|
|
{
|
|
AddToReport(string(loc) + " : <flame_motion> element has no attributes");
|
|
return false;
|
|
}
|
|
|
|
for (curAtt = att; curAtt; curAtt = curAtt->next)
|
|
{
|
|
attStr = reinterpret_cast<char*>(xmlGetProp(childNode, curAtt->name));
|
|
|
|
if (ParseAndAssign(curAtt->name, attStr, "motion_frequency", motion.m_MotionFreq, ret)) {}
|
|
else if (ParseAndAssign(curAtt->name, attStr, "motion_offset", motion.m_MotionOffset, ret)) {}
|
|
else if (!Compare(curAtt->name, "motion_function"))
|
|
{
|
|
string func(attStr);
|
|
|
|
if (func == "sin")
|
|
motion.m_MotionFunc = eMotion::MOTION_SIN;
|
|
else if (func == "triangle")
|
|
motion.m_MotionFunc = eMotion::MOTION_TRIANGLE;
|
|
else if (func == "hill")
|
|
motion.m_MotionFunc = eMotion::MOTION_HILL;
|
|
else if (func == "saw")
|
|
motion.m_MotionFunc = eMotion::MOTION_SAW;
|
|
else
|
|
{
|
|
AddToReport(string(loc) + " : invalid flame motion function " + func + ", setting to sin");
|
|
motion.m_MotionFunc = eMotion::MOTION_SIN;
|
|
}
|
|
}
|
|
else if (!Compare(curAtt->name, "zoom"))
|
|
ret = ret && AttToEmberMotionFloat(att, attStr, eEmberMotionParam::FLAME_MOTION_ZOOM, motion);
|
|
else if (!Compare(curAtt->name, "cam_zpos"))
|
|
ret = ret && AttToEmberMotionFloat(att, attStr, eEmberMotionParam::FLAME_MOTION_ZPOS, motion);
|
|
else if (!Compare(curAtt->name, "cam_persp"))
|
|
ret = ret && AttToEmberMotionFloat(att, attStr, eEmberMotionParam::FLAME_MOTION_PERSPECTIVE, motion);
|
|
else if (!Compare(curAtt->name, "cam_yaw"))
|
|
ret = ret && AttToEmberMotionFloat(att, attStr, eEmberMotionParam::FLAME_MOTION_YAW, motion);
|
|
else if (!Compare(curAtt->name, "cam_pitch"))
|
|
ret = ret && AttToEmberMotionFloat(att, attStr, eEmberMotionParam::FLAME_MOTION_PITCH, motion);
|
|
else if (!Compare(curAtt->name, "cam_dof"))
|
|
ret = ret && AttToEmberMotionFloat(att, attStr, eEmberMotionParam::FLAME_MOTION_DEPTH_BLUR, motion);
|
|
else if (!Compare(curAtt->name, "rotate"))
|
|
ret = ret && AttToEmberMotionFloat(att, attStr, eEmberMotionParam::FLAME_MOTION_ROTATE, motion);
|
|
else if (!Compare(curAtt->name, "brightness"))
|
|
ret = ret && AttToEmberMotionFloat(att, attStr, eEmberMotionParam::FLAME_MOTION_BRIGHTNESS, motion);
|
|
else if (!Compare(curAtt->name, "gamma"))
|
|
ret = ret && AttToEmberMotionFloat(att, attStr, eEmberMotionParam::FLAME_MOTION_GAMMA, motion);
|
|
else if (!Compare(curAtt->name, "gamma_threshold"))
|
|
ret = ret && AttToEmberMotionFloat(att, attStr, eEmberMotionParam::FLAME_MOTION_GAMMA_THRESH, motion);
|
|
else if (!Compare(curAtt->name, "highlight_power"))
|
|
ret = ret && AttToEmberMotionFloat(att, attStr, eEmberMotionParam::FLAME_MOTION_HIGHLIGHT_POWER, motion);
|
|
else if (!Compare(curAtt->name, "logscale_k2"))
|
|
ret = ret && AttToEmberMotionFloat(att, attStr, eEmberMotionParam::FLAME_MOTION_K2, motion);
|
|
else if (!Compare(curAtt->name, "rand_range"))
|
|
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, "background"))
|
|
{
|
|
double r = 0, g = 0, b = 0;
|
|
istringstream is(attStr);
|
|
is >> r >> g >> b;
|
|
|
|
if (r != 0)
|
|
motion.m_MotionParams.push_back(MotionParam<T>(eEmberMotionParam::FLAME_MOTION_BACKGROUND_R, T(r)));
|
|
|
|
if (g != 0)
|
|
motion.m_MotionParams.push_back(MotionParam<T>(eEmberMotionParam::FLAME_MOTION_BACKGROUND_G, T(g)));
|
|
|
|
if (b != 0)
|
|
motion.m_MotionParams.push_back(MotionParam<T>(eEmberMotionParam::FLAME_MOTION_BACKGROUND_B, T(b)));
|
|
}
|
|
else if (!Compare(curAtt->name, "center"))
|
|
{
|
|
double cx = 0, cy = 0;
|
|
istringstream is(attStr);
|
|
is >> cx >> cy;
|
|
|
|
if (cx != 0)
|
|
motion.m_MotionParams.push_back(MotionParam<T>(eEmberMotionParam::FLAME_MOTION_CENTER_X, T(cx)));
|
|
|
|
if (cy != 0)
|
|
motion.m_MotionParams.push_back(MotionParam<T>(eEmberMotionParam::FLAME_MOTION_CENTER_Y, T(cy)));
|
|
}
|
|
else
|
|
{
|
|
AddToReport(string(loc) + " : Unknown flame motion attribute " + string(CCX(curAtt->name)));
|
|
}
|
|
|
|
xmlFree(attStr);
|
|
}
|
|
|
|
currentEmber.m_EmberMotionElements.push_back(motion);
|
|
}
|
|
}
|
|
|
|
//If new_linear == 0, manually add a linear
|
|
if (!fromEmber && !newLinear)
|
|
currentEmber.Flatten(m_FlattenNames);
|
|
|
|
if (soloXform >= 0)
|
|
for (i = 0; i < currentEmber.XformCount(); i++)
|
|
currentEmber.GetXform(i)->m_Opacity = T(i == soloXform);//Will calc the cached adjusted viz value later.
|
|
|
|
return true;
|
|
}
|
|
/// <summary>
|
|
/// Parse a floating point value from an xml attribute and add the value to a EmberMotion object
|
|
/// </summary>
|
|
/// <param name="att">The current attribute</param>
|
|
/// <param name="attStr">The attribute value to parse</param>
|
|
/// <param name="param">The flame motion parameter type</param>
|
|
/// <param name="motion">The flame motion element to add the parameter to</param>
|
|
/// <returns>True if there were no errors, else false.</returns>
|
|
template <typename T>
|
|
bool XmlToEmber<T>::AttToEmberMotionFloat(xmlAttrPtr att, const char* attStr, eEmberMotionParam param, EmberMotion<T>& motion)
|
|
{
|
|
const char* loc = __FUNCTION__;
|
|
bool r = false;
|
|
T val = 0.0;
|
|
|
|
if (Aton(attStr, val))
|
|
{
|
|
motion.m_MotionParams.push_back(MotionParam<T>(param, val));
|
|
r = true;
|
|
}
|
|
else
|
|
{
|
|
AddToReport(string(loc) + " : Failed to parse float value for flame motion attribute \"" + string(CCX(att->name)) + "\" : " + string(attStr));
|
|
}
|
|
|
|
return r;
|
|
}
|
|
/// <summary>
|
|
/// Parse an xform element.
|
|
/// </summary>
|
|
/// <param name="childNode">The current node to parse</param>
|
|
/// <param name="xform">The newly constructed xform based on what was parsed</param>
|
|
/// <param name="motion">True if this xform is a motion within a parent xform, else false</param>
|
|
/// <returns>True if there were no errors, else false.</returns>
|
|
template <typename T>
|
|
bool XmlToEmber<T>::ParseXform(xmlNode* childNode, Xform<T>& xform, bool motion, bool fromEmber)
|
|
{
|
|
bool success = true;
|
|
char* attStr;
|
|
const char* loc = __FUNCTION__;
|
|
size_t j;
|
|
T temp;
|
|
double a, b, c, d, e, f;
|
|
xmlAttrPtr attPtr, curAtt;
|
|
//Loop through the attributes of the xform element.
|
|
attPtr = childNode->properties;
|
|
|
|
if (attPtr == nullptr)
|
|
{
|
|
AddToReport(string(loc) + " : Error: No attributes for element");
|
|
return false;
|
|
}
|
|
|
|
for (curAtt = attPtr; curAtt; curAtt = curAtt->next)
|
|
{
|
|
attStr = reinterpret_cast<char*>(xmlGetProp(childNode, curAtt->name));
|
|
|
|
//First parse out simple float reads.
|
|
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, "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)) {}
|
|
else if (ParseAndAssign(curAtt->name, attStr, "motion_offset", xform.m_MotionOffset, success)) {}
|
|
//Parse more complicated reads that have multiple possible values.
|
|
else if (!Compare(curAtt->name, "name"))
|
|
{
|
|
xform.m_Name = string(attStr);
|
|
std::replace(xform.m_Name.begin(), xform.m_Name.end(), ' ', '_');
|
|
}
|
|
else if (!fromEmber && !Compare(curAtt->name, "symmetry"))//Legacy support.
|
|
{
|
|
//Deprecated, set both color_speed and animate to this value.
|
|
//Huh? Either set it or not?
|
|
Aton(attStr, temp);
|
|
xform.m_ColorSpeed = (1 - temp) / 2;
|
|
xform.m_Animate = T(temp > 0 ? 0 : 1);
|
|
}
|
|
else if (!Compare(curAtt->name, "motion_function"))
|
|
{
|
|
if (!_stricmp("sin", attStr))
|
|
xform.m_MotionFunc = eMotion::MOTION_SIN;
|
|
else if (!_stricmp("triangle", attStr))
|
|
xform.m_MotionFunc = eMotion::MOTION_TRIANGLE;
|
|
else if (!_stricmp("hill", attStr))
|
|
xform.m_MotionFunc = eMotion::MOTION_HILL;
|
|
else if (!_stricmp("saw", attStr))
|
|
xform.m_MotionFunc = eMotion::MOTION_SAW;
|
|
else
|
|
{
|
|
xform.m_MotionFunc = eMotion::MOTION_SIN;
|
|
AddToReport(string(loc) + " : Unknown motion function " + string(attStr) + ", using sin");
|
|
}
|
|
}
|
|
else if (!Compare(curAtt->name, "color"))
|
|
{
|
|
istringstream is(attStr);
|
|
xform.m_ColorX = xform.m_ColorY = T(0.5);
|
|
is >> xform.m_ColorX;
|
|
is >> xform.m_ColorY;//Very unlikely to be present, but leave for future use.
|
|
}
|
|
else if (!Compare(curAtt->name, "chaos"))
|
|
{
|
|
istringstream is(attStr);
|
|
j = 0;
|
|
|
|
while (is >> temp)
|
|
{
|
|
xform.SetXaos(j, temp);
|
|
j++;
|
|
}
|
|
}
|
|
else if (!Compare(curAtt->name, "plotmode"))
|
|
{
|
|
if (motion == 1)
|
|
{
|
|
AddToReport(string(loc) + " : Motion element cannot have a plotmode attribute");
|
|
}
|
|
else if (!_stricmp("off", attStr))
|
|
xform.m_Opacity = 0;
|
|
}
|
|
else if (!Compare(curAtt->name, "coefs"))
|
|
{
|
|
istringstream is(attStr);
|
|
a = b = c = d = e = f = 0;
|
|
is >> a >> d >> b >> e >> c >> f;
|
|
xform.m_Affine.A(T(a));
|
|
xform.m_Affine.B(T(b));
|
|
xform.m_Affine.C(T(c));
|
|
xform.m_Affine.D(T(d));
|
|
xform.m_Affine.E(T(e));
|
|
xform.m_Affine.F(T(f));
|
|
}
|
|
else if (!Compare(curAtt->name, "post"))
|
|
{
|
|
istringstream is(attStr);
|
|
a = b = c = d = e = f = 0;
|
|
is >> a >> d >> b >> e >> c >> f;
|
|
xform.m_Post.A(T(a));
|
|
xform.m_Post.B(T(b));
|
|
xform.m_Post.C(T(c));
|
|
xform.m_Post.D(T(d));
|
|
xform.m_Post.E(T(e));
|
|
xform.m_Post.F(T(f));
|
|
}
|
|
else
|
|
{
|
|
//Only correct names if it came from an outside source. Names originating from this library are always considered correct.
|
|
string s = fromEmber ? string(CCX(curAtt->name)) : GetCorrectedVariationName(m_BadVariationNames, curAtt);
|
|
|
|
if (auto var = m_VariationList->GetVariation(s))
|
|
{
|
|
T weight = 0;
|
|
Aton(attStr, weight);
|
|
|
|
if (!IsNearZero(weight))//Having a variation with zero weight makes no sense, so guard against it.
|
|
{
|
|
auto varCopy = var->Copy();
|
|
varCopy->m_Weight = weight;
|
|
xform.AddVariation(varCopy);
|
|
}
|
|
}
|
|
|
|
//else
|
|
//{
|
|
// AddToReport("Unsupported variation: " + string((const char*)curAtt->name));
|
|
//}
|
|
}
|
|
|
|
xmlFree(attStr);
|
|
}
|
|
|
|
//Handle var1.
|
|
for (curAtt = attPtr; curAtt; curAtt = curAtt->next)//Legacy fields, most likely not used.
|
|
{
|
|
bool var1 = false;
|
|
|
|
if (!Compare(curAtt->name, "var1"))
|
|
{
|
|
attStr = reinterpret_cast<char*>(xmlGetProp(childNode, curAtt->name));
|
|
|
|
for (j = 0; j < xform.TotalVariationCount(); j++)
|
|
xform.GetVariation(j)->m_Weight = 0;
|
|
|
|
if (Aton(attStr, temp))
|
|
{
|
|
uint iTemp = static_cast<uint>(temp);
|
|
|
|
if (iTemp < xform.TotalVariationCount())
|
|
{
|
|
xform.GetVariation(iTemp)->m_Weight = 1;
|
|
var1 = true;
|
|
}
|
|
}
|
|
|
|
if (!var1)
|
|
AddToReport(string(loc) + " : Bad value for var1 " + string(attStr));
|
|
|
|
xmlFree(attStr);
|
|
break;
|
|
}
|
|
}
|
|
|
|
//Handle var.
|
|
for (curAtt = attPtr; curAtt; curAtt = curAtt->next)//Legacy fields, most likely not used.
|
|
{
|
|
bool var = false;
|
|
|
|
if (!Compare(curAtt->name, "var"))
|
|
{
|
|
attStr = reinterpret_cast<char*>(xmlGetProp(childNode, curAtt->name));
|
|
|
|
if (Aton(attStr, temp))
|
|
{
|
|
for (j = 0; j < xform.TotalVariationCount(); j++)
|
|
xform.GetVariation(j)->m_Weight = temp;
|
|
|
|
var = true;
|
|
}
|
|
|
|
if (!var)
|
|
AddToReport(string(loc) + " : Bad value for var " + string(attStr));
|
|
|
|
xmlFree(attStr);
|
|
break;
|
|
}
|
|
}
|
|
|
|
//Now that all xforms have been parsed, go through and try to find params for the parametric variations.
|
|
for (size_t i = 0; i < xform.TotalVariationCount(); i++)
|
|
{
|
|
if (ParametricVariation<T>* parVar = dynamic_cast<ParametricVariation<T>*>(xform.GetVariation(i)))
|
|
{
|
|
for (curAtt = attPtr; curAtt; curAtt = curAtt->next)
|
|
{
|
|
//Only correct names if it came from an outside source. Names originating from this library are always considered correct.
|
|
string s = fromEmber ? string(CCX(curAtt->name)) : GetCorrectedParamName(m_BadParamNames, ToLower(CCX(curAtt->name)).c_str());
|
|
const char* name = s.c_str();
|
|
|
|
if (parVar->ContainsParam(name))
|
|
{
|
|
T val = 0;
|
|
attStr = CX(xmlGetProp(childNode, curAtt->name));
|
|
|
|
if (Aton(attStr, val))
|
|
{
|
|
if (!parVar->SetParamVal(name, val))
|
|
AddToReport(string(loc) + " : Failed to set parametric variation parameter " + name);
|
|
}
|
|
else
|
|
{
|
|
AddToReport(string(loc) + " : Failed to parse parametric variation parameter " + s + " - " + string(attStr));
|
|
}
|
|
|
|
xmlFree(attStr);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
/// <summary>
|
|
/// Some Apophysis plugins use an inconsistent naming scheme for the parametric variation variables.
|
|
/// This function identifies and converts them to Ember's consistent naming convention.
|
|
/// </summary>
|
|
/// <param name="names">The map of corrected names to search</param>
|
|
/// <param name="name">The current Xml node to check</param>
|
|
/// <returns>The corrected name if one was found, else the passed in name.</returns>
|
|
template <typename T>
|
|
string XmlToEmber<T>::GetCorrectedParamName(const unordered_map<string, string>& names, const char* name)
|
|
{
|
|
const auto& newName = names.find(ToLower(name));
|
|
|
|
if (newName != names.end())
|
|
return newName->second;
|
|
else
|
|
return name;
|
|
}
|
|
/// <summary>
|
|
/// Some Apophysis plugins use an inconsistent naming scheme for variation names.
|
|
/// This function identifies and converts them to Ember's consistent naming convention.
|
|
/// It uses some additional intelligence to ensure the variation is the expected one,
|
|
/// by examining the rest of the xform for the existence of parameter names.
|
|
/// </summary>
|
|
/// <param name="vec">The vector of corrected names to search</param>
|
|
/// <param name="att">The xml attribute pointer whose name member is the name of the variation to check</param>
|
|
/// <returns>The corrected name if one was found, else the passed in name.</returns>
|
|
template <typename T>
|
|
string XmlToEmber<T>::GetCorrectedVariationName(vector<pair<pair<string, string>, vector<string>>>& vec, xmlAttrPtr att)
|
|
{
|
|
for (auto& v : vec)
|
|
{
|
|
if (!_stricmp(v.first.first.c_str(), CCX(att->name)))//Do case insensitive here.
|
|
{
|
|
if (!v.second.empty())
|
|
{
|
|
for (size_t j = 0; j < v.second.size(); j++)
|
|
{
|
|
if (XmlContainsTag(att, v.second[j].c_str()))
|
|
return v.first.second;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return v.first.second;
|
|
}
|
|
}
|
|
}
|
|
|
|
return string(CCX(att->name));
|
|
}
|
|
/// <summary>
|
|
/// Some Apophysis plugins use an inconsistent naming scheme for variation names.
|
|
/// This function identifies and converts them to Ember's consistent naming convention.
|
|
/// It uses some additional intelligence to ensure the variation is the expected one,
|
|
/// by examining the rest of the xform for the existence of parameter names.
|
|
/// This overload is specifically for use when parsing .chaos files from Chaotica.
|
|
/// </summary>
|
|
/// <param name="vec">The vector of corrected names to search</param>
|
|
/// <param name="varname">The name of the variation to check</param>
|
|
/// <returns>The corrected name if one was found, else the passed in name.</returns>
|
|
template <typename T>
|
|
string XmlToEmber<T>::GetCorrectedVariationName(vector<pair<pair<string, string>, vector<string>>>& vec, const string& varname)
|
|
{
|
|
if (varname == "poincare")//for Apo flames, poincare must be the same, but chaotica poincare is implemented as poincare2
|
|
return "poincare2";
|
|
else if (varname != "mobius")//Chaotica actually gets this right, but Apophysis doesn't.
|
|
for (auto& v : vec)
|
|
if (!_stricmp(v.first.first.c_str(), varname.c_str()))//Do case insensitive here.
|
|
return v.first.second;
|
|
|
|
return varname;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determine if an Xml node contains a given tag.
|
|
/// </summary>
|
|
/// <param name="att">The Xml node to search</param>
|
|
/// <param name="name">The node name to search for</param>
|
|
/// <returns>True if name was found, else false.</returns>
|
|
template <typename T>
|
|
bool XmlToEmber<T>::XmlContainsTag(xmlAttrPtr att, const char* name)
|
|
{
|
|
xmlAttrPtr temp = att;
|
|
|
|
do
|
|
{
|
|
if (!_stricmp(name, CCX(temp->name)))
|
|
return true;
|
|
}
|
|
while ((temp = temp->next));
|
|
|
|
return false;
|
|
}
|
|
/// <summary>
|
|
/// Parse hexadecimal colors.
|
|
/// This can read RGB and RGBA, however only RGB will be stored.
|
|
/// </summary>
|
|
/// <param name="colstr">The string of hex colors to parse</param>
|
|
/// <param name="ember">The ember whose palette will be assigned the colors</param>
|
|
/// <param name="numColors">The number of colors present</param>
|
|
/// <param name="chan">The number of channels in each color</param>
|
|
/// <returns>True if there were no errors, else false.</returns>
|
|
template <typename T>
|
|
bool XmlToEmber<T>::ParseHexColors(const char* colstr, Ember<T>& ember, size_t numColors, intmax_t chan)
|
|
{
|
|
stringstream ss, temp(colstr); ss >> std::hex;
|
|
string s1, s;
|
|
size_t tmp, colorCount = 0;
|
|
s.reserve(1536);
|
|
|
|
while (temp >> s1)
|
|
s += s1;
|
|
|
|
auto length = s.size();
|
|
|
|
for (size_t strIndex = 0; strIndex < length;)
|
|
{
|
|
for (glm::length_t i = 0; i < 3; i++)
|
|
{
|
|
const char tmpStr[3] = { s[strIndex++], s[strIndex++], 0 };//Read out and convert the string two characters at a time.
|
|
ss.clear();//Reset and fill the string stream.
|
|
ss.str(tmpStr);
|
|
ss >> tmp;//Do the conversion.
|
|
|
|
while (colorCount >= ember.m_Palette.Size())
|
|
ember.m_Palette.m_Entries.push_back(v4F());
|
|
|
|
ember.m_Palette.m_Entries[colorCount][i] = float(tmp) / 255.0f;//Hex palette is [0..255], convert to [0..1].
|
|
}
|
|
|
|
ember.m_Palette.m_Entries[colorCount][3] = float(1);
|
|
colorCount++;
|
|
}
|
|
|
|
return length >= 256;
|
|
}
|
|
/// <summary>
|
|
/// Wrapper to parse a numeric Xml string value and convert it.
|
|
/// </summary>
|
|
/// <param name="name">The xml tag to parse</param>
|
|
/// <param name="attStr">The name of the Xml attribute</param>
|
|
/// <param name="str">The name of the Xml tag</param>
|
|
/// <param name="val">The parsed value</param>
|
|
/// <param name="b">Bitwise ANDed with true if name matched str and the conversion succeeded, else false. Used for keeping a running value between successive calls.</param>
|
|
/// <returns>True if the tag was matched and the conversion succeeded, else false</returns>
|
|
template <typename T>
|
|
template <typename valT>
|
|
bool XmlToEmber<T>::ParseAndAssign(const xmlChar* name, const char* attStr, const char* str, valT& val, bool& b)
|
|
{
|
|
bool ret = false;
|
|
|
|
if (!Compare(name, str))
|
|
{
|
|
istringstream istr(attStr);
|
|
istr >> val;
|
|
ret = !istr.bad() && !istr.fail();//Means the Compare() was right, and the conversion succeeded.
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
//This class had to be implemented in a cpp file because the compiler was breaking.
|
|
//So the explicit instantiation must be declared here rather than in Ember.cpp where
|
|
//all of the other classes are done.
|
|
#define EXPORT_XML_TO_EMBER(T) \
|
|
template EMBER_API class XmlToEmber<T>; \
|
|
template EMBER_API bool XmlToEmber<T>::Parse(byte*, const char*, list<Ember<T>>&, bool); \
|
|
template EMBER_API bool XmlToEmber<T>::Parse(byte*, const char*, vector<Ember<T>>&, bool); \
|
|
template EMBER_API bool XmlToEmber<T>::Parse(const char*, list<Ember<T>>&, bool); \
|
|
template EMBER_API bool XmlToEmber<T>::Parse(const char*, vector<Ember<T>>&, bool); \
|
|
template EMBER_API bool XmlToEmber<T>::Aton(const char*, size_t&);
|
|
EXPORT_XML_TO_EMBER(float)
|
|
#ifdef DO_DOUBLE
|
|
EXPORT_XML_TO_EMBER(double)
|
|
#endif
|
|
}
|