// Copyright (c) 2022 Dominic Masters // // This software is released under the MIT License. // https://opensource.org/licenses/MIT #pragma once #include "util/Math.hpp" #include "input/InputBinds.hpp" namespace Dawn { class DawnGame; template<typename T> class IInputManager { protected: std::unordered_map<enum InputBind, std::vector<T>> binds; std::unordered_map<enum InputBind, float_t> valuesLeft; std::unordered_map<enum InputBind, float_t> valuesRight; bool_t currentIsLeft = true; /** * Method to be overwritten by the host, reads a RAW input value from * the pad/input device directly. * * @param axis Axis to get the value of. * @return The current input value (between 0 and 1). */ virtual float_t getInputValue(const T axis) = 0; public: /** * Binds an axis to a bind. * * @param bind Bind to bind the axis to. * @param axis Axis to use for this bind. */ void bind(const enum InputBind bind, const T axis) { this->binds[bind].push_back(axis); } /** * Unbind a previously bound axis from a bind. * * @param bind Bind to remove all binds from. */ void unbind(const enum InputBind bind) { this->binds[bind].clear(); } /** * Unbind all values, all of them. */ void unbindAll() { this->binds.clear(); this->values.clear(); } /** * Return the current bind value. * * @param bind Bind to get the value of. * @return The current input state (between 0 and 1). */ float_t getValue(const enum InputBind bind) { if(this->currentIsLeft) { auto exist = this->valuesLeft.find(bind); return exist == this->valuesLeft.end() ? 0.0f : exist->second; } else { auto exist = this->valuesRight.find(bind); return exist == this->valuesRight.end() ? 0.0f : exist->second; } } /** * Return the bind value from the previous frame. * * @param bind Bind to get the value of. * @return The value of the bind, last frame. */ float_t getValueLastUpdate(const enum InputBind bind) { if(this->currentIsLeft) { auto exist = this->valuesRight.find(bind); return exist == this->valuesRight.end() ? 0.0f : exist->second; } else { auto exist = this->valuesLeft.find(bind); return exist == this->valuesLeft.end() ? 0.0f : exist->second; } } /** * Returns an axis input for a given negative and positive bind. This will * combine and clamp the values to be between -1 and 1. For example, if * the negative bind is 100% actuated and positive is not at all actuated, * then the value will be -1. If instead positive is 50% actuated, then * the value will be -0.5. If both are equally actuacted, then the value * will be 0. * * @param negative Bind to use for the negative axis. * @param positive Bind to use for the positive axis. * @return A value between -1 and 1. */ float_t getAxis(const enum InputBind negative, const enum InputBind positive) { return -getValue(negative) + getValue(positive); } glm::vec2 getAxis2D( const enum InputBind negativeX, const enum InputBind positiveX, const enum InputBind negativeY, const enum InputBind positiveY ) { return glm::vec2( getAxis(negativeX, positiveX), getAxis(negativeY, positiveY) ); } /** * Returns the 2D Axis for the given binds. * * @param x X Axis bind. * @param y Y Axis bind. * @return 2D vector of the two given input binds. */ glm::vec2 getAxis2D(const enum InputBind x, const enum InputBind y) { return glm::vec2(getValue(x), getValue(y)); } /** * Returns true if the given bind is currently being pressed (a non-zero * value). * * @param bind Bind to check if pressed. * @return True if value is non-zero, or false for zero. */ bool_t isDown(const enum InputBind bind) { return this->getValue(bind) != 0.0f; } /** * Returns true on the first frame an input was pressed (when the state * had changed from 0 to non-zero). * * @param bind Bind to check if pressed. * @return True if down this frame and not down last frame. */ bool_t isPressed(const enum InputBind bind) { return this->getValue(bind) != 0 && this->getValueLastUpdate(bind) == 0; } /** * Returns true on the first frame an input was released (when the state * had changed from non-zero to 0). * * @param bind Bind to check if released. * @return True if up this frame, and down last frame. */ bool_t wasReleased(const enum InputBind bind) { return this->getValue(bind) == 0 && this->getValueLastUpdate(bind) != 0; } /** * Internal method to update the input state, checks current input raws * and decides what values are set for the inputs. */ void update() { auto it = this->binds.begin(); this->currentIsLeft = !this->currentIsLeft; // For each bind... while(it != this->binds.end()) { float_t value = 0.0f, valCurrent; // For each input axis... auto bindIt = it->second.begin(); while(bindIt != it->second.end()) { // Get value and make the new max. float_t inputValue = this->getInputValue(*bindIt); value = Math::max<float_t>(value, inputValue); ++bindIt; } // Set into current values if(this->currentIsLeft) { valCurrent = this->valuesRight[it->first]; this->valuesLeft[it->first] = value; } else { valCurrent = this->valuesLeft[it->first]; this->valuesRight[it->first] = value; } // Fire events when necessary. if(value == 0 && valCurrent == 1) { // eventBindReleased.invoke(it->first); } else if(valCurrent == 0 && value == 1) { // eventBindPressed.invoke(it->first); } ++it; } // TODO: trigger events } virtual ~IInputManager() { } }; }