mirror of
https://bitbucket.org/mfeemster/fractorium.git
synced 2025-01-22 13:40:06 -05:00
90ec5b8246
-Show common folder locations such as documents, downloads, pictures in the sidebar in all file dialogs. -Warning message about exceeding memory in final render dialog now suggests strips as the solution to the problem. -Strips now has a tooltip explaining what it does. -Allow more digits in the spinners on the color section the flame tab. -Add manually adjustable size spinners in the final render dialog. Percentage scale and absolute size are fully synced. -Default prefix in final render is now the filename when doing animations (coming from sequence section of the library tab). -Changed the elliptic variation back to using a less precise version for float, and a more precise version for double. The last release had it always using double. -New applied xaos table that shows a read-only view of actual weights by taking the base xform weights and multiplying them by the xaos values. -New table in the xaos tab that gives a graphical representation of the probability that each xform is chosen, with and without xaos. -Add button to transpose the xaos rows and columns. -Add support for importing .chaos files from Chaotica. --Pasting back to Chaotica will work for most, but not all, variations due to incompatible parameter names in some. -Curves are now splines instead of Bezier. This adds compatibility with Chaotica, but breaks it for Apophysis. Xmls are still pastable, but the color curves will look different. --The curve editor on the palette tab can now add points by clicking on the lines and remove points by clicking on the points themselves, just like Chaotica. --Splines are saved in four new xml fields: overall_curve, red_curve, green_curve and blue_curve. -Allow for specifying the percentage of a sub batch each thread should iterate through per kernel call when running with OpenCL. This gives a roughly 1% performance increase due to having to make less kernel calls while iterating. --This field is present for interactive editing (where it's not very useful) and in the final render dialog. --On the command line, this is specified as --sbpctth for EmberRender and EmberAnimate. -Allow double clicking to toggle the supersample field in the flame tab between 1 and 2 for easily checking the effect of the field. -When showing affine values as polar coordinates, show angles normalized to 360 to match Chaotica. -Fuse Count spinner now toggles between 15 and 100 when double clicking for easily checking the effect of the field. -Added field for limiting the range in the x and y direction that the initial points are chosen from. -Added a field called K2 which is an alternative way to set brightness, ignored when zero. --This has no effect for many variations, but hs a noticeable effect for some. -Added new variations: arcsech arcsech2 arcsinh arctanh asteria block bwraps_rand circlecrop2 coth_spiral crackle2 depth_blur depth_blur2 depth_gaussian depth_gaussian2 depth_ngon depth_ngon2 depth_sine depth_sine2 dragonfire dspherical dust excinis exp2 flipx flowerdb foci_p gaussian glynnia2 glynnsim4 glynnsim5 henon henon hex_rand hex_truchet hypershift lazyjess lens lozi lozi modulusx modulusy oscilloscope2 point_symmetry pointsymmetry projective pulse rotate scry2 shift smartshape spher squares starblur2 swirl3 swirl3r tanh_spiral target0 target2 tile_hlp truchet_glyph truchet_inv truchet_knot unicorngaloshen vibration vibration2 --hex_truchet, hex_rand should always use double. They are extremely sensitive. --Bug fixes: -Bounds sign was flipped for x coordinate of world space when center was not zero. -Right clicking and dragging spinner showed menu on mouse up, even if it was very far away. -Text boxes for size in final render dialog were hard to type in. Same bug as xform weight used to be so fix the same way. -Fix spelling to be plural in toggle color speed box. -Stop using the blank user palette to generate flames. Either put colored palettes in it, or exclude it from randoms. -Clicking the random palette button for a palette file with only one palette in it would freeze the program. -Clicking none scale in final render did not re-render the preview. -Use less precision on random xaos. No need for 12 decimal places. -The term sub batch is overloaded in the options dialog. Change the naming and tooltip of those settings for cpu and opencl. --Also made clear in the tooltip for the default opencl quality setting that the value is per device. -The arrows spinner in palette editor appears like a read-only label. Made it look like a spinner. -Fix border colors for various spin boxes and table headers in the style sheet. Requires reload. -Fix a bug in the bwraps variation which would produce different results than Chaotica and Apophysis. -Synth was allowed to be selected for random flame generation when using an Nvidia card but it shouldn't have been because Nvidia has a hard time compiling synth. -A casting bug in the OpenCL kernels for log scaling and density filtering was preventing successful compilations on Intel iGPUs. Fixed even though we don't support anything other than AMD and Nvidia. -Palette rotation (click and drag) position was not being reset when loading a new flame. -When the xform circles were hidden, opening and closing the options dialog would improperly reshow them. -Double click toggle was broken on integer spin boxes. -Fixed tab order of some controls. -Creating a palette from a jpg in the palette editor only produced a single color. --Needed to package imageformats/qjpeg.dll with the Windows installer. -The basic memory benchmark test flame was not really testing memory. Make it more spread out. -Remove the temporal samples field from the flame tab, it was never used because it's only an animation parameter which is specified in the final render dialog or on the command line with EmberAnimate. --Code changes: -Add IsEmpty() to Palette to determine if a palette is all black. -Attempt to avoid selecting a blank palette in PaletteList::GetRandomPalette(). -Add function ScanForChaosNodes() and some associated helper functions in XmlToEmber. -Make variation param name correction be case insensitive in XmlToEmber. -Report error when assigning a variation param value in XmlToEmber. -Add SubBatchPercentPerThread() method to RendererCL. -Override enterEvent() and leaveEvent() in DoubleSpinBox and SpinBox to prevent the context menu from showing up on right mouse up after already leaving the spinner. -Filtering the mouse wheel event in TableWidget no longer appears to be needed. It was probably an old Qt bug that has been fixed. -Gui/ember syncing code in the final render dialog needed to be reworked to accommodate absolute sizes.
1162 lines
35 KiB
C++
1162 lines
35 KiB
C++
#pragma once
|
|
|
|
#include "Isaac.h"
|
|
|
|
/// <summary>
|
|
/// Global utility classes and functions that don't really fit anywhere else, but are
|
|
/// too small to justify being in their own file.
|
|
/// </summary>
|
|
namespace EmberNs
|
|
{
|
|
#ifndef _WIN32
|
|
#define THREAD_PRIORITY_LOWEST 1
|
|
#define THREAD_PRIORITY_BELOW_NORMAL 25
|
|
#define THREAD_PRIORITY_NORMAL 50
|
|
#define THREAD_PRIORITY_ABOVE_NORMAL 75
|
|
#define THREAD_PRIORITY_HIGHEST 99
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Enum to encapsulate and add type safety to the thread priority defines.
|
|
/// </summary>
|
|
enum class eThreadPriority : int
|
|
{
|
|
LOWEST = THREAD_PRIORITY_LOWEST,//-2
|
|
BELOW_NORMAL = THREAD_PRIORITY_BELOW_NORMAL,//-1
|
|
NORMAL = THREAD_PRIORITY_NORMAL,//0
|
|
ABOVE_NORMAL = THREAD_PRIORITY_ABOVE_NORMAL,//1
|
|
HIGHEST = THREAD_PRIORITY_HIGHEST//2
|
|
};
|
|
|
|
/// <summary>
|
|
/// Thin wrapper around std::find_if() to relieve the caller of having to
|
|
/// pass the implicitly obvious .begin() and .end(), and then compare the results to .end().
|
|
/// </summary>
|
|
/// <param name="container">The container to call find_if() on</param>
|
|
/// <param name="pred">The lambda to call on each element</param>
|
|
/// <returns>True if pred returned true once, else false.</returns>
|
|
template<class c, class pr>
|
|
static inline bool FindIf(c& container, pr pred)
|
|
{
|
|
return std::find_if(container.begin(), container.end(), pred) != container.end();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Thin wrapper around std::find_if() determine if a value exists at least once.
|
|
/// </summary>
|
|
/// <param name="container">The container to call find_if() on</param>
|
|
/// <param name="val">The value to search for</param>
|
|
/// <returns>True if the value was contained at least once, else false.</returns>
|
|
template<class c, class T>
|
|
static inline bool Contains(c& container, const T& val)
|
|
{
|
|
return std::find_if(container.begin(), container.end(), [&](const T & t) -> bool { return t == val; }) != container.end();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Thin specialization for string because the compiler can't figure out the container template type
|
|
/// when passing string to the function above.
|
|
/// </summary>
|
|
/// <param name="str1">The string to call find() on</param>
|
|
/// <param name="str2">The string to search for</param>
|
|
/// <returns>True if str2 was present at least once, else false.</returns>
|
|
static bool Find(const string& str1, const string& str2)
|
|
{
|
|
return str1.find(str2) != string::npos;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Thin wrapper around computing the total size of a vector.
|
|
/// </summary>
|
|
/// <param name="vec">The vector to compute the size of</param>
|
|
/// <returns>The size of one element times the length.</returns>
|
|
template<typename T>
|
|
static inline size_t SizeOf(const vector<T>& vec)
|
|
{
|
|
return sizeof(vec[0]) * vec.size();
|
|
}
|
|
|
|
/// <summary>
|
|
/// After a run completes, information about what was run can be saved as strings to the comments
|
|
/// section of a jpg or png file. This class is just a container for those values.
|
|
/// </summary>
|
|
class EMBER_API EmberImageComments
|
|
{
|
|
public:
|
|
/// <summary>
|
|
/// Basic defaults.
|
|
/// </summary>
|
|
EmberImageComments() = default;
|
|
EmberImageComments(const EmberImageComments& comments) = default;
|
|
EmberImageComments& operator = (const EmberImageComments& comments) = default;
|
|
|
|
/// <summary>
|
|
/// Needed to eliminate warnings about inlining.
|
|
/// </summary>
|
|
~EmberImageComments() = default;
|
|
|
|
/// <summary>
|
|
/// Set all values to the empty string.
|
|
/// </summary>
|
|
void Clear()
|
|
{
|
|
m_Genome = "";
|
|
m_Badvals = "";
|
|
m_NumIters = "";
|
|
m_Runtime = "";
|
|
}
|
|
|
|
string m_Genome;
|
|
string m_Badvals;
|
|
string m_NumIters;
|
|
string m_Runtime;
|
|
};
|
|
|
|
/// <summary>
|
|
/// Since running is an incredibly complex process with multiple points of possible failure,
|
|
/// it's important that as much information as possible is captured if something goes wrong.
|
|
/// Classes wishing to capture this failure information will derive from this class and populate
|
|
/// the vector of strings with any useful error information. Note that a small complication can occur
|
|
/// when a class derives from this class, yet also has one or more members which do too. In that case, they should
|
|
/// override the methods to aggregate the error information from themselves, as well as their members.
|
|
/// </summary>
|
|
class EMBER_API EmberReport
|
|
{
|
|
public:
|
|
/// <summary>
|
|
/// Virtual destructor needed for virtual classes.
|
|
/// </summary>
|
|
virtual ~EmberReport() { }
|
|
|
|
/// <summary>
|
|
/// Write the entire error report as a single string to the console.
|
|
/// Derived classes with members that also derive from EmberReport should override this to capture
|
|
/// their error information as well as that of their members.
|
|
/// </summary>
|
|
virtual void DumpErrorReport() { cout << ErrorReportString(); }
|
|
|
|
/// <summary>
|
|
/// Clear the error report string vector.
|
|
/// Derived classes with members that also derive from EmberReport should override this to clear
|
|
/// their error information as well as that of their members.
|
|
/// </summary>
|
|
virtual void ClearErrorReport() { m_ErrorReport.clear(); }
|
|
|
|
/// <summary>
|
|
/// Return the entire error report as a single string.
|
|
/// Derived classes with members that also derive from EmberReport should override this to capture
|
|
/// their error information as well as that of their members.
|
|
/// </summary>
|
|
/// <returns>The entire error report as a single string. Empty if no errors.</returns>
|
|
virtual string ErrorReportString() { return StaticErrorReportString(m_ErrorReport); }
|
|
|
|
/// <summary>
|
|
/// Return the entire error report as a vector of strings.
|
|
/// Derived classes with members that also derive from EmberReport should override this to capture
|
|
/// their error information as well as that of their members.
|
|
/// </summary>
|
|
/// <returns>The entire error report as a vector of strings. Empty if no errors.</returns>
|
|
virtual vector<string> ErrorReport() { return m_ErrorReport; }
|
|
|
|
/// <summary>
|
|
/// Add string to report.
|
|
/// </summary>
|
|
/// <param name="s">The string to add</param>
|
|
virtual void AddToReport(const string& s) { if (!Contains(m_ErrorReport, s)) m_ErrorReport.push_back(s); }
|
|
|
|
/// <summary>
|
|
/// Add a vector of strings to report.
|
|
/// </summary>
|
|
/// <param name="vec">The vector of strings to add</param>
|
|
virtual void AddToReport(const vector<string>& vec) { for (auto& v : vec) AddToReport(v); }
|
|
|
|
/// <summary>
|
|
/// Static function to dump a vector of strings passed in.
|
|
/// </summary>
|
|
/// <param name="errorReport">The vector of strings to dump</param>
|
|
static void StaticDumpErrorReport(const vector<string>& errorReport) { cout << StaticErrorReportString(errorReport); }
|
|
|
|
/// <summary>
|
|
/// Static function to return the entire error report passed in as a single string.
|
|
/// </summary>
|
|
/// <param name="errorReport">The vector of strings to concatenate</param>
|
|
/// <returns>A string containing all strings in the vector passed in separated by newlines</returns>
|
|
static string StaticErrorReportString(const vector<string>& errorReport)
|
|
{
|
|
stringstream ss;
|
|
|
|
for (auto& s : errorReport) ss << s << "\n";
|
|
|
|
return ss.str();
|
|
}
|
|
|
|
private:
|
|
vector<string> m_ErrorReport;
|
|
};
|
|
|
|
/// <summary>
|
|
/// A base class for handling singletons that ensures only one instance exists, but
|
|
/// also deletes the instance after there are no more references to it.
|
|
/// This fixes the problem of the normal singleton pattern that uses a static function
|
|
/// variable. That pattern does not delete the instance until after main() exits
|
|
/// which can cause serious problems with certain libraries.
|
|
/// This class will delete before main exits.
|
|
/// Note that it still uses a local static variable because static templated
|
|
/// member variables cannot be exported across module boundaries.
|
|
/// Derived classes should inherit from this using the CRTP, and declare a friend to it.
|
|
/// They also should make their constructors private and destructors public.
|
|
/// Attribution: This class is a combination of
|
|
/// http://btorpey.github.io/blog/2014/02/12/shared-singletons/
|
|
/// and
|
|
/// http://enki-tech.blogspot.com/2012/08/c11-generic-singleton.html
|
|
/// </summary>
|
|
template <class T>
|
|
class Singleton
|
|
{
|
|
public:
|
|
/// <summary>
|
|
/// Create and return an instance of T.
|
|
/// </summary>
|
|
/// <param name="...args">The args to forward to the constructor of T</param>
|
|
/// <returns>A shared_ptr<T></returns>
|
|
template <typename... Args>
|
|
static shared_ptr<T> Instance(Args... args)
|
|
{
|
|
auto& staticInstance = GetStaticInstance();
|
|
auto temp = staticInstance.lock();
|
|
|
|
if (!temp)
|
|
{
|
|
temp.reset(new T(std::forward<Args>(args)...));
|
|
staticInstance = temp;
|
|
}
|
|
|
|
return temp;
|
|
}
|
|
|
|
/// <summary>
|
|
/// For creating an object without passing parameters.
|
|
/// When the derived class has a default constructor, this should
|
|
/// not be called. This is only for when the derived class constructor
|
|
/// requires arguments. In that case, Instance() must first be called
|
|
/// with the proper values. Then once the singleton is constructed, this
|
|
/// can be called to just retrieve the object without having to worry about
|
|
/// parameters.
|
|
/// This is enforced by throwing if this has been called before Instance() is called.
|
|
/// </summary>
|
|
/// <returns>The constructed object</returns>
|
|
static std::shared_ptr<T> DefInstance()
|
|
{
|
|
auto& staticInstance = GetStaticInstance();
|
|
auto temp = staticInstance.lock();
|
|
|
|
if (!temp)
|
|
throw "Cannot create singleton with defaults, must first call at least once with proper arguments.";
|
|
|
|
return temp;
|
|
}
|
|
|
|
protected:
|
|
/// <summary>
|
|
/// Clever hack to get a static to behave like a member variable that can be seen between classes and functions in the hierarchy.
|
|
/// Also has the added benefit that it makes this work across module boundaries.
|
|
/// </summary>
|
|
/// <returns>static weak_ptr reference</returns>
|
|
static std::weak_ptr<T>& GetStaticInstance()
|
|
{
|
|
static std::weak_ptr<T> staticInstance;
|
|
return staticInstance;
|
|
}
|
|
};
|
|
|
|
//Use this if the body of the destructor will be implemented in a cpp file.
|
|
#define SINGLETON_DERIVED_DECL(x) \
|
|
friend class Singleton<x>; \
|
|
public: \
|
|
~x(); \
|
|
x(const x& other) = delete; \
|
|
const x& operator=(const x& other) = delete//Semicolon deliberately omitted to force it on the caller.
|
|
|
|
//Use this if the body of the destructor is empty and is will be implemented inline in the header file.
|
|
#define SINGLETON_DERIVED_IMPL(x) \
|
|
friend class Singleton<x>; \
|
|
public: \
|
|
~x(){} \
|
|
x(const x& other) = delete; \
|
|
const x& operator=(const x& other) = delete
|
|
|
|
/// <summary>
|
|
/// Open a file in binary mode and read its entire contents into a vector of bytes. Optionally null terminate.
|
|
/// </summary>
|
|
/// <param name="filename">The full path to the file to read</param>
|
|
/// <param name="buf">The string which will be populated with the file's contents</param>
|
|
/// <param name="nullTerminate">Whether to append a NULL character as the last element of the vector. Needed when reading text files. Default: true.</param>
|
|
/// <returns>True if successfully read and populated, else false</returns>
|
|
static bool ReadFile(const char* filename, string& buf, bool nullTerminate = true)
|
|
{
|
|
try
|
|
{
|
|
ifstream ifs;
|
|
ifs.exceptions(ifstream::failbit);
|
|
ifs.open(filename, ios::binary | ios::ate);
|
|
|
|
if (auto pos = ifs.tellg())//Ensure it exists and wasn't empty.
|
|
{
|
|
buf.resize(pos + streampos(nullTerminate ? 1 : 0));
|
|
ifs.seekg(0, ios::beg);
|
|
ifs.read(&buf[0], pos);
|
|
|
|
if (nullTerminate)//Optionally NULL terminate if they want to treat it as a string.
|
|
buf[buf.size() - 1] = 0;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
cout << "Error: Reading file " << filename << " failed: " << e.what() << "\n";
|
|
}
|
|
catch (...)
|
|
{
|
|
cout << "Error: Reading file " << filename << " failed.\n";
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Thin wrapper around std::advance that returns the advanced iterator.
|
|
/// Note the passed in iterator is constant so it will not be changed, unlike
|
|
/// std::advance does.
|
|
/// </summary>
|
|
/// <param name="it">A const reference to an iterator, a copy of which will be advanced.</param>
|
|
/// <param name="off">How far to move the iterator forward</param>
|
|
/// <returns>A copy of the passed in iterator, advanced the specified number of elements</returns>
|
|
template<class iter, class diff>
|
|
static iter Advance(const iter& it, diff off)
|
|
{
|
|
auto temp = it;
|
|
std::advance(temp, off);
|
|
return temp;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clear dest and copy all of the elements of container source with elements of type U to the container
|
|
/// dest with elements of type T.
|
|
/// </summary>
|
|
/// <param name="dest">The container of type Cdest with elements of type T to copy to</param>
|
|
/// <param name="source">The container of type Csource with elements of type U to copy from</param>
|
|
template <typename T, typename U, typename Dalloc, typename Salloc, template <typename, typename> class Cdest, template <typename, typename> class Csource>
|
|
static void CopyCont(Cdest<T, Dalloc>& dest, const Csource<U, Salloc>& source)
|
|
{
|
|
dest.clear();
|
|
dest.resize(source.size());
|
|
auto it1 = source.begin();
|
|
auto it2 = dest.begin();
|
|
|
|
for (; it1 != source.end(); it1++, it2++)
|
|
*it2 = static_cast<T>(*it1);//Valid assignment operator between T and U types must be defined somewhere.
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clear dest and copy all of the elements of container source with elements of type U to the container
|
|
/// dest with elements of type T.
|
|
/// Call a function on each element after it's been copied.
|
|
/// </summary>
|
|
/// <param name="dest">The container of type Cdest with elements of type T to copy to</param>
|
|
/// <param name="source">The container of type Csource with elements of type U to copy from</param>
|
|
/// <param name="perElementOperation">A function to call on each element after it's copied</param>
|
|
template <typename T, typename U, typename Dalloc, typename Salloc, template <typename, typename> class Cdest, template <typename, typename> class Csource>
|
|
static void CopyCont(Cdest<T, Dalloc>& dest, const Csource<U, Salloc>& source, std::function<void(T& t)> perElementOperation)
|
|
{
|
|
dest.clear();
|
|
dest.resize(source.size());
|
|
auto it1 = source.begin();
|
|
auto it2 = dest.begin();
|
|
|
|
for (; it1 != source.end(); it1++, it2++)
|
|
{
|
|
*it2 = static_cast<T>(*it1);//Valid assignment operator between T and U types must be defined somewhere.
|
|
perElementOperation(*it2);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clear a container of pointers to any type by checking each element for nullptr and calling delete on it, then clearing the entire vector.
|
|
/// Optionally call array delete if the elements themselves are pointers to dynamically allocated arrays.
|
|
/// </summary>
|
|
/// <param name="vec">The vector to be cleared</param>
|
|
/// <param name="arrayDelete">Whether to call delete or delete []. Default: false.</param>
|
|
template <typename T, typename Alloc, template <typename, typename> class C>
|
|
static void ClearVec(C<T*, Alloc>& cont, bool arrayDelete = false)
|
|
{
|
|
for (auto& it : cont)
|
|
{
|
|
if (it)
|
|
{
|
|
if (arrayDelete)
|
|
delete [] it;
|
|
else
|
|
delete it;
|
|
}
|
|
|
|
it = nullptr;
|
|
}
|
|
|
|
cont.clear();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determine whether all elements in two containers are equal.
|
|
/// The container types do not have to match, but their element types do.
|
|
/// </summary>
|
|
/// <param name="c1">The first collection to compare</param>
|
|
/// <param name="c2">The second collection to compare</param>
|
|
/// <returns>True if the sizes and all elements in both collections are equal, else false.</returns>
|
|
template <typename T, typename C1Alloc, typename C2Alloc, template <typename, typename> class C1, template <typename, typename> class C2>
|
|
static bool Equal(const C1<T, C1Alloc>& c1, const C2<T, C2Alloc>& c2)
|
|
{
|
|
bool equal = c1.size() == c2.size();
|
|
|
|
if (equal)
|
|
{
|
|
auto it1 = c1.begin();
|
|
auto it2 = c2.begin();
|
|
|
|
for (; it1 != c1.end(); ++it1, ++it2)
|
|
{
|
|
if (*it1 != *it2)
|
|
{
|
|
equal = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return equal;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Thin wrapper around passing a vector to memset() to relieve
|
|
/// the caller of having to pass the size.
|
|
/// </summary>
|
|
/// <param name="vec">The vector to memset</param>
|
|
/// <param name="val">The value to set each element to, default 0.</param>
|
|
template<typename T>
|
|
static inline void Memset(vector<T>& vec, int val = 0)
|
|
{
|
|
memset(static_cast<void*>(vec.data()), val, SizeOf(vec));
|
|
}
|
|
|
|
/// <summary>
|
|
/// System floor() extremely slow because it accounts for various error conditions.
|
|
/// This is a much faster version that works on data that is not NaN.
|
|
/// </summary>
|
|
/// <param name="x">The value to return the floor of</param>
|
|
/// <returns>The floored value</returns>
|
|
template <typename T>
|
|
static inline intmax_t Floor(T val)
|
|
{
|
|
if (val >= 0)
|
|
{
|
|
return static_cast<intmax_t>(val);
|
|
}
|
|
else
|
|
{
|
|
intmax_t i = static_cast<intmax_t>(val);//Truncate.
|
|
return i - (i > val);//Convert trunc to floor.
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clamp and return a value to be greater than or equal to a specified minimum and less than
|
|
/// or equal to a specified maximum.
|
|
/// </summary>
|
|
/// <param name="val">The value to be clamped</param>
|
|
/// <param name="min">A value which the clamped value must be greater than or equal to</param>
|
|
/// <param name="max">A value which the clamped value must be less than or equal to</param>
|
|
/// <returns>The clamped value</returns>
|
|
template <typename T>
|
|
static inline T Clamp(T val, T min, T max)
|
|
{
|
|
if (val < min)
|
|
return min;
|
|
else if (val > max)
|
|
return max;
|
|
else
|
|
return val;
|
|
}
|
|
|
|
template <>
|
|
STATIC float Clamp<float>(float val, float min, float max)
|
|
{
|
|
if (val < min)
|
|
return min;
|
|
else if (val > max)
|
|
return max;
|
|
else if (!std::isfinite(val))
|
|
return min;
|
|
else
|
|
return val;
|
|
}
|
|
|
|
template <>
|
|
STATIC double Clamp<double>(double val, double min, double max)
|
|
{
|
|
if (val < min)
|
|
return min;
|
|
else if (val > max)
|
|
return max;
|
|
else if (!std::isfinite(val))
|
|
return min;
|
|
else
|
|
return val;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clamp and return a value to be greater than or equal to a specified minimum and less than
|
|
/// or equal to a specified maximum. If lesser, the value is fmod(val - min, max - min). If greater,
|
|
/// the value is max - fmod(max - val, max - min).
|
|
/// </summary>
|
|
/// <param name="val">The value to be clamped</param>
|
|
/// <param name="min">A value which the clamped value must be greater than or equal to</param>
|
|
/// <param name="max">A value which the clamped value must be less than or equal to</param>
|
|
/// <returns>The clamped and modded value</returns>
|
|
template <typename T>
|
|
static inline T ClampMod(T val, T min, T max)
|
|
{
|
|
if (val < min)
|
|
return min + fmod(val - min, max - min);
|
|
else if (val > max)
|
|
return max - fmod(max - val, max - min);
|
|
else if (!std::isfinite(val))
|
|
return min;
|
|
else
|
|
return val;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Similar to Clamp(), but clamps a reference value in place rather than returning.
|
|
/// </summary>
|
|
/// <param name="val">The reference value to be clamped in place</param>
|
|
/// <param name="min">A value which the clamped value must be greater than or equal to</param>
|
|
/// <param name="max">A value which the clamped value must be less than or equal to</param>
|
|
template <typename T>
|
|
static inline void ClampRef(T& val, T min, T max)
|
|
{
|
|
if (val < min)
|
|
val = min;
|
|
else if (val > max)
|
|
val = max;
|
|
}
|
|
|
|
template <>
|
|
STATIC void ClampRef<float>(float& val, float min, float max)
|
|
{
|
|
if (val < min)
|
|
val = min;
|
|
else if (val > max)
|
|
val = max;
|
|
else if (!std::isfinite(val))
|
|
val = min;
|
|
}
|
|
|
|
template <>
|
|
STATIC void ClampRef<double>(double& val, double min, double max)
|
|
{
|
|
if (val < min)
|
|
val = min;
|
|
else if (val > max)
|
|
val = max;
|
|
else if (!std::isfinite(val))
|
|
val = min;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Similar to Clamp(), but clamps a reference value in place rather than returning.
|
|
/// </summary>
|
|
/// <param name="val">The reference value to be clamped in place</param>
|
|
/// <param name="gte">A value which the clamped value must be less than or equal to</param>
|
|
template <typename T>
|
|
static inline void ClampLteRef(T& val, T lte)
|
|
{
|
|
if (val > lte)
|
|
val = lte;
|
|
}
|
|
|
|
template <>
|
|
STATIC void ClampLteRef<float>(float& val, float lte)
|
|
{
|
|
if (val > lte || !std::isfinite(val))
|
|
val = lte;
|
|
}
|
|
|
|
template <>
|
|
STATIC void ClampLteRef<double>(double& val, double lte)
|
|
{
|
|
if (val > lte || !std::isfinite(val))
|
|
val = lte;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clamp and return a value to be greater than or equal to a specified value.
|
|
/// Useful for ensuring something is not less than zero.
|
|
/// </summary>
|
|
/// <param name="val">The value to be clamped</param>
|
|
/// <param name="gte">A value which the clamped value must be greater than or equal to</param>
|
|
/// <returns>The clamped value</returns>
|
|
template <typename T>
|
|
static inline T ClampGte(T val, T gte)
|
|
{
|
|
return (val < gte) ? gte : val;
|
|
}
|
|
|
|
template <>
|
|
STATIC float ClampGte<float>(float val, float gte)
|
|
{
|
|
if (val < gte || !std::isfinite(val))
|
|
return gte;
|
|
else
|
|
return val;
|
|
}
|
|
|
|
template <>
|
|
STATIC double ClampGte<double>(double val, double gte)
|
|
{
|
|
if (val < gte || !std::isfinite(val))
|
|
return gte;
|
|
else
|
|
return val;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Similar to Clamp(), but clamps a reference value in place rather than returning.
|
|
/// </summary>
|
|
/// <param name="val">The reference value to be clamped in place</param>
|
|
/// <param name="gte">A value which the clamped value must be greater than or equal to</param>
|
|
template <typename T>
|
|
static inline void ClampGteRef(T& val, T gte)
|
|
{
|
|
if (val < gte)
|
|
val = gte;
|
|
}
|
|
|
|
template <>
|
|
STATIC void ClampGteRef<float>(float& val, float gte)
|
|
{
|
|
if (val < gte || !std::isfinite(val))
|
|
val = gte;
|
|
}
|
|
|
|
template <>
|
|
STATIC void ClampGteRef<double>(double& val, double gte)
|
|
{
|
|
if (val < gte || !std::isfinite(val))
|
|
val = gte;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Thin wrapper around a call to ClampGte() with a gte value of zero.
|
|
/// </summary>
|
|
/// <param name="val">The value to be clamped</param>
|
|
/// <returns>The clamped value</returns>
|
|
template <typename T>
|
|
static inline T ClampGte0(T val)
|
|
{
|
|
return ClampGte<T>(val, 0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Thin wrapper around a call to ClampGteRef() with a gte value of zero.
|
|
/// </summary>
|
|
/// <param name="val">The reference value to be clamped in place</param>
|
|
template <typename T>
|
|
static inline void ClampGte0Ref(T& val)
|
|
{
|
|
ClampGteRef<T>(val, 0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return a value rounded up or down. Works for positive and negative numbers.
|
|
/// </summary>
|
|
/// <param name="r">The value to round</param>
|
|
/// <returns>The rounded value</returns>
|
|
template <typename T>
|
|
static inline T Round(T r)
|
|
{
|
|
return (r > 0) ? static_cast<T>(Floor<T>(r + T(0.5))) : std::ceil(r - T(0.5));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Never really understood what this did.
|
|
/// </summary>
|
|
/// <param name="r">The value to round</param>
|
|
/// <returns>The rounded value</returns>
|
|
template <typename T>
|
|
static inline T Round6(T r)
|
|
{
|
|
r *= 1e6;
|
|
|
|
if (r < 0)
|
|
r -= 1;
|
|
|
|
return static_cast<T>(1e-6 * static_cast<int>(r + T(0.5)));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return the square of the passed in value.
|
|
/// This is useful when the value is a result of a computation
|
|
/// rather than a fixed number. Otherwise, use the SQR macro.
|
|
/// </summary>
|
|
/// <param name="v">The value to square</param>
|
|
/// <returns>The squared value</returns>
|
|
template <typename T>
|
|
static inline T Sqr(T t)
|
|
{
|
|
return t * t;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return the cube of the passed in value.
|
|
/// This is useful when the value is a result of a computation
|
|
/// rather than a fixed number. Otherwise, use the CUBE macro.
|
|
/// </summary>
|
|
/// <param name="v">The value to cube</param>
|
|
/// <returns>The cubed value</returns>
|
|
template <typename T>
|
|
static inline T Cube(T t)
|
|
{
|
|
return t * t * t;
|
|
}
|
|
|
|
template <typename T>
|
|
static inline T SafeTan(T x)
|
|
{
|
|
return x;
|
|
}
|
|
|
|
template <>
|
|
STATIC float SafeTan<float>(float x)
|
|
{
|
|
return std::tan(Clamp<float>(x, FLOAT_MIN_TAN, FLOAT_MAX_TAN));
|
|
}
|
|
|
|
template <>
|
|
STATIC double SafeTan<double>(double x)
|
|
{
|
|
return std::tan(x);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return EPS if the passed in value was zero, else return the value.
|
|
/// </summary>
|
|
/// <param name="x">The value</param>
|
|
/// <param name="y">The y distance</param>
|
|
/// <returns>EPS or the value if it was non-zero</returns>
|
|
template <typename T>
|
|
static inline T Zeps(T x)
|
|
{
|
|
return x == 0 ? EPS : x;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Interpolate a given percentage between two values.
|
|
/// </summary>
|
|
/// <param name="a">The first value to interpolate between.</param>
|
|
/// <param name="b">The secod value to interpolate between.</param>
|
|
/// <param name="p">The percentage between the two values to calculate.</param>
|
|
/// <returns>The interpolated value.</returns>
|
|
template <typename T>
|
|
static inline T Lerp(T a, T b, T p)
|
|
{
|
|
return a + (b - a) * p;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Thin wrapper around calling xmlStrcmp() on an Xml tag to tell
|
|
/// if its name is a given value.
|
|
/// </summary>
|
|
/// <param name="name">The name of the tag of the to inspect</param>
|
|
/// <param name="val">The value compare against</param>
|
|
/// <returns>True if the comparison matched, else false</returns>
|
|
static inline bool Compare(const xmlChar* name, const char* val)
|
|
{
|
|
return xmlStrcmp(name, XC(val)) != 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determine whether the specified value is very close to zero.
|
|
/// This is useful for determining equality of float/double types.
|
|
/// </summary>
|
|
/// <param name="val">The value to compare against</param>
|
|
/// <param name="tolerance">The tolerance. Default: 1e-6.</param>
|
|
/// <returns>True if the value was very close to zero, else false</returns>
|
|
template <typename T>
|
|
static inline bool IsNearZero(T val, T tolerance = 1e-6)
|
|
{
|
|
return (val > -tolerance && val < tolerance);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determine whether a specified value is very close to another value.
|
|
/// This is useful for determining equality of float/double types.
|
|
/// </summary>
|
|
/// <param name="val1">The first value.</param>
|
|
/// <param name="val2">The second value.</param>
|
|
/// <param name="tolerance">The tolerance. Default: 1e-6.</param>
|
|
/// <returns>True if the values were very close to each other, else false</returns>
|
|
template <typename T>
|
|
static inline bool IsClose(T val1, T val2, T tolerance = 1e-6)
|
|
{
|
|
return IsNearZero(val1 - val2, tolerance);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Put an angular measurement in degrees into the range of -180 - 180.
|
|
/// </summary>
|
|
/// <param name="angle">The angle to normalize</param>
|
|
/// <returns>The normalized angle in a range of -180 - 180</returns>
|
|
template <typename T>
|
|
static inline T NormalizeDeg180(T angle)
|
|
{
|
|
auto a = fmod(angle, T(360));
|
|
|
|
if (a > 180)
|
|
a -= 360;
|
|
else if (a < -180)
|
|
a += 360;
|
|
|
|
return a;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Put an angular measurement in degrees into the range of 0 - 360.
|
|
/// </summary>
|
|
/// <param name="angle">The angle to normalize</param>
|
|
/// <returns>The normalized angle in a range of 0 - 360</returns>
|
|
template <typename T>
|
|
static inline T NormalizeDeg360(T angle)
|
|
{
|
|
if (angle > 360 || angle < -360)
|
|
angle = fmod(angle, T(360));
|
|
|
|
if (angle < 0)
|
|
angle += 360;
|
|
|
|
return angle;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert an angle where 0 is up to a trigonometry style angle where 0 is to the right.
|
|
/// </summary>
|
|
/// <param name="Angle">The angle to convert</param>
|
|
/// <returns>The trig equivalent of the angle passed in</returns>
|
|
template <typename T>
|
|
static inline T ToTrigAngle(T angle)
|
|
{
|
|
return NormalizeDeg360(90 - angle);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determine whether the passed in string ends with the passed in suffix, case sensitive.
|
|
/// </summary>
|
|
/// <param name="str">The string to test</param>
|
|
/// <param name="suffix">The string to test for</param>
|
|
/// <returns>True if str ends with suffix, else false.</returns>
|
|
static bool EndsWith(const std::string& str, const std::string& suffix)
|
|
{
|
|
return str.size() >= suffix.size() &&
|
|
str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determine whether the passed in string starts with the passed in prefix, case sensitive.
|
|
/// </summary>
|
|
/// <param name="str">The string to test</param>
|
|
/// <param name="suffix">The string to test for</param>
|
|
/// <returns>True if str starts with suffix, else false.</returns>
|
|
static bool StartsWith(const std::string& str, const std::string& prefix)
|
|
{
|
|
return str.size() >= prefix.size() &&
|
|
str.compare(0, prefix.size(), prefix) == 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return a lower case copy of a string.
|
|
/// </summary>
|
|
/// <param name="str">The string to copy and make lower case</param>
|
|
/// <returns>The lower case string</returns>
|
|
static string ToLower(const string& str)
|
|
{
|
|
string lower;
|
|
lower.resize(str.size());//Allocate the destination space.
|
|
std::transform(str.begin(), str.end(), lower.begin(), ::tolower);//Convert the source string to lower case storing the result in the destination string.
|
|
return lower;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return an upper case copy of a string.
|
|
/// </summary>
|
|
/// <param name="str">The string to copy and make upper case</param>
|
|
/// <returns>The upper case string</returns>
|
|
static string ToUpper(const string& str)
|
|
{
|
|
string upper;
|
|
upper.resize(str.size());//Allocate the destination space.
|
|
std::transform(str.begin(), str.end(), upper.begin(), ::toupper);//Convert the source string to lower case storing the result in the destination string.
|
|
return upper;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return a copy of a string with leading and trailing occurrences of a specified character removed.
|
|
/// The default character is a space.
|
|
/// </summary>
|
|
/// <param name="str">The string to trim</param>
|
|
/// <param name="ch">The character to trim. Default: space.</param>
|
|
/// <returns>The trimmed string</returns>
|
|
static string Trim(const string& str, char ch = ' ')
|
|
{
|
|
string ret;
|
|
|
|
if (str != "")
|
|
{
|
|
size_t firstChar = str.find_first_not_of(ch);
|
|
size_t lastChar = str.find_last_not_of(ch);
|
|
|
|
if (firstChar == string::npos)
|
|
firstChar = 0;
|
|
|
|
if (lastChar == string::npos)
|
|
lastChar = str.size();
|
|
|
|
ret = str.substr(firstChar, lastChar - firstChar + 1);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Split a string into tokens and place them in a vector.
|
|
/// </summary>
|
|
/// <param name="str">The string to split</param>
|
|
/// <param name="del">The delimiter to split the string on. Note that only one character has to match, so this is a good way to use multiple delimiters</param>
|
|
/// <param name="removeEmpty">True to omit empty strings, else false to keep them.</param>
|
|
/// <returns>The vector containing the split tokens</returns>
|
|
static vector<std::string> Split(const string& str, const string& del, bool removeEmpty = false)
|
|
{
|
|
int current = 0;
|
|
int next = -1;
|
|
vector<string> vec;
|
|
|
|
do
|
|
{
|
|
current = next + 1;
|
|
next = int(str.find_first_of(del, current));
|
|
string ent(Trim(str.substr(current, next - current)));
|
|
|
|
if (!removeEmpty || ent.length() > 0)
|
|
vec.push_back(ent);
|
|
}
|
|
while (next != int(string::npos));
|
|
|
|
return vec;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return a copy of a file path string with the file portion removed.
|
|
/// If no path is present, such as having only a filename present, then
|
|
/// empty string is returned.
|
|
/// </summary>
|
|
/// <param name="filename">The string to retrieve the path from</param>
|
|
/// <returns>The path portion of the string</returns>
|
|
static string GetPath(const string& filename)
|
|
{
|
|
const size_t lastSlash = filename.find_last_of("\\/");
|
|
|
|
if (lastSlash != std::string::npos)
|
|
return filename.substr(0, lastSlash + 1);
|
|
else
|
|
return "";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return a copy of a file path string with the path portion removed.
|
|
/// </summary>
|
|
/// <param name="filename">The string to retrieve the filename from</param>
|
|
/// <returns>The filename portion of the string</returns>
|
|
static string GetFilename(const string& filename)
|
|
{
|
|
string s;
|
|
const size_t lastSlash = filename.find_last_of("\\/");
|
|
|
|
if (std::string::npos != lastSlash)
|
|
return filename.substr(lastSlash + 1, filename.size() - lastSlash);
|
|
else
|
|
return filename;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Placeholder for a templated function to query the value of a specified system environment variable
|
|
/// of a specific type. This function does nothing as the functions for specific types implement the behavior
|
|
/// via template specialization.
|
|
/// </summary>
|
|
/// <param name="name">The name of the environment variable to query</param>
|
|
/// <param name="def">The default value to return if the environment variable was not present</param>
|
|
/// <returns>The value of the specified environment variable if found, else default</returns>
|
|
template <typename T>
|
|
static inline T Arg(char* name, T def)
|
|
{
|
|
char* ch;
|
|
T returnVal;
|
|
#ifdef _WIN32
|
|
size_t len;
|
|
errno_t err = _dupenv_s(&ch, &len, name);
|
|
#else
|
|
int err = 1;
|
|
ch = getenv(name);
|
|
#endif
|
|
|
|
if (err || !ch)
|
|
returnVal = def;
|
|
else
|
|
{
|
|
T tempVal;
|
|
istringstream istr(ch);
|
|
istr >> tempVal;
|
|
|
|
if (!istr.bad() && !istr.fail())
|
|
returnVal = tempVal;
|
|
else
|
|
returnVal = def;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
free(ch);
|
|
#endif
|
|
return returnVal;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Template specialization for Arg<>() with a type of bool.
|
|
/// </summary>
|
|
/// <param name="name">The name of the environment variable to query</param>
|
|
/// <param name="def">The default value to return if the environment variable was not present</param>
|
|
/// <returns>The value of the specified environment variable if found, else default</returns>
|
|
template <>
|
|
STATIC bool Arg<bool>(char* name, bool def)
|
|
{
|
|
return (Arg<int>(name, -999) != -999) ? true : def;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Template specialization for Arg<>() with a type of string.
|
|
/// </summary>
|
|
/// <param name="name">The name of the environment variable to query</param>
|
|
/// <param name="def">The default value to return if the environment variable was not present</param>
|
|
/// <returns>The value of the specified environment variable if found, else default</returns>
|
|
template <>
|
|
STATIC string Arg<string>(char* name, string def)
|
|
{
|
|
char* ch;
|
|
string returnVal;
|
|
#ifdef _WIN32
|
|
size_t len;
|
|
errno_t err = _dupenv_s(&ch, &len, name);
|
|
#else
|
|
int err = 1;
|
|
ch = getenv(name);
|
|
#endif
|
|
|
|
if (err || !ch)
|
|
{
|
|
if (def != "")
|
|
returnVal = def;
|
|
}
|
|
else
|
|
returnVal = string(ch);
|
|
|
|
#ifdef _WIN32
|
|
free(ch);
|
|
#endif
|
|
return returnVal;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Replaces all instances of a value within a collection, with the specified value.
|
|
/// Taken from a StackOverflow.com post.
|
|
/// Modified to account for the scenario where the find and replace strings each start with
|
|
/// the same character.
|
|
/// Template argument should be any STL container.
|
|
/// </summary>
|
|
/// <param name="source">Collection to replace values in</param>
|
|
/// <param name="find">The value to replace</param>
|
|
/// <param name="replace">The value to replace with</param>
|
|
/// <returns>The number of instances replaced</returns>
|
|
template<typename T>
|
|
static uint FindAndReplace(T& source, const T& find, const T& replace)
|
|
{
|
|
uint replaceCount = 0;
|
|
typename T::size_type fLen = find.size();
|
|
typename T::size_type rLen = replace.size();
|
|
|
|
for (typename T::size_type pos = 0; (pos = source.find(find, pos)) != T::npos; pos += rLen)
|
|
{
|
|
typename T::size_type pos2 = source.find(replace, pos);
|
|
|
|
if (pos != pos2)
|
|
{
|
|
replaceCount++;
|
|
source.replace(pos, fLen, replace);
|
|
}
|
|
}
|
|
|
|
return replaceCount;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Split a string into tokens and place them in a vector.
|
|
/// </summary>
|
|
/// <param name="str">The string to split</param>
|
|
/// <param name="del">The delimiter to split the string on</param>
|
|
/// <returns>The split strings, each as an element in a vector.</returns>
|
|
static vector<string> Split(const string& str, char del)
|
|
{
|
|
string tok;
|
|
vector<string> vec;
|
|
stringstream ss(str);
|
|
|
|
while (getline(ss, tok, del))
|
|
vec.push_back(tok);
|
|
|
|
return vec;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Thin wrapper around joining a thread.
|
|
/// </summary>
|
|
/// <param name="th">The thread to join</param>
|
|
static void Join(std::thread& th)
|
|
{
|
|
if (th.joinable())
|
|
th.join();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Thin wrapper around joining a vector of threads.
|
|
/// </summary>
|
|
/// <param name="vec">The vector of threads to join</param>
|
|
static void Join(std::vector<std::thread>& vec)
|
|
{
|
|
for (auto& it : vec)
|
|
Join(it);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return a character pointer to a version string composed of the EMBER_OS and EMBER_VERSION values.
|
|
/// </summary>
|
|
static inline const char* EmberVersion()
|
|
{
|
|
return EMBER_OS "-" EMBER_VERSION;
|
|
}
|
|
}
|