Cogs.Core
InputManager.cpp
1#include "InputManager.h"
2
3#include "GamepadMapping.h"
4#include "KeyboardMapping.h"
5#include "Platform/Gamepad.h"
6
7#include "../Engine.h"
8#include "../ViewContext.h"
9#include "../Serialization/JsonParser.h"
10#include "../Services/Variables.h"
11#include "../Utilities/Parsing.h"
12
13#include "Foundation/Logging/Logger.h"
14
15#include <algorithm>
16#include <cmath>
17#include <limits>
18
19namespace {
20 const Cogs::Logging::Log logger = Cogs::Logging::getLogger("InputManager");
21
22 void readInputMapping(Cogs::Core::InputManager& inputManager, const Cogs::StringView& axisName, const rapidjson::Value& jsonValue, bool isAxis) {
23 std::string device;
24 std::string inputName;
25 float scale = 1.0f;
26
27 if (jsonValue.IsString()) {
28 auto source = Cogs::Core::toKey(jsonValue);
29 auto splits = Cogs::Core::split(source, ":");
30
31 if (splits.size() < 2) {
32 LOG_ERROR(logger, "Invalid input source: %.*s", StringViewFormat(source));
33 return;
34 }
35
36 if (splits.size() == 3) {
37 scale = Cogs::Core::parseFloat(splits[2], 1.0f);
38 }
39
40 device = splits[0].to_string();
41 inputName = splits[1].to_string();
42 }
43 else if (jsonValue.IsObject()) {
44 for (auto & mm : jsonValue.GetObject()) {
45 auto mmKey = Cogs::Core::toKey(mm.name);
46
47 if (mmKey == "device") {
48 device = Cogs::Core::toString(mm.value);
49 }
50 else if (mmKey == "axis" || mmKey == "action") {
51 inputName = Cogs::Core::toString(mm.value);
52 }
53 else if (mmKey == "scale") {
54 scale = mm.value.GetFloat();
55 }
56 }
57 }
58
59 if (!device.empty() && !inputName.empty()) {
60 if (isAxis) {
61 inputManager.addAxisMapping(axisName, device, inputName, scale);
62 }
63 else {
64 inputManager.addActionMapping(axisName, device, inputName);
65 }
66 }
67 }
68}
69
71 view(viewContext),
72 deviceStates(16)
73{
74}
75
77{
78}
79
81{
82 auto & mouse = deviceStates[InputDevices::Mouse];
83
84 mouse.deviceId = InputDevices::Mouse;
85 mouse.id = "Mouse";
86 mouse.isConnected = true;
87 mouse.isValid = true;
88 mouse.buttonMask = 0x7;
89 mouse.axisMask = 0x7;
90
91 mouse.actionMap = {
92 { "LeftButton", 0 },
93 { "RightButton", 1 },
94 { "MiddleButton", 2 },
95 };
96
97 mouse.axisMap = {
98 { "X", 0 },
99 { "Y", 1 },
100 { "Wheel", 2 }
101 };
102
103 addAxisMapping("MouseX", "Mouse", "X", 1.0f);
104 addAxisMapping("MouseY", "Mouse", "Y", 1.0f);
105
106 addAxisMapping("MouseWheel", "Mouse", "Wheel", 1.0f, 1);
107
108 addActionMapping("LeftMouse", "Mouse", "LeftButton");
109 addActionMapping("MiddleMouse", "Mouse", "MiddleButton");
110 addActionMapping("RightMouse", "Mouse", "RightButton");
111
112 auto & keyboard = deviceStates[InputDevices::Keyboard];
113
114 keyboard.id = "Keyboard";
115 keyboard.deviceId = InputDevices::Keyboard;
116 keyboard.isConnected = true;
117 keyboard.isValid = true;
118
119 keyboard.buttonMask = static_cast<size_t>(-1);
120 keyboard.axisMask = static_cast<size_t>(-1);
121 keyboard.actionMap = keyMapping;
122 keyboard.axisMap = keyMapping;
123
124 for (int i = 0; i < 4; ++i) {
125 auto & gamepad = deviceStates[InputDevices::Gamepad0 + i];
126
127 gamepad.id = "Gamepad" + std::to_string(i);
128 if (i == 0) gamepad.alias = "Gamepad";
129
130 gamepad.deviceId = InputDevices::Gamepad0 + i;
131
132 gamepad.axisMap = gamepadAxisMapping;
133 gamepad.actionMap = gamepadButtonMapping;
134
135 gamepad.linkedDeadZones[Gamepad::StickRightX] = Gamepad::StickRightY;
136 gamepad.linkedDeadZones[Gamepad::StickRightY] = Gamepad::StickRightX;
137
138 gamepad.linkedDeadZones[Gamepad::StickLeftX] = Gamepad::StickLeftY;
139 gamepad.linkedDeadZones[Gamepad::StickLeftY] = Gamepad::StickLeftX;
140 }
141
142 readInputConfig(view->getContext()->variables->get("input.config", "Input.config"));
143}
144
146{
147 auto deviceId = axisId.deviceId.driver + ":" + axisId.deviceId.id;
148
149 addAxisMapping(name, deviceId, axisId.inputName);
150}
151
153{
154 auto deviceId = actionId.deviceId.driver + ":" + actionId.deviceId.id;
155
156 addActionMapping(name, deviceId, actionId.inputName);
157}
158
159void Cogs::Core::InputManager::readInputConfig(const StringView & path)
160{
161 auto document = parseJson(view->getContext(), path, JsonParseFlags::NoCachedContent);
162
163 if (!document.IsObject()) return;
164
165 for (auto & m : document.GetObject()) {
166 auto key = toKey(m.name);
167
168 if (key == "axes" || key == "actions") {
169 auto axesObj = m.value.GetObject();
170
171 for (auto & axesMember : axesObj) {
172 auto axisName = toKey(axesMember.name);
173
174 if (axesMember.value.IsArray()) {
175 for (auto & element : axesMember.value.GetArray()) {
176 readInputMapping(*this, axisName, element, key == "axes");
177 }
178 } else {
179 readInputMapping(*this, axisName, axesMember.value, key == "axes");
180 }
181 }
182 }
183 }
184}
185
186void Cogs::Core::InputManager::addProvider(std::unique_ptr<IInputProvider> provider)
187{
188 inputProviders.push_back(std::move(provider));
189}
190
191Cogs::Core::InputDeviceState * Cogs::Core::InputManager::getDevice(const StringView & name)
192{
193 for (auto & d : deviceStates) {
194 if (name == d.id || name == d.alias) {
195 return &d;
196 }
197 }
198
199 for (size_t i = InputDevices::Custom; i < deviceStates.size(); ++i) {
200 auto & d = deviceStates[i];
201
202 if (d.id.empty()) {
203 d.id = name.to_string();
204 d.deviceId = i;
205
206 auto splits = split(name, ":");
207
208 if (splits.size() >= 2) {
209 pendingDevices.push_back(InputDeviceIdentifier{ splits[0].to_string(), splits[1].to_string() });
210 }
211
212 return &d;
213 }
214 }
215
216 LOG_ERROR(logger, "No device with identifier %.*s found.", StringViewFormat(name));
217
218 return nullptr;
219}
220
221size_t Cogs::Core::InputManager::getAxisIndex(InputDeviceState * device, const StringView & inputAxis) const
222{
223 if (device->axisMap.empty()) {
224 // Axis map has not yet been set. Defer axis lookup until present.
225 return NoAxis;
226 }
227
228 auto found = device->axisMap.find(inputAxis.to_string());
229
230 if (found == device->axisMap.end()) {
231 LOG_ERROR(logger, "Axis %.*s does not exist on device %s.", StringViewFormat(inputAxis), device->id.c_str());
232 return ErrorAxis;
233 }
234
235 return found->second;
236}
237
238void Cogs::Core::InputManager::addAxisMapping(const StringView & name, const StringView & deviceName, const StringView & inputAxis, float scale, int flags)
239{
240 auto device = getDevice(deviceName);
241
242 if (!device) return;
243
244 addAxisMapping(name, device, inputAxis, scale, flags);
245}
246
247void Cogs::Core::InputManager::addAxisMapping(const StringView & name, InputDeviceState * device, const StringView & inputAxis, float scale, int flags)
248{
249 auto & axisMapping = axisMappings[name.hash()];
250
251 if (axisMapping.name.empty()) {
252 axisMapping.name = name.to_string();
253 }
254
255 SourceAxisMapping sourceAxis = {};
256 sourceAxis.scale = scale;
257 sourceAxis.deviceId = device->deviceId;
258 sourceAxis.axisName = inputAxis.to_string();
259 sourceAxis.axisId = getAxisIndex(device, inputAxis);
260 sourceAxis.flags = flags;
261
262 if (sourceAxis.axisId == ErrorAxis) return;
263
264 axisMapping.inputs.push_back(sourceAxis);
265}
266
267size_t Cogs::Core::InputManager::getButtonIndex(InputDeviceState * device, const StringView & inputAction) const
268{
269 if (device->actionMap.empty()) {
270 return NoButton;
271 }
272
273 auto found = device->actionMap.find(inputAction.to_string());
274
275 if (found == device->actionMap.end()) {
276 LOG_ERROR(logger, "Action %.*s does not exist on device %s.", StringViewFormat(inputAction), device->id.c_str());
277 return ErrorButton;
278 }
279
280 return found->second;
281}
282
283void Cogs::Core::InputManager::addActionMapping(const StringView & name, const StringView & deviceName, const StringView & inputAction, int placeholder)
284{
285 auto device = getDevice(deviceName);
286
287 if (!device) return;
288
289 addActionMapping(name, device, inputAction, placeholder);
290}
291
292void Cogs::Core::InputManager::addActionMapping(const StringView & name, InputDeviceState * device, const StringView & inputAction, int /*placeholder*/)
293{
294 auto & actionMapping = actionMappings[name.hash()];
295
296 if (actionMapping.name.empty()) {
297 actionMapping.name = name.to_string();
298 }
299
300 SourceActionMapping sourceAction = {};
301 sourceAction.deviceId = device->deviceId;
302 sourceAction.buttonId = getButtonIndex(device, inputAction);
303 sourceAction.actionName = inputAction.to_string();
304
305 if (sourceAction.buttonId == ErrorButton) return;
306
307 actionMapping.inputs.push_back(sourceAction);
308}
309
310void Cogs::Core::InputManager::gainedFocus(double /*timestamp_ms*/) {
311 // Nothing to do, yay!
312}
313
314void Cogs::Core::InputManager::lostFocus(double timestamp_ms) {
315 timestamp_ms = checkTimeStamp(timestamp_ms);
316 view->refGestures().reset();
317 view->refMouse().submitReset(timestamp_ms);
318 view->refKeyboard().submitReset(timestamp_ms);
319}
320
321void Cogs::Core::InputManager::triggerPointerPress(PointerType pointerType, PointerId pointerId, MouseButton button, const glm::ivec2& position, double timestamp_ms) {
322 timestamp_ms = checkTimeStamp(timestamp_ms);
323 if (pointerType == PointerType::Mouse) {
324 view->refMouse().submitButtonDown(button, timestamp_ms);
325 }
326 view->refGestures().pointerDown(pointerType, pointerId, button, position, timestamp_ms);
327
328 view->getContext()->engine->setDirty();
329}
330
331void Cogs::Core::InputManager::triggerPointerRelease(PointerType pointerType, PointerId pointerId, MouseButton button, const glm::ivec2& position, double timestamp_ms) {
332 timestamp_ms = checkTimeStamp(timestamp_ms);
333 if (pointerType == PointerType::Mouse) {
334 view->refMouse().submitButtonUp(button, timestamp_ms);
335 }
336 view->refGestures().pointerUp(pointerType, pointerId, button, position, timestamp_ms);
337
338 view->getContext()->engine->setDirty();
339}
340
341void Cogs::Core::InputManager::triggerPointerMove(PointerType pointerType, PointerId pointerId, const glm::ivec2& position, double timestamp_ms) {
342 timestamp_ms = checkTimeStamp(timestamp_ms);
343 if (pointerType == PointerType::Mouse) {
344 view->refMouse().submitMove(position, timestamp_ms);
345 }
346 view->refGestures().pointerMove(pointerType, pointerId, position, timestamp_ms);
347
348 view->getContext()->engine->setDirty();
349}
350
351void Cogs::Core::InputManager::triggerMouseWheel(int32_t deltaValue, double timestamp_ms) {
352 timestamp_ms = checkTimeStamp(timestamp_ms);
353 view->refMouse().submitWheel(deltaValue, timestamp_ms);
354 view->refGestures().mouseWheelMove(deltaValue);
355
356 view->getContext()->engine->setDirty();
357}
358
359void Cogs::Core::InputManager::triggerKeyDown(Key key, double timestamp_ms) {
360 timestamp_ms = checkTimeStamp(timestamp_ms);
361 view->refKeyboard().submitKeyDown(key, timestamp_ms);
362
363 view->getContext()->engine->setDirty();
364}
365
366void Cogs::Core::InputManager::triggerKeyUp(Key key, double timestamp_ms) {
367 timestamp_ms = checkTimeStamp(timestamp_ms);
368 view->refKeyboard().submitKeyUp(key, timestamp_ms);
369
370 view->getContext()->engine->setDirty();
371}
372
373void Cogs::Core::InputManager::triggerKeyChar(std::string ch, double timestamp_ms) {
374 timestamp_ms = checkTimeStamp(timestamp_ms);
375 view->refKeyboard().submitChar(std::move(ch), timestamp_ms);
376
377 view->getContext()->engine->setDirty();
378}
379
380Cogs::Core::InputDeviceState & Cogs::Core::InputManager::getDevice(InputDevices::EValues deviceId)
381{
382 return deviceStates[deviceId];
383}
384
386{
387 auto mapping = axisMappings.find(name.hash());
388
389 if (mapping == axisMappings.end()) {
390 LOG_WARNING(logger, "No such axis: %.*s", StringViewFormat(name));
391 return 0.f;
392 }
393
394 return mapping->second.value;
395}
396
398{
399 auto mapping = actionMappings.find(name.hash());
400
401 if (mapping == actionMappings.end()) {
402 LOG_WARNING(logger, "No such action: %.*s", StringViewFormat(name));
403 return false;
404 }
405
406 return mapping->second.value;
407}
408
410{
411 view->refKeyboard().update();
412 view->refMouse().update();
413 view->refGestures().update();
414
415 updateConnections();
416 updateInputs();
417
418 for (auto & d : deviceStates) {
419 if (d.isConnected && !d.actionMap.empty() && d.actionNames.empty()) {
420 d.actionNames.resize(d.highestBit(d.buttonMask) + 1);
421
422 for (const auto & a : d.actionMap) {
423 if (a.second < d.actionNames.size()) {
424 d.actionNames[a.second] = a.first;
425 }
426 }
427 }
428
429 if (d.isConnected && !d.axisMap.empty() && d.axisNames.empty()) {
430 d.axisNames.resize(d.highestBit(d.axisMask) + 1);
431
432 for (auto & a : d.axisMap) {
433 if (a.second < d.axisNames.size()) {
434 d.axisNames[a.second] = a.first;
435 }
436 }
437 }
438 }
439
440 updateAxes();
441 updateActions();
442}
443
444void Cogs::Core::InputManager::updateConnections()
445{
446 std::vector<InputDeviceIdentifier> found;
447
448 for (auto & deviceId : pendingDevices) {
449 for (auto & provider : inputProviders) {
450 if (provider->canCreate(deviceId)) {
451 auto device = provider->create(deviceId);
452
453 if (device) {
454 found.push_back(deviceId);
455
456 auto & state = *getDevice(deviceId.driver + ":" + deviceId.id);
457
458 if (!state.isConnected) {
459 state.isConnected = true;
460 state.device = device;
461 } else {
462 // Log error, already connected.
463 }
464 }
465 }
466 }
467 }
468
469 for (auto & toRemove : found) {
470 pendingDevices.erase(std::remove(pendingDevices.begin(), pendingDevices.end(), toRemove), pendingDevices.end());
471 }
472}
473
474void Cogs::Core::InputManager::updateInputs()
475{
476 // Reset deltas before update.
477 for (auto & d : deviceStates) {
478 d.axisDeltas.fill(0.0f);
479 d.buttonDeltas.fill(false);
480 }
481
482 InputDeviceState& keyboardDeviceState = getDevice(Core::InputDevices::Keyboard);
483 for (int i = 0; i < static_cast<int>(Key::Count); ++i) {
484 if (view->refKeyboard().isKeyDown(static_cast<Key>(i))) {
485 keyboardDeviceState.setButton(i, true);
486 keyboardDeviceState.setAxis(i, 1.0f);
487 }
488 else {
489 keyboardDeviceState.setButton(i, false);
490 keyboardDeviceState.setAxis(i, 0.0f);
491 }
492 }
493
494 InputDeviceState& mouseDeviceState = getDevice(Core::InputDevices::Mouse);
495 const Mouse::State& mouseState = view->refMouse().getState();
496
497 for (size_t i = 0; i < MouseButton::Count; ++i) {
498 mouseDeviceState.setButton(i, mouseState.buttonDown[i]);
499 }
500
501 mouseDeviceState.setAxis(0, static_cast<float>(mouseState.position.x));
502 mouseDeviceState.setAxis(1, static_cast<float>(mouseState.position.y));
503 mouseDeviceState.setAxis(2, static_cast<float>(mouseState.wheel));
504
505 view->gamepadHandler->update();
506
507 // Connected devices
508 for (auto & d : deviceStates) {
509 if (d.device) {
510 d.device->getState(d);
511 }
512 }
513
514 // Extensions
515 for (const auto & p : inputProviders) {
516 p->update(*this);
517 }
518}
519
520void Cogs::Core::InputManager::updateAxes()
521{
522 for (auto & p : axisMappings) {
523 auto & axisMapping = p.second;
524
525 for (auto & input : axisMapping.inputs) {
526 auto & d = deviceStates[input.deviceId];
527
528 if (input.axisId == NoAxis) {
529 input.axisId = getAxisIndex(&d, input.axisName);
530 if (input.axisId == NoAxis) continue;
531 }
532 if (input.axisId == ErrorAxis) continue;
533
534 const size_t axis = input.axisId;
535 const float rawValue = d.axes[axis];
536
537 if (d.axisDeltas[axis] || (rawValue != 0 && axisMapping.value == 0)) {
538 const float deadZone = d.deadZones[axis];
539 const size_t deadZoneLink = d.linkedDeadZones[axis];
540 const float sensitivity = d.sensitivity[axis];
541
542 float magnitude = glm::abs(rawValue);
543
544 if (deadZoneLink != kNoDeadZoneLink) {
545 const glm::vec2 pos(rawValue, d.axes[deadZoneLink]);
546 magnitude = glm::length(pos);
547 }
548
549 if (magnitude > deadZone) {
550 const float axisValue = rawValue * sensitivity * input.scale;
551
552 if (input.flags) {
553 axisMapping.value = axisValue;
554 } else {
555 axisMapping.value = axisValue * (magnitude - deadZone) / (1 - deadZone);
556 }
557 } else {
558 axisMapping.value = 0.0f;
559 }
560 }
561 }
562 }
563}
564
565void Cogs::Core::InputManager::updateActions()
566{
567 for (auto & p : actionMappings) {
568 auto & actionMapping = p.second;
569
570 actionMapping.value = false;
571
572 for (auto & input : actionMapping.inputs) {
573 auto & d = deviceStates[input.deviceId];
574
575 if (input.buttonId == NoButton) {
576 input.buttonId = getButtonIndex(&d, input.actionName);
577 if (input.buttonId == NoButton) continue;
578 }
579 if (input.buttonId == ErrorButton) continue;
580
581 bool actionState = d.buttons[input.buttonId];
582
583 //TODO: Check modifier state etc.
584
585 actionMapping.value |= actionState;
586 }
587 }
588}
589
591{
593 double result = std::max(timestamp_ms, prevTimeStamp);
594 if (result == prevTimeStamp) {
595 result = std::nextafter(result, std::numeric_limits<double>::max());
596 }
597 prevTimeStamp = result;
598 return result;
599}
Input manager responsible for handling input from various devices, including mouse,...
Definition: InputManager.h:37
double checkTimeStamp(double timestamp_ms)
void createAxisMapping(const InputIdentifier &axisId, const StringView &name)
Map input axis to a friendly name.
void initialize()
Initialize the input managers default configuration.
bool getActionState(const StringView &name) const
Get last action state for the given mapped action.
float getAxisValue(const StringView &name) const
Get last value for the given mapped axis.
void addProvider(std::unique_ptr< IInputProvider > provider)
Register a new input provider.
void createActionMapping(const InputIdentifier &actionId, const StringView &name)
Map input button to a friendly action name.
void update()
Update the input state.
~InputManager()
Destructs the InputManager.
InputManager(ViewContext *viewContext)
Constructs a new InputManager.
Log implementation class.
Definition: LogManager.h:139
Provides a weakly referenced view over the contents of a string.
Definition: StringView.h:24
std::string to_string() const
String conversion method.
Definition: StringView.cpp:9
constexpr size_t hash() const noexcept
Get the hash code of the string.
Definition: StringView.h:200
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:180
std::string driver
Input subsystem to handle this device (openvr, directinput etc)
std::string id
Device identifier within the input subsystem.
Identifier for an input device axis.
std::string inputName
Axis or button name.
InputDeviceIdentifier deviceId
Identifier for the specific input device.