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 | |
58 | QT_BEGIN_NAMESPACE |
59 | |
60 | namespace { |
61 | |
62 | Q_DECL_CONSTEXPR int signum(float v) |
63 | { |
64 | return (v > 0.0f) - (v < 0.0f); |
65 | } |
66 | |
67 | } |
68 | |
69 | namespace Qt3DInput { |
70 | |
71 | QAbstractPhysicalDeviceBackendNodePrivate::QAbstractPhysicalDeviceBackendNodePrivate(Qt3DCore::QBackendNode::Mode mode) |
72 | : Qt3DCore::QBackendNodePrivate(mode) |
73 | , m_axisSettings() |
74 | , m_inputAspect(nullptr) |
75 | { |
76 | } |
77 | |
78 | void 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 | |
100 | void 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 | |
111 | Input::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 | |
126 | Input::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 | |
135 | QVector<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 | |
150 | void 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> ¤t, Qt3DCore::QNodeId axisSettingId) { |
159 | return current + convertToAxisIdSettingVector(axisSettingId); |
160 | }); |
161 | m_pendingAxisSettingIds.clear(); |
162 | } |
163 | |
164 | QAbstractPhysicalDeviceBackendNode::QAbstractPhysicalDeviceBackendNode(QBackendNode::Mode mode) |
165 | : Qt3DCore::QBackendNode(*new QAbstractPhysicalDeviceBackendNodePrivate(mode)) |
166 | { |
167 | } |
168 | |
169 | QAbstractPhysicalDeviceBackendNode::QAbstractPhysicalDeviceBackendNode(QAbstractPhysicalDeviceBackendNodePrivate &dd) |
170 | : Qt3DCore::QBackendNode(dd) |
171 | { |
172 | } |
173 | |
174 | void 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 | |
182 | void 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 | |
191 | void 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 | |
220 | void QAbstractPhysicalDeviceBackendNode::setInputAspect(QInputAspect *aspect) |
221 | { |
222 | Q_D(QAbstractPhysicalDeviceBackendNode); |
223 | d->m_inputAspect = aspect; |
224 | } |
225 | |
226 | QInputAspect *QAbstractPhysicalDeviceBackendNode::inputAspect() const |
227 | { |
228 | Q_D(const QAbstractPhysicalDeviceBackendNode); |
229 | return d->m_inputAspect; |
230 | } |
231 | |
232 | float 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 | |
297 | QT_END_NAMESPACE |
298 | |