Merge pull request #6105 from ZappoMan/controllers

[Controllers Branch] - various updates
This commit is contained in:
Brad Davis 2015-10-17 14:28:34 -07:00
commit f4380da857
13 changed files with 171 additions and 85 deletions

View file

@ -70,7 +70,7 @@ mapping2.enable();
Controller.enableMapping("example2");
*/
var mapping3 = Controller.loadMapping("E:/Github/hifi/examples/controllers/example3.json");
var mapping3 = Controller.loadMapping(Script.resolvePath("example3.json"));
Controller.enableMapping("example3");
/*

View file

@ -23,17 +23,24 @@ function findAction(name) {
}
var hydra = Controller.Hardware.Hydra2;
var hydra = Controller.Hardware.Hydra;
if (hydra !== undefined) {
print("-----------------------------------");
var mapping = NewControllers.newMapping("Default");
var mapping = Controller.newMapping("Test");
var standard = Controller.Standard;
print("standard:" + standard);
mapping.from(hydra.LeftButton1).to(standard.A);
mapping.from(hydra.LeftButton2).to(standard.B);
mapping.from(hydra.LeftButton3).to(standard.X);
NewControllers.enableMapping("Default");
mapping.from(function () { return Math.sin(Date.now() / 250); }).to(function (newValue, oldValue, source) {
print("function source newValue:" + newValue + ", oldValue:" + oldValue + ", source:" + source);
});
mapping.from(hydra.L1).to(standard.A);
mapping.from(hydra.L2).to(standard.B);
mapping.from(hydra.L3).to(function (newValue, oldValue, source) {
print("hydra.L3 newValue:" + newValue + ", oldValue:" + oldValue + ", source:" + source);
});
Controller.enableMapping("Test");
print("-----------------------------------");
} else {
print("couldn't find hydra");
}
Object.keys(Controller.Standard).forEach(function (input) {

View file

@ -1035,9 +1035,9 @@
<div class="property">
<div class="label">Position</div>
<div class="value">
<div class="input-area">X <br><input class="coord" type='number' id="property-pos-x"></div>
<div class="input-area">Y <br><input class="coord" type='number' id="property-pos-y"></div>
<div class="input-area">Z <br><input class="coord" type='number' id="property-pos-z"></div>
<div class="input-area ">X<input class="coord" type='number' id="property-pos-x"><div class="prop-x"></div></div>
<div class="input-area ">Y<input class="coord" type='number' id="property-pos-y"><div class="prop-y"></div></div>
<div class="input-area ">Z<input class="coord" type='number' id="property-pos-z"><div class="prop-z"></div></div>
<div>
<input type="button" id="move-selection-to-grid" value="Selection to Grid">
<input type="button" id="move-all-to-grid" value="All to Grid">
@ -1048,18 +1048,18 @@
<div class="property">
<div class="label">Registration</div>
<div class="value">
<div class="input-area">X <input class="coord" type='number' id="property-reg-x"></div>
<div class="input-area">Y <input class="coord" type='number' id="property-reg-y"></div>
<div class="input-area">Z <input class="coord" type='number' id="property-reg-z"></div>
<div class="input-area">X <input class="coord" type='number' id="property-reg-x"><div class="prop-x"></div></div>
<div class="input-area">Y <input class="coord" type='number' id="property-reg-y"><div class="prop-y"></div></div>
<div class="input-area">Z <input class="coord" type='number' id="property-reg-z"><div class="prop-z"></div></div>
</div>
</div>
<div class="property">
<div class="label">Dimensions</div>
<div class="value">
<div class="input-area">X <input class="coord" type='number' id="property-dim-x"></div>
<div class="input-area">Y <input class="coord" type='number' id="property-dim-y"></div>
<div class="input-area">Z <input class="coord" type='number' id="property-dim-z"></div>
<div class="input-area">X <input class="coord" type='number' id="property-dim-x"><div class="prop-x"></div></div>
<div class="input-area">Y <input class="coord" type='number' id="property-dim-y"><div class="prop-y"></div></div>
<div class="input-area">Z <input class="coord" type='number' id="property-dim-z"><div class="prop-z"></div></div>
<div>
<input type="button" id="reset-to-natural-dimensions" value="Reset to Natural Dimensions">
</div>
@ -1123,9 +1123,9 @@
<div class="property">
<div class="label">Linear Velocity</div>
<div class="value">
<div class="input-area">X <input class="coord" type='number' id="property-lvel-x"></div>
<div class="input-area">Y <input class="coord" type='number' id="property-lvel-y"></div>
<div class="input-area">Z <input class="coord" type='number' id="property-lvel-z"></div>
<div class="input-area">X <input class="coord" type='number' id="property-lvel-x"><div class="prop-x"></div></div>
<div class="input-area">Y <input class="coord" type='number' id="property-lvel-y"><div class="prop-y"></div></div>
<div class="input-area">Z <input class="coord" type='number' id="property-lvel-z"><div class="prop-z"></div></div>
</div>
</div>
<div class="property">
@ -1164,18 +1164,18 @@
<div class="property">
<div class="label">Gravity</div>
<div class="value">
<div class="input-area">X <input class="coord" type='number' id="property-grav-x"></div>
<div class="input-area">Y <input class="coord" type='number' id="property-grav-y"></div>
<div class="input-area">Z <input class="coord" type='number' id="property-grav-z"></div>
<div class="input-area">X <input class="coord" type='number' id="property-grav-x"><div class="prop-x"></div></div>
<div class="input-area">Y <input class="coord" type='number' id="property-grav-y"><div class="prop-y"></div></div>
<div class="input-area">Z <input class="coord" type='number' id="property-grav-z"><div class="prop-z"></div></div>
</div>
</div>
<div class="property">
<div class="label">Acceleration</div>
<div class="value">
<div class="input-area">X <input class="coord" type='number' id="property-lacc-x"></div>
<div class="input-area">Y <input class="coord" type='number' id="property-lacc-y"></div>
<div class="input-area">Z <input class="coord" type='number' id="property-lacc-z"></div>
<div class="input-area">X <input class="coord" type='number' id="property-lacc-x"><div class="prop-x"></div></div>
<div class="input-area">Y <input class="coord" type='number' id="property-lacc-y"><div class="prop-y"></div></div>
<div class="input-area">Z <input class="coord" type='number' id="property-lacc-z"><div class="prop-z"></div></div>
</div>
</div>

View file

@ -75,6 +75,7 @@ body {
height: 22.5pt;
}
.property-section label {
font-weight: bold;
}
@ -342,3 +343,21 @@ input#property-name {
margin: 5px;
border: 1px solid white;
}
.prop-x {
color:red !important;
background: rgba(255, 0, 0, .9);
height:2px !important;
}
.prop-y {
color:green !important;
background: rgba(0, 255, 0, .9);
height:2px !important;
}
.prop-z{
color:blue !important;
background: rgba(0, 0, 255, .9);
height:2px !important;
}

View file

@ -11,3 +11,8 @@
namespace controller {
}
// FIXME - do we want to include the source Endpoint::Pointer in our calls to JS? If
// so we need to marshall this across the invokeMethod() properly
//static int EndpointPointerMetaTypeId = qRegisterMetaType<controller::Endpoint::Pointer>("controller::Endpoint::Pointer");

View file

@ -23,7 +23,8 @@ namespace controller {
* Encapsulates a particular input / output,
* i.e. Hydra.Button0, Standard.X, Action.Yaw
*/
class Endpoint {
class Endpoint : public QObject {
Q_OBJECT;
public:
using Pointer = std::shared_ptr<Endpoint>;
using List = std::list<Pointer>;
@ -31,12 +32,12 @@ namespace controller {
using ReadLambda = std::function<float()>;
using WriteLambda = std::function<void(float)>;
Endpoint(const UserInputMapper::Input& id) : _id(id) {}
Endpoint(const UserInputMapper::Input& input) : _input(input) {}
virtual float value() = 0;
virtual void apply(float newValue, float oldValue, const Pointer& source) = 0;
const UserInputMapper::Input& getId() { return _id; }
const UserInputMapper::Input& getInput() { return _input; }
protected:
UserInputMapper::Input _id;
UserInputMapper::Input _input;
};
class LambdaEndpoint : public Endpoint {
@ -53,4 +54,8 @@ namespace controller {
};
}
// FIXME - do we want to include the source Endpoint::Pointer in our calls to JS? If
// so we need to marshall this across the invokeMethod() properly
//Q_DECLARE_METATYPE(controller::Endpoint::Pointer);
#endif

View file

@ -15,6 +15,7 @@
#include <QJsonDocument>
#include <QJsonObject>
#include <QEventLoop>
#include <QThread>
#include <GLMHelpers.h>
#include <DependencyManager.h>
@ -60,24 +61,35 @@ namespace controller {
QJSValue _callable;
};
class ScriptEndpoint : public Endpoint {
public:
ScriptEndpoint(const QScriptValue& callable)
: Endpoint(UserInputMapper::Input::INVALID_INPUT), _callable(callable) {
float ScriptEndpoint::value() {
updateValue();
return _lastValue;
}
void ScriptEndpoint::updateValue() {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "updateValue", Qt::QueuedConnection);
return;
}
virtual float value() {
float result = (float)_callable.call().toNumber();
return result;
}
_lastValue = (float)_callable.call().toNumber();
}
virtual void apply(float newValue, float oldValue, const Pointer& source) {
_callable.call(QScriptValue(), QScriptValueList({ QScriptValue(newValue) }));
}
void ScriptEndpoint::apply(float newValue, float oldValue, const Pointer& source) {
internalApply(newValue, oldValue, source->getInput().getID());
}
private:
QScriptValue _callable;
};
void ScriptEndpoint::internalApply(float newValue, float oldValue, int sourceID) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "internalApply", Qt::QueuedConnection,
Q_ARG(float, newValue),
Q_ARG(float, oldValue),
Q_ARG(int, sourceID));
return;
}
_callable.call(QScriptValue(),
QScriptValueList({ QScriptValue(newValue), QScriptValue(oldValue), QScriptValue(sourceID) }));
}
class CompositeEndpoint : public Endpoint, Endpoint::Pair {
public:
@ -108,9 +120,9 @@ namespace controller {
virtual void apply(float newValue, float oldValue, const Pointer& source) override {
_currentValue += newValue;
if (!(_id == UserInputMapper::Input::INVALID_INPUT)) {
if (!(_input == UserInputMapper::Input::INVALID_INPUT)) {
auto userInputMapper = DependencyManager::get<UserInputMapper>();
userInputMapper->deltaActionState(UserInputMapper::Action(_id.getChannel()), newValue);
userInputMapper->deltaActionState(UserInputMapper::Action(_input.getChannel()), newValue);
}
}
@ -209,6 +221,7 @@ namespace controller {
}
QObject* ScriptingInterface::loadMapping(const QString& jsonUrl) {
QObject* result = nullptr;
auto request = ResourceManager::createResourceRequest(nullptr, QUrl(jsonUrl));
if (request) {
QEventLoop eventLoop;
@ -220,12 +233,13 @@ namespace controller {
}
if (request->getResult() == ResourceRequest::Success) {
return parseMapping(QString(request->getData()));
result = parseMapping(QString(request->getData()));
} else {
qDebug() << "Failed to load mapping url <" << jsonUrl << ">" << endl;
return nullptr;
}
request->deleteLater();
}
return result;
}
Q_INVOKABLE QObject* newMapping(const QJsonObject& json);
@ -325,7 +339,7 @@ namespace controller {
}
// Standard controller destinations can only be can only be used once.
if (userInputMapper->getStandardDeviceID() == destination->getId().getDevice()) {
if (userInputMapper->getStandardDeviceID() == destination->getInput().getDevice()) {
writtenEndpoints.insert(destination);
}
@ -479,31 +493,34 @@ namespace controller {
auto devices = userInputMapper->getDevices();
QSet<QString> foundDevices;
for (const auto& deviceMapping : devices) {
auto device = deviceMapping.second.get();
auto deviceName = QString(device->getName()).remove(ScriptingInterface::SANITIZE_NAME_EXPRESSION);
qCDebug(controllers) << "Device" << deviceMapping.first << ":" << deviceName;
foundDevices.insert(device->getName());
if (_hardware.contains(deviceName)) {
continue;
}
// Expose the IDs to JS
_hardware.insert(deviceName, createDeviceMap(device));
// Create the endpoints
for (const auto& inputMapping : device->getAvailabeInputs()) {
const auto& input = inputMapping.first;
// Ignore aliases
if (_endpoints.count(input)) {
auto deviceID = deviceMapping.first;
if (deviceID != userInputMapper->getStandardDeviceID()) {
auto device = deviceMapping.second.get();
auto deviceName = QString(device->getName()).remove(ScriptingInterface::SANITIZE_NAME_EXPRESSION);
qCDebug(controllers) << "Device" << deviceMapping.first << ":" << deviceName;
foundDevices.insert(device->getName());
if (_hardware.contains(deviceName)) {
continue;
}
_endpoints[input] = std::make_shared<LambdaEndpoint>([=] {
auto deviceProxy = userInputMapper->getDeviceProxy(input);
if (!deviceProxy) {
return 0.0f;
// Expose the IDs to JS
_hardware.insert(deviceName, createDeviceMap(device));
// Create the endpoints
for (const auto& inputMapping : device->getAvailabeInputs()) {
const auto& input = inputMapping.first;
// Ignore aliases
if (_endpoints.count(input)) {
continue;
}
return deviceProxy->getValue(input, 0);
});
_endpoints[input] = std::make_shared<LambdaEndpoint>([=] {
auto deviceProxy = userInputMapper->getDeviceProxy(input);
if (!deviceProxy) {
return 0.0f;
}
return deviceProxy->getValue(input, 0);
});
}
}
}
}

View file

@ -21,6 +21,7 @@
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
#include <QThread>
#include <QtCore/QObject>
#include <QtCore/QVariant>
@ -138,6 +139,25 @@ namespace controller {
MappingMap _mappingsByName;
MappingStack _activeMappings;
};
class ScriptEndpoint : public Endpoint {
Q_OBJECT;
public:
ScriptEndpoint(const QScriptValue& callable)
: Endpoint(UserInputMapper::Input::INVALID_INPUT), _callable(callable) {
}
virtual float value();
virtual void apply(float newValue, float oldValue, const Pointer& source);
protected:
Q_INVOKABLE void updateValue();
Q_INVOKABLE virtual void internalApply(float newValue, float oldValue, int sourceID);
private:
QScriptValue _callable;
float _lastValue = 0.0f;
};
}

View file

@ -31,14 +31,28 @@ UserInputMapper::UserInputMapper() {
UserInputMapper::~UserInputMapper() {
}
bool UserInputMapper::registerDevice(uint16 deviceID, const DeviceProxy::Pointer& proxy){
proxy->_name += " (" + QString::number(deviceID) + ")";
_registeredDevices[deviceID] = proxy;
qCDebug(controllers) << "Registered input device <" << proxy->_name << "> deviceID = " << deviceID;
return true;
int UserInputMapper::recordDeviceOfType(const QString& deviceName) {
if (!_deviceCounts.contains(deviceName)) {
_deviceCounts[deviceName] = 0;
}
_deviceCounts[deviceName] += 1;
return _deviceCounts[deviceName];
}
bool UserInputMapper::registerDevice(uint16 deviceID, const DeviceProxy::Pointer& proxy) {
int numberOfType = recordDeviceOfType(proxy->_name);
if (numberOfType > 1) {
proxy->_name += QString::number(numberOfType);
}
qCDebug(controllers) << "Registered input device <" << proxy->_name << "> deviceID = " << deviceID;
_registeredDevices[deviceID] = proxy;
return true;
}
bool UserInputMapper::registerStandardDevice(const DeviceProxy::Pointer& device) {
device->_name = "Standard"; // Just to make sure
_registeredDevices[getStandardDeviceID()] = device;

View file

@ -39,7 +39,7 @@ QObject* MappingBuilderProxy::from(const Endpoint::Pointer& source) {
return new RouteBuilderProxy(_parent, _mapping, route);
}
QObject* MappingBuilderProxy::join(const QJSValue& source1, const QJSValue& source2) {
QObject* MappingBuilderProxy::makeAxis(const QJSValue& source1, const QJSValue& source2) {
auto source1Endpoint = _parent.endpointFor(source1);
auto source2Endpoint = _parent.endpointFor(source2);
return from(_parent.compositeEndpointFor(source1Endpoint, source2Endpoint));
@ -82,6 +82,7 @@ QObject* MappingBuilderProxy::from(const QJsonValue& json) {
// Endpoint is defined as an object, we expect a js function then
return nullptr;
}
return nullptr;
}
QObject* MappingBuilderProxy::enable(bool enable) {

View file

@ -33,7 +33,7 @@ public:
Q_INVOKABLE QObject* from(const QJSValue& source);
Q_INVOKABLE QObject* from(const QScriptValue& source);
Q_INVOKABLE QObject* join(const QJSValue& source1, const QJSValue& source2);
Q_INVOKABLE QObject* makeAxis(const QJSValue& source1, const QJSValue& source2);
Q_INVOKABLE QObject* enable(bool enable = true);
Q_INVOKABLE QObject* disable() { return enable(false); }

View file

@ -644,14 +644,13 @@ void ParticleEffectEntityItem::stepSimulation(float deltaTime) {
// update particles between head and tail
for (quint32 i = _particleHeadIndex; i != _particleTailIndex; i = (i + 1) % _maxParticles) {
_particleLifetimes[i] -= deltaTime;
_particleLifetimes[i] += deltaTime;
// if particle has died.
if (_particleLifetimes[i] <= 0.0f || _lifespan == 0.0f) {
if (_particleLifetimes[i] >= _lifespan || _lifespan < EPSILON) {
// move head forward
_particleHeadIndex = (_particleHeadIndex + 1) % _maxParticles;
}
else {
} else {
float age = 1.0f - _particleLifetimes[i] / _lifespan; // 0.0 .. 1.0
updateRadius(i, age);
updateColor(i, age);
@ -672,7 +671,7 @@ void ParticleEffectEntityItem::stepSimulation(float deltaTime) {
// emit a new particle at tail index.
quint32 i = _particleTailIndex;
_particleLifetimes[i] = _lifespan;
_particleLifetimes[i] = 0.0f;
// Radius
if (_radiusSpread == 0.0f) {

View file

@ -83,8 +83,7 @@ Column {
mapping.from(function() { return Math.sin(Date.now() / 250); }).to(standard.RX);
// Constrainting a value to -1, 0, or 1, with a deadzone
mapping.from(xbox.LY).deadZone(0.5).constrainToInteger().to(standard.LY);
// change join to makeAxis
mapping.join(standard.LB, standard.RB).to(actions.Yaw);
mapping.makeAxis(standard.LB, standard.RB).to(actions.Yaw);
mapping.from(actions.Yaw).clamp(0, 1).invert().to(actions.YAW_RIGHT);
mapping.from(actions.Yaw).clamp(-1, 0).to(actions.YAW_LEFT);
// mapping.modifier(keyboard.Ctrl).scale(2.0)