mirror of
https://github.com/overte-org/overte.git
synced 2025-08-12 14:34:43 +02:00
Merge branch 'controllers' of https://github.com/highfidelity/hifi into controllers
This commit is contained in:
commit
bffce7b92c
28 changed files with 305 additions and 163 deletions
|
@ -11,7 +11,7 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
Script.include("../libraries/utils.js");
|
||||
Script.include("libraries/utils.js");
|
||||
Script.include("breakdanceCore.js");
|
||||
breakdanceStart();
|
||||
Script.update.connect(breakdanceUpdate);
|
|
@ -43,7 +43,8 @@ strokeSpeed[1] = 0.0;
|
|||
|
||||
function checkSticks(deltaTime) {
|
||||
for (var palm = 0; palm < 2; palm++) {
|
||||
var palmVelocity = Controller.getSpatialControlVelocity(palm * 2 + 1);
|
||||
var handPose = (palm == 0) ? MyAvatar.leftHandPose : MyAvatar.rightHandPose;
|
||||
var palmVelocity = handPose.velocity;
|
||||
var speed = length(palmVelocity);
|
||||
|
||||
const TRIGGER_SPEED = 0.30; // Lower this value to let you 'drum' more gently
|
||||
|
@ -64,7 +65,7 @@ function checkSticks(deltaTime) {
|
|||
if ((palmVelocity.y > 0.0) || (speed < STOP_SPEED)) {
|
||||
state[palm] = 0;
|
||||
|
||||
var options = { position: Controller.getSpatialControlPosition(palm * 2 + 1) };
|
||||
var options = { position: handPose.translation };
|
||||
|
||||
if (strokeSpeed[palm] > 1.0) { strokeSpeed[palm] = 1.0; }
|
||||
options.volume = strokeSpeed[palm];
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
//
|
||||
|
||||
(function() {
|
||||
Script.include("../toys/breakdanceCore.js");
|
||||
Script.include("../breakdanceCore.js");
|
||||
Script.include("../libraries/utils.js");
|
||||
|
||||
var _this;
|
||||
|
|
|
@ -10,25 +10,19 @@
|
|||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
// initialize our triggers
|
||||
var triggerPulled = new Array();
|
||||
var numberOfTriggers = Controller.getNumberOfTriggers();
|
||||
for (t = 0; t < numberOfTriggers; t++) {
|
||||
var NUMBER_OF_TRIGGERS = 2;
|
||||
for (t = 0; t < NUMBER_OF_TRIGGERS; t++) {
|
||||
triggerPulled[t] = false;
|
||||
}
|
||||
|
||||
var triggers = new Array();
|
||||
triggers[0] = Controller.Standard.LT;
|
||||
triggers[1] = Controller.Standard.RT;
|
||||
function checkController(deltaTime) {
|
||||
var numberOfTriggers = Controller.getNumberOfTriggers();
|
||||
var numberOfSpatialControls = Controller.getNumberOfSpatialControls();
|
||||
var controllersPerTrigger = numberOfSpatialControls / numberOfTriggers;
|
||||
var triggerToggled = false;
|
||||
|
||||
// this is expected for hydras
|
||||
if (numberOfTriggers == 2 && controllersPerTrigger == 2) {
|
||||
for (var t = 0; t < numberOfTriggers; t++) {
|
||||
var triggerValue = Controller.getTriggerValue(t);
|
||||
|
||||
for (var t = 0; t < NUMBER_OF_TRIGGERS; t++) {
|
||||
var triggerValue = Controller.getValue(triggers[t]]);
|
||||
if (triggerPulled[t]) {
|
||||
// must release to at least 0.1
|
||||
if (triggerValue < 0.1) {
|
||||
|
@ -41,17 +35,14 @@ function checkController(deltaTime) {
|
|||
triggerToggled = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (triggerToggled) {
|
||||
print("a trigger was toggled");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// register the call back so it fires before each data send
|
||||
Script.update.connect(checkController);
|
||||
|
||||
function printKeyEvent(eventName, event) {
|
||||
print(eventName);
|
||||
print(" event.key=" + event.key);
|
||||
|
@ -64,7 +55,6 @@ function printKeyEvent(eventName, event) {
|
|||
}
|
||||
function keyPressEvent(event) {
|
||||
printKeyEvent("keyPressEvent", event);
|
||||
|
||||
if (event.text == "A") {
|
||||
print("the A key was pressed");
|
||||
}
|
||||
|
@ -72,10 +62,8 @@ function keyPressEvent(event) {
|
|||
print("the <space> key was pressed");
|
||||
}
|
||||
}
|
||||
|
||||
function keyReleaseEvent(event) {
|
||||
printKeyEvent("keyReleaseEvent", event);
|
||||
|
||||
if (event.text == "A") {
|
||||
print("the A key was released");
|
||||
}
|
||||
|
@ -83,11 +71,9 @@ function keyReleaseEvent(event) {
|
|||
print("the <space> key was pressed");
|
||||
}
|
||||
}
|
||||
|
||||
// Map keyPress and mouse move events to our callbacks
|
||||
Controller.keyPressEvent.connect(keyPressEvent);
|
||||
Controller.keyReleaseEvent.connect(keyReleaseEvent);
|
||||
|
||||
// prevent the A key from going through to the application
|
||||
Controller.captureKeyEvents({ text: "A" });
|
||||
Controller.captureKeyEvents({ key: "A".charCodeAt(0) }); // same as above, just another example of how to capture the key
|
||||
|
@ -95,8 +81,6 @@ Controller.captureKeyEvents({ text: " " });
|
|||
Controller.captureKeyEvents({ text: "@", isMeta: true });
|
||||
Controller.captureKeyEvents({ text: "page up" });
|
||||
Controller.captureKeyEvents({ text: "page down" });
|
||||
|
||||
|
||||
function printMouseEvent(eventName, event) {
|
||||
print(eventName);
|
||||
print(" event.x,y=" + event.x + ", " + event.y);
|
||||
|
@ -109,22 +93,18 @@ function printMouseEvent(eventName, event) {
|
|||
print(" event.isMeta=" + event.isMeta);
|
||||
print(" event.isAlt=" + event.isAlt);
|
||||
}
|
||||
|
||||
function mouseMoveEvent(event) {
|
||||
printMouseEvent("mouseMoveEvent", event);
|
||||
}
|
||||
function mousePressEvent(event) {
|
||||
printMouseEvent("mousePressEvent", event);
|
||||
}
|
||||
|
||||
function mouseReleaseEvent(event) {
|
||||
printMouseEvent("mouseReleaseEvent", event);
|
||||
}
|
||||
|
||||
Controller.mouseMoveEvent.connect(mouseMoveEvent);
|
||||
Controller.mousePressEvent.connect(mousePressEvent);
|
||||
Controller.mouseReleaseEvent.connect(mouseReleaseEvent);
|
||||
|
||||
function printTouchEvent(eventName, event) {
|
||||
print(eventName);
|
||||
|
||||
|
@ -143,7 +123,6 @@ function printTouchEvent(eventName, event) {
|
|||
print(" event.radius=" + event.radius);
|
||||
print(" event.isPinching=" + event.isPinching);
|
||||
print(" event.isPinchOpening=" + event.isPinchOpening);
|
||||
|
||||
print(" event.angle=" + event.angle);
|
||||
for (var i = 0; i < event.points.length; i++) {
|
||||
print(" event.angles[" + i + "]:" + event.angles[i]);
|
||||
|
@ -151,15 +130,12 @@ function printTouchEvent(eventName, event) {
|
|||
print(" event.isRotating=" + event.isRotating);
|
||||
print(" event.rotating=" + event.rotating);
|
||||
}
|
||||
|
||||
function touchBeginEvent(event) {
|
||||
printTouchEvent("touchBeginEvent", event);
|
||||
}
|
||||
|
||||
function touchUpdateEvent(event) {
|
||||
printTouchEvent("touchUpdateEvent", event);
|
||||
}
|
||||
|
||||
function touchEndEvent(event) {
|
||||
printTouchEvent("touchEndEvent", event);
|
||||
}
|
||||
|
@ -167,8 +143,6 @@ function touchEndEvent(event) {
|
|||
Controller.touchBeginEvent.connect(touchBeginEvent);
|
||||
Controller.touchUpdateEvent.connect(touchUpdateEvent);
|
||||
Controller.touchEndEvent.connect(touchEndEvent);
|
||||
|
||||
|
||||
function wheelEvent(event) {
|
||||
print("wheelEvent");
|
||||
print(" event.x,y=" + event.x + ", " + event.y);
|
||||
|
@ -182,9 +156,7 @@ function wheelEvent(event) {
|
|||
print(" event.isMeta=" + event.isMeta);
|
||||
print(" event.isAlt=" + event.isAlt);
|
||||
}
|
||||
|
||||
Controller.wheelEvent.connect(wheelEvent);
|
||||
|
||||
function scriptEnding() {
|
||||
// re-enabled the standard application for touch events
|
||||
Controller.releaseKeyEvents({ text: "A" });
|
||||
|
@ -194,5 +166,4 @@ function scriptEnding() {
|
|||
Controller.releaseKeyEvents({ text: "page up" });
|
||||
Controller.releaseKeyEvents({ text: "page down" });
|
||||
}
|
||||
|
||||
Script.scriptEnding.connect(scriptEnding);
|
||||
Script.scriptEnding.connect(scriptEnding);
|
|
@ -15,16 +15,18 @@ Script.include("omniTool/models/invisibleWand.js");
|
|||
|
||||
OmniToolModules = {};
|
||||
OmniToolModuleType = null;
|
||||
LOG_DEBUG = 1;
|
||||
|
||||
OmniTool = function(side) {
|
||||
OmniTool = function(left) {
|
||||
this.OMNI_KEY = "OmniTool";
|
||||
this.MAX_FRAMERATE = 60;
|
||||
this.UPDATE_INTERVAL = 1.0 / this.MAX_FRAMERATE
|
||||
this.SIDE = side;
|
||||
this.PALM = 2 * side;
|
||||
this.ACTION = findAction(side ? "ACTION2" : "ACTION1");
|
||||
this.ALT_ACTION = findAction(side ? "ACTION1" : "ACTION2");
|
||||
|
||||
this.left = left;
|
||||
this.triggered = false;
|
||||
var actions = Controller.Actions;
|
||||
var standard = Controller.Standard;
|
||||
this.palmControl = left ? actions.LeftHand : actions.RightHand;
|
||||
logDebug("Init OmniTool " + (left ? "left" : "right"));
|
||||
this.highlighter = new Highlighter();
|
||||
this.ignoreEntities = {};
|
||||
this.nearestOmniEntity = {
|
||||
|
@ -47,22 +49,25 @@ OmniTool = function(side) {
|
|||
this.showWand(false);
|
||||
|
||||
// Connect to desired events
|
||||
var _this = this;
|
||||
Controller.actionEvent.connect(function(action, state) {
|
||||
_this.onActionEvent(action, state);
|
||||
});
|
||||
var that = this;
|
||||
|
||||
Script.update.connect(function(deltaTime) {
|
||||
_this.lastUpdateInterval += deltaTime;
|
||||
if (_this.lastUpdateInterval >= _this.UPDATE_INTERVAL) {
|
||||
_this.onUpdate(_this.lastUpdateInterval);
|
||||
_this.lastUpdateInterval = 0;
|
||||
that.lastUpdateInterval += deltaTime;
|
||||
if (that.lastUpdateInterval >= that.UPDATE_INTERVAL) {
|
||||
that.onUpdate(that.lastUpdateInterval);
|
||||
that.lastUpdateInterval = 0;
|
||||
}
|
||||
});
|
||||
|
||||
Script.scriptEnding.connect(function() {
|
||||
_this.onCleanup();
|
||||
that.onCleanup();
|
||||
});
|
||||
|
||||
this.mapping = Controller.newMapping();
|
||||
this.mapping.from(left ? standard.LeftPrimaryThumb : standard.RightPrimaryThumb).to(function(value){
|
||||
that.onUpdateTrigger(value);
|
||||
})
|
||||
this.mapping.enable();
|
||||
}
|
||||
|
||||
OmniTool.prototype.showWand = function(show) {
|
||||
|
@ -81,30 +86,23 @@ OmniTool.prototype.showWand = function(show) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
OmniTool.prototype.onCleanup = function(action) {
|
||||
this.mapping.disable();
|
||||
this.unloadModule();
|
||||
}
|
||||
|
||||
OmniTool.prototype.onActionEvent = function(action, state) {
|
||||
// FIXME figure out the issues when only one spatial controller is active
|
||||
// logDebug("Action: " + action + " " + state);
|
||||
|
||||
if (this.module && this.module.onActionEvent) {
|
||||
this.module.onActionEvent(action, state);
|
||||
}
|
||||
|
||||
if (action == this.ACTION) {
|
||||
if (state) {
|
||||
OmniTool.prototype.onUpdateTrigger = function (value) {
|
||||
//logDebug("Trigger update value " + value);
|
||||
var triggered = value != 0;
|
||||
if (triggered != this.triggered) {
|
||||
this.triggered = triggered;
|
||||
if (this.triggered) {
|
||||
this.onClick();
|
||||
} else {
|
||||
this.onRelease();
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME Does not work
|
||||
//// with only one controller active (listed as 2 here because 'tip' + 'palm')
|
||||
//// then treat the alt action button as the action button
|
||||
}
|
||||
|
||||
OmniTool.prototype.getOmniToolData = function(entityId) {
|
||||
|
@ -127,7 +125,7 @@ OmniTool.prototype.setActive = function(active) {
|
|||
if (active === this.active) {
|
||||
return;
|
||||
}
|
||||
logDebug("OmniTool changing active state: " + active);
|
||||
logDebug("OmniTool " + this.left + " changing active state: " + active);
|
||||
this.active = active;
|
||||
this.model.setVisible(this.active);
|
||||
if (this.module && this.module.onActiveChanged) {
|
||||
|
@ -138,17 +136,17 @@ OmniTool.prototype.setActive = function(active) {
|
|||
|
||||
OmniTool.prototype.onUpdate = function(deltaTime) {
|
||||
// FIXME this returns data if either the left or right controller is not on the base
|
||||
this.position = Controller.getSpatialControlPosition(this.PALM);
|
||||
this.pose = Controller.getPoseValue(this.palmControl);
|
||||
this.position = this.left ? MyAvatar.leftHandTipPosition : MyAvatar.rightHandTipPosition;
|
||||
// When on the base, hydras report a position of 0
|
||||
this.setActive(Vec3.length(this.position) > 0.001);
|
||||
if (!this.active) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (this.model) {
|
||||
// Update the wand
|
||||
var rawRotation = Controller.getSpatialControlRawRotation(this.PALM);
|
||||
var rawRotation = this.pose.rotation;
|
||||
this.rotation = Quat.multiply(MyAvatar.orientation, rawRotation);
|
||||
this.model.setTransform({
|
||||
rotation: this.rotation,
|
||||
|
@ -306,6 +304,7 @@ OmniTool.prototype.scan = function() {
|
|||
}
|
||||
|
||||
OmniTool.prototype.unloadModule = function() {
|
||||
logDebug("Unloading omniTool module")
|
||||
if (this.module && this.module.onUnload) {
|
||||
this.module.onUnload();
|
||||
}
|
||||
|
@ -348,4 +347,4 @@ OmniTool.prototype.activateNewOmniModule = function() {
|
|||
}
|
||||
|
||||
// FIXME find a good way to sync the two omni tools
|
||||
OMNI_TOOLS = [ new OmniTool(0), new OmniTool(1) ];
|
||||
OMNI_TOOLS = [ new OmniTool(true), new OmniTool(false) ];
|
||||
|
|
|
@ -31,13 +31,7 @@ scaleLine = function (start, end, scale) {
|
|||
}
|
||||
|
||||
findAction = function(name) {
|
||||
var actions = Controller.getAllActions();
|
||||
for (var i = 0; i < actions.length; i++) {
|
||||
if (actions[i].actionName == name) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
return Controller.findAction(name);
|
||||
}
|
||||
|
||||
addLine = function(origin, vector, color) {
|
||||
|
|
|
@ -7,10 +7,10 @@
|
|||
//
|
||||
|
||||
// FIXME Script paths have to be relative to the caller, in this case libraries/OmniTool.js
|
||||
Script.include("../entityScripts/magBalls/constants.js");
|
||||
Script.include("../entityScripts/magBalls/graph.js");
|
||||
Script.include("../entityScripts/magBalls/edgeSpring.js");
|
||||
Script.include("../entityScripts/magBalls/magBalls.js");
|
||||
Script.include("../magBalls/constants.js");
|
||||
Script.include("../magBalls/graph.js");
|
||||
Script.include("../magBalls/edgeSpring.js");
|
||||
Script.include("../magBalls/magBalls.js");
|
||||
Script.include("avatarRelativeOverlays.js");
|
||||
|
||||
OmniToolModuleType = "MagBallsController"
|
|
@ -627,7 +627,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
|
||||
// Setup the userInputMapper with the actions
|
||||
auto userInputMapper = DependencyManager::get<UserInputMapper>();
|
||||
connect(userInputMapper.data(), &UserInputMapper::actionEvent, _controllerScriptingInterface, &ControllerScriptingInterface::actionEvent);
|
||||
connect(userInputMapper.data(), &UserInputMapper::actionEvent, [this](int action, float state) {
|
||||
if (state && action == toInt(controller::Action::TOGGLE_MUTE)) {
|
||||
DependencyManager::get<AudioClient>()->toggleMute();
|
||||
|
|
|
@ -596,6 +596,34 @@ glm::vec3 MyAvatar::getRightHandTipPosition() const {
|
|||
return palmData ? palmData->getTipPosition() : glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
controller::Pose MyAvatar::getLeftHandPose() const {
|
||||
const int LEFT_HAND = 0;
|
||||
auto palmData = getActivePalm(LEFT_HAND);
|
||||
return palmData ? controller::Pose(palmData->getPosition(), palmData->getRotation(),
|
||||
palmData->getVelocity(), palmData->getRawAngularVelocityAsQuat()) : controller::Pose();
|
||||
}
|
||||
|
||||
controller::Pose MyAvatar::getRightHandPose() const {
|
||||
const int RIGHT_HAND = 1;
|
||||
auto palmData = getActivePalm(RIGHT_HAND);
|
||||
return palmData ? controller::Pose(palmData->getPosition(), palmData->getRotation(),
|
||||
palmData->getVelocity(), palmData->getRawAngularVelocityAsQuat()) : controller::Pose();
|
||||
}
|
||||
|
||||
controller::Pose MyAvatar::getLeftHandTipPose() const {
|
||||
const int LEFT_HAND = 0;
|
||||
auto palmData = getActivePalm(LEFT_HAND);
|
||||
return palmData ? controller::Pose(palmData->getTipPosition(), palmData->getRotation(),
|
||||
palmData->getTipVelocity(), palmData->getRawAngularVelocityAsQuat()) : controller::Pose();
|
||||
}
|
||||
|
||||
controller::Pose MyAvatar::getRightHandTipPose() const {
|
||||
const int RIGHT_HAND = 1;
|
||||
auto palmData = getActivePalm(RIGHT_HAND);
|
||||
return palmData ? controller::Pose(palmData->getTipPosition(), palmData->getRotation(),
|
||||
palmData->getTipVelocity(), palmData->getRawAngularVelocityAsQuat()) : controller::Pose();
|
||||
}
|
||||
|
||||
// virtual
|
||||
void MyAvatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
|
||||
// don't render if we've been asked to disable local rendering
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
#include <DynamicCharacterController.h>
|
||||
#include <Rig.h>
|
||||
|
||||
#include <controllers/Pose.h>
|
||||
|
||||
#include "Avatar.h"
|
||||
#include "AtRestDetector.h"
|
||||
|
||||
|
@ -64,10 +66,15 @@ class MyAvatar : public Avatar {
|
|||
//TODO: make gravity feature work Q_PROPERTY(glm::vec3 gravity READ getGravity WRITE setGravity)
|
||||
|
||||
|
||||
Q_PROPERTY(glm::vec3 leftHandPosition READ getLeftHandPosition)
|
||||
Q_PROPERTY(glm::vec3 rightHandPosition READ getRightHandPosition)
|
||||
Q_PROPERTY(glm::vec3 leftHandTipPosition READ getLeftHandTipPosition)
|
||||
Q_PROPERTY(glm::vec3 rightHandTipPosition READ getRightHandTipPosition)
|
||||
Q_PROPERTY(glm::vec3 leftHandPosition READ getLeftHandPosition)
|
||||
Q_PROPERTY(glm::vec3 rightHandPosition READ getRightHandPosition)
|
||||
Q_PROPERTY(glm::vec3 leftHandTipPosition READ getLeftHandTipPosition)
|
||||
Q_PROPERTY(glm::vec3 rightHandTipPosition READ getRightHandTipPosition)
|
||||
|
||||
Q_PROPERTY(controller::Pose leftHandPose READ getLeftHandPose)
|
||||
Q_PROPERTY(controller::Pose rightHandPose READ getRightHandPose)
|
||||
Q_PROPERTY(controller::Pose leftHandTipPose READ getLeftHandTipPose)
|
||||
Q_PROPERTY(controller::Pose rightHandTipPose READ getRightHandTipPose)
|
||||
|
||||
public:
|
||||
MyAvatar(RigPointer rig);
|
||||
|
@ -160,6 +167,11 @@ public:
|
|||
Q_INVOKABLE glm::vec3 getLeftHandTipPosition() const;
|
||||
Q_INVOKABLE glm::vec3 getRightHandTipPosition() const;
|
||||
|
||||
Q_INVOKABLE controller::Pose getLeftHandPose() const;
|
||||
Q_INVOKABLE controller::Pose getRightHandPose() const;
|
||||
Q_INVOKABLE controller::Pose getLeftHandTipPose() const;
|
||||
Q_INVOKABLE controller::Pose getRightHandTipPose() const;
|
||||
|
||||
AvatarWeakPointer getLookAtTargetAvatar() const { return _lookAtTargetAvatar; }
|
||||
void updateLookAtTargetAvatar();
|
||||
void clearLookAtTargetAvatar();
|
||||
|
|
|
@ -120,8 +120,6 @@ signals:
|
|||
|
||||
void wheelEvent(const WheelEvent& event);
|
||||
|
||||
void actionEvent(int action, float state);
|
||||
|
||||
private:
|
||||
QString sanatizeName(const QString& name); /// makes a name clean for inclusing in JavaScript
|
||||
|
||||
|
|
|
@ -101,8 +101,11 @@ public:
|
|||
void setRawPosition(const glm::vec3& pos) { _rawPosition = pos; }
|
||||
void setRawVelocity(const glm::vec3& velocity) { _rawVelocity = velocity; }
|
||||
const glm::vec3& getRawVelocity() const { return _rawVelocity; }
|
||||
|
||||
void setRawAngularVelocity(const glm::vec3& angularVelocity) { _rawAngularVelocity = angularVelocity; }
|
||||
const glm::vec3& getRawAngularVelocity() const { return _rawAngularVelocity; }
|
||||
glm::quat getRawAngularVelocityAsQuat() const { return glm::quat(_rawAngularVelocity); }
|
||||
|
||||
void addToPosition(const glm::vec3& delta);
|
||||
|
||||
void addToPenetration(const glm::vec3& penetration) { _totalPenetration += penetration; }
|
||||
|
@ -155,6 +158,7 @@ private:
|
|||
glm::vec3 _rawPosition;
|
||||
glm::vec3 _rawVelocity;
|
||||
glm::vec3 _rawAngularVelocity;
|
||||
glm::quat _rawDeltaRotation;
|
||||
glm::quat _lastRotation;
|
||||
|
||||
glm::vec3 _tipPosition;
|
||||
|
|
37
libraries/controllers/src/controllers/Forward.h
Normal file
37
libraries/controllers/src/controllers/Forward.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis 2015/10/20
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef hifi_Controllers_Forward_h
|
||||
#define hifi_Controllers_Forward_h
|
||||
|
||||
namespace controller {
|
||||
|
||||
class Endpoint;
|
||||
using EndpointPointer = std::shared_ptr<Endpoint>;
|
||||
using EndpointList = std::list<EndpointPointer>;
|
||||
|
||||
class Filter;
|
||||
using FilterPointer = std::shared_ptr<Filter>;
|
||||
using FilterList = std::list<FilterPointer>;
|
||||
|
||||
class Route;
|
||||
using RoutePointer = std::shared_ptr<Route>;
|
||||
using RouteList = std::list<RoutePointer>;
|
||||
|
||||
class Conditional;
|
||||
using ConditionalPointer = std::shared_ptr<Conditional>;
|
||||
using ConditionalList = std::list<ConditionalPointer>;
|
||||
|
||||
class Mapping;
|
||||
using MappingPointer = std::shared_ptr<Mapping>;
|
||||
using MappingList = std::list<MappingPointer>;
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -9,10 +9,9 @@
|
|||
#include "Input.h"
|
||||
|
||||
namespace controller {
|
||||
|
||||
const uint16_t Input::INVALID_DEVICE = 0xffff;
|
||||
const uint16_t Input::INVALID_CHANNEL = 0x1fff;
|
||||
const uint16_t Input::INVALID_TYPE = (uint16_t)ChannelType::INVALID;
|
||||
const Input Input::INVALID_INPUT = Input(INVALID_DEVICE, INVALID_CHANNEL, ChannelType::INVALID);
|
||||
const Input Input::INVALID_INPUT = Input(0x7fffffff);
|
||||
const uint16_t Input::INVALID_DEVICE = Input::INVALID_INPUT.device;
|
||||
const uint16_t Input::INVALID_CHANNEL = Input::INVALID_INPUT.channel;
|
||||
const uint16_t Input::INVALID_TYPE = Input::INVALID_INPUT.type;
|
||||
}
|
||||
|
||||
|
|
|
@ -16,10 +16,12 @@
|
|||
namespace controller {
|
||||
|
||||
enum class ChannelType {
|
||||
INVALID = 0,
|
||||
BUTTON = 1,
|
||||
UNKNOWN = 0,
|
||||
BUTTON,
|
||||
AXIS,
|
||||
POSE,
|
||||
RUMBLE,
|
||||
INVALID = 0x7
|
||||
};
|
||||
|
||||
// Input is the unique identifier to find a n input channel of a particular device
|
||||
|
@ -30,8 +32,8 @@ struct Input {
|
|||
uint32_t id{ 0 }; // by default Input is 0 meaning invalid
|
||||
struct {
|
||||
uint16_t device; // Up to 64K possible devices
|
||||
uint16_t channel : 13 ; // 2^13 possible channel per Device
|
||||
uint16_t type : 2; // 2 bits to store the Type directly in the ID
|
||||
uint16_t channel : 12 ; // 2^12 possible channel per Device
|
||||
uint16_t type : 3; // 2 bits to store the Type directly in the ID
|
||||
uint16_t padding : 1; // 2 bits to store the Type directly in the ID
|
||||
};
|
||||
};
|
||||
|
|
|
@ -44,6 +44,9 @@ static QVariantMap createDeviceMap(const controller::DeviceProxy::Pointer device
|
|||
controller::ScriptingInterface::ScriptingInterface() {
|
||||
auto userInputMapper = DependencyManager::get<UserInputMapper>();
|
||||
|
||||
connect(userInputMapper.data(), &UserInputMapper::actionEvent, this, &controller::ScriptingInterface::actionEvent);
|
||||
connect(userInputMapper.data(), &UserInputMapper::inputEvent, this, &controller::ScriptingInterface::inputEvent);
|
||||
|
||||
// FIXME make this thread safe
|
||||
connect(userInputMapper.data(), &UserInputMapper::hardwareChanged, [=] {
|
||||
updateMaps();
|
||||
|
|
|
@ -129,6 +129,9 @@ namespace controller {
|
|||
virtual void captureActionEvents() { _actionsCaptured = true; }
|
||||
virtual void releaseActionEvents() { _actionsCaptured = false; }
|
||||
|
||||
signals:
|
||||
void actionEvent(int action, float state);
|
||||
void inputEvent(int action, float state);
|
||||
|
||||
private:
|
||||
// Update the exposed variant maps reporting active hardware
|
||||
|
|
|
@ -21,8 +21,13 @@
|
|||
#include <NumericalConstants.h>
|
||||
|
||||
#include "StandardController.h"
|
||||
|
||||
#include "Logging.h"
|
||||
|
||||
#include "Endpoint.h"
|
||||
#include "Route.h"
|
||||
#include "Mapping.h"
|
||||
|
||||
namespace controller {
|
||||
const uint16_t UserInputMapper::ACTIONS_DEVICE = Input::INVALID_DEVICE - 0xFF;
|
||||
const uint16_t UserInputMapper::STANDARD_DEVICE = 0;
|
||||
|
@ -226,7 +231,15 @@ public:
|
|||
qFatal("AnyEndpoint is read only");
|
||||
}
|
||||
|
||||
virtual bool writeable() const override { return false; }
|
||||
// AnyEndpoint is used for reading, so return false if any child returns false (has been written to)
|
||||
virtual bool writeable() const override {
|
||||
for (auto& child : _children) {
|
||||
if (!child->writeable()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool readable() const override {
|
||||
for (auto& child : _children) {
|
||||
|
@ -278,13 +291,11 @@ public:
|
|||
|
||||
virtual void apply(const Pose& newValue, const Pose& oldValue, const Pointer& source) override { }
|
||||
|
||||
virtual bool writeable() const { return !_written; }
|
||||
virtual bool writeable() const { return false; }
|
||||
virtual bool readable() const { return !_read; }
|
||||
virtual void reset() { _written = _read = false; }
|
||||
virtual void reset() { _read = false; }
|
||||
|
||||
private:
|
||||
|
||||
bool _written { false };
|
||||
bool _read { false };
|
||||
};
|
||||
|
||||
|
@ -516,6 +527,24 @@ void UserInputMapper::update(float deltaTime) {
|
|||
}
|
||||
// TODO: emit signal for pose changes
|
||||
}
|
||||
|
||||
auto standardInputs = getStandardInputs();
|
||||
if (_lastStandardStates.size() != standardInputs.size()) {
|
||||
_lastStandardStates.resize(standardInputs.size());
|
||||
for (auto& lastValue : _lastStandardStates) {
|
||||
lastValue = 0;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < standardInputs.size(); ++i) {
|
||||
const auto& input = standardInputs[i].first;
|
||||
float value = getValue(input);
|
||||
float& oldValue = _lastStandardStates[i];
|
||||
if (value != oldValue) {
|
||||
oldValue = value;
|
||||
emit inputEvent(input.id, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Input::NamedVector UserInputMapper::getAvailableInputs(uint16 deviceID) const {
|
||||
|
@ -656,51 +685,87 @@ Input UserInputMapper::makeStandardInput(controller::StandardPoseChannel pose) {
|
|||
|
||||
static auto lastDebugTime = usecTimestampNow();
|
||||
static auto debugRoutes = false;
|
||||
static auto debuggableRoutes = false;
|
||||
static const auto DEBUG_INTERVAL = USECS_PER_SECOND;
|
||||
|
||||
void UserInputMapper::runMappings() {
|
||||
auto now = usecTimestampNow();
|
||||
if (now - lastDebugTime > DEBUG_INTERVAL) {
|
||||
if (debuggableRoutes && now - lastDebugTime > DEBUG_INTERVAL) {
|
||||
lastDebugTime = now;
|
||||
debugRoutes = true;
|
||||
}
|
||||
static auto deviceNames = getDeviceNames();
|
||||
|
||||
if (debugRoutes) {
|
||||
qCDebug(controllers) << "Beginning mapping frame";
|
||||
}
|
||||
for (auto endpointEntry : this->_endpointsByInput) {
|
||||
endpointEntry.second->reset();
|
||||
}
|
||||
|
||||
// Now process the current values for each level of the stack
|
||||
for (const auto& route : _deviceRoutes) {
|
||||
if (!route) {
|
||||
continue;
|
||||
}
|
||||
applyRoute(route);
|
||||
if (debugRoutes) {
|
||||
qCDebug(controllers) << "Processing device routes";
|
||||
}
|
||||
// Now process the current values for each level of the stack
|
||||
applyRoutes(_deviceRoutes);
|
||||
|
||||
for (const auto& route : _standardRoutes) {
|
||||
if (!route) {
|
||||
continue;
|
||||
}
|
||||
applyRoute(route);
|
||||
if (debugRoutes) {
|
||||
qCDebug(controllers) << "Processing standard routes";
|
||||
}
|
||||
applyRoutes(_standardRoutes);
|
||||
|
||||
if (debugRoutes) {
|
||||
qCDebug(controllers) << "Done with mappings";
|
||||
}
|
||||
debugRoutes = false;
|
||||
}
|
||||
|
||||
void UserInputMapper::applyRoute(const Route::Pointer& route) {
|
||||
// Encapsulate the logic that routes should not be read before they are written
|
||||
void UserInputMapper::applyRoutes(const Route::List& routes) {
|
||||
Route::List deferredRoutes;
|
||||
|
||||
for (const auto& route : routes) {
|
||||
if (!route) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try all the deferred routes
|
||||
deferredRoutes.remove_if([](Route::Pointer route) {
|
||||
return UserInputMapper::applyRoute(route);
|
||||
});
|
||||
|
||||
if (!applyRoute(route)) {
|
||||
deferredRoutes.push_back(route);
|
||||
}
|
||||
}
|
||||
|
||||
bool force = true;
|
||||
for (const auto& route : deferredRoutes) {
|
||||
UserInputMapper::applyRoute(route, force);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool UserInputMapper::applyRoute(const Route::Pointer& route, bool force) {
|
||||
if (debugRoutes && route->debug) {
|
||||
qCDebug(controllers) << "Applying route " << route->json;
|
||||
}
|
||||
|
||||
// If the source hasn't been written yet, defer processing of this route
|
||||
auto source = route->source;
|
||||
if (!force && source->writeable()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (route->conditional) {
|
||||
// FIXME for endpoint conditionals we need to check if they've been written
|
||||
if (!route->conditional->satisfied()) {
|
||||
if (debugRoutes && route->debug) {
|
||||
qCDebug(controllers) << "Conditional failed";
|
||||
}
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
auto source = route->source;
|
||||
|
||||
// Most endpoints can only be read once (though a given mapping can route them to
|
||||
// multiple places). Consider... If the default is to wire the A button to JUMP
|
||||
|
@ -711,7 +776,7 @@ void UserInputMapper::applyRoute(const Route::Pointer& route) {
|
|||
if (debugRoutes && route->debug) {
|
||||
qCDebug(controllers) << "Source unreadable";
|
||||
}
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto destination = route->destination;
|
||||
|
@ -721,14 +786,14 @@ void UserInputMapper::applyRoute(const Route::Pointer& route) {
|
|||
if (debugRoutes && route->debug) {
|
||||
qCDebug(controllers) << "Bad Destination";
|
||||
}
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!destination->writeable()) {
|
||||
if (debugRoutes && route->debug) {
|
||||
qCDebug(controllers) << "Destination unwritable";
|
||||
}
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fetch the value, may have been overriden by previous loopback routes
|
||||
|
@ -764,6 +829,7 @@ void UserInputMapper::applyRoute(const Route::Pointer& route) {
|
|||
|
||||
destination->apply(value, 0, source);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Endpoint::Pointer UserInputMapper::endpointFor(const QJSValue& endpoint) {
|
||||
|
@ -869,12 +935,12 @@ void UserInputMapper::enableMapping(const QString& mappingName, bool enable) {
|
|||
}
|
||||
}
|
||||
|
||||
float UserInputMapper::getValue(const Endpoint::Pointer& endpoint) const {
|
||||
Locker locker(_lock);
|
||||
float UserInputMapper::getValue(const Endpoint::Pointer& endpoint) {
|
||||
return endpoint->value();
|
||||
}
|
||||
|
||||
float UserInputMapper::getValue(const Input& input) const {
|
||||
Locker locker(_lock);
|
||||
auto endpoint = endpointFor(input);
|
||||
if (!endpoint) {
|
||||
return 0;
|
||||
|
@ -882,7 +948,7 @@ float UserInputMapper::getValue(const Input& input) const {
|
|||
return endpoint->value();
|
||||
}
|
||||
|
||||
Pose UserInputMapper::getPose(const Endpoint::Pointer& endpoint) const {
|
||||
Pose UserInputMapper::getPose(const Endpoint::Pointer& endpoint) {
|
||||
if (!endpoint->isPose()) {
|
||||
return Pose();
|
||||
}
|
||||
|
@ -890,6 +956,7 @@ Pose UserInputMapper::getPose(const Endpoint::Pointer& endpoint) const {
|
|||
}
|
||||
|
||||
Pose UserInputMapper::getPose(const Input& input) const {
|
||||
Locker locker(_lock);
|
||||
auto endpoint = endpointFor(input);
|
||||
if (!endpoint) {
|
||||
return Pose();
|
||||
|
@ -1156,6 +1223,16 @@ Mapping::Pointer UserInputMapper::parseMapping(const QString& json) {
|
|||
return parseMapping(doc.object());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool hasDebuggableRoute(const T& routes) {
|
||||
for (auto route : routes) {
|
||||
if (route->debug) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void UserInputMapper::enableMapping(const Mapping::Pointer& mapping) {
|
||||
Locker locker(_lock);
|
||||
|
@ -1174,6 +1251,10 @@ void UserInputMapper::enableMapping(const Mapping::Pointer& mapping) {
|
|||
return (value->source->getInput().device == STANDARD_DEVICE);
|
||||
});
|
||||
_deviceRoutes.insert(_deviceRoutes.begin(), deviceRoutes.begin(), deviceRoutes.end());
|
||||
|
||||
if (!debuggableRoutes) {
|
||||
debuggableRoutes = hasDebuggableRoute(_deviceRoutes) || hasDebuggableRoute(_standardRoutes);
|
||||
}
|
||||
}
|
||||
|
||||
void UserInputMapper::disableMapping(const Mapping::Pointer& mapping) {
|
||||
|
@ -1186,6 +1267,10 @@ void UserInputMapper::disableMapping(const Mapping::Pointer& mapping) {
|
|||
_standardRoutes.remove_if([&](const Route::Pointer& value) {
|
||||
return routeSet.count(value) != 0;
|
||||
});
|
||||
|
||||
if (debuggableRoutes) {
|
||||
debuggableRoutes = hasDebuggableRoute(_deviceRoutes) || hasDebuggableRoute(_standardRoutes);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -23,13 +23,12 @@
|
|||
#include <DependencyManager.h>
|
||||
#include <RegisteredMetaTypes.h>
|
||||
|
||||
#include "Forward.h"
|
||||
#include "Pose.h"
|
||||
#include "Input.h"
|
||||
#include "InputDevice.h"
|
||||
#include "DeviceProxy.h"
|
||||
#include "StandardControls.h"
|
||||
#include "Mapping.h"
|
||||
#include "Endpoint.h"
|
||||
#include "Actions.h"
|
||||
|
||||
namespace controller {
|
||||
|
@ -45,15 +44,14 @@ namespace controller {
|
|||
public:
|
||||
using InputPair = Input::NamedPair;
|
||||
// FIXME move to unordered set / map
|
||||
using EndpointToInputMap = std::map<Endpoint::Pointer, Input>;
|
||||
using MappingNameMap = std::map<QString, Mapping::Pointer>;
|
||||
using MappingDeviceMap = std::map<uint16_t, Mapping::Pointer>;
|
||||
using MappingStack = std::list<Mapping::Pointer>;
|
||||
using InputToEndpointMap = std::map<Input, Endpoint::Pointer>;
|
||||
using EndpointSet = std::unordered_set<Endpoint::Pointer>;
|
||||
using EndpointOverrideMap = std::map<Endpoint::Pointer, Endpoint::Pointer>;
|
||||
using EndpointPair = std::pair<Endpoint::Pointer, Endpoint::Pointer>;
|
||||
using EndpointPairMap = std::map<EndpointPair, Endpoint::Pointer>;
|
||||
using EndpointToInputMap = std::map<EndpointPointer, Input>;
|
||||
using MappingNameMap = std::map<QString, MappingPointer>;
|
||||
using MappingDeviceMap = std::map<uint16_t, MappingPointer>;
|
||||
using MappingStack = std::list<MappingPointer>;
|
||||
using InputToEndpointMap = std::map<Input, EndpointPointer>;
|
||||
using EndpointSet = std::unordered_set<EndpointPointer>;
|
||||
using EndpointPair = std::pair<EndpointPointer, EndpointPointer>;
|
||||
using EndpointPairMap = std::map<EndpointPair, EndpointPointer>;
|
||||
using DevicesMap = std::map<int, DeviceProxy::Pointer>;
|
||||
using uint16 = uint16_t;
|
||||
using uint32 = uint32_t;
|
||||
|
@ -107,9 +105,9 @@ namespace controller {
|
|||
uint16 getStandardDeviceID() const { return STANDARD_DEVICE; }
|
||||
DeviceProxy::Pointer getStandardDevice() { return _registeredDevices[getStandardDeviceID()]; }
|
||||
|
||||
Mapping::Pointer newMapping(const QString& mappingName);
|
||||
Mapping::Pointer parseMapping(const QString& json);
|
||||
Mapping::Pointer loadMapping(const QString& jsonFile);
|
||||
MappingPointer newMapping(const QString& mappingName);
|
||||
MappingPointer parseMapping(const QString& json);
|
||||
MappingPointer loadMapping(const QString& jsonFile);
|
||||
|
||||
void enableMapping(const QString& mappingName, bool enable = true);
|
||||
float getValue(const Input& input) const;
|
||||
|
@ -117,6 +115,7 @@ namespace controller {
|
|||
|
||||
signals:
|
||||
void actionEvent(int action, float state);
|
||||
void inputEvent(int input, float state);
|
||||
void hardwareChanged();
|
||||
|
||||
protected:
|
||||
|
@ -130,36 +129,38 @@ namespace controller {
|
|||
std::vector<float> _actionScales = std::vector<float>(toInt(Action::NUM_ACTIONS), 1.0f);
|
||||
std::vector<float> _lastActionStates = std::vector<float>(toInt(Action::NUM_ACTIONS), 0.0f);
|
||||
std::vector<Pose> _poseStates = std::vector<Pose>(toInt(Action::NUM_ACTIONS));
|
||||
std::vector<float> _lastStandardStates = std::vector<float>();
|
||||
|
||||
glm::mat4 _sensorToWorldMat;
|
||||
|
||||
int recordDeviceOfType(const QString& deviceName);
|
||||
QHash<const QString&, int> _deviceCounts;
|
||||
|
||||
float getValue(const Endpoint::Pointer& endpoint) const;
|
||||
Pose getPose(const Endpoint::Pointer& endpoint) const;
|
||||
static float getValue(const EndpointPointer& endpoint);
|
||||
static Pose getPose(const EndpointPointer& endpoint);
|
||||
|
||||
friend class RouteBuilderProxy;
|
||||
friend class MappingBuilderProxy;
|
||||
|
||||
void runMappings();
|
||||
void applyRoute(const Route::Pointer& route);
|
||||
void enableMapping(const Mapping::Pointer& mapping);
|
||||
void disableMapping(const Mapping::Pointer& mapping);
|
||||
Endpoint::Pointer endpointFor(const QJSValue& endpoint);
|
||||
Endpoint::Pointer endpointFor(const QScriptValue& endpoint);
|
||||
Endpoint::Pointer endpointFor(const Input& endpoint) const;
|
||||
Endpoint::Pointer compositeEndpointFor(Endpoint::Pointer first, Endpoint::Pointer second);
|
||||
static void applyRoutes(const RouteList& route);
|
||||
static bool applyRoute(const RoutePointer& route, bool force = false);
|
||||
void enableMapping(const MappingPointer& mapping);
|
||||
void disableMapping(const MappingPointer& mapping);
|
||||
EndpointPointer endpointFor(const QJSValue& endpoint);
|
||||
EndpointPointer endpointFor(const QScriptValue& endpoint);
|
||||
EndpointPointer endpointFor(const Input& endpoint) const;
|
||||
EndpointPointer compositeEndpointFor(EndpointPointer first, EndpointPointer second);
|
||||
|
||||
Mapping::Pointer parseMapping(const QJsonValue& json);
|
||||
Route::Pointer parseRoute(const QJsonValue& value);
|
||||
Endpoint::Pointer parseDestination(const QJsonValue& value);
|
||||
Endpoint::Pointer parseSource(const QJsonValue& value);
|
||||
Endpoint::Pointer parseEndpoint(const QJsonValue& value);
|
||||
Conditional::Pointer parseConditional(const QJsonValue& value);
|
||||
MappingPointer parseMapping(const QJsonValue& json);
|
||||
RoutePointer parseRoute(const QJsonValue& value);
|
||||
EndpointPointer parseDestination(const QJsonValue& value);
|
||||
EndpointPointer parseSource(const QJsonValue& value);
|
||||
EndpointPointer parseEndpoint(const QJsonValue& value);
|
||||
ConditionalPointer parseConditional(const QJsonValue& value);
|
||||
|
||||
static Filter::Pointer parseFilter(const QJsonValue& value);
|
||||
static Filter::List parseFilters(const QJsonValue& value);
|
||||
static FilterPointer parseFilter(const QJsonValue& value);
|
||||
static FilterList parseFilters(const QJsonValue& value);
|
||||
|
||||
InputToEndpointMap _endpointsByInput;
|
||||
EndpointToInputMap _inputsByEndpoint;
|
||||
|
@ -168,8 +169,8 @@ namespace controller {
|
|||
MappingNameMap _mappingsByName;
|
||||
MappingDeviceMap _mappingsByDevice;
|
||||
|
||||
Route::List _deviceRoutes;
|
||||
Route::List _standardRoutes;
|
||||
RouteList _deviceRoutes;
|
||||
RouteList _standardRoutes;
|
||||
|
||||
using Locker = std::unique_lock<std::recursive_mutex>;
|
||||
|
||||
|
|
|
@ -39,6 +39,11 @@ void RouteBuilderProxy::to(const Endpoint::Pointer& destination) {
|
|||
deleteLater();
|
||||
}
|
||||
|
||||
QObject* RouteBuilderProxy::debug(bool enable) {
|
||||
_route->debug = enable;
|
||||
return this;
|
||||
}
|
||||
|
||||
QObject* RouteBuilderProxy::filterQml(const QJSValue& expression) {
|
||||
if (expression.isCallable()) {
|
||||
addFilter([=](float value) {
|
||||
|
|
|
@ -35,6 +35,7 @@ class RouteBuilderProxy : public QObject {
|
|||
Q_INVOKABLE QObject* filterQml(const QJSValue& expression);
|
||||
|
||||
Q_INVOKABLE void to(const QScriptValue& destination);
|
||||
Q_INVOKABLE QObject* debug(bool enable = true);
|
||||
Q_INVOKABLE QObject* filter(const QScriptValue& expression);
|
||||
Q_INVOKABLE QObject* clamp(float min, float max);
|
||||
Q_INVOKABLE QObject* pulse(float interval);
|
||||
|
|
Loading…
Reference in a new issue