1/****************************************************************************
2**
3** Copyright (C) 2015 Klaralvdalens Datakonsult AB (KDAB).
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the Qt3D module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qabstractphysicaldevicebackendnode_p.h"
41#include "qabstractphysicaldevicebackendnode_p_p.h"
42
43#include <Qt3DInput/qabstractphysicaldevice.h>
44#include <Qt3DInput/qaxissetting.h>
45#include <Qt3DInput/qinputaspect.h>
46#include <Qt3DInput/qphysicaldevicecreatedchange.h>
47#include <Qt3DCore/qpropertynodeaddedchange.h>
48#include <Qt3DCore/qpropertynoderemovedchange.h>
49#include <Qt3DCore/qpropertyupdatedchange.h>
50
51#include <cmath>
52
53#include <Qt3DInput/private/inputhandler_p.h>
54#include <Qt3DInput/private/inputmanagers_p.h>
55#include <Qt3DInput/private/qinputaspect_p.h>
56#include <Qt3DCore/private/qabstractaspect_p.h>
57
58QT_BEGIN_NAMESPACE
59
60namespace {
61
62Q_DECL_CONSTEXPR int signum(float v)
63{
64 return (v > 0.0f) - (v < 0.0f);
65}
66
67}
68
69namespace Qt3DInput {
70
71QAbstractPhysicalDeviceBackendNodePrivate::QAbstractPhysicalDeviceBackendNodePrivate(Qt3DCore::QBackendNode::Mode mode)
72 : Qt3DCore::QBackendNodePrivate(mode)
73 , m_axisSettings()
74 , m_inputAspect(nullptr)
75{
76}
77
78void QAbstractPhysicalDeviceBackendNodePrivate::addAxisSetting(int axisIdentifier, Qt3DCore::QNodeId axisSettingsId)
79{
80 Input::AxisIdSetting axisIdSetting;
81 axisIdSetting.m_axisIdentifier = axisIdentifier;
82 axisIdSetting.m_axisSettingsId = axisSettingsId;
83
84 // Replace if already present, otherwise append
85 bool replaced = false;
86 QVector<Input::AxisIdSetting>::iterator it;
87 QVector<Input::AxisIdSetting>::iterator end = m_axisSettings.end();
88 for (it = m_axisSettings.begin(); it != end; ++it) {
89 if (it->m_axisIdentifier == axisIdentifier) {
90 *it = axisIdSetting;
91 replaced = true;
92 break;
93 }
94 }
95
96 if (!replaced)
97 m_axisSettings.push_back(axisIdSetting);
98}
99
100void QAbstractPhysicalDeviceBackendNodePrivate::removeAxisSetting(Qt3DCore::QNodeId axisSettingsId)
101{
102 QVector<Input::AxisIdSetting>::iterator it;
103 for (it = m_axisSettings.begin(); it != m_axisSettings.end(); ++it) {
104 if (it->m_axisSettingsId == axisSettingsId) {
105 m_axisSettings.erase(it);
106 break;
107 }
108 }
109}
110
111Input::MovingAverage &QAbstractPhysicalDeviceBackendNodePrivate::getOrCreateFilter(int axisIdentifier)
112{
113 QVector<Input::AxisIdFilter>::iterator it;
114 QVector<Input::AxisIdFilter>::iterator end = m_axisFilters.end();
115 for (it = m_axisFilters.begin(); it != end; ++it) {
116 if (it->m_axisIdentifier == axisIdentifier)
117 return it->m_filter;
118 }
119
120 Input::AxisIdFilter axisIdFilter;
121 axisIdFilter.m_axisIdentifier = axisIdentifier;
122 m_axisFilters.push_back(axisIdFilter);
123 return m_axisFilters.last().m_filter;
124}
125
126Input::AxisSetting *QAbstractPhysicalDeviceBackendNodePrivate::getAxisSetting(Qt3DCore::QNodeId axisSettingId) const
127{
128 Q_Q(const QAbstractPhysicalDeviceBackendNode);
129 QInputAspectPrivate *aspectPrivate = static_cast<QInputAspectPrivate *>(Qt3DCore::QAbstractAspectPrivate::get(q->inputAspect()));
130 Input::InputHandler *handler = aspectPrivate->m_inputHandler.data();
131 Input::AxisSetting *axisSetting = handler->axisSettingManager()->getOrCreateResource(axisSettingId);
132 return axisSetting;
133}
134
135QVector<Input::AxisIdSetting> QAbstractPhysicalDeviceBackendNodePrivate::convertToAxisIdSettingVector(Qt3DCore::QNodeId axisSettingId) const
136{
137 const auto axisSetting = getAxisSetting(axisSettingId);
138 const auto axisIds = axisSetting->axes();
139
140 auto result = QVector<Input::AxisIdSetting>();
141 result.reserve(axisIds.size());
142 std::transform(axisIds.constBegin(), axisIds.constEnd(),
143 std::back_inserter(result),
144 [axisSettingId] (int axisId) {
145 return Input::AxisIdSetting{ axisId, axisSettingId };
146 });
147 return result;
148}
149
150void QAbstractPhysicalDeviceBackendNodePrivate::updatePendingAxisSettings()
151{
152 if (m_pendingAxisSettingIds.isEmpty())
153 return;
154
155 m_axisSettings = std::accumulate(
156 m_pendingAxisSettingIds.constBegin(), m_pendingAxisSettingIds.constEnd(),
157 QVector<Input::AxisIdSetting>(),
158 [this] (const QVector<Input::AxisIdSetting> &current, Qt3DCore::QNodeId axisSettingId) {
159 return current + convertToAxisIdSettingVector(axisSettingId);
160 });
161 m_pendingAxisSettingIds.clear();
162}
163
164QAbstractPhysicalDeviceBackendNode::QAbstractPhysicalDeviceBackendNode(QBackendNode::Mode mode)
165 : Qt3DCore::QBackendNode(*new QAbstractPhysicalDeviceBackendNodePrivate(mode))
166{
167}
168
169QAbstractPhysicalDeviceBackendNode::QAbstractPhysicalDeviceBackendNode(QAbstractPhysicalDeviceBackendNodePrivate &dd)
170 : Qt3DCore::QBackendNode(dd)
171{
172}
173
174void QAbstractPhysicalDeviceBackendNode::initializeFromPeer(const Qt3DCore::QNodeCreatedChangeBasePtr &change)
175{
176 const auto deviceChange = qSharedPointerCast<QPhysicalDeviceCreatedChangeBase>(change);
177 Q_D(QAbstractPhysicalDeviceBackendNode);
178 // Store the axis setting Ids. We will update the settings themselves when needed
179 d->m_pendingAxisSettingIds = deviceChange->axisSettingIds();
180}
181
182void QAbstractPhysicalDeviceBackendNode::cleanup()
183{
184 Q_D(QAbstractPhysicalDeviceBackendNode);
185 QBackendNode::setEnabled(false);
186 d->m_axisSettings.clear();
187 d->m_axisFilters.clear();
188 d->m_inputAspect = nullptr;
189}
190
191void QAbstractPhysicalDeviceBackendNode::sceneChangeEvent(const Qt3DCore::QSceneChangePtr &e)
192{
193 Q_D(QAbstractPhysicalDeviceBackendNode);
194 switch (e->type()) {
195 case Qt3DCore::PropertyValueAdded: {
196 const auto change = qSharedPointerCast<Qt3DCore::QPropertyNodeAddedChange>(e);
197 if (change->propertyName() == QByteArrayLiteral("axisSettings")) {
198 const auto axisSettingId = change->addedNodeId();
199 Input::AxisSetting *axisSetting = d->getAxisSetting(axisSettingId);
200 const auto axisIds = axisSetting->axes();
201 for (int axisId : axisIds)
202 d->addAxisSetting(axisId, axisSettingId);
203 }
204 break;
205 }
206
207 case Qt3DCore::PropertyValueRemoved: {
208 const auto change = qSharedPointerCast<Qt3DCore::QPropertyNodeRemovedChange>(e);
209 if (change->propertyName() == QByteArrayLiteral("axisSettings"))
210 d->removeAxisSetting(change->removedNodeId());
211 break;
212 }
213
214 default:
215 break;
216 }
217 QBackendNode::sceneChangeEvent(e);
218}
219
220void QAbstractPhysicalDeviceBackendNode::setInputAspect(QInputAspect *aspect)
221{
222 Q_D(QAbstractPhysicalDeviceBackendNode);
223 d->m_inputAspect = aspect;
224}
225
226QInputAspect *QAbstractPhysicalDeviceBackendNode::inputAspect() const
227{
228 Q_D(const QAbstractPhysicalDeviceBackendNode);
229 return d->m_inputAspect;
230}
231
232float QAbstractPhysicalDeviceBackendNode::processedAxisValue(int axisIdentifier)
233{
234 Q_D(QAbstractPhysicalDeviceBackendNode);
235 d->updatePendingAxisSettings();
236
237 // Find axis settings for this axis (if any)
238 Qt3DCore::QNodeId axisSettingId;
239 QVector<Input::AxisIdSetting>::const_iterator it;
240 QVector<Input::AxisIdSetting>::const_iterator end = d->m_axisSettings.cend();
241 for (it = d->m_axisSettings.cbegin(); it != end; ++it) {
242 if (it->m_axisIdentifier == axisIdentifier) {
243 axisSettingId = it->m_axisSettingsId;
244 break;
245 }
246 }
247
248 const float rawAxisValue = axisValue(axisIdentifier);
249 if (axisSettingId.isNull()) {
250 // No special processing. Just return the raw value
251 return rawAxisValue;
252 } else {
253 // Process the raw value in accordance with the settings
254 Input::AxisSetting *axisSetting = d->getAxisSetting(axisSettingId);
255 Q_ASSERT(axisSetting);
256 float val = rawAxisValue;
257
258 // Low pass smoothing
259 if (axisSetting->isSmoothEnabled()) {
260 // Get the filter corresponding to this axis
261 Input::MovingAverage &filter = d->getOrCreateFilter(axisIdentifier);
262 filter.addSample(val);
263 val = filter.average();
264 }
265
266 // Deadzone handling
267 const float d = axisSetting->deadZoneRadius();
268 if (!qFuzzyIsNull(d)) {
269 if (std::abs(val) <= d) {
270 val = 0.0f;
271 } else {
272 // Calculate value that goes from 0 to 1 linearly from the boundary of
273 // the dead zone up to 1. That is we with a dead zone value of d, we do not
274 // want a step change from 0 to d when the axis leaves the deadzone. Instead
275 // we want to increase the gradient of the line so that it goes from 0 to 1
276 // over the range d to 1. So instead of having y = x, the equation of the
277 // line becomes
278 //
279 // y = x / (1-d) - d / (1-d) = (x - d) / (1 - d)
280 //
281 // for positive values, and
282 //
283 // y = x / (1-d) + d / (1-d) = (x + d) / (1 - d)
284 //
285 // for negative values.
286 val = (val - signum(val) * d) / (1.0f - d);
287 }
288 }
289
290 return val;
291 }
292
293}
294
295} // Qt3DInput
296
297QT_END_NAMESPACE
298