Веб-сайт самохостера Lotigara

summaryrefslogtreecommitdiff
path: root/source/game/StarStoredFunctions.cpp
diff options
context:
space:
mode:
authorKae <80987908+Novaenia@users.noreply.github.com>2023-06-20 14:33:09 +1000
committerKae <80987908+Novaenia@users.noreply.github.com>2023-06-20 14:33:09 +1000
commit6352e8e3196f78388b6c771073f9e03eaa612673 (patch)
treee23772f79a7fbc41bc9108951e9e136857484bf4 /source/game/StarStoredFunctions.cpp
parent6741a057e5639280d85d0f88ba26f000baa58f61 (diff)
everything everywhere
all at once
Diffstat (limited to 'source/game/StarStoredFunctions.cpp')
-rw-r--r--source/game/StarStoredFunctions.cpp314
1 files changed, 314 insertions, 0 deletions
diff --git a/source/game/StarStoredFunctions.cpp b/source/game/StarStoredFunctions.cpp
new file mode 100644
index 0000000..27a1eef
--- /dev/null
+++ b/source/game/StarStoredFunctions.cpp
@@ -0,0 +1,314 @@
+#include "StarStoredFunctions.hpp"
+#include "StarAssets.hpp"
+#include "StarRoot.hpp"
+
+namespace Star {
+
+double const StoredFunction::DefaultSearchTolerance = 0.001;
+
+StoredFunction::StoredFunction(ParametricFunction<double, double> data) {
+ if (data.empty())
+ throw StoredFunctionException("StoredFunction constructor called on function with no data points");
+
+ m_function = move(data);
+
+ // Determine whether the function is monotonically increasing, monotonically
+ // decreasing, totally flat (technically both monotonically increasing and
+ // decreasing) or neither.
+ m_monotonicity = Monotonicity::Flat;
+ for (size_t i = 0; i < m_function.size() - 1; ++i) {
+ if (m_function.value(i) < m_function.value(i + 1)) {
+ if (m_monotonicity == Monotonicity::Flat)
+ m_monotonicity = Monotonicity::Increasing;
+ else if (m_monotonicity == Monotonicity::Decreasing)
+ m_monotonicity = Monotonicity::None;
+ } else if (m_function.value(i + 1) < m_function.value(i)) {
+ if (m_monotonicity == Monotonicity::Flat)
+ m_monotonicity = Monotonicity::Decreasing;
+ else if (m_monotonicity == Monotonicity::Increasing)
+ m_monotonicity = Monotonicity::None;
+ }
+ if (m_monotonicity == Monotonicity::None)
+ break;
+ }
+}
+
+Monotonicity StoredFunction::monotonicity() const {
+ return m_monotonicity;
+}
+
+double StoredFunction::evaluate(double value) const {
+ return m_function.interpolate(value);
+}
+
+StoredFunction::SearchResult StoredFunction::search(double targetValue, double valueTolerance) const {
+ double minIndex = m_function.index(0);
+ double minValue = m_function.value(0);
+
+ double maxIndex = m_function.index(m_function.size() - 1);
+ double maxValue = m_function.value(m_function.size() - 1);
+
+ if (maxValue < minValue) {
+ std::swap(minIndex, maxIndex);
+ std::swap(minValue, maxValue);
+ }
+
+ double index;
+ double value;
+
+ if (targetValue < minValue) {
+ index = minIndex;
+ value = minValue;
+ } else if (targetValue > maxValue) {
+ index = maxIndex;
+ value = maxValue;
+ } else {
+ index = (minIndex + maxIndex) / 2;
+ value = m_function.interpolate(index);
+
+ int searchDepth = 0;
+
+ while ((std::fabs(targetValue - value) > valueTolerance) && (searchDepth < 64)) {
+ searchDepth++;
+ if (value < targetValue) {
+ minIndex = index;
+ minValue = value;
+ } else if (value > targetValue) {
+ maxIndex = index;
+ maxValue = value;
+ }
+
+ double newIndex = (minIndex + maxIndex) / 2;
+ double newValue = m_function.interpolate(newIndex);
+
+ // If at any point we move outside of the established upper and lower
+ // bound
+ // the function is not monotonic increasing or decreasing, and binary
+ // search
+ // can not be used so we have to bail out.
+ if (newValue > maxValue || newValue < minValue)
+ throw StarException("StoredFunction is not monotonic.");
+
+ index = newIndex;
+ value = newValue;
+ }
+ }
+
+ SearchResult result;
+ result.targetValue = targetValue;
+ result.searchTolerance = valueTolerance;
+ result.found = std::fabs(targetValue - value) <= valueTolerance;
+ result.solution = index;
+ result.value = value;
+
+ return result;
+}
+
+StoredFunction2::StoredFunction2(MultiTable2D table) : table(move(table)) {}
+
+double StoredFunction2::evaluate(double x, double y) const {
+ return table.interpolate({x, y});
+}
+
+StoredConfigFunction::StoredConfigFunction(ParametricTable<int, Json> data) {
+ m_data = move(data);
+}
+
+Json StoredConfigFunction::get(double value) const {
+ return m_data.get(value);
+}
+
+FunctionDatabase::FunctionDatabase() {
+ auto assets = Root::singleton().assets();
+
+ auto functions = assets->scanExtension("functions");
+ auto sndFunctions = assets->scanExtension("2functions");
+ auto configFunctions = assets->scanExtension("configfunctions");
+
+ assets->queueJsons(functions);
+ assets->queueJsons(sndFunctions);
+ assets->queueJsons(configFunctions);
+
+ for (auto file : functions) {
+ for (auto const& functionPair : assets->json(file).iterateObject()) {
+ if (m_functions.contains(functionPair.first))
+ throw StarException(strf("Named Function '%s' defined twice, second time from %s", functionPair.first, file));
+ m_functions[functionPair.first] = make_shared<StoredFunction>(parametricFunctionFromConfig(functionPair.second));
+ }
+ }
+
+ for (auto file : sndFunctions) {
+ for (auto const& functionPair : assets->json(file).iterateObject()) {
+ if (m_functions2.contains(functionPair.first))
+ throw StarException(
+ strf("Named 2-ary Function '%s' defined twice, second time from %s", functionPair.first, file));
+ m_functions2[functionPair.first] = make_shared<StoredFunction2>(multiTable2DFromConfig(functionPair.second));
+ }
+ }
+
+ for (auto file : configFunctions) {
+ for (auto const& tablePair : assets->json(file).iterateObject()) {
+ if (m_configFunctions.contains(tablePair.first))
+ throw StarException(
+ strf("Named config function '%s' defined twice, second time from %s", tablePair.first, file));
+ m_configFunctions[tablePair.first] =
+ make_shared<StoredConfigFunction>(parametricTableFromConfig(tablePair.second));
+ }
+ }
+}
+
+StringList FunctionDatabase::namedFunctions() const {
+ return m_functions.keys();
+}
+
+StringList FunctionDatabase::namedFunctions2() const {
+ return m_functions2.keys();
+}
+
+StringList FunctionDatabase::namedConfigFunctions() const {
+ return m_configFunctions.keys();
+}
+
+StoredFunctionPtr FunctionDatabase::function(Json const& configOrName) const {
+ if (configOrName.type() == Json::Type::String)
+ return m_functions.get(configOrName.toString());
+ else
+ return make_shared<StoredFunction>(parametricFunctionFromConfig(configOrName));
+}
+
+StoredFunction2Ptr FunctionDatabase::function2(Json const& configOrName) const {
+ if (configOrName.type() == Json::Type::String)
+ return m_functions2.get(configOrName.toString());
+ else
+ return make_shared<StoredFunction2>(multiTable2DFromConfig(configOrName));
+}
+
+StoredConfigFunctionPtr FunctionDatabase::configFunction(Json const& configOrName) const {
+ if (configOrName.type() == Json::Type::String)
+ return m_configFunctions.get(configOrName.toString());
+ else
+ return make_shared<StoredConfigFunction>(parametricTableFromConfig(configOrName));
+}
+
+ParametricFunction<double, double> FunctionDatabase::parametricFunctionFromConfig(Json descriptor) {
+ try {
+ String interpolationModeString = descriptor.getString(0);
+ String boundModeString = descriptor.getString(1);
+
+ List<pair<double, double>> points;
+ for (size_t i = 2; i < descriptor.size(); ++i) {
+ auto pointPair = descriptor.get(i);
+ if (pointPair.size() != 2)
+ throw StoredFunctionException("Each point must be a list of size 2");
+ points.append({pointPair.getDouble(0), pointPair.getDouble(1)});
+ }
+
+ InterpolationMode interpolationMode;
+ if (interpolationModeString.equalsIgnoreCase("HalfStep")) {
+ interpolationMode = InterpolationMode::HalfStep;
+ } else if (interpolationModeString.equalsIgnoreCase("Linear")) {
+ interpolationMode = InterpolationMode::Linear;
+ } else if (interpolationModeString.equalsIgnoreCase("Cubic")) {
+ interpolationMode = InterpolationMode::Cubic;
+ } else {
+ throw StoredFunctionException(strf("Unrecognized InterpolationMode '%s'", interpolationModeString));
+ }
+
+ BoundMode boundMode;
+ if (boundModeString.equalsIgnoreCase("Clamp")) {
+ boundMode = BoundMode::Clamp;
+ } else if (boundModeString.equalsIgnoreCase("Extrapolate")) {
+ boundMode = BoundMode::Extrapolate;
+ } else if (boundModeString.equalsIgnoreCase("Wrap")) {
+ boundMode = BoundMode::Wrap;
+ } else {
+ throw StoredFunctionException(strf("Unrecognized BoundMode '%s'", boundModeString));
+ }
+
+ return ParametricFunction<double, double>(points, interpolationMode, boundMode);
+ } catch (StarException const& e) {
+ throw StoredFunctionException("Error parsing StoredFunction descriptor", e);
+ }
+}
+
+ParametricTable<int, Json> FunctionDatabase::parametricTableFromConfig(Json descriptor) {
+ try {
+ List<pair<int, Json>> points;
+ for (size_t i = 0; i < descriptor.size(); ++i) {
+ auto pointPair = descriptor.get(i);
+ if (pointPair.size() != 2)
+ throw StoredFunctionException("Each point must be a list of size 2");
+ points.append({pointPair.getInt(0), pointPair.get(1)});
+ }
+
+ return ParametricTable<int, Json>(points);
+ } catch (StarException const& e) {
+ throw StoredFunctionException("Error parsing StoredConfigFunction descriptor", e);
+ }
+}
+
+MultiTable2D FunctionDatabase::multiTable2DFromConfig(Json descriptor) {
+ try {
+ String interpolationModeString = descriptor.getString(0);
+ String boundModeString = descriptor.getString(1);
+
+ List<double> xaxis;
+ List<double> yaxis;
+ MultiArray2D points;
+
+ auto grid = descriptor.getArray(2);
+
+ for (size_t y = 0; y < grid.size(); ++y) {
+ auto row = grid[y].toArray();
+ if (y == 0) {
+ for (size_t x = 0; x < row.size(); ++x) {
+ if (x > 0)
+ xaxis.append(row[x].toFloat());
+ }
+ points.resize({row.size() - 1, grid.size() - 1});
+ } else {
+ yaxis.append(row[0].toFloat());
+ auto cells = row[1].toArray();
+ if (cells.size() != xaxis.size())
+ throw StarException("Number of sample points doesn't match axis size.");
+ for (size_t x = 0; x < cells.size(); x++)
+ points.set({x, y - 1}, cells[x].toFloat());
+ }
+ }
+
+ InterpolationMode interpolationMode;
+ if (interpolationModeString.equalsIgnoreCase("HalfStep")) {
+ interpolationMode = InterpolationMode::HalfStep;
+ } else if (interpolationModeString.equalsIgnoreCase("Linear")) {
+ interpolationMode = InterpolationMode::Linear;
+ } else if (interpolationModeString.equalsIgnoreCase("Cubic")) {
+ interpolationMode = InterpolationMode::Cubic;
+ } else {
+ throw StoredFunctionException(strf("Unrecognized InterpolationMode '%s'", interpolationModeString));
+ }
+
+ BoundMode boundMode;
+ if (boundModeString.equalsIgnoreCase("Clamp")) {
+ boundMode = BoundMode::Clamp;
+ } else if (boundModeString.equalsIgnoreCase("Extrapolate")) {
+ boundMode = BoundMode::Extrapolate;
+ } else if (boundModeString.equalsIgnoreCase("Wrap")) {
+ boundMode = BoundMode::Wrap;
+ } else {
+ throw StoredFunctionException(strf("Unrecognized BoundMode '%s'", boundModeString));
+ }
+
+ MultiTable2D table;
+ table.setRange(0, std::vector<double>(xaxis.begin(), xaxis.end()));
+ table.setRange(1, std::vector<double>(yaxis.begin(), yaxis.end()));
+ table.setInterpolationMode(interpolationMode);
+ table.setBoundMode(boundMode);
+ table.array() = points;
+
+ return table;
+ } catch (StarException const& e) {
+ throw StoredFunctionException("Error parsing function2 descriptor", e);
+ }
+}
+
+}