#pragma once #include "globalincs/pstypes.h" #include "parse/parselo.h" #include "math/curve.h" #include "globalincs/type_traits.h" #include #include #include #include namespace util { /** * @defgroup randomUtils Random Utilities * * Utility functions for handling random values */ namespace { /** * @brief Parses a generic list of numbers * @param list The array where the numbers should be stored. * @return The amount of parsed numbers * * @ingroup randomUtils */ template size_t parse_number_list(T (&list)[N]) { ignore_white_space(); if (*Mp != '(') { // Probably one a single value so stuff that and don't parse a list. This makes it easier to specify single // values float val; stuff_float(&val); list[0] = static_cast(val); return 1; } float helpList[N]; auto num = stuff_float_list(helpList, N); for (size_t i = 0; i < num; ++i) { list[i] = static_cast(helpList[i]); } return num; } } // namespace //Sampling a random_device is REALLY expensive. //Instead of sampling one for each seed, create a pseudorandom seeder which is initialized ONCE from a random_device. extern thread_local std::mt19937 seeder; /** * @brief Generic class for generating numbers in a specific range * * This allows to use a generic value, distribution and generator type. It's valid to only use one value for the range * in which case the returned value is constant. * * @ingroup randomUtils */ template class RandomRange { public: typedef Distribution DistributionType; typedef Generator GeneratorType; typedef Value ValueType; private: mutable GeneratorType m_generator; mutable DistributionType m_distribution; bool m_constant; ValueType m_minValue; ValueType m_maxValue; public: template =1 || !std::is_convertible::value) && !std::is_same_v, RandomRange>, int>::type> explicit RandomRange(T&& distributionFirstParameter, Ts&&... distributionParameters) : m_generator(seeder()), m_distribution(distributionFirstParameter, distributionParameters...) { m_minValue = static_cast(m_distribution.min()); m_maxValue = static_cast(m_distribution.max()); m_constant = false; } explicit RandomRange(const ValueType& val) : RandomRange() { m_minValue = val; m_maxValue = val; m_constant = true; } RandomRange() : m_generator(seeder()), m_distribution() { m_minValue = static_cast(0.0); m_maxValue = static_cast(0.0); m_constant = true; } /** * @brief Determines the next random number of this range * @return The random number */ ValueType next() const { if (m_constant) { return m_minValue; } return m_distribution(m_generator); } /** * @brief Gets the minimum value that may be returned by this random range * * @return The minimum value */ ValueType min() const { return m_minValue; } /** * @brief Gets the maximum value that may be returned by this random range * * @return The maximum value */ ValueType max() const { return m_maxValue; } /** * @brief Gets the average expected value returned by this random range * * @return The average expected value */ ValueType avg() const { if (m_constant) return m_minValue; if constexpr (has_member(DistributionType, avg())) { return m_distribution.avg(); } else { return (m_distribution.min() + m_distribution.max()) / 2.0f; } } void seed(typename GeneratorType::result_type new_seed) const { if (m_constant) return; m_generator.seed(new_seed); } }; /** * @brief A random range with a normal distribution * * The range parameters are passed directly to std::normal_distribution * * @ingroup randomUtils */ template using NormalRange = RandomRange, std::minstd_rand>; /** * @brief A normal range which uses floats * * @ingroup randomUtils */ typedef NormalRange NormalFloatRange; class BoundedNormalDistribution { public: using result_type = float; using param_type = struct { std::normal_distribution::param_type normal_parameters; float min; float max; }; param_type m_param; inline BoundedNormalDistribution() : BoundedNormalDistribution(param_type{std::normal_distribution::param_type{0.5f, 1.f}, 0.f, 1.f}) {} inline BoundedNormalDistribution(param_type curve) : m_param(curve) {} inline void reset() {} inline param_type param() const { return m_param; } inline void param(param_type p) { m_param = p; } template inline result_type operator()(Generator& generator, const param_type& param) { float unbounded = std::normal_distribution()(generator, param.normal_parameters); CLAMP(unbounded, param.min, param.max); return unbounded; } template inline result_type operator()(Generator& generator) { return this->operator()(generator, m_param); } inline result_type min() const { return m_param.min; } inline result_type max() const { return m_param.max; } inline result_type avg() const { return m_param.normal_parameters.mean(); } inline bool operator==(const BoundedNormalDistribution& other) const { return (m_param.normal_parameters == other.m_param.normal_parameters && fl_equal(m_param.min, other.m_param.min) && fl_equal(m_param.max, other.m_param.max)); } inline bool operator!=(const BoundedNormalDistribution& other) const { return !(*this == other); } }; using BoundedNormalFloatRange = RandomRange; /** * @brief A function for parsing a normal range * @return The parsed normal range * * @ingroup randomUtils */ inline BoundedNormalFloatRange parseNormalFloatRange(float min = std::numeric_limits::lowest()/2.1f, float max = std::numeric_limits::max()/2.1f) { float valueList[2]; auto num = parse_number_list(valueList); float parsed_min = min; float parsed_max = max; if (num == 0) { error_display(0, "Need at least one value to form a random range!"); return {}; } else if (num == 1) { return BoundedNormalFloatRange(BoundedNormalDistribution::param_type{std::normal_distribution::param_type(valueList[0]), parsed_min, parsed_max}); } stuff_float_optional(&parsed_min); stuff_float_optional(&parsed_max); if (parsed_min > parsed_max) { error_display(0, "Minimum value %f is more than maximum value %f!", (float)parsed_min, (float)parsed_max); std::swap(parsed_min, parsed_max); } if (parsed_min < min) { error_display(0, "First value (%f) is less than the minimum %f!", (float)parsed_min, (float)min); parsed_min = min; } if (parsed_min > max) { error_display(0, "First value (%f) is greater than the maximum %f!", (float)parsed_min, (float)max); parsed_min = max; } if (parsed_max < min) { error_display(0, "Second value (%f) is less than the minimum %f!", (float)parsed_max, (float)min); parsed_max = min; } if (parsed_max > max) { error_display(0, "Second value (%f) is greater than the maximum %f!", (float)parsed_max, (float)max); parsed_max = max; } return BoundedNormalFloatRange(BoundedNormalDistribution::param_type{std::normal_distribution::param_type(valueList[0], valueList[1]), parsed_min, parsed_max}); } /** * @brief A generic random range which uses a uniform distribution * * @ingroup randomUtils */ template using UniformRange = RandomRange::value, std::uniform_int_distribution, std::uniform_real_distribution>::type, std::minstd_rand>; /** * @brief A uniform range which uses floats * * @ingroup randomUtils */ typedef UniformRange UniformFloatRange; /** * @brief A uniform range which uses ints * * @ingroup randomUtils */ typedef UniformRange UniformIntRange; /** * @brief A uniform range which uses uints * * @ingroup randomUtils */ typedef UniformRange UniformUIntRange; /** * @brief Parses a generic uniform range * @param allowNegativ If @c true, negative values will be allowed * @param min The minimum value the random range may return * @param max The maximum value the random range may return * * @return The parsed uniform range * * @ingroup randomUtils */ template UniformRange parseUniformRange(Value min = std::numeric_limits::lowest()/2.1f, Value max = std::numeric_limits::max()/2.1f) { Assertion(min <= max, "Invalid min-max values specified!"); Value valueList[2]; auto num = parse_number_list(valueList); if (num == 0) { error_display(0, "Need at least one value to form a random range!"); return UniformRange(); } else if (num == 1) { return UniformRange(valueList[0]); } if (valueList[0] > valueList[1]) { error_display(0, "Minimum value %f is more than maximum value %f!", (float)valueList[0], (float)valueList[1]); std::swap(valueList[0], valueList[1]); } if (valueList[0] < min) { error_display(0, "First value (%f) is less than the minimum %f!", (float)valueList[0], (float)min); valueList[0] = min; } if (valueList[0] > max) { error_display(0, "First value (%f) is greater than the maximum %f!", (float)valueList[0], (float)max); valueList[0] = max; } if (valueList[1] < min) { error_display(0, "Second value (%f) is less than the minimum %f!", (float)valueList[1], (float)min); valueList[1] = min; } if (valueList[1] > max) { error_display(0, "Second value (%f) is greater than the maximum %f!", (float)valueList[1], (float)max); valueList[1] = max; } if (valueList[0] == valueList[1]) { // If the two values are equal then this is slightly more efficient return UniformRange(valueList[0]); } else { return UniformRange(valueList[0], valueList[1]); } } class CurveNumberDistribution { public: using result_type = float; using param_type = struct { int curve; float min; float max; }; param_type m_param; inline CurveNumberDistribution() : CurveNumberDistribution(param_type{-1, NAN, NAN}) {} inline CurveNumberDistribution(param_type curve) : m_param(curve) {} inline void reset() {} inline param_type param() const { return m_param; } inline void param(param_type p) { m_param = p; } static inline result_type getRandAt(float rand, float max_integral, const param_type& param) { float lower_bound = Curves[param.curve].keyframes.front().pos.x; float upper_bound = Curves[param.curve].keyframes.back().pos.x; float curve_min = lower_bound; float curve_max = upper_bound; for (size_t count = 0; count < 16; count++) { float current_pos = (lower_bound + upper_bound) / 2.f; float current_value = Curves[param.curve].GetValueIntegrated(current_pos); if (fl_equal(current_value, rand, max_integral * 0.01f)) { // remap the values to the distribution's range return (current_pos - curve_min) / (curve_max - curve_min) * (param.max - param.min) + param.min; } if (current_value > rand) { upper_bound = current_pos; } else { lower_bound = current_pos; } } // remap the values to the distribution's range return (((lower_bound + upper_bound) / 2.f) - curve_min) / (curve_max - curve_min) * (param.max - param.min) + param.min; } template inline result_type operator()(Generator& generator, const param_type& param) { if (param.curve < 0) { return 0.f; } float max_integral = Curves[param.curve].GetValueIntegrated(Curves[param.curve].keyframes.back().pos.x); float rand = std::generate_canonical::digits, Generator>(generator) * max_integral; return getRandAt(rand, max_integral, param); } template inline result_type operator()(Generator& generator) { return this->operator()(generator, m_param); } inline result_type min() const { return Curves[m_param.curve].keyframes.front().pos.x; } inline result_type max() const { return Curves[m_param.curve].keyframes.back().pos.x; } inline result_type avg() const { if (m_param.curve < 0) { return 0.f; } float lower_bound = Curves[m_param.curve].keyframes.front().pos.x; float upper_bound = Curves[m_param.curve].keyframes.back().pos.x; float max_integral = Curves[m_param.curve].GetValueIntegrated(Curves[m_param.curve].keyframes.back().pos.x); return getRandAt((lower_bound + upper_bound) * 0.5f, max_integral, m_param); } inline bool operator==(const CurveNumberDistribution& other) const { return (m_param.curve == other.m_param.curve && fl_equal(m_param.min, other.m_param.min) && fl_equal(m_param.max, other.m_param.max)); } inline bool operator!=(const CurveNumberDistribution& other) const { return !(*this == other); } }; using CurveFloatRange = RandomRange; inline CurveFloatRange parseCurveFloatRange(float min = std::numeric_limits::lowest()/2.1f, float max = std::numeric_limits::max()/2.1f) { CurveNumberDistribution::param_type curve_params; curve_params.curve = curve_parse(" Random distributions using this curve will return 0."); optional_string("("); stuff_float_optional(&curve_params.min); stuff_float_optional(&curve_params.max); optional_string(")"); if (curve_params.curve < 0) { return CurveFloatRange{curve_params}; } else { bool y_below_0 = false; bool no_y_above_0 = true; for (const auto& kframe : Curves[curve_params.curve].keyframes) { if (kframe.pos.y < 0.f) { y_below_0 = true; } if (kframe.pos.y > 0.f) { no_y_above_0 = false; } } if (y_below_0) { error_display(0, "Curve %s goes below zero along the Y axis. Random distributions using this curve will return 0.", Curves[curve_params.curve].name.c_str()); curve_params.curve = -1; return CurveFloatRange{curve_params}; } if (no_y_above_0) { error_display(0, "Curve %s has no values above zero along the Y axis. Random distributions using this curve will return 0.", Curves[curve_params.curve].name.c_str()); curve_params.curve = -1; return CurveFloatRange{curve_params}; } } if (fl_is_nan(curve_params.min) || fl_is_nan(curve_params.max)) { if (!fl_is_nan(curve_params.min)) { error_display(0, "Minimum value but no maximum value specified for curve distribution %s!", Curves[curve_params.curve].name.c_str()); } curve_params.min = Curves[curve_params.curve].keyframes.front().pos.x; curve_params.max = Curves[curve_params.curve].keyframes.back().pos.x; } if (curve_params.min > curve_params.max) { error_display(0, "Minimum value %f is more than maximum value %f!", (float)curve_params.min, (float)curve_params.max); std::swap(curve_params.min, curve_params.max); } if (curve_params.min < min) { error_display(0, "First value (%f) is less than the minimum %f!", (float)curve_params.min, (float)min); curve_params.min = min; } if (curve_params.min > max) { error_display(0, "First value (%f) is greater than the maximum %f!", (float)curve_params.min, (float)max); curve_params.min = max; } if (curve_params.max < min) { error_display(0, "Second value (%f) is less than the minimum %f!", (float)curve_params.max, (float)min); curve_params.max = min; } if (curve_params.max > max) { error_display(0, "Second value (%f) is greater than the maximum %f!", (float)curve_params.max, (float)max); curve_params.max = max; } return CurveFloatRange{curve_params}; } template class ParsedRandomRange { public: using variant = std::variant; private: variant m_random_range; public: template, ParsedRandomRange>, bool> = true> ParsedRandomRange(T&& random_range) { m_random_range = std::forward(random_range); } template, ParsedRandomRange>, bool> = true> ParsedRandomRange(const T& random_range) { m_random_range = random_range; } ParsedRandomRange() { m_random_range = UniformFloatRange(); } inline result_type next() const { return static_cast(std::visit([](auto& range) {return range.next();}, m_random_range)); } inline result_type min() const { return static_cast(std::visit([](auto& range) {return range.min();}, m_random_range)); } inline result_type max() const { return static_cast(std::visit([](auto& range) {return range.max();}, m_random_range)); } inline result_type avg() const { return static_cast(std::visit([](auto& range) {return range.avg();}, m_random_range)); } inline void seed(unsigned int new_seed) const { std::visit([new_seed](auto& range) {return range.seed(new_seed);}, m_random_range); } static ParsedRandomRange parseRandomRange(float min = std::numeric_limits::lowest()/2.1f, float max = std::numeric_limits::max()/2.1f) { switch (optional_string_either("NORMAL", "CURVE")) { case 0: { return ParsedRandomRange(parseNormalFloatRange(min, max)); } case 1: { return ParsedRandomRange(parseCurveFloatRange(min, max)); } default: { return ParsedRandomRange(parseUniformRange(min, max)); } } } template, ParsedRandomRange>, bool> = true> ParsedRandomRange& operator=(T&& random_range) { m_random_range = std::forward(random_range); return *this; } template, ParsedRandomRange>, bool> = true> ParsedRandomRange& operator=(const T& random_range) { m_random_range = random_range; return *this; } }; using ParsedRandomFloatRange = ParsedRandomRange; using ParsedRandomUintRange = ParsedRandomRange; }