Overte C++ Documentation
RouteBuilderProxy.h
1 //
2 // Created by Bradley Austin Davis 2015/10/09
3 // Copyright 2015 High Fidelity, Inc.
4 // Copyright 2023 Overte e.V.
5 //
6 // Distributed under the Apache License, Version 2.0.
7 // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
8 // SPDX-License-Identifier: Apache-2.0
9 //
10 
11 #pragma once
12 #ifndef hifi_Controllers_Impl_RouteBuilderProxy_h
13 #define hifi_Controllers_Impl_RouteBuilderProxy_h
14 
15 #include <QtCore/QObject>
16 
17 #include "Filter.h"
18 #include "Route.h"
19 #include "Mapping.h"
20 
21 #include "../UserInputMapper.h"
22 
23 class QJSValue;
24 class QJsonValue;
25 class ScriptValue;
26 
27 namespace controller {
28 
29 class ScriptingInterface;
30 
31 /*@jsdoc
32  * <p>A route in a {@link MappingObject} used by the {@link Controller} API.</p>
33  *
34  * <p>Create a route using {@link MappingObject} methods and apply this object's methods to process it, terminating with
35  * {@link RouteObject#to} to apply it to a <code>Standard</code> control, action, or script function. Note: Loops are not
36  * permitted.</p>
37  *
38  * <p>Some methods apply to routes with number data, some apply routes with {@link Pose} data, and some apply to both route
39  * types.<p>
40  *
41  * @class RouteObject
42  * @hideconstructor
43  *
44  * @hifi-interface
45  * @hifi-client-entity
46  * @hifi-avatar
47  */
48 
49 // TODO migrate functionality to a RouteBuilder class and make the proxy defer to that
50 // (for easier use in both C++ and JS)
51 class RouteBuilderProxy : public QObject {
52  Q_OBJECT
53  public:
54  RouteBuilderProxy(UserInputMapper& parent, Mapping::Pointer mapping, Route::Pointer route)
55  : _parent(parent), _mapping(mapping), _route(route) { }
56 
57  /*@jsdoc
58  * Terminates the route with a standard control, an action, or a script function. The output value from the route is
59  * sent to the specified destination.
60  * <p>This is a QML-specific version of {@link MappingObject#to|to}: use this version in QML files.</p>
61  * @function RouteObject#toQml
62  * @param {Controller.Standard|Controller.Actions|function} destination - The standard control, action, or JavaScript
63  * function that the route output is mapped to. For a function, the parameter can be either the name of the function or
64  * an in-line function definition.
65  */
66  Q_INVOKABLE void toQml(const QJSValue& destination);
67 
68  /*@jsdoc
69  * Processes the route only if a condition is satisfied. The condition is evaluated before the route input is read, and
70  * the input is read only if the condition is <code>true</code>. Thus, if the condition is not met then subsequent
71  * routes using the same input are processed.
72  * <p>This is a QML-specific version of {@link MappingObject#when|when}: use this version in QML files.</p>
73  * @function RouteObject#whenQml
74  * @param {condition|condition[]} expression - <p>A <code>condition</code> may be a:</p>
75  * <ul>
76  * <li>A boolean or numeric {@link Controller.Hardware} property, which is evaluated as a boolean.</li>
77  * <li><code>!</code> followed by a {@link Controller.Hardware} property, indicating the logical NOT should be
78  * used.</li>
79  * <li>A script function returning a boolean value. This can be either the name of the function or an in-line
80  * definition.</li>
81  * </ul>
82  * <p>If an array of conditions is provided, their values are ANDed together.</p>
83  * @returns {RouteObject} The <code>RouteObject</code> with the condition added.
84  */
85  Q_INVOKABLE QObject* whenQml(const QJSValue& expression);
86 
87  /*@jsdoc
88  * Terminates the route with a standard control, an action, or a script function. The output value from the route is
89  * sent to the specified destination.
90  * @function RouteObject#to
91  * @param {Controller.Standard|Controller.Actions|function} destination - The standard control, action, or JavaScript
92  * function that the route output is mapped to. For a function, the parameter can be either the name of the function or
93  * an in-line function definition.
94  *
95  * @example <caption>Make the right trigger move your avatar up.</caption>
96  * var MAPPING_NAME = "org.overte.controllers.example.newMapping";
97  * var mapping = Controller.newMapping(MAPPING_NAME);
98  *
99  * mapping.from(Controller.Standard.RT).to(Controller.Actions.TranslateY);
100  * Controller.enableMapping(MAPPING_NAME);
101  *
102  * Script.scriptEnding.connect(function () {
103  * Controller.disableMapping(MAPPING_NAME);
104  * });
105  *
106  * @example <caption>Make the right trigger call a function.</caption>
107  * function onRightTrigger(value) {
108  * print("Trigger value: " + value);
109  * }
110  *
111  * var MAPPING_NAME = "org.overte.controllers.example.newMapping";
112  * var mapping = Controller.newMapping(MAPPING_NAME);
113  *
114  * mapping.from(Controller.Standard.RT).to(onRightTrigger);
115  * Controller.enableMapping(MAPPING_NAME);
116  *
117  * Script.scriptEnding.connect(function () {
118  * Controller.disableMapping(MAPPING_NAME);
119  * });
120  */
121  Q_INVOKABLE void to(const ScriptValue& destination);
122 
123  /*@jsdoc
124  * Enables or disables writing debug information for a route to the program log.
125  * @function RouteObject#debug
126  * @param {boolean} [enable=true] - If <code>true</code> then writing debug information is enabled for the route,
127  * otherwise it is disabled.
128  * @returns {RouteObject} The <code>RouteObject</code> with debug output enabled or disabled.
129  * @example <caption>Write debug information to the program log for a right trigger mapping.</caption>
130  * var MAPPING_NAME = "org.overte.controllers.example.newMapping";
131  * var mapping = Controller.newMapping(MAPPING_NAME);
132  *
133  * mapping.from(Controller.Standard.RT).debug().to(function (value) {
134  * print("Value: " + value);
135  * });
136  *
137  * // Information similar to the following is written each frame:
138  * [DEBUG] [hifi.controllers] Beginning mapping frame
139  * [DEBUG] [hifi.controllers] Processing device routes
140  * [DEBUG] [hifi.controllers] Processing standard routes
141  * [DEBUG] [hifi.controllers] Applying route ""
142  * [DEBUG] [hifi.controllers] Value was 5.96046e-07
143  * [DEBUG] [hifi.controllers] Filtered value was 5.96046e-07
144  *
145  * Controller.enableMapping(MAPPING_NAME);
146  *
147  * Script.scriptEnding.connect(function () {
148  * Controller.disableMapping(MAPPING_NAME);
149  * });
150  */
151  Q_INVOKABLE QObject* debug(bool enable = true);
152 
153  /*@jsdoc
154  * Processes the route without marking the controller output as having been read, so that other routes from the same
155  * controller output can also process.
156  * @function RouteObject#peek
157  * @param {boolean} [enable=true] - If <code>true</code> then the route is processed without marking the route's
158  * controller source as having been read.
159  * @returns {RouteObject} The <code>RouteObject</code> with the peek feature enabled.
160  */
161  Q_INVOKABLE QObject* peek(bool enable = true);
162 
163  /*@jsdoc
164  * Processes the route only if a condition is satisfied. The condition is evaluated before the route input is read, and
165  * the input is read only if the condition is <code>true</code>. Thus, if the condition is not met then subsequent
166  * routes using the same input are processed.
167  * @function RouteObject#when
168  * @param {condition|condition[]} expression - <p>A <code>condition</code> may be a:</p>
169  * <ul>
170  * <li>A numeric {@link Controller.Hardware} property, which is evaluated as a boolean.</li>
171  * <li><code>!</code> followed by a {@link Controller.Hardware} property to use the logical NOT of the property
172  * value.</li>
173  * <li>A script function returning a boolean value. This can be either the name of the function or an in-line
174  * definition.</li>
175  * </ul>
176  * <p>If an array of conditions is provided, their values are ANDed together.</p>
177  * <p><strong>Warning:</strong> The use of <code>!</code> is not currently supported in JavaScript <code>.when()</code>
178  * calls.</p>
179  * @returns {RouteObject} The <code>RouteObject</code> with the condition added.
180  * @example <caption>Process the right trigger differently in HMD and desktop modes.</caption>
181  * var MAPPING_NAME = "org.overte.controllers.example.newMapping";
182  * var mapping = Controller.newMapping(MAPPING_NAME);
183  *
184  * // Processed only if in HMD mode.
185  * mapping.from(Controller.Standard.RT)
186  * .when(Controller.Hardware.Application.InHMD)
187  * .to(function () { print("Trigger pressed in HMD mode."); });
188  *
189  * // Processed only if previous route not processed.
190  * mapping.from(Controller.Standard.RT)
191  * .to(function () { print("Trigger pressed in desktop mode."); });
192  *
193  * Controller.enableMapping(MAPPING_NAME);
194  *
195  * Script.scriptEnding.connect(function () {
196  * Controller.disableMapping(MAPPING_NAME);
197  * });
198  */
199  Q_INVOKABLE QObject* when(const ScriptValue& expression);
200 
201  /*@jsdoc
202  * Filters numeric route values to lie between two values; values outside this range are not passed on through the
203  * route.
204  * @function RouteObject#clamp
205  * @param {number} min - The minimum value to pass through.
206  * @param {number} max - The maximum value to pass through.
207  * @returns {RouteObject} The route object with the clamp filter added.
208  * @example <caption>Clamp right trigger values to between 0.3 and 0.7.</caption>
209  * var MAPPING_NAME = "org.overte.controllers.example.newMapping";
210  * var mapping = Controller.newMapping(MAPPING_NAME);
211  * mapping.from(Controller.Standard.RT).clamp(0.3, 0.7).to(function (value) {
212  * print("Value: " + value);
213  * });
214  * Controller.enableMapping(MAPPING_NAME);
215  *
216  * Script.scriptEnding.connect(function () {
217  * Controller.disableMapping(MAPPING_NAME);
218  * });
219  */
220  Q_INVOKABLE QObject* clamp(float min, float max);
221 
222  /*@jsdoc
223  * Filters numeric route values such that they are rounded to <code>0</code> or <code>1</code> without output values
224  * flickering when the input value hovers around <code>0.5</code>. For example, this enables you to use an analog input
225  * as if it were a toggle.
226  * @function RouteObject#hysteresis
227  * @param {number} min - When the input value drops below this value the output value changes to <code>0</code>.
228  * @param {number} max - When the input value rises above this value the output value changes to <code>1</code>.
229  * @returns {RouteObject} The <code>RouteObject</code> with the filter applied.
230  * @example <caption>Round the right joystick forward/back values to 0 or 1 with hysteresis.</caption>
231  * var MAPPING_NAME = "org.overte.controllers.example.newMapping";
232  * var mapping = Controller.newMapping(MAPPING_NAME);
233  * mapping.from(Controller.Standard.RY).peek().to(function (value) {
234  * print("Raw value: " + value); // 0.0 - 1.0.
235  * });
236  * mapping.from(Controller.Standard.RY).hysteresis(0.3, 0.7).to(function (value) {
237  * print("Hysteresis value: " + value); // 0 or 1.
238  * });
239  * Controller.enableMapping(MAPPING_NAME);
240  *
241  * Script.scriptEnding.connect(function () {
242  * Controller.disableMapping(MAPPING_NAME);
243  * });
244  */
245  Q_INVOKABLE QObject* hysteresis(float min, float max);
246 
247  /*@jsdoc
248  * Filters numeric route values to send at a specified interval.
249  * @function RouteObject#pulse
250  * @param {number} interval - The interval between sending values, in seconds.
251  * @returns {RouteObject} The <code>RouteObject</code> with the filter applied.
252  * @example <caption>Send right trigger values every half second.</caption>
253  * var MAPPING_NAME = "org.overte.controllers.example.newMapping";
254  * var mapping = Controller.newMapping(MAPPING_NAME);
255  * mapping.from(Controller.Standard.RT).pulse(0.5).to(function (value) {
256  * print("Value: " + value);
257  * });
258  * Controller.enableMapping(MAPPING_NAME);
259  *
260  * Script.scriptEnding.connect(function () {
261  * Controller.disableMapping(MAPPING_NAME);
262  * });
263  */
264  Q_INVOKABLE QObject* pulse(float interval);
265 
266  /*@jsdoc
267  * Filters numeric and {@link Pose} route values to be scaled by a constant amount.
268  * @function RouteObject#scale
269  * @param {number} multiplier - The scale to multiply the value by.
270  * @returns {RouteObject} The <code>RouteObject</code> with the filter applied.
271  * @example <caption>Scale the value of the right joystick forward/back values by 10.</caption>
272  * var MAPPING_NAME = "org.overte.controllers.example.newMapping";
273  * var mapping = Controller.newMapping(MAPPING_NAME);
274  * mapping.from(Controller.Standard.LY).to(function (value) {
275  * print("L value: " + value); // -1.0 to 1.0 values.
276  * });
277  * mapping.from(Controller.Standard.RY).scale(10.0).to(function (value) {
278  * print("R value: " + value); // -10.0 to -10.0 values.
279  * });
280  * Controller.enableMapping(MAPPING_NAME);
281  *
282  * Script.scriptEnding.connect(function () {
283  * Controller.disableMapping(MAPPING_NAME);
284  * });
285  */
286  Q_INVOKABLE QObject* scale(float multiplier);
287 
288  /*@jsdoc
289  * Filters numeric and {@link Pose} route values to have the opposite sign, e.g., <code>0.5</code> is changed to
290  * <code>-0.5</code>.
291  * @function RouteObject#invert
292  * @returns {RouteObject} The <code>RouteObject</code> with the filter applied.
293  * @example <caption>Invert the value of the right joystick forward/back values.</caption>
294  * var MAPPING_NAME = "org.overte.controllers.example.newMapping";
295  * var mapping = Controller.newMapping(MAPPING_NAME);
296  * mapping.from(Controller.Standard.LY).to(function (value) {
297  * print("L value: " + value); // -1.0 to 1.0 values, forward to back.
298  * });
299  * mapping.from(Controller.Standard.RY).invert().to(function (value) {
300  * print("R value: " + value); // 1.0 to -1.0 values, forward to back.
301  * });
302  * Controller.enableMapping(MAPPING_NAME);
303  *
304  * Script.scriptEnding.connect(function () {
305  * Controller.disableMapping(MAPPING_NAME);
306  * });
307  */
308  Q_INVOKABLE QObject* invert();
309 
310  /*@jsdoc
311  * Filters numeric route values such that they're sent only when the input value is outside a dead-zone. When the input
312  * passes the dead-zone value, output is sent starting at <code>0.0</code> and catching up with the input value. As the
313  * input returns toward the dead-zone value, output values reduce to <code>0.0</code> at the dead-zone value.
314  * @function RouteObject#deadZone
315  * @param {number} min - The minimum input value at which to start sending output. For negative input values, the
316  * negative of this value is used.
317  * @returns {RouteObject} The <code>RouteObject</code> with the filter applied.
318  * @example <caption>Apply a dead-zone to the right joystick forward/back values.</caption>
319  * var MAPPING_NAME = "org.overte.controllers.example.newMapping";
320  * var mapping = Controller.newMapping(MAPPING_NAME);
321  * mapping.from(Controller.Standard.RY).deadZone(0.2).to(function (value) {
322  * print("Value: " + value); // 0.0 - 1.0 values once outside the dead-zone.
323  * });
324  * Controller.enableMapping(MAPPING_NAME);
325  *
326  * Script.scriptEnding.connect(function () {
327  * Controller.disableMapping(MAPPING_NAME);
328  * });
329  */
330  Q_INVOKABLE QObject* deadZone(float min);
331 
332  /*@jsdoc
333  * Filters numeric route values such that they are rounded to <code>-1</code>, <code>0</code>, or <code>1</code>.
334  * For example, this enables you to use an analog input as if it were a toggle or, in the case of a bidirectional axis,
335  * a tri-state switch.
336  * @function RouteObject#constrainToInteger
337  * @returns {RouteObject} The <code>RouteObject</code> with the filter applied.
338  * @example <caption>Round the right joystick forward/back values to <code>-1</code>, <code>0</code>, or
339  * <code>1</code>.</caption>
340  * var MAPPING_NAME = "org.overte.controllers.example.newMapping";
341  * var mapping = Controller.newMapping(MAPPING_NAME);
342  * mapping.from(Controller.Standard.RY).constrainToInteger().to(function (value) {
343  * print("Value: " + value); // -1, 0, or 1
344  * });
345  * Controller.enableMapping(MAPPING_NAME);
346  *
347  * Script.scriptEnding.connect(function () {
348  * Controller.disableMapping(MAPPING_NAME);
349  * });
350  */
351  Q_INVOKABLE QObject* constrainToInteger();
352 
353  /*@jsdoc
354  * Filters numeric route values such that they are rounded to <code>0</code> or <code>1</code>. For example, this
355  * enables you to use an analog input as if it were a toggle.
356  * @function RouteObject#constrainToPositiveInteger
357  * @returns {RouteObject} The <code>RouteObject</code> with the filter applied.
358  * @example <caption>Round the right joystick forward/back values to <code>0</code> or <code>1</code>.</caption>
359  * var MAPPING_NAME = "org.overte.controllers.example.newMapping";
360  * var mapping = Controller.newMapping(MAPPING_NAME);
361  * mapping.from(Controller.Standard.RY).constrainToPositiveInteger().to(function (value) {
362  * print("Value: " + value); // 0, or 1
363  * });
364  * Controller.enableMapping(MAPPING_NAME);
365  *
366  * Script.scriptEnding.connect(function () {
367  * Controller.disableMapping(MAPPING_NAME);
368  * });
369  */
370  Q_INVOKABLE QObject* constrainToPositiveInteger();
371 
372  /*@jsdoc
373  * Filters {@link Pose} route values to have a pre-translation applied.
374  * @function RouteObject#translate
375  * @param {Vec3} translate - The pre-translation to add to the pose.
376  * @returns {RouteObject} The <code>RouteObject</code> with the pre-translation applied.
377  */
378  // No JSDoc example because filter not currently used.
379  Q_INVOKABLE QObject* translate(glm::vec3 translate);
380 
381  /*@jsdoc
382  * Filters {@link Pose} route values to have a pre-transform applied.
383  * @function RouteObject#transform
384  * @param {Mat4} transform - The pre-transform to apply.
385  * @returns {RouteObject} The <code>RouteObject</code> with the pre-transform applied.
386  */
387  // No JSDoc example because filter not currently used.
388  Q_INVOKABLE QObject* transform(glm::mat4 transform);
389 
390  /*@jsdoc
391  * Filters {@link Pose} route values to have a post-transform applied.
392  * @function RouteObject#postTransform
393  * @param {Mat4} transform - The post-transform to apply.
394  * @returns {RouteObject} The <code>RouteObject</code> with the post-transform applied.
395  */
396  // No JSDoc example because filter not currently used.
397  Q_INVOKABLE QObject* postTransform(glm::mat4 transform);
398 
399  /*@jsdoc
400  * Filters {@link Pose} route values to have a pre-rotation applied.
401  * @function RouteObject#rotate
402  * @param {Quat} rotation - The pre-rotation to add to the pose.
403  * @returns {RouteObject} The <code>RouteObject</code> with the pre-rotation applied.
404  */
405  // No JSDoc example because filter not currently used.
406  Q_INVOKABLE QObject* rotate(glm::quat rotation);
407 
408  /*@jsdoc
409  * Filters {@link Pose} route values to be smoothed by a low velocity filter. The filter's rotation and translation
410  * values are calculated as: <code>(1 - f) * currentValue + f * previousValue</code> where
411  * <code>f = currentVelocity / filterConstant</code>. At low velocities, the filter value is largely the previous
412  * value; at high velocities the value is wholly the current controller value.
413  * @function RouteObject#lowVelocity
414  * @param {number} rotationConstant - The rotational velocity, in rad/s, at which the filter value is wholly the latest
415  * controller value.
416  * @param {number} translationConstant - The linear velocity, in m/s, at which the filter value is wholly the latest
417  * controller value.
418  * @returns {RouteObject} The <code>RouteObject</code> smoothed by low velocity filtering.
419  */
420  // No JSDoc example because filter not currently used.
421  Q_INVOKABLE QObject* lowVelocity(float rotationConstant, float translationConstant);
422 
423  /*@jsdoc
424  * Filters {@link Pose} route values to be smoothed by an exponential decay filter. The filter's rotation and
425  * translation values are calculated as: <code>filterConstant * currentValue + (1 - filterConstant) *
426  * previousValue</code>. Values near 1 are less smooth with lower latency; values near 0 are more smooth with higher
427  * latency.
428  * @function RouteObject#exponentialSmoothing
429  * @param {number} rotationConstant - Rotation filter constant, <code>0.0&ndash;1.0</code>.
430  * @param {number} translationConstant - Translation filter constant, <code>0.0&ndash;1.0</code>.
431  * @returns {RouteObject} The <code>RouteObject</code> smoothed by an exponential filter.
432  */
433  // No JSDoc example because filter used only in Vive.json.
434  Q_INVOKABLE QObject* exponentialSmoothing(float rotationConstant, float translationConstant);
435 
436  /*@jsdoc
437  * Filters numeric route values such that a value of <code>0.0</code> is changed to <code>1.0</code>, and other values
438  * are changed to <code>0.0</code>.
439  * @function RouteObject#logicalNot
440  * @returns {RouteObject} The <code>RouteObject</code> with the filter applied.
441  * @example <caption>Logical NOT of LSTouch value.</caption>
442  * var MAPPING_NAME = "org.overte.controllers.example.newMapping";
443  * var mapping = Controller.newMapping(MAPPING_NAME);
444  *
445  * mapping.from(Controller.Standard.RSTouch).peek().to(function (value) {
446  * print("RSTouch: " + value);
447  * });
448  * mapping.from(Controller.Standard.RSTouch).logicalNot().to(function (value) {
449  * print("localNot of RSTouch: " + value);
450  * });
451  * Controller.enableMapping(MAPPING_NAME);
452  *
453  * Script.scriptEnding.connect(function () {
454  * Controller.disableMapping(MAPPING_NAME);
455  * });
456  */
457  Q_INVOKABLE QObject* logicalNot();
458 
459  private:
460  void to(const Endpoint::Pointer& destination);
461  void conditional(const Conditional::Pointer& conditional);
462  void addFilter(Filter::Pointer filter);
463  UserInputMapper& _parent;
464  Mapping::Pointer _mapping;
465  Route::Pointer _route;
466  };
467 
468 }
469 #endif
[ScriptInterface] Provides an engine-independent interface for QScriptValue
Definition: ScriptValue.h:40