mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-09 01:22:21 +02:00
Merge remote-tracking branch 'upstream/master' into vive-ui
Conflicts: scripts/system/controllers/handControllerGrab.js
This commit is contained in:
commit
472a3540d7
66 changed files with 994 additions and 398 deletions
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": 1.2,
|
||||
"version": 1.3,
|
||||
"settings": [
|
||||
{
|
||||
"name": "metaverse",
|
||||
|
@ -71,6 +71,76 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "descriptors",
|
||||
"label": "Description",
|
||||
"help": "This data will be queryable from your server. It may be collected by High Fidelity and used to share your domain with others.",
|
||||
"settings": [
|
||||
{
|
||||
"name": "description",
|
||||
"label": "Description",
|
||||
"help": "A description of your domain (256 character limit)."
|
||||
},
|
||||
{
|
||||
"name": "maturity",
|
||||
"label": "Maturity",
|
||||
"help": "A maturity rating, available as a guideline for content on your domain.",
|
||||
"default": "unrated",
|
||||
"type": "select",
|
||||
"options": [
|
||||
{
|
||||
"value": "unrated",
|
||||
"label": "Unrated"
|
||||
},
|
||||
{
|
||||
"value": "everyone",
|
||||
"label": "Everyone"
|
||||
},
|
||||
{
|
||||
"value": "teen",
|
||||
"label": "Teen (13+)"
|
||||
},
|
||||
{
|
||||
"value": "mature",
|
||||
"label": "Mature (17+)"
|
||||
},
|
||||
{
|
||||
"value": "adult",
|
||||
"label": "Adult (18+)"
|
||||
}
|
||||
]
|
||||
|
||||
},
|
||||
{
|
||||
"name": "hosts",
|
||||
"label": "Hosts",
|
||||
"type": "table",
|
||||
"help": "Usernames of hosts who can reliably show your domain to new visitors.",
|
||||
"numbered": false,
|
||||
"columns": [
|
||||
{
|
||||
"name": "host",
|
||||
"label": "Username",
|
||||
"can_set": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "tags",
|
||||
"label": "Tags",
|
||||
"type": "table",
|
||||
"help": "Common categories under which your domain falls.",
|
||||
"numbered": false,
|
||||
"columns": [
|
||||
{
|
||||
"name": "tag",
|
||||
"label": "Tag",
|
||||
"can_set": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "security",
|
||||
"label": "Security",
|
||||
|
|
132
domain-server/src/DomainMetadata.cpp
Normal file
132
domain-server/src/DomainMetadata.cpp
Normal file
|
@ -0,0 +1,132 @@
|
|||
//
|
||||
// DomainMetadata.cpp
|
||||
// domain-server/src
|
||||
//
|
||||
// Created by Zach Pomerantz on 5/25/2016.
|
||||
// Copyright 2016 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
|
||||
|
||||
#include "DomainMetadata.h"
|
||||
|
||||
#include <HifiConfigVariantMap.h>
|
||||
#include <DependencyManager.h>
|
||||
#include <LimitedNodeList.h>
|
||||
|
||||
#include "DomainServerNodeData.h"
|
||||
|
||||
const QString DomainMetadata::USERS = "users";
|
||||
const QString DomainMetadata::USERS_NUM_TOTAL = "num_users";
|
||||
const QString DomainMetadata::USERS_NUM_ANON = "num_anon_users";
|
||||
const QString DomainMetadata::USERS_HOSTNAMES = "user_hostnames";
|
||||
// users metadata will appear as (JSON):
|
||||
// { "num_users": Number,
|
||||
// "num_anon_users": Number,
|
||||
// "user_hostnames": { <HOSTNAME>: Number }
|
||||
// }
|
||||
|
||||
const QString DomainMetadata::DESCRIPTORS = "descriptors";
|
||||
const QString DomainMetadata::DESCRIPTORS_DESCRIPTION = "description";
|
||||
const QString DomainMetadata::DESCRIPTORS_CAPACITY = "capacity"; // parsed from security
|
||||
const QString DomainMetadata::DESCRIPTORS_RESTRICTION = "restriction"; // parsed from ACL
|
||||
const QString DomainMetadata::DESCRIPTORS_MATURITY = "maturity";
|
||||
const QString DomainMetadata::DESCRIPTORS_HOSTS = "hosts";
|
||||
const QString DomainMetadata::DESCRIPTORS_TAGS = "tags";
|
||||
// descriptors metadata will appear as (JSON):
|
||||
// { "capacity": Number,
|
||||
// TODO: "hours": String, // UTF-8 representation of the week, split into 15" segments
|
||||
// "restriction": String, // enum of either open, hifi, or acl
|
||||
// "maturity": String, // enum corresponding to ESRB ratings
|
||||
// "hosts": [ String ], // capped list of usernames
|
||||
// "description": String, // capped description
|
||||
// TODO: "img": {
|
||||
// "src": String,
|
||||
// "type": String,
|
||||
// "size": Number,
|
||||
// "updated_at": Number,
|
||||
// },
|
||||
// "tags": [ String ], // capped list of tags
|
||||
// }
|
||||
|
||||
// metadata will appear as (JSON):
|
||||
// { users: <USERS>, descriptors: <DESCRIPTORS> }
|
||||
//
|
||||
// it is meant to be sent to and consumed by an external API
|
||||
|
||||
DomainMetadata::DomainMetadata() {
|
||||
_metadata[USERS] = {};
|
||||
_metadata[DESCRIPTORS] = {};
|
||||
}
|
||||
|
||||
void DomainMetadata::setDescriptors(QVariantMap& settings) {
|
||||
const QString CAPACITY = "security.maximum_user_capacity";
|
||||
const QVariant* capacityVariant = valueForKeyPath(settings, CAPACITY);
|
||||
unsigned int capacity = capacityVariant ? capacityVariant->toUInt() : 0;
|
||||
|
||||
// TODO: Keep parity with ACL development.
|
||||
const QString RESTRICTION = "security.restricted_access";
|
||||
const QString RESTRICTION_OPEN = "open";
|
||||
// const QString RESTRICTION_HIFI = "hifi";
|
||||
const QString RESTRICTION_ACL = "acl";
|
||||
const QVariant* isRestrictedVariant = valueForKeyPath(settings, RESTRICTION);
|
||||
bool isRestricted = isRestrictedVariant ? isRestrictedVariant->toBool() : false;
|
||||
QString restriction = isRestricted ? RESTRICTION_ACL : RESTRICTION_OPEN;
|
||||
|
||||
QVariantMap descriptors = settings[DESCRIPTORS].toMap();
|
||||
descriptors[DESCRIPTORS_CAPACITY] = capacity;
|
||||
descriptors[DESCRIPTORS_RESTRICTION] = restriction;
|
||||
_metadata[DESCRIPTORS] = descriptors;
|
||||
|
||||
#if DEV_BUILD || PR_BUILD
|
||||
qDebug() << "Domain metadata descriptors set:" << descriptors;
|
||||
#endif
|
||||
}
|
||||
|
||||
void DomainMetadata::updateUsers() {
|
||||
static const QString DEFAULT_HOSTNAME = "*";
|
||||
|
||||
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
||||
int numConnected = 0;
|
||||
int numConnectedAnonymously = 0;
|
||||
QVariantMap userHostnames;
|
||||
|
||||
// figure out the breakdown of currently connected interface clients
|
||||
nodeList->eachNode([&numConnected, &numConnectedAnonymously, &userHostnames](const SharedNodePointer& node) {
|
||||
auto linkedData = node->getLinkedData();
|
||||
if (linkedData) {
|
||||
auto nodeData = static_cast<DomainServerNodeData*>(linkedData);
|
||||
|
||||
if (!nodeData->wasAssigned()) {
|
||||
++numConnected;
|
||||
|
||||
if (nodeData->getUsername().isEmpty()) {
|
||||
++numConnectedAnonymously;
|
||||
}
|
||||
|
||||
// increment the count for this hostname (or the default if we don't have one)
|
||||
auto placeName = nodeData->getPlaceName();
|
||||
auto hostname = placeName.isEmpty() ? DEFAULT_HOSTNAME : placeName;
|
||||
userHostnames[hostname] = userHostnames[hostname].toInt() + 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
QVariantMap users = {
|
||||
{ USERS_NUM_TOTAL, numConnected },
|
||||
{ USERS_NUM_ANON, numConnectedAnonymously },
|
||||
{ USERS_HOSTNAMES, userHostnames }};
|
||||
_metadata[USERS] = users;
|
||||
|
||||
#if DEV_BUILD || PR_BUILD
|
||||
qDebug() << "Domain metadata users updated:" << users;
|
||||
#endif
|
||||
}
|
||||
|
||||
void DomainMetadata::usersChanged() {
|
||||
++_tic;
|
||||
|
||||
#if DEV_BUILD || PR_BUILD
|
||||
qDebug() << "Domain metadata users change detected";
|
||||
#endif
|
||||
}
|
65
domain-server/src/DomainMetadata.h
Normal file
65
domain-server/src/DomainMetadata.h
Normal file
|
@ -0,0 +1,65 @@
|
|||
//
|
||||
// DomainMetadata.h
|
||||
// domain-server/src
|
||||
//
|
||||
// Created by Zach Pomerantz on 5/25/2016.
|
||||
// Copyright 2016 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
|
||||
|
||||
#ifndef hifi_DomainMetadata_h
|
||||
#define hifi_DomainMetadata_h
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <QVariantMap>
|
||||
#include <QJsonObject>
|
||||
|
||||
class DomainMetadata : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
static const QString USERS;
|
||||
static const QString USERS_NUM_TOTAL;
|
||||
static const QString USERS_NUM_ANON;
|
||||
static const QString USERS_HOSTNAMES;
|
||||
|
||||
static const QString DESCRIPTORS;
|
||||
static const QString DESCRIPTORS_DESCRIPTION;
|
||||
static const QString DESCRIPTORS_CAPACITY;
|
||||
static const QString DESCRIPTORS_HOURS;
|
||||
static const QString DESCRIPTORS_RESTRICTION;
|
||||
static const QString DESCRIPTORS_MATURITY;
|
||||
static const QString DESCRIPTORS_HOSTS;
|
||||
static const QString DESCRIPTORS_TAGS;
|
||||
static const QString DESCRIPTORS_IMG;
|
||||
static const QString DESCRIPTORS_IMG_SRC;
|
||||
static const QString DESCRIPTORS_IMG_TYPE;
|
||||
static const QString DESCRIPTORS_IMG_SIZE;
|
||||
static const QString DESCRIPTORS_IMG_UPDATED_AT;
|
||||
|
||||
public:
|
||||
DomainMetadata();
|
||||
|
||||
// Returns the last set metadata
|
||||
// If connected users have changed, metadata may need to be updated
|
||||
// this should be checked by storing tic = getTic() between calls
|
||||
// and testing it for equality before the next get (tic == getTic())
|
||||
QJsonObject get() { return QJsonObject::fromVariantMap(_metadata); }
|
||||
QJsonObject getUsers() { return QJsonObject::fromVariantMap(_metadata[USERS].toMap()); }
|
||||
QJsonObject getDescriptors() { return QJsonObject::fromVariantMap(_metadata[DESCRIPTORS].toMap()); }
|
||||
|
||||
uint32_t getTic() { return _tic; }
|
||||
|
||||
void setDescriptors(QVariantMap& settings);
|
||||
void updateUsers();
|
||||
|
||||
public slots:
|
||||
void usersChanged();
|
||||
|
||||
protected:
|
||||
QVariantMap _metadata;
|
||||
uint32_t _tic{ 0 };
|
||||
};
|
||||
|
||||
#endif // hifi_DomainMetadata_h
|
|
@ -94,6 +94,10 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
qRegisterMetaType<DomainServerWebSessionData>("DomainServerWebSessionData");
|
||||
qRegisterMetaTypeStreamOperators<DomainServerWebSessionData>("DomainServerWebSessionData");
|
||||
|
||||
// update the metadata when a user (dis)connects
|
||||
connect(this, &DomainServer::userConnected, &_metadata, &DomainMetadata::usersChanged);
|
||||
connect(this, &DomainServer::userDisconnected, &_metadata, &DomainMetadata::usersChanged);
|
||||
|
||||
// make sure we hear about newly connected nodes from our gatekeeper
|
||||
connect(&_gatekeeper, &DomainGatekeeper::connectedNode, this, &DomainServer::handleConnectedNode);
|
||||
|
||||
|
@ -112,6 +116,9 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
|
||||
optionallyGetTemporaryName(args);
|
||||
}
|
||||
|
||||
// update the metadata with current descriptors
|
||||
_metadata.setDescriptors(_settingsManager.getSettingsMap());
|
||||
}
|
||||
|
||||
DomainServer::~DomainServer() {
|
||||
|
@ -767,12 +774,16 @@ QUrl DomainServer::oauthAuthorizationURL(const QUuid& stateUUID) {
|
|||
}
|
||||
|
||||
void DomainServer::handleConnectedNode(SharedNodePointer newNode) {
|
||||
|
||||
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(newNode->getLinkedData());
|
||||
|
||||
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(newNode->getLinkedData());
|
||||
|
||||
// reply back to the user with a PacketType::DomainList
|
||||
sendDomainListToNode(newNode, nodeData->getSendingSockAddr());
|
||||
|
||||
|
||||
// if this node is a user (unassigned Agent), signal
|
||||
if (newNode->getType() == NodeType::Agent && !nodeData->wasAssigned()) {
|
||||
emit userConnected();
|
||||
}
|
||||
|
||||
// send out this node to our other connected nodes
|
||||
broadcastNewNode(newNode);
|
||||
}
|
||||
|
@ -1067,62 +1078,39 @@ void DomainServer::performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr)
|
|||
sendHeartbeatToMetaverse(newPublicSockAddr.getAddress().toString());
|
||||
}
|
||||
|
||||
|
||||
void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) {
|
||||
const QString DOMAIN_UPDATE = "/api/v1/domains/%1";
|
||||
|
||||
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
||||
const QUuid& domainID = nodeList->getSessionUUID();
|
||||
|
||||
// setup the domain object to send to the data server
|
||||
const QString PUBLIC_NETWORK_ADDRESS_KEY = "network_address";
|
||||
const QString AUTOMATIC_NETWORKING_KEY = "automatic_networking";
|
||||
|
||||
// Setup the domain object to send to the data server
|
||||
QJsonObject domainObject;
|
||||
|
||||
if (!networkAddress.isEmpty()) {
|
||||
static const QString PUBLIC_NETWORK_ADDRESS_KEY = "network_address";
|
||||
domainObject[PUBLIC_NETWORK_ADDRESS_KEY] = networkAddress;
|
||||
}
|
||||
|
||||
static const QString AUTOMATIC_NETWORKING_KEY = "automatic_networking";
|
||||
domainObject[AUTOMATIC_NETWORKING_KEY] = _automaticNetworkingSetting;
|
||||
|
||||
// add a flag to indicate if this domain uses restricted access - for now that will exclude it from listings
|
||||
const QString RESTRICTED_ACCESS_FLAG = "restricted";
|
||||
|
||||
// Add a flag to indicate if this domain uses restricted access -
|
||||
// for now that will exclude it from listings
|
||||
static const QString RESTRICTED_ACCESS_FLAG = "restricted";
|
||||
domainObject[RESTRICTED_ACCESS_FLAG] =
|
||||
_settingsManager.valueOrDefaultValueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH).toBool();
|
||||
|
||||
// figure out the breakdown of currently connected interface clients
|
||||
int numConnectedUnassigned = 0;
|
||||
QJsonObject userHostnames;
|
||||
|
||||
static const QString DEFAULT_HOSTNAME = "*";
|
||||
|
||||
nodeList->eachNode([&numConnectedUnassigned, &userHostnames](const SharedNodePointer& node) {
|
||||
if (node->getLinkedData()) {
|
||||
auto nodeData = static_cast<DomainServerNodeData*>(node->getLinkedData());
|
||||
|
||||
if (!nodeData->wasAssigned()) {
|
||||
++numConnectedUnassigned;
|
||||
|
||||
// increment the count for this hostname (or the default if we don't have one)
|
||||
auto hostname = nodeData->getPlaceName().isEmpty() ? DEFAULT_HOSTNAME : nodeData->getPlaceName();
|
||||
userHostnames[hostname] = userHostnames[hostname].toInt() + 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Add the metadata to the heartbeat
|
||||
static const QString DOMAIN_HEARTBEAT_KEY = "heartbeat";
|
||||
static const QString HEARTBEAT_NUM_USERS_KEY = "num_users";
|
||||
static const QString HEARTBEAT_USER_HOSTNAMES_KEY = "user_hostnames";
|
||||
auto tic = _metadata.getTic();
|
||||
if (_metadataTic != tic) {
|
||||
_metadataTic = tic;
|
||||
_metadata.updateUsers();
|
||||
}
|
||||
domainObject[DOMAIN_HEARTBEAT_KEY] = _metadata.getUsers();
|
||||
|
||||
QJsonObject heartbeatObject;
|
||||
heartbeatObject[HEARTBEAT_NUM_USERS_KEY] = numConnectedUnassigned;
|
||||
heartbeatObject[HEARTBEAT_USER_HOSTNAMES_KEY] = userHostnames;
|
||||
|
||||
domainObject[DOMAIN_HEARTBEAT_KEY] = heartbeatObject;
|
||||
|
||||
QString domainUpdateJSON = QString("{\"domain\": %1 }").arg(QString(QJsonDocument(domainObject).toJson()));
|
||||
QString domainUpdateJSON = QString("{\"domain\":%1}").arg(QString(QJsonDocument(domainObject).toJson(QJsonDocument::Compact)));
|
||||
|
||||
static const QString DOMAIN_UPDATE = "/api/v1/domains/%1";
|
||||
DependencyManager::get<AccountManager>()->sendRequest(DOMAIN_UPDATE.arg(uuidStringWithoutCurlyBraces(domainID)),
|
||||
AccountManagerAuth::Required,
|
||||
QNetworkAccessManager::PutOperation,
|
||||
|
@ -1918,11 +1906,10 @@ void DomainServer::nodeAdded(SharedNodePointer node) {
|
|||
}
|
||||
|
||||
void DomainServer::nodeKilled(SharedNodePointer node) {
|
||||
|
||||
// if this peer connected via ICE then remove them from our ICE peers hash
|
||||
_gatekeeper.removeICEPeer(node->getUUID());
|
||||
|
||||
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
|
||||
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(node->getLinkedData());
|
||||
|
||||
if (nodeData) {
|
||||
// if this node's UUID matches a static assignment we need to throw it back in the assignment queue
|
||||
|
@ -1934,15 +1921,22 @@ void DomainServer::nodeKilled(SharedNodePointer node) {
|
|||
}
|
||||
}
|
||||
|
||||
// If this node was an Agent ask DomainServerNodeData to potentially remove the interpolation we stored
|
||||
nodeData->removeOverrideForKey(USERNAME_UUID_REPLACEMENT_STATS_KEY,
|
||||
uuidStringWithoutCurlyBraces(node->getUUID()));
|
||||
|
||||
// cleanup the connection secrets that we set up for this node (on the other nodes)
|
||||
foreach (const QUuid& otherNodeSessionUUID, nodeData->getSessionSecretHash().keys()) {
|
||||
SharedNodePointer otherNode = DependencyManager::get<LimitedNodeList>()->nodeWithUUID(otherNodeSessionUUID);
|
||||
if (otherNode) {
|
||||
reinterpret_cast<DomainServerNodeData*>(otherNode->getLinkedData())->getSessionSecretHash().remove(node->getUUID());
|
||||
static_cast<DomainServerNodeData*>(otherNode->getLinkedData())->getSessionSecretHash().remove(node->getUUID());
|
||||
}
|
||||
}
|
||||
|
||||
if (node->getType() == NodeType::Agent) {
|
||||
// if this node was an Agent ask DomainServerNodeData to remove the interpolation we potentially stored
|
||||
nodeData->removeOverrideForKey(USERNAME_UUID_REPLACEMENT_STATS_KEY,
|
||||
uuidStringWithoutCurlyBraces(node->getUUID()));
|
||||
|
||||
// if this node is a user (unassigned Agent), signal
|
||||
if (!nodeData->wasAssigned()) {
|
||||
emit userDisconnected();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#include <LimitedNodeList.h>
|
||||
|
||||
#include "DomainGatekeeper.h"
|
||||
#include "DomainMetadata.h"
|
||||
#include "DomainServerSettingsManager.h"
|
||||
#include "DomainServerWebSessionData.h"
|
||||
#include "WalletTransaction.h"
|
||||
|
@ -91,6 +92,8 @@ private slots:
|
|||
|
||||
signals:
|
||||
void iceServerChanged();
|
||||
void userConnected();
|
||||
void userDisconnected();
|
||||
|
||||
private:
|
||||
void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid());
|
||||
|
@ -167,6 +170,9 @@ private:
|
|||
|
||||
DomainServerSettingsManager _settingsManager;
|
||||
|
||||
DomainMetadata _metadata;
|
||||
uint32_t _metadataTic{ 0 };
|
||||
|
||||
HifiSockAddr _iceServerSocket;
|
||||
std::unique_ptr<NLPacket> _iceServerHeartbeatPacket;
|
||||
|
||||
|
|
|
@ -1,22 +1,40 @@
|
|||
{
|
||||
"name": "Oculus Touch to Standard",
|
||||
"channels": [
|
||||
{ "from": "OculusTouch.LY", "filters": "invert", "to": "Standard.LY" },
|
||||
{ "from": "OculusTouch.LX", "to": "Standard.LX" },
|
||||
{ "from": "OculusTouch.A", "to": "Standard.A" },
|
||||
{ "from": "OculusTouch.B", "to": "Standard.B" },
|
||||
{ "from": "OculusTouch.X", "to": "Standard.X" },
|
||||
{ "from": "OculusTouch.Y", "to": "Standard.Y" },
|
||||
|
||||
{ "from": "OculusTouch.LY", "filters": "invert", "to": "Standard.LY" },
|
||||
{ "from": "OculusTouch.LX", "to": "Standard.LX" },
|
||||
{ "from": "OculusTouch.LT", "to": "Standard.LT" },
|
||||
{ "from": "OculusTouch.LS", "to": "Standard.LS" },
|
||||
{ "from": "OculusTouch.LeftGrip", "to": "Standard.LeftGrip" },
|
||||
{ "from": "OculusTouch.LeftHand", "to": "Standard.LeftHand" },
|
||||
|
||||
{ "from": "OculusTouch.RY", "filters": "invert", "to": "Standard.RY" },
|
||||
{ "from": "OculusTouch.RX", "to": "Standard.RX" },
|
||||
|
||||
{ "from": "OculusTouch.RY", "filters": "invert", "to": "Standard.RY" },
|
||||
{ "from": "OculusTouch.RX", "to": "Standard.RX" },
|
||||
{ "from": "OculusTouch.RT", "to": "Standard.RT" },
|
||||
{ "from": "OculusTouch.RB", "to": "Standard.RB" },
|
||||
{ "from": "OculusTouch.RS", "to": "Standard.RS" },
|
||||
{ "from": "OculusTouch.RightGrip", "to": "Standard.RightGrip" },
|
||||
{ "from": "OculusTouch.RightHand", "to": "Standard.RightHand" },
|
||||
|
||||
{ "from": "OculusTouch.LeftApplicationMenu", "to": "Standard.Back" },
|
||||
{ "from": "OculusTouch.RightApplicationMenu", "to": "Standard.Start" },
|
||||
|
||||
{ "from": "OculusTouch.LeftHand", "to": "Standard.LeftHand" },
|
||||
{ "from": "OculusTouch.RightHand", "to": "Standard.RightHand" }
|
||||
{ "from": "OculusTouch.LeftPrimaryThumbTouch", "to": "Standard.LeftPrimaryThumbTouch" },
|
||||
{ "from": "OculusTouch.LeftSecondaryThumbTouch", "to": "Standard.LeftSecondaryThumbTouch" },
|
||||
{ "from": "OculusTouch.RightPrimaryThumbTouch", "to": "Standard.RightPrimaryThumbTouch" },
|
||||
{ "from": "OculusTouch.RightSecondaryThumbTouch", "to": "Standard.RightSecondaryThumbTouch" },
|
||||
{ "from": "OculusTouch.LeftPrimaryIndexTouch", "to": "Standard.LeftPrimaryIndexTouch" },
|
||||
{ "from": "OculusTouch.RightPrimaryIndexTouch", "to": "Standard.RightPrimaryIndexTouch" },
|
||||
{ "from": "OculusTouch.LSTouch", "to": "Standard.LSTouch" },
|
||||
{ "from": "OculusTouch.RSTouch", "to": "Standard.RSTouch" },
|
||||
{ "from": "OculusTouch.LeftThumbUp", "to": "Standard.LeftThumbUp" },
|
||||
{ "from": "OculusTouch.RightThumbUp", "to": "Standard.RightThumbUp" },
|
||||
{ "from": "OculusTouch.LeftIndexPoint", "to": "Standard.LeftIndexPoint" },
|
||||
{ "from": "OculusTouch.RightIndexPoint", "to": "Standard.RightIndexPoint" }
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
{ "from": "Vive.LX", "when": "Vive.LSX", "to": "Standard.LX" },
|
||||
|
||||
{ "from": "Vive.LT", "to": "Standard.LT" },
|
||||
{ "from": "Vive.LeftGrip", "to": "Standard.LB" },
|
||||
{ "from": "Vive.LeftGrip", "to": "Standard.LeftGrip" },
|
||||
{ "from": "Vive.LS", "to": "Standard.LS" },
|
||||
{ "from": "Vive.LSTouch", "to": "Standard.LSTouch" },
|
||||
|
||||
|
@ -13,7 +13,7 @@
|
|||
{ "from": "Vive.RX", "when": "Vive.RSX", "to": "Standard.RX" },
|
||||
|
||||
{ "from": "Vive.RT", "to": "Standard.RT" },
|
||||
{ "from": "Vive.RightGrip", "to": "Standard.RB" },
|
||||
{ "from": "Vive.RightGrip", "to": "Standard.RightGrip" },
|
||||
{ "from": "Vive.RS", "to": "Standard.RS" },
|
||||
{ "from": "Vive.RSTouch", "to": "Standard.RSTouch" },
|
||||
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
{
|
||||
"name": "XBox to Standard",
|
||||
"channels": [
|
||||
{ "from": "GamePad.LY", "to": "Standard.LY" },
|
||||
{ "from": "GamePad.LX", "to": "Standard.LX" },
|
||||
{ "from": "GamePad.LY", "filters": { "type": "deadZone", "min": 0.05 }, "to": "Standard.LY" },
|
||||
{ "from": "GamePad.LX", "filters": { "type": "deadZone", "min": 0.05 }, "to": "Standard.LX" },
|
||||
{ "from": "GamePad.LT", "to": "Standard.LT" },
|
||||
{ "from": "GamePad.LB", "to": "Standard.LB" },
|
||||
{ "from": "GamePad.LS", "to": "Standard.LS" },
|
||||
|
||||
{ "from": "GamePad.RY", "to": "Standard.RY" },
|
||||
{ "from": "GamePad.RX", "to": "Standard.RX" },
|
||||
{ "from": "GamePad.RY", "filters": { "type": "deadZone", "min": 0.05 }, "to": "Standard.RY" },
|
||||
{ "from": "GamePad.RX", "filters": { "type": "deadZone", "min": 0.05 }, "to": "Standard.RX" },
|
||||
{ "from": "GamePad.RT", "to": "Standard.RT" },
|
||||
{ "from": "GamePad.RB", "to": "Standard.RB" },
|
||||
{ "from": "GamePad.RS", "to": "Standard.RS" },
|
||||
|
|
|
@ -264,6 +264,7 @@ Window {
|
|||
HifiControls.Button {
|
||||
text: "Load Defaults"
|
||||
color: hifi.buttons.black
|
||||
height: 26
|
||||
onClicked: loadDefaults()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,16 +10,20 @@
|
|||
//
|
||||
|
||||
|
||||
#ifdef HAS_BUGSPLAT
|
||||
#include "Application.h"
|
||||
#include "CrashReporter.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <new.h>
|
||||
#include <Windows.h>
|
||||
#include <DbgHelp.h>
|
||||
|
||||
#include <csignal>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
|
||||
#pragma comment(lib, "Dbghelp.lib")
|
||||
|
||||
// SetUnhandledExceptionFilter can be overridden by the CRT at the point that an error occurs. More information
|
||||
// can be found here: http://www.codeproject.com/Articles/154686/SetUnhandledExceptionFilter-and-the-C-C-Runtime-Li
|
||||
// A fairly common approach is to patch the SetUnhandledExceptionFilter so that it cannot be overridden and so
|
||||
|
@ -77,13 +81,37 @@ BOOL redirectLibraryFunctionToFunction(char* library, char* function, void* fn)
|
|||
return bRet;
|
||||
}
|
||||
|
||||
void printStackTrace(ULONG framesToSkip = 1) {
|
||||
HANDLE process = GetCurrentProcess();
|
||||
SymInitialize(process, NULL, TRUE);
|
||||
void* stack[100];
|
||||
uint16_t frames = CaptureStackBackTrace(framesToSkip, 100, stack, NULL);
|
||||
SYMBOL_INFO* symbol = (SYMBOL_INFO *)calloc(sizeof(SYMBOL_INFO) + 256 * sizeof(char), 1);
|
||||
symbol->MaxNameLen = 255;
|
||||
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
|
||||
|
||||
for (uint16_t i = 0; i < frames; ++i) {
|
||||
SymFromAddr(process, (DWORD64)(stack[i]), 0, symbol);
|
||||
qWarning() << QString("%1: %2 - 0x%0X").arg(QString::number(frames - i - 1), QString(symbol->Name), QString::number(symbol->Address, 16));
|
||||
}
|
||||
|
||||
free(symbol);
|
||||
|
||||
// Try to force the log to sync to the filesystem
|
||||
auto app = qApp;
|
||||
if (app && app->getLogger()) {
|
||||
app->getLogger()->sync();
|
||||
}
|
||||
}
|
||||
|
||||
void handleSignal(int signal) {
|
||||
// Throw so BugSplat can handle
|
||||
throw(signal);
|
||||
}
|
||||
|
||||
void handlePureVirtualCall() {
|
||||
void __cdecl handlePureVirtualCall() {
|
||||
qWarning() << "Pure virtual function call detected";
|
||||
printStackTrace(2);
|
||||
// Throw so BugSplat can handle
|
||||
throw("ERROR: Pure virtual call");
|
||||
}
|
||||
|
@ -107,6 +135,8 @@ _purecall_handler __cdecl noop_set_purecall_handler(_purecall_handler pNew) {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
#ifdef HAS_BUGSPLAT
|
||||
|
||||
static const DWORD BUG_SPLAT_FLAGS = MDSF_PREVENTHIJACKING | MDSF_USEGUARDMEMORY;
|
||||
|
||||
CrashReporter::CrashReporter(QString bugSplatDatabase, QString bugSplatApplicationName, QString version)
|
||||
|
@ -133,3 +163,4 @@ CrashReporter::CrashReporter(QString bugSplatDatabase, QString bugSplatApplicati
|
|||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
|
|
@ -115,3 +115,7 @@ QString FileLogger::getLogData() {
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void FileLogger::sync() {
|
||||
_persistThreadInstance->waitIdle();
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ public:
|
|||
virtual void addMessage(const QString&) override;
|
||||
virtual QString getLogData() override;
|
||||
virtual void locateLog() override;
|
||||
void sync();
|
||||
|
||||
signals:
|
||||
void rollingLogFile(QString newFilename);
|
||||
|
|
|
@ -84,7 +84,6 @@ Avatar::Avatar(RigPointer rig) :
|
|||
_acceleration(0.0f),
|
||||
_lastAngularVelocity(0.0f),
|
||||
_lastOrientation(),
|
||||
_leanScale(0.5f),
|
||||
_worldUpDirection(DEFAULT_UP_DIRECTION),
|
||||
_moving(false),
|
||||
_initialized(false),
|
||||
|
|
|
@ -210,7 +210,6 @@ protected:
|
|||
glm::vec3 _angularAcceleration;
|
||||
glm::quat _lastOrientation;
|
||||
|
||||
float _leanScale;
|
||||
glm::vec3 _worldUpDirection;
|
||||
float _stringLength;
|
||||
bool _moving; ///< set when position is changing
|
||||
|
|
|
@ -54,8 +54,6 @@ Head::Head(Avatar* owningAvatar) :
|
|||
_deltaPitch(0.0f),
|
||||
_deltaYaw(0.0f),
|
||||
_deltaRoll(0.0f),
|
||||
_deltaLeanSideways(0.0f),
|
||||
_deltaLeanForward(0.0f),
|
||||
_isCameraMoving(false),
|
||||
_isLookingAtMe(false),
|
||||
_lookingAtMeStarted(0),
|
||||
|
@ -70,7 +68,6 @@ void Head::init() {
|
|||
|
||||
void Head::reset() {
|
||||
_baseYaw = _basePitch = _baseRoll = 0.0f;
|
||||
_leanForward = _leanSideways = 0.0f;
|
||||
}
|
||||
|
||||
void Head::simulate(float deltaTime, bool isMine, bool billboard) {
|
||||
|
@ -118,13 +115,6 @@ void Head::simulate(float deltaTime, bool isMine, bool billboard) {
|
|||
auto eyeTracker = DependencyManager::get<EyeTracker>();
|
||||
_isEyeTrackerConnected = eyeTracker->isTracking();
|
||||
}
|
||||
|
||||
// Twist the upper body to follow the rotation of the head, but only do this with my avatar,
|
||||
// since everyone else will see the full joint rotations for other people.
|
||||
const float BODY_FOLLOW_HEAD_YAW_RATE = 0.1f;
|
||||
const float BODY_FOLLOW_HEAD_FACTOR = 0.66f;
|
||||
float currentTwist = getTorsoTwist();
|
||||
setTorsoTwist(currentTwist + (getFinalYaw() * BODY_FOLLOW_HEAD_FACTOR - currentTwist) * BODY_FOLLOW_HEAD_YAW_RATE);
|
||||
}
|
||||
|
||||
if (!(_isFaceTrackerConnected || billboard)) {
|
||||
|
@ -301,17 +291,13 @@ void Head::applyEyelidOffset(glm::quat headOrientation) {
|
|||
}
|
||||
}
|
||||
|
||||
void Head::relaxLean(float deltaTime) {
|
||||
void Head::relax(float deltaTime) {
|
||||
// restore rotation, lean to neutral positions
|
||||
const float LEAN_RELAXATION_PERIOD = 0.25f; // seconds
|
||||
float relaxationFactor = 1.0f - glm::min(deltaTime / LEAN_RELAXATION_PERIOD, 1.0f);
|
||||
_deltaYaw *= relaxationFactor;
|
||||
_deltaPitch *= relaxationFactor;
|
||||
_deltaRoll *= relaxationFactor;
|
||||
_leanSideways *= relaxationFactor;
|
||||
_leanForward *= relaxationFactor;
|
||||
_deltaLeanSideways *= relaxationFactor;
|
||||
_deltaLeanForward *= relaxationFactor;
|
||||
}
|
||||
|
||||
void Head::setScale (float scale) {
|
||||
|
@ -419,8 +405,3 @@ float Head::getFinalPitch() const {
|
|||
float Head::getFinalRoll() const {
|
||||
return glm::clamp(_baseRoll + _deltaRoll, MIN_HEAD_ROLL, MAX_HEAD_ROLL);
|
||||
}
|
||||
|
||||
void Head::addLeanDeltas(float sideways, float forward) {
|
||||
_deltaLeanSideways += sideways;
|
||||
_deltaLeanForward += forward;
|
||||
}
|
||||
|
|
|
@ -59,8 +59,6 @@ public:
|
|||
glm::vec3 getRightDirection() const { return getOrientation() * IDENTITY_RIGHT; }
|
||||
glm::vec3 getUpDirection() const { return getOrientation() * IDENTITY_UP; }
|
||||
glm::vec3 getFrontDirection() const { return getOrientation() * IDENTITY_FRONT; }
|
||||
float getFinalLeanSideways() const { return _leanSideways + _deltaLeanSideways; }
|
||||
float getFinalLeanForward() const { return _leanForward + _deltaLeanForward; }
|
||||
|
||||
glm::quat getEyeRotation(const glm::vec3& eyePosition) const;
|
||||
|
||||
|
@ -91,8 +89,7 @@ public:
|
|||
virtual float getFinalYaw() const;
|
||||
virtual float getFinalRoll() const;
|
||||
|
||||
void relaxLean(float deltaTime);
|
||||
void addLeanDeltas(float sideways, float forward);
|
||||
void relax(float deltaTime);
|
||||
|
||||
float getTimeWithoutTalking() const { return _timeWithoutTalking; }
|
||||
|
||||
|
@ -132,10 +129,6 @@ private:
|
|||
float _deltaYaw;
|
||||
float _deltaRoll;
|
||||
|
||||
// delta lean angles for lean perturbations (driven by collisions)
|
||||
float _deltaLeanSideways;
|
||||
float _deltaLeanForward;
|
||||
|
||||
bool _isCameraMoving;
|
||||
bool _isLookingAtMe;
|
||||
quint64 _lookingAtMeStarted;
|
||||
|
|
|
@ -190,9 +190,6 @@ MyAvatar::MyAvatar(RigPointer rig) :
|
|||
if (!headData->getBlendshapeCoefficients().isEmpty()) {
|
||||
_headData->setBlendshapeCoefficients(headData->getBlendshapeCoefficients());
|
||||
}
|
||||
// head lean
|
||||
_headData->setLeanForward(headData->getLeanForward());
|
||||
_headData->setLeanSideways(headData->getLeanSideways());
|
||||
// head orientation
|
||||
_headData->setLookAtPosition(headData->getLookAtPosition());
|
||||
}
|
||||
|
@ -237,7 +234,7 @@ QByteArray MyAvatar::toByteArray(bool cullSmallChanges, bool sendAll) {
|
|||
void MyAvatar::reset(bool andRecenter, bool andReload, bool andHead) {
|
||||
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "reset", Q_ARG(bool, andRecenter));
|
||||
QMetaObject::invokeMethod(this, "reset", Q_ARG(bool, andRecenter), Q_ARG(bool, andReload), Q_ARG(bool, andHead));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -306,7 +303,7 @@ void MyAvatar::update(float deltaTime) {
|
|||
}
|
||||
|
||||
Head* head = getHead();
|
||||
head->relaxLean(deltaTime);
|
||||
head->relax(deltaTime);
|
||||
updateFromTrackers(deltaTime);
|
||||
|
||||
// Get audio loudness data from audio input device
|
||||
|
@ -574,16 +571,6 @@ void MyAvatar::updateFromTrackers(float deltaTime) {
|
|||
head->setDeltaYaw(estimatedRotation.y * magnifyFieldOfView);
|
||||
head->setDeltaRoll(estimatedRotation.z);
|
||||
}
|
||||
|
||||
// Update torso lean distance based on accelerometer data
|
||||
const float TORSO_LENGTH = 0.5f;
|
||||
glm::vec3 relativePosition = estimatedPosition - glm::vec3(0.0f, -TORSO_LENGTH, 0.0f);
|
||||
|
||||
const float MAX_LEAN = 45.0f;
|
||||
head->setLeanSideways(glm::clamp(glm::degrees(atanf(relativePosition.x * _leanScale / TORSO_LENGTH)),
|
||||
-MAX_LEAN, MAX_LEAN));
|
||||
head->setLeanForward(glm::clamp(glm::degrees(atanf(relativePosition.z * _leanScale / TORSO_LENGTH)),
|
||||
-MAX_LEAN, MAX_LEAN));
|
||||
}
|
||||
|
||||
glm::vec3 MyAvatar::getLeftHandPosition() const {
|
||||
|
@ -692,7 +679,6 @@ void MyAvatar::saveData() {
|
|||
|
||||
settings.setValue("headPitch", getHead()->getBasePitch());
|
||||
|
||||
settings.setValue("leanScale", _leanScale);
|
||||
settings.setValue("scale", _targetScale);
|
||||
|
||||
settings.setValue("fullAvatarURL",
|
||||
|
@ -809,7 +795,6 @@ void MyAvatar::loadData() {
|
|||
|
||||
getHead()->setBasePitch(loadSetting(settings, "headPitch", 0.0f));
|
||||
|
||||
_leanScale = loadSetting(settings, "leanScale", 0.05f);
|
||||
_targetScale = loadSetting(settings, "scale", 1.0f);
|
||||
setScale(glm::vec3(_targetScale));
|
||||
|
||||
|
@ -1271,13 +1256,13 @@ void MyAvatar::prepareForPhysicsSimulation() {
|
|||
void MyAvatar::harvestResultsFromPhysicsSimulation(float deltaTime) {
|
||||
glm::vec3 position = getPosition();
|
||||
glm::quat orientation = getOrientation();
|
||||
if (_characterController.isEnabled()) {
|
||||
if (_characterController.isEnabledAndReady()) {
|
||||
_characterController.getPositionAndOrientation(position, orientation);
|
||||
}
|
||||
nextAttitude(position, orientation);
|
||||
_bodySensorMatrix = _follow.postPhysicsUpdate(*this, _bodySensorMatrix);
|
||||
|
||||
if (_characterController.isEnabled()) {
|
||||
if (_characterController.isEnabledAndReady()) {
|
||||
setVelocity(_characterController.getLinearVelocity() + _characterController.getFollowVelocity());
|
||||
} else {
|
||||
setVelocity(getVelocity() + _characterController.getFollowVelocity());
|
||||
|
@ -1660,7 +1645,7 @@ void MyAvatar::updatePosition(float deltaTime) {
|
|||
|
||||
vec3 velocity = getVelocity();
|
||||
const float MOVING_SPEED_THRESHOLD_SQUARED = 0.0001f; // 0.01 m/s
|
||||
if (!_characterController.isEnabled()) {
|
||||
if (!_characterController.isEnabledAndReady()) {
|
||||
// _characterController is not in physics simulation but it can still compute its target velocity
|
||||
updateMotors();
|
||||
_characterController.computeNewVelocity(deltaTime, velocity);
|
||||
|
@ -1834,6 +1819,16 @@ void MyAvatar::updateMotionBehaviorFromMenu() {
|
|||
_motionBehaviors &= ~AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED;
|
||||
}
|
||||
|
||||
setCharacterControllerEnabled(menu->isOptionChecked(MenuOption::EnableCharacterController));
|
||||
}
|
||||
|
||||
void MyAvatar::setCharacterControllerEnabled(bool enabled) {
|
||||
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "setCharacterControllerEnabled", Q_ARG(bool, enabled));
|
||||
return;
|
||||
}
|
||||
|
||||
bool ghostingAllowed = true;
|
||||
EntityTreeRenderer* entityTreeRenderer = qApp->getEntities();
|
||||
if (entityTreeRenderer) {
|
||||
|
@ -1842,12 +1837,11 @@ void MyAvatar::updateMotionBehaviorFromMenu() {
|
|||
ghostingAllowed = zone->getGhostingAllowed();
|
||||
}
|
||||
}
|
||||
bool checked = menu->isOptionChecked(MenuOption::EnableCharacterController);
|
||||
if (!ghostingAllowed) {
|
||||
checked = true;
|
||||
}
|
||||
_characterController.setEnabled(ghostingAllowed ? enabled : true);
|
||||
}
|
||||
|
||||
_characterController.setEnabled(checked);
|
||||
bool MyAvatar::getCharacterControllerEnabled() {
|
||||
return _characterController.isEnabled();
|
||||
}
|
||||
|
||||
void MyAvatar::clearDriveKeys() {
|
||||
|
@ -2055,14 +2049,17 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, co
|
|||
|
||||
void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput) {
|
||||
_desiredBodyMatrix = desiredBodyMatrix;
|
||||
if (!isActive(Rotation) && shouldActivateRotation(myAvatar, desiredBodyMatrix, currentBodyMatrix)) {
|
||||
activate(Rotation);
|
||||
}
|
||||
if (!isActive(Horizontal) && shouldActivateHorizontal(myAvatar, desiredBodyMatrix, currentBodyMatrix)) {
|
||||
activate(Horizontal);
|
||||
}
|
||||
if (!isActive(Vertical) && (shouldActivateVertical(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) {
|
||||
activate(Vertical);
|
||||
|
||||
if (myAvatar.getHMDLeanRecenterEnabled()) {
|
||||
if (!isActive(Rotation) && shouldActivateRotation(myAvatar, desiredBodyMatrix, currentBodyMatrix)) {
|
||||
activate(Rotation);
|
||||
}
|
||||
if (!isActive(Horizontal) && shouldActivateHorizontal(myAvatar, desiredBodyMatrix, currentBodyMatrix)) {
|
||||
activate(Horizontal);
|
||||
}
|
||||
if (!isActive(Vertical) && (shouldActivateVertical(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) {
|
||||
activate(Vertical);
|
||||
}
|
||||
}
|
||||
|
||||
glm::mat4 desiredWorldMatrix = myAvatar.getSensorToWorldMatrix() * _desiredBodyMatrix;
|
||||
|
|
|
@ -69,7 +69,6 @@ class MyAvatar : public Avatar {
|
|||
Q_PROPERTY(AudioListenerMode audioListenerModeCustom READ getAudioListenerModeCustom)
|
||||
//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)
|
||||
|
@ -84,6 +83,9 @@ class MyAvatar : public Avatar {
|
|||
|
||||
Q_PROPERTY(float energy READ getEnergy WRITE setEnergy)
|
||||
|
||||
Q_PROPERTY(bool hmdLeanRecenterEnabled READ getHMDLeanRecenterEnabled WRITE setHMDLeanRecenterEnabled)
|
||||
Q_PROPERTY(bool characterControllerEnabled READ getCharacterControllerEnabled WRITE setCharacterControllerEnabled)
|
||||
|
||||
public:
|
||||
explicit MyAvatar(RigPointer rig);
|
||||
~MyAvatar();
|
||||
|
@ -123,9 +125,6 @@ public:
|
|||
|
||||
void setRealWorldFieldOfView(float realWorldFov) { _realWorldFieldOfView.set(realWorldFov); }
|
||||
|
||||
void setLeanScale(float scale) { _leanScale = scale; }
|
||||
float getLeanScale() const { return _leanScale; }
|
||||
|
||||
Q_INVOKABLE glm::vec3 getDefaultEyePosition() const;
|
||||
|
||||
float getRealWorldFieldOfView() { return _realWorldFieldOfView.get(); }
|
||||
|
@ -163,6 +162,9 @@ public:
|
|||
Q_INVOKABLE bool getClearOverlayWhenDriving() const { return _clearOverlayWhenDriving; }
|
||||
Q_INVOKABLE void setClearOverlayWhenDriving(bool on) { _clearOverlayWhenDriving = on; }
|
||||
|
||||
Q_INVOKABLE void setHMDLeanRecenterEnabled(bool value) { _hmdLeanRecenterEnabled = value; }
|
||||
Q_INVOKABLE bool getHMDLeanRecenterEnabled() const { return _hmdLeanRecenterEnabled; }
|
||||
|
||||
// get/set avatar data
|
||||
void saveData();
|
||||
void loadData();
|
||||
|
@ -264,6 +266,9 @@ public:
|
|||
controller::Pose getLeftHandControllerPoseInAvatarFrame() const;
|
||||
controller::Pose getRightHandControllerPoseInAvatarFrame() const;
|
||||
|
||||
Q_INVOKABLE void setCharacterControllerEnabled(bool enabled);
|
||||
Q_INVOKABLE bool getCharacterControllerEnabled();
|
||||
|
||||
public slots:
|
||||
void increaseSize();
|
||||
void decreaseSize();
|
||||
|
@ -470,6 +475,8 @@ private:
|
|||
ThreadSafeValueCache<controller::Pose> _leftHandControllerPoseInSensorFrameCache { controller::Pose() };
|
||||
ThreadSafeValueCache<controller::Pose> _rightHandControllerPoseInSensorFrameCache { controller::Pose() };
|
||||
|
||||
bool _hmdLeanRecenterEnabled = true;
|
||||
|
||||
float AVATAR_MOVEMENT_ENERGY_CONSTANT { 0.001f };
|
||||
float AUDIO_ENERGY_CONSTANT { 0.000001f };
|
||||
float MAX_AVATAR_MOVEMENT_PER_FRAME { 30.0f };
|
||||
|
|
|
@ -106,10 +106,6 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
|||
MyAvatar* myAvatar = static_cast<MyAvatar*>(_owningAvatar);
|
||||
|
||||
Rig::HeadParameters headParams;
|
||||
headParams.enableLean = qApp->isHMDMode();
|
||||
headParams.leanSideways = head->getFinalLeanSideways();
|
||||
headParams.leanForward = head->getFinalLeanForward();
|
||||
headParams.torsoTwist = head->getTorsoTwist();
|
||||
|
||||
if (qApp->isHMDMode()) {
|
||||
headParams.isInHMD = true;
|
||||
|
@ -131,7 +127,6 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
|||
headParams.worldHeadOrientation = head->getFinalOrientationInWorldFrame();
|
||||
}
|
||||
|
||||
headParams.leanJointIndex = geometry.leanJointIndex;
|
||||
headParams.neckJointIndex = geometry.neckJointIndex;
|
||||
headParams.isTalking = head->getTimeWithoutTalking() <= 1.5f;
|
||||
|
||||
|
|
|
@ -129,16 +129,6 @@ void setupPreferences() {
|
|||
preference->setStep(1);
|
||||
preferences->addPreference(preference);
|
||||
}
|
||||
{
|
||||
auto getter = [=]()->float { return myAvatar->getLeanScale(); };
|
||||
auto setter = [=](float value) { myAvatar->setLeanScale(value); };
|
||||
auto preference = new SpinnerPreference(AVATAR_TUNING, "Lean scale (applies to Faceshift users)", getter, setter);
|
||||
preference->setMin(0);
|
||||
preference->setMax(99.9f);
|
||||
preference->setDecimals(2);
|
||||
preference->setStep(1);
|
||||
preferences->addPreference(preference);
|
||||
}
|
||||
{
|
||||
auto getter = [=]()->float { return myAvatar->getUniformScale(); };
|
||||
auto setter = [=](float value) { myAvatar->setTargetScaleVerbose(value); }; // The hell?
|
||||
|
|
|
@ -931,11 +931,6 @@ glm::quat Rig::getJointDefaultRotationInParentFrame(int jointIndex) {
|
|||
}
|
||||
|
||||
void Rig::updateFromHeadParameters(const HeadParameters& params, float dt) {
|
||||
if (params.enableLean) {
|
||||
updateLeanJoint(params.leanJointIndex, params.leanSideways, params.leanForward, params.torsoTwist);
|
||||
} else {
|
||||
_animVars.unset("lean");
|
||||
}
|
||||
updateNeckJoint(params.neckJointIndex, params);
|
||||
|
||||
_animVars.set("isTalking", params.isTalking);
|
||||
|
@ -953,15 +948,6 @@ static const glm::vec3 X_AXIS(1.0f, 0.0f, 0.0f);
|
|||
static const glm::vec3 Y_AXIS(0.0f, 1.0f, 0.0f);
|
||||
static const glm::vec3 Z_AXIS(0.0f, 0.0f, 1.0f);
|
||||
|
||||
void Rig::updateLeanJoint(int index, float leanSideways, float leanForward, float torsoTwist) {
|
||||
if (isIndexValid(index)) {
|
||||
glm::quat absRot = (glm::angleAxis(-RADIANS_PER_DEGREE * leanSideways, Z_AXIS) *
|
||||
glm::angleAxis(-RADIANS_PER_DEGREE * leanForward, X_AXIS) *
|
||||
glm::angleAxis(RADIANS_PER_DEGREE * torsoTwist, Y_AXIS));
|
||||
_animVars.set("lean", absRot);
|
||||
}
|
||||
}
|
||||
|
||||
void Rig::computeHeadNeckAnimVars(const AnimPose& hmdPose, glm::vec3& headPositionOut, glm::quat& headOrientationOut,
|
||||
glm::vec3& neckPositionOut, glm::quat& neckOrientationOut) const {
|
||||
|
||||
|
|
|
@ -42,15 +42,10 @@ public:
|
|||
};
|
||||
|
||||
struct HeadParameters {
|
||||
float leanSideways = 0.0f; // degrees
|
||||
float leanForward = 0.0f; // degrees
|
||||
float torsoTwist = 0.0f; // degrees
|
||||
bool enableLean = false;
|
||||
glm::quat worldHeadOrientation = glm::quat(); // world space (-z forward)
|
||||
glm::quat rigHeadOrientation = glm::quat(); // rig space (-z forward)
|
||||
glm::vec3 rigHeadPosition = glm::vec3(); // rig space
|
||||
bool isInHMD = false;
|
||||
int leanJointIndex = -1;
|
||||
int neckJointIndex = -1;
|
||||
bool isTalking = false;
|
||||
};
|
||||
|
@ -222,7 +217,6 @@ protected:
|
|||
void applyOverridePoses();
|
||||
void buildAbsoluteRigPoses(const AnimPoseVec& relativePoses, AnimPoseVec& absolutePosesOut);
|
||||
|
||||
void updateLeanJoint(int index, float leanSideways, float leanForward, float torsoTwist);
|
||||
void updateNeckJoint(int index, const HeadParameters& params);
|
||||
void computeHeadNeckAnimVars(const AnimPose& hmdPose, glm::vec3& headPositionOut, glm::quat& headOrientationOut,
|
||||
glm::vec3& neckPositionOut, glm::quat& neckOrientationOut) const;
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
//
|
||||
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
|
@ -119,68 +118,18 @@ static void FIR_1x4_SSE(float* src, float* dst0, float* dst1, float* dst2, float
|
|||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Detect AVX/AVX2 support
|
||||
//
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
|
||||
#include <intrin.h>
|
||||
|
||||
static bool cpuSupportsAVX() {
|
||||
int info[4];
|
||||
int mask = (1 << 27) | (1 << 28); // OSXSAVE and AVX
|
||||
|
||||
__cpuidex(info, 0x1, 0);
|
||||
|
||||
bool result = false;
|
||||
if ((info[2] & mask) == mask) {
|
||||
|
||||
if ((_xgetbv(_XCR_XFEATURE_ENABLED_MASK) & 0x6) == 0x6) {
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#elif defined(__GNUC__)
|
||||
|
||||
#include <cpuid.h>
|
||||
|
||||
static bool cpuSupportsAVX() {
|
||||
unsigned int eax, ebx, ecx, edx;
|
||||
unsigned int mask = (1 << 27) | (1 << 28); // OSXSAVE and AVX
|
||||
|
||||
bool result = false;
|
||||
if (__get_cpuid(0x1, &eax, &ebx, &ecx, &edx) && ((ecx & mask) == mask)) {
|
||||
|
||||
__asm__("xgetbv" : "=a"(eax), "=d"(edx) : "c"(0));
|
||||
if ((eax & 0x6) == 0x6) {
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static bool cpuSupportsAVX() {
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
//
|
||||
// Runtime CPU dispatch
|
||||
//
|
||||
|
||||
typedef void FIR_1x4_t(float* src, float* dst0, float* dst1, float* dst2, float* dst3, float coef[4][HRTF_TAPS], int numFrames);
|
||||
FIR_1x4_t FIR_1x4_AVX; // separate compilation with VEX-encoding enabled
|
||||
#include "CPUDetect.h"
|
||||
|
||||
void FIR_1x4_AVX(float* src, float* dst0, float* dst1, float* dst2, float* dst3, float coef[4][HRTF_TAPS], int numFrames);
|
||||
|
||||
static void FIR_1x4(float* src, float* dst0, float* dst1, float* dst2, float* dst3, float coef[4][HRTF_TAPS], int numFrames) {
|
||||
|
||||
static FIR_1x4_t* f = cpuSupportsAVX() ? FIR_1x4_AVX : FIR_1x4_SSE; // init on first call
|
||||
(*f)(src, dst0, dst1, dst2, dst3, coef, numFrames); // dispatch
|
||||
static auto f = cpuSupportsAVX() ? FIR_1x4_AVX : FIR_1x4_SSE;
|
||||
(*f)(src, dst0, dst1, dst2, dst3, coef, numFrames); // dispatch
|
||||
}
|
||||
|
||||
// 4 channel planar to interleaved
|
||||
|
|
|
@ -32,7 +32,7 @@ public:
|
|||
// input: mono source
|
||||
// output: interleaved stereo mix buffer (accumulates into existing output)
|
||||
// index: HRTF subject index
|
||||
// azimuth: clockwise panning angle [0, 360] in degrees
|
||||
// azimuth: clockwise panning angle in radians
|
||||
// gain: gain factor for distance attenuation
|
||||
// numFrames: must be HRTF_BLOCK in this version
|
||||
//
|
||||
|
|
|
@ -31,9 +31,6 @@ HeadData::HeadData(AvatarData* owningAvatar) :
|
|||
_baseYaw(0.0f),
|
||||
_basePitch(0.0f),
|
||||
_baseRoll(0.0f),
|
||||
_leanSideways(0.0f),
|
||||
_leanForward(0.0f),
|
||||
_torsoTwist(0.0f),
|
||||
_lookAtPosition(0.0f, 0.0f, 0.0f),
|
||||
_audioLoudness(0.0f),
|
||||
_isFaceTrackerConnected(false),
|
||||
|
@ -132,12 +129,6 @@ QJsonObject HeadData::toJson() const {
|
|||
if (getRawOrientation() != quat()) {
|
||||
headJson[JSON_AVATAR_HEAD_ROTATION] = toJsonValue(getRawOrientation());
|
||||
}
|
||||
if (getLeanForward() != 0.0f) {
|
||||
headJson[JSON_AVATAR_HEAD_LEAN_FORWARD] = getLeanForward();
|
||||
}
|
||||
if (getLeanSideways() != 0.0f) {
|
||||
headJson[JSON_AVATAR_HEAD_LEAN_SIDEWAYS] = getLeanSideways();
|
||||
}
|
||||
auto lookat = getLookAtPosition();
|
||||
if (lookat != vec3()) {
|
||||
vec3 relativeLookAt = glm::inverse(_owningAvatar->getOrientation()) *
|
||||
|
@ -171,12 +162,6 @@ void HeadData::fromJson(const QJsonObject& json) {
|
|||
if (json.contains(JSON_AVATAR_HEAD_ROTATION)) {
|
||||
setOrientation(quatFromJsonValue(json[JSON_AVATAR_HEAD_ROTATION]));
|
||||
}
|
||||
if (json.contains(JSON_AVATAR_HEAD_LEAN_FORWARD)) {
|
||||
setLeanForward((float)json[JSON_AVATAR_HEAD_LEAN_FORWARD].toDouble());
|
||||
}
|
||||
if (json.contains(JSON_AVATAR_HEAD_LEAN_SIDEWAYS)) {
|
||||
setLeanSideways((float)json[JSON_AVATAR_HEAD_LEAN_SIDEWAYS].toDouble());
|
||||
}
|
||||
|
||||
if (json.contains(JSON_AVATAR_HEAD_LOOKAT)) {
|
||||
auto relativeLookAt = vec3FromJsonValue(json[JSON_AVATAR_HEAD_LOOKAT]);
|
||||
|
|
|
@ -68,17 +68,6 @@ public:
|
|||
const glm::vec3& getLookAtPosition() const { return _lookAtPosition; }
|
||||
void setLookAtPosition(const glm::vec3& lookAtPosition) { _lookAtPosition = lookAtPosition; }
|
||||
|
||||
|
||||
float getLeanSideways() const { return _leanSideways; }
|
||||
float getLeanForward() const { return _leanForward; }
|
||||
float getTorsoTwist() const { return _torsoTwist; }
|
||||
virtual float getFinalLeanSideways() const { return _leanSideways; }
|
||||
virtual float getFinalLeanForward() const { return _leanForward; }
|
||||
|
||||
void setLeanSideways(float leanSideways) { _leanSideways = leanSideways; }
|
||||
void setLeanForward(float leanForward) { _leanForward = leanForward; }
|
||||
void setTorsoTwist(float torsoTwist) { _torsoTwist = torsoTwist; }
|
||||
|
||||
friend class AvatarData;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
|
@ -89,9 +78,6 @@ protected:
|
|||
float _baseYaw;
|
||||
float _basePitch;
|
||||
float _baseRoll;
|
||||
float _leanSideways;
|
||||
float _leanForward;
|
||||
float _torsoTwist;
|
||||
|
||||
glm::vec3 _lookAtPosition;
|
||||
float _audioLoudness;
|
||||
|
|
|
@ -31,6 +31,12 @@ namespace controller {
|
|||
class Endpoint;
|
||||
using EndpointPointer = std::shared_ptr<Endpoint>;
|
||||
|
||||
enum Hand {
|
||||
LEFT = 0,
|
||||
RIGHT,
|
||||
BOTH
|
||||
};
|
||||
|
||||
// NOTE: If something inherits from both InputDevice and InputPlugin, InputPlugin must go first.
|
||||
// e.g. class Example : public InputPlugin, public InputDevice
|
||||
// instead of class Example : public InputDevice, public InputPlugin
|
||||
|
@ -55,6 +61,9 @@ public:
|
|||
|
||||
const QString& getName() const { return _name; }
|
||||
|
||||
// By default, Input Devices do not support haptics
|
||||
virtual bool triggerHapticPulse(float strength, float duration, controller::Hand hand) { return false; }
|
||||
|
||||
// Update call MUST be called once per simulation loop
|
||||
// It takes care of updating the action states and deltas
|
||||
virtual void update(float deltaTime, const InputCalibrationData& inputCalibrationData) {};
|
||||
|
|
|
@ -140,6 +140,24 @@ namespace controller {
|
|||
return DependencyManager::get<UserInputMapper>()->getActionNames();
|
||||
}
|
||||
|
||||
bool ScriptingInterface::triggerHapticPulse(float strength, float duration, controller::Hand hand) const {
|
||||
return DependencyManager::get<UserInputMapper>()->triggerHapticPulse(strength, duration, hand);
|
||||
}
|
||||
|
||||
bool ScriptingInterface::triggerShortHapticPulse(float strength, controller::Hand hand) const {
|
||||
const float SHORT_HAPTIC_DURATION_MS = 250.0f;
|
||||
return DependencyManager::get<UserInputMapper>()->triggerHapticPulse(strength, SHORT_HAPTIC_DURATION_MS, hand);
|
||||
}
|
||||
|
||||
bool ScriptingInterface::triggerHapticPulseOnDevice(unsigned int device, float strength, float duration, controller::Hand hand) const {
|
||||
return DependencyManager::get<UserInputMapper>()->triggerHapticPulseOnDevice(device, strength, duration, hand);
|
||||
}
|
||||
|
||||
bool ScriptingInterface::triggerShortHapticPulseOnDevice(unsigned int device, float strength, controller::Hand hand) const {
|
||||
const float SHORT_HAPTIC_DURATION_MS = 250.0f;
|
||||
return DependencyManager::get<UserInputMapper>()->triggerHapticPulseOnDevice(device, strength, SHORT_HAPTIC_DURATION_MS, hand);
|
||||
}
|
||||
|
||||
void ScriptingInterface::updateMaps() {
|
||||
QVariantMap newHardware;
|
||||
auto userInputMapper = DependencyManager::get<controller::UserInputMapper>();
|
||||
|
|
|
@ -84,6 +84,11 @@ namespace controller {
|
|||
Q_INVOKABLE Pose getPoseValue(const int& source) const;
|
||||
Q_INVOKABLE Pose getPoseValue(StandardPoseChannel source, uint16_t device = 0) const;
|
||||
|
||||
Q_INVOKABLE bool triggerHapticPulse(float strength, float duration, controller::Hand hand = BOTH) const;
|
||||
Q_INVOKABLE bool triggerShortHapticPulse(float strength, controller::Hand hand = BOTH) const;
|
||||
Q_INVOKABLE bool triggerHapticPulseOnDevice(unsigned int device, float strength, float duration, controller::Hand hand = BOTH) const;
|
||||
Q_INVOKABLE bool triggerShortHapticPulseOnDevice(unsigned int device, float strength, controller::Hand hand = BOTH) const;
|
||||
|
||||
Q_INVOKABLE QObject* newMapping(const QString& mappingName = QUuid::createUuid().toString());
|
||||
Q_INVOKABLE void enableMapping(const QString& mappingName, bool enable = true);
|
||||
Q_INVOKABLE void disableMapping(const QString& mappingName) { enableMapping(mappingName, false); }
|
||||
|
|
|
@ -68,9 +68,7 @@ namespace controller {
|
|||
RIGHT_SECONDARY_INDEX_TOUCH,
|
||||
RIGHT_INDEX_POINT,
|
||||
|
||||
LEFT_GRIP,
|
||||
LEFT_GRIP_TOUCH,
|
||||
RIGHT_GRIP,
|
||||
RIGHT_GRIP_TOUCH,
|
||||
|
||||
NUM_STANDARD_BUTTONS
|
||||
|
@ -87,9 +85,9 @@ namespace controller {
|
|||
// Triggers
|
||||
LT,
|
||||
RT,
|
||||
// Grips (Oculus touch squeeze)
|
||||
LG,
|
||||
RG,
|
||||
// Grips
|
||||
LEFT_GRIP,
|
||||
RIGHT_GRIP,
|
||||
NUM_STANDARD_AXES,
|
||||
LZ = LT,
|
||||
RZ = RT
|
||||
|
|
|
@ -62,14 +62,6 @@ namespace controller {
|
|||
UserInputMapper::~UserInputMapper() {
|
||||
}
|
||||
|
||||
int UserInputMapper::recordDeviceOfType(const QString& deviceName) {
|
||||
if (!_deviceCounts.contains(deviceName)) {
|
||||
_deviceCounts[deviceName] = 0;
|
||||
}
|
||||
_deviceCounts[deviceName] += 1;
|
||||
return _deviceCounts[deviceName];
|
||||
}
|
||||
|
||||
void UserInputMapper::registerDevice(InputDevice::Pointer device) {
|
||||
Locker locker(_lock);
|
||||
if (device->_deviceID == Input::INVALID_DEVICE) {
|
||||
|
@ -77,8 +69,6 @@ void UserInputMapper::registerDevice(InputDevice::Pointer device) {
|
|||
}
|
||||
const auto& deviceID = device->_deviceID;
|
||||
|
||||
recordDeviceOfType(device->getName());
|
||||
|
||||
qCDebug(controllers) << "Registered input device <" << device->getName() << "> deviceID = " << deviceID;
|
||||
|
||||
for (const auto& inputMapping : device->getAvailableInputs()) {
|
||||
|
@ -134,6 +124,15 @@ void UserInputMapper::removeDevice(int deviceID) {
|
|||
_mappingsByDevice.erase(mappingsEntry);
|
||||
}
|
||||
|
||||
for (const auto& inputMapping : device->getAvailableInputs()) {
|
||||
const auto& input = inputMapping.first;
|
||||
auto endpoint = _endpointsByInput.find(input);
|
||||
if (endpoint != _endpointsByInput.end()) {
|
||||
_inputsByEndpoint.erase((*endpoint).second);
|
||||
_endpointsByInput.erase(input);
|
||||
}
|
||||
}
|
||||
|
||||
_registeredDevices.erase(proxyEntry);
|
||||
|
||||
emit hardwareChanged();
|
||||
|
@ -336,10 +335,28 @@ QVector<QString> UserInputMapper::getActionNames() const {
|
|||
return result;
|
||||
}
|
||||
|
||||
bool UserInputMapper::triggerHapticPulse(float strength, float duration, controller::Hand hand) {
|
||||
Locker locker(_lock);
|
||||
bool toReturn = false;
|
||||
for (auto device : _registeredDevices) {
|
||||
toReturn = toReturn || device.second->triggerHapticPulse(strength, duration, hand);
|
||||
}
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
bool UserInputMapper::triggerHapticPulseOnDevice(uint16 deviceID, float strength, float duration, controller::Hand hand) {
|
||||
Locker locker(_lock);
|
||||
if (_registeredDevices.find(deviceID) != _registeredDevices.end()) {
|
||||
return _registeredDevices[deviceID]->triggerHapticPulse(strength, duration, hand);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int actionMetaTypeId = qRegisterMetaType<Action>();
|
||||
int inputMetaTypeId = qRegisterMetaType<Input>();
|
||||
int inputPairMetaTypeId = qRegisterMetaType<Input::NamedPair>();
|
||||
int poseMetaTypeId = qRegisterMetaType<controller::Pose>("Pose");
|
||||
int handMetaTypeId = qRegisterMetaType<controller::Hand>();
|
||||
|
||||
QScriptValue inputToScriptValue(QScriptEngine* engine, const Input& input);
|
||||
void inputFromScriptValue(const QScriptValue& object, Input& input);
|
||||
|
@ -347,6 +364,8 @@ QScriptValue actionToScriptValue(QScriptEngine* engine, const Action& action);
|
|||
void actionFromScriptValue(const QScriptValue& object, Action& action);
|
||||
QScriptValue inputPairToScriptValue(QScriptEngine* engine, const Input::NamedPair& inputPair);
|
||||
void inputPairFromScriptValue(const QScriptValue& object, Input::NamedPair& inputPair);
|
||||
QScriptValue handToScriptValue(QScriptEngine* engine, const controller::Hand& hand);
|
||||
void handFromScriptValue(const QScriptValue& object, controller::Hand& hand);
|
||||
|
||||
QScriptValue inputToScriptValue(QScriptEngine* engine, const Input& input) {
|
||||
QScriptValue obj = engine->newObject();
|
||||
|
@ -385,12 +404,21 @@ void inputPairFromScriptValue(const QScriptValue& object, Input::NamedPair& inpu
|
|||
inputPair.second = QString(object.property("inputName").toVariant().toString());
|
||||
}
|
||||
|
||||
QScriptValue handToScriptValue(QScriptEngine* engine, const controller::Hand& hand) {
|
||||
return engine->newVariant((int)hand);
|
||||
}
|
||||
|
||||
void handFromScriptValue(const QScriptValue& object, controller::Hand& hand) {
|
||||
hand = Hand(object.toVariant().toInt());
|
||||
}
|
||||
|
||||
void UserInputMapper::registerControllerTypes(QScriptEngine* engine) {
|
||||
qScriptRegisterSequenceMetaType<QVector<Action> >(engine);
|
||||
qScriptRegisterSequenceMetaType<Input::NamedVector>(engine);
|
||||
qScriptRegisterMetaType(engine, actionToScriptValue, actionFromScriptValue);
|
||||
qScriptRegisterMetaType(engine, inputToScriptValue, inputFromScriptValue);
|
||||
qScriptRegisterMetaType(engine, inputPairToScriptValue, inputPairFromScriptValue);
|
||||
qScriptRegisterMetaType(engine, handToScriptValue, handFromScriptValue);
|
||||
|
||||
qScriptRegisterMetaType(engine, Pose::toScriptValue, Pose::fromScriptValue);
|
||||
}
|
||||
|
|
|
@ -89,6 +89,8 @@ namespace controller {
|
|||
void setActionState(Action action, float value) { _actionStates[toInt(action)] = value; }
|
||||
void deltaActionState(Action action, float delta) { _actionStates[toInt(action)] += delta; }
|
||||
void setActionState(Action action, const Pose& value) { _poseStates[toInt(action)] = value; }
|
||||
bool triggerHapticPulse(float strength, float duration, controller::Hand hand);
|
||||
bool triggerHapticPulseOnDevice(uint16 deviceID, float strength, float duration, controller::Hand hand);
|
||||
|
||||
static Input makeStandardInput(controller::StandardButtonChannel button);
|
||||
static Input makeStandardInput(controller::StandardAxisChannel axis);
|
||||
|
@ -141,9 +143,6 @@ namespace controller {
|
|||
std::vector<Pose> _poseStates = std::vector<Pose>(toInt(Action::NUM_ACTIONS));
|
||||
std::vector<float> _lastStandardStates = std::vector<float>();
|
||||
|
||||
int recordDeviceOfType(const QString& deviceName);
|
||||
QHash<const QString&, int> _deviceCounts;
|
||||
|
||||
static float getValue(const EndpointPointer& endpoint, bool peek = false);
|
||||
static Pose getPose(const EndpointPointer& endpoint, bool peek = false);
|
||||
|
||||
|
@ -199,6 +198,7 @@ Q_DECLARE_METATYPE(QVector<controller::Input::NamedPair>)
|
|||
Q_DECLARE_METATYPE(controller::Input)
|
||||
Q_DECLARE_METATYPE(controller::Action)
|
||||
Q_DECLARE_METATYPE(QVector<controller::Action>)
|
||||
Q_DECLARE_METATYPE(controller::Hand)
|
||||
|
||||
// Cheating.
|
||||
using UserInputMapper = controller::UserInputMapper;
|
||||
|
|
|
@ -759,7 +759,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS
|
|||
model.preTransform = glm::translate(rotationOffset) * glm::translate(rotationPivot);
|
||||
model.preRotation = glm::quat(glm::radians(preRotation));
|
||||
model.rotation = glm::quat(glm::radians(rotation));
|
||||
model.postRotation = glm::quat(glm::radians(postRotation));
|
||||
model.postRotation = glm::inverse(glm::quat(glm::radians(postRotation)));
|
||||
model.postTransform = glm::translate(-rotationPivot) * glm::translate(scaleOffset) *
|
||||
glm::translate(scalePivot) * glm::scale(scale) * glm::translate(-scalePivot);
|
||||
// NOTE: angles from the FBX file are in degrees
|
||||
|
@ -927,6 +927,9 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS
|
|||
// material.emissiveColor = getVec3(property.properties, index);
|
||||
// material.emissiveFactor = 1.0;
|
||||
|
||||
} else if (property.properties.at(0) == "AmbientFactor") {
|
||||
material.ambientFactor = property.properties.at(index).value<double>();
|
||||
// Detected just for BLender AO vs lightmap
|
||||
} else if (property.properties.at(0) == "Shininess") {
|
||||
material.shininess = property.properties.at(index).value<double>();
|
||||
|
||||
|
@ -1128,8 +1131,10 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS
|
|||
emissiveTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1));
|
||||
} else if (type.contains("tex_emissive_map")) {
|
||||
emissiveTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1));
|
||||
} else if (type.contains("ambient")) {
|
||||
} else if (type.contains("ambientcolor")) {
|
||||
ambientTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1));
|
||||
} else if (type.contains("ambientfactor")) {
|
||||
ambientFactorTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1));
|
||||
} else if (type.contains("tex_ao_map")) {
|
||||
occlusionTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1));
|
||||
} else if (type == "lcl rotation") {
|
||||
|
|
|
@ -151,6 +151,7 @@ public:
|
|||
float metallic{ 0.0f };
|
||||
float roughness{ 1.0f };
|
||||
float emissiveIntensity{ 1.0f };
|
||||
float ambientFactor{ 1.0f };
|
||||
|
||||
QString materialID;
|
||||
QString name;
|
||||
|
@ -437,6 +438,7 @@ public:
|
|||
QHash<QString, QString> shininessTextures;
|
||||
QHash<QString, QString> emissiveTextures;
|
||||
QHash<QString, QString> ambientTextures;
|
||||
QHash<QString, QString> ambientFactorTextures;
|
||||
QHash<QString, QString> occlusionTextures;
|
||||
|
||||
QHash<QString, FBXMaterial> _fbxMaterials;
|
||||
|
|
|
@ -186,6 +186,14 @@ void FBXReader::consolidateFBXMaterials() {
|
|||
|
||||
FBXTexture occlusionTexture;
|
||||
QString occlusionTextureID = occlusionTextures.value(material.materialID);
|
||||
if (occlusionTextureID.isNull()) {
|
||||
// 2nd chance
|
||||
// For blender we use the ambient factor texture as AOMap ONLY if the ambientFactor value is > 0.0
|
||||
if (material.ambientFactor > 0.0f) {
|
||||
occlusionTextureID = ambientFactorTextures.value(material.materialID);
|
||||
}
|
||||
}
|
||||
|
||||
if (!occlusionTextureID.isNull()) {
|
||||
occlusionTexture = getTexture(occlusionTextureID);
|
||||
detectDifferentUVs |= (occlusionTexture.texcoordSet != 0) || (!emissiveTexture.transform.isIdentity());
|
||||
|
@ -198,6 +206,14 @@ void FBXReader::consolidateFBXMaterials() {
|
|||
|
||||
FBXTexture ambientTexture;
|
||||
QString ambientTextureID = ambientTextures.value(material.materialID);
|
||||
if (ambientTextureID.isNull()) {
|
||||
// 2nd chance
|
||||
// For blender we use the ambient factor texture as Lightmap ONLY if the ambientFactor value is set to 0
|
||||
if (material.ambientFactor == 0.0f) {
|
||||
ambientTextureID = ambientFactorTextures.value(material.materialID);
|
||||
}
|
||||
}
|
||||
|
||||
if (_loadLightmaps && !ambientTextureID.isNull()) {
|
||||
ambientTexture = getTexture(ambientTextureID);
|
||||
detectDifferentUVs |= (ambientTexture.texcoordSet != 0) || (!ambientTexture.transform.isIdentity());
|
||||
|
|
|
@ -418,6 +418,8 @@ model::TextureMapPointer NetworkMaterial::fetchTextureMap(const QUrl& baseUrl, c
|
|||
|
||||
auto map = std::make_shared<model::TextureMap>();
|
||||
map->setTextureSource(texture->_textureSource);
|
||||
map->setTextureTransform(fbxTexture.transform);
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
|
@ -427,6 +429,7 @@ model::TextureMapPointer NetworkMaterial::fetchTextureMap(const QUrl& url, Textu
|
|||
|
||||
auto map = std::make_shared<model::TextureMap>();
|
||||
map->setTextureSource(texture->_textureSource);
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
|
@ -475,6 +478,7 @@ NetworkMaterial::NetworkMaterial(const FBXMaterial& material, const QUrl& textur
|
|||
|
||||
if (!material.occlusionTexture.filename.isEmpty()) {
|
||||
auto map = fetchTextureMap(textureBaseUrl, material.occlusionTexture, NetworkTexture::OCCLUSION_TEXTURE, MapChannel::OCCLUSION_MAP);
|
||||
map->setTextureTransform(material.occlusionTexture.transform);
|
||||
setTextureMap(MapChannel::OCCLUSION_MAP, map);
|
||||
}
|
||||
|
||||
|
|
|
@ -122,6 +122,10 @@ void Material::setTextureMap(MapChannel channel, const TextureMapPointer& textur
|
|||
_texMapArrayBuffer.edit<TexMapArraySchema>()._texcoordTransforms[0] = (textureMap ? textureMap->getTextureTransform().getMatrix() : glm::mat4());
|
||||
}
|
||||
|
||||
if (channel == MaterialKey::OCCLUSION_MAP) {
|
||||
_texMapArrayBuffer.edit<TexMapArraySchema>()._texcoordTransforms[1] = (textureMap ? textureMap->getTextureTransform().getMatrix() : glm::mat4());
|
||||
}
|
||||
|
||||
if (channel == MaterialKey::LIGHTMAP_MAP) {
|
||||
// update the texcoord1 with lightmap
|
||||
_texMapArrayBuffer.edit<TexMapArraySchema>()._texcoordTransforms[1] = (textureMap ? textureMap->getTextureTransform().getMatrix() : glm::mat4());
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <atomic>
|
||||
#include <btBulletDynamicsCommon.h>
|
||||
#include <BulletDynamics/Character/btCharacterControllerInterface.h>
|
||||
|
||||
|
@ -105,8 +106,9 @@ public:
|
|||
|
||||
void setLocalBoundingBox(const glm::vec3& corner, const glm::vec3& scale);
|
||||
|
||||
bool isEnabled() const { return _enabled; } // thread-safe
|
||||
void setEnabled(bool enabled);
|
||||
bool isEnabled() const { return _enabled && _dynamicsWorld; }
|
||||
bool isEnabledAndReady() const { return _enabled && _dynamicsWorld; }
|
||||
|
||||
bool getRigidBodyLocation(glm::vec3& avatarRigidBodyPosition, glm::quat& avatarRigidBodyRotation);
|
||||
|
||||
|
@ -167,7 +169,7 @@ protected:
|
|||
btQuaternion _followAngularDisplacement;
|
||||
btVector3 _linearAcceleration;
|
||||
|
||||
bool _enabled;
|
||||
std::atomic_bool _enabled;
|
||||
State _state;
|
||||
bool _isPushingUp;
|
||||
|
||||
|
|
|
@ -83,7 +83,7 @@ static const std::string DEFAULT_NORMAL_SHADER {
|
|||
static const std::string DEFAULT_OCCLUSION_SHADER{
|
||||
"vec4 getFragmentColor() {"
|
||||
" DeferredFragment frag = unpackDeferredFragmentNoPosition(uv);"
|
||||
" return vec4(vec3(frag.obscurance), 1.0);"
|
||||
" return vec4(vec3(pow(frag.obscurance, 1.0 / 2.2)), 1.0);"
|
||||
" }"
|
||||
};
|
||||
|
||||
|
|
|
@ -90,7 +90,7 @@ float fetchOcclusionMap(vec2 uv) {
|
|||
<@endfunc@>
|
||||
|
||||
|
||||
<@func fetchMaterialTextures(matKey, texcoord0, albedo, roughness, normal, metallic, emissive, occlusion)@>
|
||||
<@func fetchMaterialTexturesCoord0(matKey, texcoord0, albedo, roughness, normal, metallic, emissive)@>
|
||||
<@if albedo@>
|
||||
vec4 <$albedo$> = (((<$matKey$> & (ALBEDO_MAP_BIT | OPACITY_MASK_MAP_BIT | OPACITY_TRANSLUCENT_MAP_BIT)) != 0) ? fetchAlbedoMap(<$texcoord0$>) : vec4(1.0));
|
||||
<@endif@>
|
||||
|
@ -106,12 +106,19 @@ float fetchOcclusionMap(vec2 uv) {
|
|||
<@if emissive@>
|
||||
vec3 <$emissive$> = (((<$matKey$> & EMISSIVE_MAP_BIT) != 0) ? fetchEmissiveMap(<$texcoord0$>) : vec3(0.0));
|
||||
<@endif@>
|
||||
<@endfunc@>
|
||||
|
||||
<@func fetchMaterialTexturesCoord1(matKey, texcoord1, occlusion, lightmapVal)@>
|
||||
<@if occlusion@>
|
||||
float <$occlusion$> = (((<$matKey$> & OCCLUSION_MAP_BIT) != 0) ? fetchOcclusionMap(<$texcoord0$>) : 1.0);
|
||||
float <$occlusion$> = (((<$matKey$> & OCCLUSION_MAP_BIT) != 0) ? fetchOcclusionMap(<$texcoord1$>) : 1.0);
|
||||
<@endif@>
|
||||
<@if lightmapVal@>
|
||||
vec3 <$lightmapVal$> = fetchLightmapMap(<$texcoord1$>);
|
||||
<@endif@>
|
||||
<@endfunc@>
|
||||
|
||||
|
||||
|
||||
<@func declareMaterialLightmap()@>
|
||||
|
||||
<$declareMaterialTexMapArrayBuffer()$>
|
||||
|
@ -123,11 +130,6 @@ vec3 fetchLightmapMap(vec2 uv) {
|
|||
}
|
||||
<@endfunc@>
|
||||
|
||||
<@func fetchMaterialLightmap(texcoord1, lightmapVal)@>
|
||||
vec3 <$lightmapVal$> = fetchLightmapMap(<$texcoord1$>);
|
||||
<@endfunc@>
|
||||
|
||||
|
||||
<@func tangentToViewSpace(fetchedNormal, interpolatedNormal, interpolatedTangent, normal)@>
|
||||
{
|
||||
vec3 normalizedNormal = normalize(<$interpolatedNormal$>.xyz);
|
||||
|
|
|
@ -22,12 +22,14 @@ in vec4 _position;
|
|||
in vec3 _normal;
|
||||
in vec3 _color;
|
||||
in vec2 _texCoord0;
|
||||
in vec2 _texCoord1;
|
||||
|
||||
|
||||
void main(void) {
|
||||
Material mat = getMaterial();
|
||||
int matKey = getMaterialKey(mat);
|
||||
<$fetchMaterialTextures(matKey, _texCoord0, albedoTex, roughnessTex, _SCRIBE_NULL, _SCRIBE_NULL, emissiveTex, occlusionTex)$>
|
||||
<$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex, roughnessTex, _SCRIBE_NULL, _SCRIBE_NULL, emissiveTex)$>
|
||||
<$fetchMaterialTexturesCoord1(matKey, _texCoord1, occlusionTex)$>
|
||||
|
||||
float opacity = 1.0;
|
||||
<$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>;
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
out vec3 _color;
|
||||
out float _alpha;
|
||||
out vec2 _texCoord0;
|
||||
out vec2 _texCoord1;
|
||||
out vec4 _position;
|
||||
out vec3 _normal;
|
||||
|
||||
|
@ -31,6 +32,7 @@ void main(void) {
|
|||
|
||||
TexMapArray texMapArray = getTexMapArray();
|
||||
<$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord0)$>
|
||||
<$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord1)$>
|
||||
|
||||
// standard transform
|
||||
TransformCamera cam = getTransformCamera();
|
||||
|
|
|
@ -29,8 +29,8 @@ in vec3 _color;
|
|||
void main(void) {
|
||||
Material mat = getMaterial();
|
||||
int matKey = getMaterialKey(mat);
|
||||
<$fetchMaterialTextures(matKey, _texCoord0, albedo, roughness)$>
|
||||
<$fetchMaterialLightmap(_texCoord1, lightmapVal)$>
|
||||
<$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedo, roughness)$>
|
||||
<$fetchMaterialTexturesCoord1(matKey, _texCoord1, _SCRIBE_NULL, lightmapVal)$>
|
||||
|
||||
|
||||
packDeferredFragmentLightmap(
|
||||
|
|
|
@ -30,8 +30,8 @@ in vec3 _color;
|
|||
void main(void) {
|
||||
Material mat = getMaterial();
|
||||
int matKey = getMaterialKey(mat);
|
||||
<$fetchMaterialTextures(matKey, _texCoord0, albedo, roughness, normalTexel)$>
|
||||
<$fetchMaterialLightmap(_texCoord1, lightmapVal)$>
|
||||
<$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedo, roughness, normalTexel)$>
|
||||
<$fetchMaterialTexturesCoord1(matKey, _texCoord1, _SCRIBE_NULL, lightmapVal)$>
|
||||
|
||||
vec3 viewNormal;
|
||||
<$tangentToViewSpace(normalTexel, _normal, _tangent, viewNormal)$>
|
||||
|
|
|
@ -30,8 +30,8 @@ in vec3 _color;
|
|||
void main(void) {
|
||||
Material mat = getMaterial();
|
||||
int matKey = getMaterialKey(mat);
|
||||
<$fetchMaterialTextures(matKey, _texCoord0, albedo, roughness, normalTexel, metallicTex)$>
|
||||
<$fetchMaterialLightmap(_texCoord1, lightmapVal)$>
|
||||
<$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedo, roughness, normalTexel, metallicTex)$>
|
||||
<$fetchMaterialTexturesCoord1(matKey, _texCoord1, _SCRIBE_NULL, lightmapVal)$>
|
||||
|
||||
vec3 viewNormal;
|
||||
<$tangentToViewSpace(normalTexel, _normal, _tangent, viewNormal)$>
|
||||
|
|
|
@ -29,8 +29,8 @@ in vec3 _color;
|
|||
void main(void) {
|
||||
Material mat = getMaterial();
|
||||
int matKey = getMaterialKey(mat);
|
||||
<$fetchMaterialTextures(matKey, _texCoord0, albedo, roughness, _SCRIBE_NULL, metallicTex)$>
|
||||
<$fetchMaterialLightmap(_texCoord1, lightmapVal)$>
|
||||
<$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedo, roughness, _SCRIBE_NULL, metallicTex)$>
|
||||
<$fetchMaterialTexturesCoord1(matKey, _texCoord1, _SCRIBE_NULL, lightmapVal)$>
|
||||
|
||||
packDeferredFragmentLightmap(
|
||||
normalize(_normal),
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
in vec4 _position;
|
||||
in vec2 _texCoord0;
|
||||
in vec2 _texCoord1;
|
||||
in vec3 _normal;
|
||||
in vec3 _tangent;
|
||||
in vec3 _color;
|
||||
|
@ -28,7 +29,8 @@ in vec3 _color;
|
|||
void main(void) {
|
||||
Material mat = getMaterial();
|
||||
int matKey = getMaterialKey(mat);
|
||||
<$fetchMaterialTextures(matKey, _texCoord0, albedoTex, roughnessTex, normalTex, _SCRIBE_NULL, emissiveTex, occlusionTex)$>
|
||||
<$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex, roughnessTex, normalTex, _SCRIBE_NULL, emissiveTex)$>
|
||||
<$fetchMaterialTexturesCoord1(matKey, _texCoord1, occlusionTex)$>
|
||||
|
||||
float opacity = 1.0;
|
||||
<$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>;
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
|
||||
out vec4 _position;
|
||||
out vec2 _texCoord0;
|
||||
out vec2 _texCoord1;
|
||||
out vec3 _normal;
|
||||
out vec3 _tangent;
|
||||
out vec3 _color;
|
||||
|
@ -34,6 +35,7 @@ void main(void) {
|
|||
|
||||
TexMapArray texMapArray = getTexMapArray();
|
||||
<$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord0)$>
|
||||
<$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord1)$>
|
||||
|
||||
// standard transform
|
||||
TransformCamera cam = getTransformCamera();
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
in vec4 _position;
|
||||
in vec2 _texCoord0;
|
||||
in vec2 _texCoord1;
|
||||
in vec3 _normal;
|
||||
in vec3 _tangent;
|
||||
in vec3 _color;
|
||||
|
@ -28,7 +29,8 @@ in vec3 _color;
|
|||
void main(void) {
|
||||
Material mat = getMaterial();
|
||||
int matKey = getMaterialKey(mat);
|
||||
<$fetchMaterialTextures(matKey, _texCoord0, albedoTex, roughnessTex, normalTex, metallicTex, emissiveTex, occlusionTex)$>
|
||||
<$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex, roughnessTex, normalTex, metallicTex, emissiveTex)$>
|
||||
<$fetchMaterialTexturesCoord1(matKey, _texCoord1, occlusionTex)$>
|
||||
|
||||
float opacity = 1.0;
|
||||
<$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)&>;
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
in vec4 _position;
|
||||
in vec2 _texCoord0;
|
||||
in vec2 _texCoord1;
|
||||
in vec3 _normal;
|
||||
in vec3 _color;
|
||||
|
||||
|
@ -28,7 +29,8 @@ in vec3 _color;
|
|||
void main(void) {
|
||||
Material mat = getMaterial();
|
||||
int matKey = getMaterialKey(mat);
|
||||
<$fetchMaterialTextures(matKey, _texCoord0, albedoTex, roughnessTex, _SCRIBE_NULL, metallicTex, emissiveTex, occlusionTex)$>
|
||||
<$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex, roughnessTex, _SCRIBE_NULL, metallicTex, emissiveTex)$>
|
||||
<$fetchMaterialTexturesCoord1(matKey, _texCoord1, occlusionTex)$>
|
||||
|
||||
float opacity = 1.0;
|
||||
<$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>;
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
<$declareMaterialTextures(ALBEDO, ROUGHNESS, _SCRIBE_NULL, _SCRIBE_NULL, EMISSIVE, OCCLUSION)$>
|
||||
|
||||
in vec2 _texCoord0;
|
||||
in vec2 _texCoord1;
|
||||
in vec4 _position;
|
||||
in vec3 _normal;
|
||||
in vec3 _color;
|
||||
|
@ -35,7 +36,8 @@ out vec4 _fragColor;
|
|||
void main(void) {
|
||||
Material mat = getMaterial();
|
||||
int matKey = getMaterialKey(mat);
|
||||
<$fetchMaterialTextures(matKey, _texCoord0, albedoTex, roughnessTex, _SCRIBE_NULL, _SCRIBE_NULL, emissiveTex, occlusionTex)$>
|
||||
<$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex, roughnessTex, _SCRIBE_NULL, _SCRIBE_NULL, emissiveTex)$>
|
||||
<$fetchMaterialTexturesCoord1(matKey, _texCoord1, occlusionTex)$>
|
||||
|
||||
float opacity = getMaterialOpacity(mat) * _alpha;
|
||||
<$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>;
|
||||
|
@ -61,7 +63,7 @@ void main(void) {
|
|||
_fragColor = vec4(evalGlobalLightingAlphaBlended(
|
||||
cam._viewInverse,
|
||||
1.0,
|
||||
1.0,
|
||||
occlusionTex,
|
||||
fragPosition,
|
||||
fragNormal,
|
||||
albedo,
|
||||
|
|
|
@ -26,7 +26,7 @@ out vec4 _fragColor;
|
|||
void main(void) {
|
||||
Material mat = getMaterial();
|
||||
int matKey = getMaterialKey(mat);
|
||||
<$fetchMaterialTextures(matKey, _texCoord0, albedoTex, _SCRIBE_NULL, _SCRIBE_NULL, _SCRIBE_NULL, _SCRIBE_NULL, _SCRIBE_NULL)$>
|
||||
<$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex)$>
|
||||
|
||||
float opacity = getMaterialOpacity(mat) * _alpha;
|
||||
<$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>;
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
<@include model/Material.slh@>
|
||||
|
||||
<@include MaterialTextures.slh@>
|
||||
<$declareMaterialTextures(ALBEDO, _SCRIBE_NULL, _SCRIBE_NULL, _SCRIBE_NULL, _SCRIBE_NULL, _SCRIBE_NULL)$>
|
||||
<$declareMaterialTextures(ALBEDO)$>
|
||||
|
||||
in vec2 _texCoord0;
|
||||
in vec3 _normal;
|
||||
|
@ -27,7 +27,7 @@ void main(void) {
|
|||
|
||||
Material mat = getMaterial();
|
||||
int matKey = getMaterialKey(mat);
|
||||
<$fetchMaterialTextures(matKey, _texCoord0, albedoTex, _SCRIBE_NULL, _SCRIBE_NULL, _SCRIBE_NULL, _SCRIBE_NULL, _SCRIBE_NULL)$>
|
||||
<$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex)$>
|
||||
|
||||
float opacity = 1.0;
|
||||
<$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>;
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
|
||||
out vec4 _position;
|
||||
out vec2 _texCoord0;
|
||||
out vec2 _texCoord1;
|
||||
out vec3 _normal;
|
||||
out vec3 _color;
|
||||
out float _alpha;
|
||||
|
@ -40,6 +41,7 @@ void main(void) {
|
|||
|
||||
TexMapArray texMapArray = getTexMapArray();
|
||||
<$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord0)$>
|
||||
<$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord1)$>
|
||||
|
||||
// standard transform
|
||||
TransformCamera cam = getTransformCamera();
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
|
||||
out vec4 _position;
|
||||
out vec2 _texCoord0;
|
||||
out vec2 _texCoord1;
|
||||
out vec3 _normal;
|
||||
out vec3 _tangent;
|
||||
out vec3 _color;
|
||||
|
@ -42,6 +43,7 @@ void main(void) {
|
|||
|
||||
TexMapArray texMapArray = getTexMapArray();
|
||||
<$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord0)$>
|
||||
<$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord1)$>
|
||||
|
||||
interpolatedNormal = vec4(normalize(interpolatedNormal.xyz), 0.0);
|
||||
interpolatedTangent = vec4(normalize(interpolatedTangent.xyz), 0.0);
|
||||
|
|
|
@ -12,9 +12,10 @@
|
|||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <QQueue>
|
||||
#include <QMutex>
|
||||
#include <QWaitCondition>
|
||||
#include <QtCore/QElapsedTimer>
|
||||
#include <QtCore/QMutex>
|
||||
#include <QtCore/QQueue>
|
||||
#include <QtCore/QWaitCondition>
|
||||
|
||||
#include "GenericThread.h"
|
||||
#include "NumericalConstants.h"
|
||||
|
@ -35,6 +36,25 @@ public:
|
|||
_hasItems.wakeAll();
|
||||
}
|
||||
|
||||
void waitIdle(uint32_t maxWaitMs = UINT32_MAX) {
|
||||
QElapsedTimer timer;
|
||||
timer.start();
|
||||
|
||||
// FIXME this will work as long as the thread doing the wait
|
||||
// is the only thread which can add work to the queue.
|
||||
// It would be better if instead we had a queue empty condition to wait on
|
||||
// that would ensure that we get woken as soon as we're idle the very
|
||||
// first time the queue was empty.
|
||||
while (timer.elapsed() < maxWaitMs) {
|
||||
lock();
|
||||
if (!_items.size()) {
|
||||
unlock();
|
||||
return;
|
||||
}
|
||||
unlock();
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void queueItemInternal(const T& t) {
|
||||
_items.push_back(t);
|
||||
|
@ -44,6 +64,7 @@ protected:
|
|||
return MSECS_PER_SECOND;
|
||||
}
|
||||
|
||||
|
||||
virtual bool process() {
|
||||
lock();
|
||||
if (!_items.size()) {
|
||||
|
|
|
@ -21,9 +21,16 @@ Joystick::Joystick(SDL_JoystickID instanceId, SDL_GameController* sdlGameControl
|
|||
InputDevice("GamePad"),
|
||||
_sdlGameController(sdlGameController),
|
||||
_sdlJoystick(SDL_GameControllerGetJoystick(_sdlGameController)),
|
||||
_sdlHaptic(SDL_HapticOpenFromJoystick(_sdlJoystick)),
|
||||
_instanceId(instanceId)
|
||||
{
|
||||
|
||||
if (!_sdlHaptic) {
|
||||
qDebug() << "SDL Haptic Open Failure: " << QString(SDL_GetError());
|
||||
} else {
|
||||
if (SDL_HapticRumbleInit(_sdlHaptic) != 0) {
|
||||
qDebug() << "SDL Haptic Rumble Init Failure: " << QString(SDL_GetError());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Joystick::~Joystick() {
|
||||
|
@ -31,6 +38,9 @@ Joystick::~Joystick() {
|
|||
}
|
||||
|
||||
void Joystick::closeJoystick() {
|
||||
if (_sdlHaptic) {
|
||||
SDL_HapticClose(_sdlHaptic);
|
||||
}
|
||||
SDL_GameControllerClose(_sdlGameController);
|
||||
}
|
||||
|
||||
|
@ -62,55 +72,64 @@ void Joystick::handleButtonEvent(const SDL_ControllerButtonEvent& event) {
|
|||
}
|
||||
}
|
||||
|
||||
bool Joystick::triggerHapticPulse(float strength, float duration, controller::Hand hand) {
|
||||
if (SDL_HapticRumblePlay(_sdlHaptic, strength, duration) != 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
controller::Input::NamedVector Joystick::getAvailableInputs() const {
|
||||
using namespace controller;
|
||||
static const Input::NamedVector availableInputs{
|
||||
makePair(A, "A"),
|
||||
makePair(B, "B"),
|
||||
makePair(X, "X"),
|
||||
makePair(Y, "Y"),
|
||||
// DPad
|
||||
makePair(DU, "DU"),
|
||||
makePair(DD, "DD"),
|
||||
makePair(DL, "DL"),
|
||||
makePair(DR, "DR"),
|
||||
// Bumpers
|
||||
makePair(LB, "LB"),
|
||||
makePair(RB, "RB"),
|
||||
// Stick press
|
||||
makePair(LS, "LS"),
|
||||
makePair(RS, "RS"),
|
||||
// Center buttons
|
||||
makePair(START, "Start"),
|
||||
makePair(BACK, "Back"),
|
||||
// Analog sticks
|
||||
makePair(LX, "LX"),
|
||||
makePair(LY, "LY"),
|
||||
makePair(RX, "RX"),
|
||||
makePair(RY, "RY"),
|
||||
|
||||
// Triggers
|
||||
makePair(LT, "LT"),
|
||||
makePair(RT, "RT"),
|
||||
if (_availableInputs.length() == 0) {
|
||||
_availableInputs = {
|
||||
makePair(A, "A"),
|
||||
makePair(B, "B"),
|
||||
makePair(X, "X"),
|
||||
makePair(Y, "Y"),
|
||||
// DPad
|
||||
makePair(DU, "DU"),
|
||||
makePair(DD, "DD"),
|
||||
makePair(DL, "DL"),
|
||||
makePair(DR, "DR"),
|
||||
// Bumpers
|
||||
makePair(LB, "LB"),
|
||||
makePair(RB, "RB"),
|
||||
// Stick press
|
||||
makePair(LS, "LS"),
|
||||
makePair(RS, "RS"),
|
||||
// Center buttons
|
||||
makePair(START, "Start"),
|
||||
makePair(BACK, "Back"),
|
||||
// Analog sticks
|
||||
makePair(LX, "LX"),
|
||||
makePair(LY, "LY"),
|
||||
makePair(RX, "RX"),
|
||||
makePair(RY, "RY"),
|
||||
|
||||
// Aliases, PlayStation style names
|
||||
makePair(LB, "L1"),
|
||||
makePair(RB, "R1"),
|
||||
makePair(LT, "L2"),
|
||||
makePair(RT, "R2"),
|
||||
makePair(LS, "L3"),
|
||||
makePair(RS, "R3"),
|
||||
makePair(BACK, "Select"),
|
||||
makePair(A, "Cross"),
|
||||
makePair(B, "Circle"),
|
||||
makePair(X, "Square"),
|
||||
makePair(Y, "Triangle"),
|
||||
makePair(DU, "Up"),
|
||||
makePair(DD, "Down"),
|
||||
makePair(DL, "Left"),
|
||||
makePair(DR, "Right"),
|
||||
};
|
||||
return availableInputs;
|
||||
// Triggers
|
||||
makePair(LT, "LT"),
|
||||
makePair(RT, "RT"),
|
||||
|
||||
// Aliases, PlayStation style names
|
||||
makePair(LB, "L1"),
|
||||
makePair(RB, "R1"),
|
||||
makePair(LT, "L2"),
|
||||
makePair(RT, "R2"),
|
||||
makePair(LS, "L3"),
|
||||
makePair(RS, "R3"),
|
||||
makePair(BACK, "Select"),
|
||||
makePair(A, "Cross"),
|
||||
makePair(B, "Circle"),
|
||||
makePair(X, "Square"),
|
||||
makePair(Y, "Triangle"),
|
||||
makePair(DU, "Up"),
|
||||
makePair(DD, "Down"),
|
||||
makePair(DL, "Left"),
|
||||
makePair(DR, "Right"),
|
||||
};
|
||||
}
|
||||
return _availableInputs;
|
||||
}
|
||||
|
||||
QString Joystick::getDefaultMappingConfig() const {
|
||||
|
|
|
@ -36,6 +36,8 @@ public:
|
|||
virtual QString getDefaultMappingConfig() const override;
|
||||
virtual void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override;
|
||||
virtual void focusOutEvent() override;
|
||||
|
||||
bool triggerHapticPulse(float strength, float duration, controller::Hand hand) override;
|
||||
|
||||
Joystick() : InputDevice("GamePad") {}
|
||||
~Joystick();
|
||||
|
@ -52,7 +54,10 @@ public:
|
|||
private:
|
||||
SDL_GameController* _sdlGameController;
|
||||
SDL_Joystick* _sdlJoystick;
|
||||
SDL_Haptic* _sdlHaptic;
|
||||
SDL_JoystickID _instanceId;
|
||||
|
||||
mutable controller::Input::NamedVector _availableInputs;
|
||||
};
|
||||
|
||||
#endif // hifi_Joystick_h
|
||||
|
|
|
@ -49,7 +49,7 @@ SDL_JoystickID SDL2Manager::getInstanceId(SDL_GameController* controller) {
|
|||
}
|
||||
|
||||
void SDL2Manager::init() {
|
||||
bool initSuccess = (SDL_Init(SDL_INIT_GAMECONTROLLER) == 0);
|
||||
bool initSuccess = (SDL_Init(SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC) == 0);
|
||||
|
||||
if (initSuccess) {
|
||||
int joystickCount = SDL_NumJoysticks();
|
||||
|
|
|
@ -105,6 +105,12 @@ void OculusControllerManager::pluginFocusOutEvent() {
|
|||
}
|
||||
}
|
||||
|
||||
void OculusControllerManager::stopHapticPulse(bool leftHand) {
|
||||
if (_touch) {
|
||||
_touch->stopHapticPulse(leftHand);
|
||||
}
|
||||
}
|
||||
|
||||
using namespace controller;
|
||||
|
||||
static const std::vector<std::pair<ovrButton, StandardButtonChannel>> BUTTON_MAP { {
|
||||
|
@ -120,14 +126,14 @@ static const std::vector<std::pair<ovrButton, StandardButtonChannel>> BUTTON_MAP
|
|||
{ ovrButton_B, B },
|
||||
{ ovrButton_LThumb, LS },
|
||||
{ ovrButton_RThumb, RS },
|
||||
{ ovrButton_LShoulder, LB },
|
||||
{ ovrButton_RShoulder, RB },
|
||||
//{ ovrButton_LShoulder, LB },
|
||||
//{ ovrButton_RShoulder, RB },
|
||||
} };
|
||||
|
||||
static const std::vector<std::pair<ovrTouch, StandardButtonChannel>> TOUCH_MAP { {
|
||||
{ ovrTouch_X, LEFT_SECONDARY_THUMB_TOUCH },
|
||||
{ ovrTouch_X, LEFT_PRIMARY_THUMB_TOUCH },
|
||||
{ ovrTouch_Y, LEFT_SECONDARY_THUMB_TOUCH },
|
||||
{ ovrTouch_A, RIGHT_SECONDARY_THUMB_TOUCH },
|
||||
{ ovrTouch_A, RIGHT_PRIMARY_THUMB_TOUCH },
|
||||
{ ovrTouch_B, RIGHT_SECONDARY_THUMB_TOUCH },
|
||||
{ ovrTouch_LIndexTrigger, LEFT_PRIMARY_INDEX_TOUCH },
|
||||
{ ovrTouch_RIndexTrigger, RIGHT_PRIMARY_INDEX_TOUCH },
|
||||
|
@ -183,6 +189,8 @@ void OculusControllerManager::TouchDevice::update(float deltaTime, const control
|
|||
++numTrackedControllers;
|
||||
if (REQUIRED_HAND_STATUS == (tracking.HandStatusFlags[hand] & REQUIRED_HAND_STATUS)) {
|
||||
handlePose(deltaTime, inputCalibrationData, hand, tracking.HandPoses[hand]);
|
||||
} else {
|
||||
_poseStateMap[hand == ovrHand_Left ? controller::LEFT_HAND : controller::RIGHT_HAND].valid = false;
|
||||
}
|
||||
});
|
||||
using namespace controller;
|
||||
|
@ -191,12 +199,12 @@ void OculusControllerManager::TouchDevice::update(float deltaTime, const control
|
|||
_axisStateMap[LX] = inputState.Thumbstick[ovrHand_Left].x;
|
||||
_axisStateMap[LY] = inputState.Thumbstick[ovrHand_Left].y;
|
||||
_axisStateMap[LT] = inputState.IndexTrigger[ovrHand_Left];
|
||||
_axisStateMap[LG] = inputState.HandTrigger[ovrHand_Left];
|
||||
_axisStateMap[LEFT_GRIP] = inputState.HandTrigger[ovrHand_Left];
|
||||
|
||||
_axisStateMap[RX] = inputState.Thumbstick[ovrHand_Right].x;
|
||||
_axisStateMap[RY] = inputState.Thumbstick[ovrHand_Right].y;
|
||||
_axisStateMap[RT] = inputState.IndexTrigger[ovrHand_Right];
|
||||
_axisStateMap[RG] = inputState.HandTrigger[ovrHand_Right];
|
||||
_axisStateMap[RIGHT_GRIP] = inputState.HandTrigger[ovrHand_Right];
|
||||
|
||||
// Buttons
|
||||
for (const auto& pair : BUTTON_MAP) {
|
||||
|
@ -210,6 +218,21 @@ void OculusControllerManager::TouchDevice::update(float deltaTime, const control
|
|||
_buttonPressedMap.insert(pair.second);
|
||||
}
|
||||
}
|
||||
|
||||
// Haptics
|
||||
{
|
||||
Locker locker(_lock);
|
||||
if (_leftHapticDuration > 0.0f) {
|
||||
_leftHapticDuration -= deltaTime * 1000.0f; // milliseconds
|
||||
} else {
|
||||
stopHapticPulse(true);
|
||||
}
|
||||
if (_rightHapticDuration > 0.0f) {
|
||||
_rightHapticDuration -= deltaTime * 1000.0f; // milliseconds
|
||||
} else {
|
||||
stopHapticPulse(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OculusControllerManager::TouchDevice::focusOutEvent() {
|
||||
|
@ -220,38 +243,177 @@ void OculusControllerManager::TouchDevice::focusOutEvent() {
|
|||
void OculusControllerManager::TouchDevice::handlePose(float deltaTime,
|
||||
const controller::InputCalibrationData& inputCalibrationData, ovrHandType hand,
|
||||
const ovrPoseStatef& handPose) {
|
||||
// When the sensor-to-world rotation is identity the coordinate axes look like this:
|
||||
//
|
||||
// user
|
||||
// forward
|
||||
// -z
|
||||
// |
|
||||
// y| user
|
||||
// y o----x right
|
||||
// o-----x user
|
||||
// | up
|
||||
// |
|
||||
// z
|
||||
//
|
||||
// Rift
|
||||
|
||||
// From ABOVE the hand canonical axes looks like this:
|
||||
//
|
||||
// | | | | y | | | |
|
||||
// | | | | | | | | |
|
||||
// | | | | |
|
||||
// |left | / x---- + \ |right|
|
||||
// | _/ z \_ |
|
||||
// | | | |
|
||||
// | | | |
|
||||
//
|
||||
|
||||
// So when the user is in Rift space facing the -zAxis with hands outstretched and palms down
|
||||
// the rotation to align the Touch axes with those of the hands is:
|
||||
//
|
||||
// touchToHand = halfTurnAboutY * quaterTurnAboutX
|
||||
|
||||
// Due to how the Touch controllers fit into the palm there is an offset that is different for each hand.
|
||||
// You can think of this offset as the inverse of the measured rotation when the hands are posed, such that
|
||||
// the combination (measurement * offset) is identity at this orientation.
|
||||
//
|
||||
// Qoffset = glm::inverse(deltaRotation when hand is posed fingers forward, palm down)
|
||||
//
|
||||
// An approximate offset for the Touch can be obtained by inspection:
|
||||
//
|
||||
// Qoffset = glm::inverse(glm::angleAxis(sign * PI/2.0f, zAxis) * glm::angleAxis(PI/4.0f, xAxis))
|
||||
//
|
||||
// So the full equation is:
|
||||
//
|
||||
// Q = combinedMeasurement * touchToHand
|
||||
//
|
||||
// Q = (deltaQ * QOffset) * (yFlip * quarterTurnAboutX)
|
||||
//
|
||||
// Q = (deltaQ * inverse(deltaQForAlignedHand)) * (yFlip * quarterTurnAboutX)
|
||||
|
||||
auto poseId = hand == ovrHand_Left ? controller::LEFT_HAND : controller::RIGHT_HAND;
|
||||
auto& pose = _poseStateMap[poseId];
|
||||
|
||||
static const glm::quat yFlip = glm::angleAxis(PI, Vectors::UNIT_Y);
|
||||
static const glm::quat quarterX = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_X);
|
||||
static const glm::quat touchToHand = yFlip * quarterX;
|
||||
|
||||
static const glm::quat leftQuarterZ = glm::angleAxis(-PI_OVER_TWO, Vectors::UNIT_Z);
|
||||
static const glm::quat rightQuarterZ = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_Z);
|
||||
static const glm::quat eighthX = glm::angleAxis(PI / 4.0f, Vectors::UNIT_X);
|
||||
|
||||
static const glm::quat leftRotationOffset = glm::inverse(leftQuarterZ * eighthX) * touchToHand;
|
||||
static const glm::quat rightRotationOffset = glm::inverse(rightQuarterZ * eighthX) * touchToHand;
|
||||
|
||||
static const float CONTROLLER_LENGTH_OFFSET = 0.0762f; // three inches
|
||||
static const glm::vec3 CONTROLLER_OFFSET = glm::vec3(CONTROLLER_LENGTH_OFFSET / 2.0f,
|
||||
CONTROLLER_LENGTH_OFFSET / 2.0f,
|
||||
CONTROLLER_LENGTH_OFFSET * 2.0f);
|
||||
static const glm::vec3 leftTranslationOffset = glm::vec3(-1.0f, 1.0f, 1.0f) * CONTROLLER_OFFSET;
|
||||
static const glm::vec3 rightTranslationOffset = CONTROLLER_OFFSET;
|
||||
|
||||
auto translationOffset = (hand == ovrHand_Left ? leftTranslationOffset : rightTranslationOffset);
|
||||
auto rotationOffset = (hand == ovrHand_Left ? leftRotationOffset : rightRotationOffset);
|
||||
|
||||
glm::quat rotation = toGlm(handPose.ThePose.Orientation);
|
||||
|
||||
pose.translation = toGlm(handPose.ThePose.Position);
|
||||
pose.rotation = toGlm(handPose.ThePose.Orientation);
|
||||
pose.translation += rotation * translationOffset;
|
||||
pose.rotation = rotation * rotationOffset;
|
||||
pose.angularVelocity = toGlm(handPose.AngularVelocity);
|
||||
pose.velocity = toGlm(handPose.LinearVelocity);
|
||||
pose.valid = true;
|
||||
|
||||
// transform into avatar frame
|
||||
glm::mat4 controllerToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat;
|
||||
pose = pose.transform(controllerToAvatar);
|
||||
}
|
||||
|
||||
bool OculusControllerManager::TouchDevice::triggerHapticPulse(float strength, float duration, controller::Hand hand) {
|
||||
Locker locker(_lock);
|
||||
bool toReturn = true;
|
||||
if (hand == controller::BOTH || hand == controller::LEFT) {
|
||||
if (strength == 0.0f) {
|
||||
_leftHapticStrength = 0.0f;
|
||||
_leftHapticDuration = 0.0f;
|
||||
} else {
|
||||
_leftHapticStrength = (duration > _leftHapticDuration) ? strength : _leftHapticStrength;
|
||||
if (ovr_SetControllerVibration(_parent._session, ovrControllerType_LTouch, 1.0f, _leftHapticStrength) != ovrSuccess) {
|
||||
toReturn = false;
|
||||
}
|
||||
_leftHapticDuration = std::max(duration, _leftHapticDuration);
|
||||
}
|
||||
}
|
||||
if (hand == controller::BOTH || hand == controller::RIGHT) {
|
||||
if (strength == 0.0f) {
|
||||
_rightHapticStrength = 0.0f;
|
||||
_rightHapticDuration = 0.0f;
|
||||
} else {
|
||||
_rightHapticStrength = (duration > _rightHapticDuration) ? strength : _rightHapticStrength;
|
||||
if (ovr_SetControllerVibration(_parent._session, ovrControllerType_RTouch, 1.0f, _rightHapticStrength) != ovrSuccess) {
|
||||
toReturn = false;
|
||||
}
|
||||
_rightHapticDuration = std::max(duration, _rightHapticDuration);
|
||||
}
|
||||
}
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
void OculusControllerManager::TouchDevice::stopHapticPulse(bool leftHand) {
|
||||
auto handType = (leftHand ? ovrControllerType_LTouch : ovrControllerType_RTouch);
|
||||
ovr_SetControllerVibration(_parent._session, handType, 0.0f, 0.0f);
|
||||
}
|
||||
|
||||
controller::Input::NamedVector OculusControllerManager::TouchDevice::getAvailableInputs() const {
|
||||
using namespace controller;
|
||||
QVector<Input::NamedPair> availableInputs{
|
||||
// Trackpad analogs
|
||||
// buttons
|
||||
makePair(A, "A"),
|
||||
makePair(B, "B"),
|
||||
makePair(X, "X"),
|
||||
makePair(Y, "Y"),
|
||||
|
||||
// trackpad analogs
|
||||
makePair(LX, "LX"),
|
||||
makePair(LY, "LY"),
|
||||
makePair(RX, "RX"),
|
||||
makePair(RY, "RY"),
|
||||
// trigger analogs
|
||||
|
||||
// triggers
|
||||
makePair(LT, "LT"),
|
||||
makePair(RT, "RT"),
|
||||
|
||||
makePair(LB, "LB"),
|
||||
makePair(RB, "RB"),
|
||||
// trigger buttons
|
||||
//makePair(LB, "LB"),
|
||||
//makePair(RB, "RB"),
|
||||
|
||||
// side grip triggers
|
||||
makePair(LEFT_GRIP, "LeftGrip"),
|
||||
makePair(RIGHT_GRIP, "RightGrip"),
|
||||
|
||||
// joystick buttons
|
||||
makePair(LS, "LS"),
|
||||
makePair(RS, "RS"),
|
||||
|
||||
makePair(LEFT_HAND, "LeftHand"),
|
||||
makePair(RIGHT_HAND, "RightHand"),
|
||||
|
||||
makePair(LEFT_PRIMARY_THUMB, "LeftPrimaryThumb"),
|
||||
makePair(LEFT_SECONDARY_THUMB, "LeftSecondaryThumb"),
|
||||
makePair(RIGHT_PRIMARY_THUMB, "RightPrimaryThumb"),
|
||||
makePair(RIGHT_SECONDARY_THUMB, "RightSecondaryThumb"),
|
||||
makePair(LEFT_PRIMARY_THUMB_TOUCH, "LeftPrimaryThumbTouch"),
|
||||
makePair(LEFT_SECONDARY_THUMB_TOUCH, "LeftSecondaryThumbTouch"),
|
||||
makePair(RIGHT_PRIMARY_THUMB_TOUCH, "RightPrimaryThumbTouch"),
|
||||
makePair(RIGHT_SECONDARY_THUMB_TOUCH, "RightSecondaryThumbTouch"),
|
||||
makePair(LEFT_PRIMARY_INDEX_TOUCH, "LeftPrimaryIndexTouch"),
|
||||
makePair(RIGHT_PRIMARY_INDEX_TOUCH, "RightPrimaryIndexTouch"),
|
||||
makePair(LS_TOUCH, "LSTouch"),
|
||||
makePair(RS_TOUCH, "RSTouch"),
|
||||
makePair(LEFT_THUMB_UP, "LeftThumbUp"),
|
||||
makePair(RIGHT_THUMB_UP, "RightThumbUp"),
|
||||
makePair(LEFT_INDEX_POINT, "LeftIndexPoint"),
|
||||
makePair(RIGHT_INDEX_POINT, "RightIndexPoint"),
|
||||
|
||||
makePair(BACK, "LeftApplicationMenu"),
|
||||
makePair(START, "RightApplicationMenu"),
|
||||
};
|
||||
return availableInputs;
|
||||
}
|
||||
|
|
|
@ -32,6 +32,9 @@ public:
|
|||
void pluginFocusOutEvent() override;
|
||||
void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override;
|
||||
|
||||
private slots:
|
||||
void stopHapticPulse(bool leftHand);
|
||||
|
||||
private:
|
||||
class OculusInputDevice : public controller::InputDevice {
|
||||
public:
|
||||
|
@ -64,9 +67,24 @@ private:
|
|||
void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override;
|
||||
void focusOutEvent() override;
|
||||
|
||||
bool triggerHapticPulse(float strength, float duration, controller::Hand hand) override;
|
||||
|
||||
private:
|
||||
void stopHapticPulse(bool leftHand);
|
||||
void handlePose(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, ovrHandType hand, const ovrPoseStatef& handPose);
|
||||
int _trackedControllers { 0 };
|
||||
|
||||
// perform an action when the TouchDevice mutex is acquired.
|
||||
using Locker = std::unique_lock<std::recursive_mutex>;
|
||||
template <typename F>
|
||||
void withLock(F&& f) { Locker locker(_lock); f(); }
|
||||
|
||||
float _leftHapticDuration { 0.0f };
|
||||
float _leftHapticStrength { 0.0f };
|
||||
float _rightHapticDuration { 0.0f };
|
||||
float _rightHapticStrength { 0.0f };
|
||||
mutable std::recursive_mutex _lock;
|
||||
|
||||
friend class OculusControllerManager;
|
||||
};
|
||||
|
||||
|
|
|
@ -126,7 +126,6 @@ bool ViveControllerManager::activate() {
|
|||
auto userInputMapper = DependencyManager::get<controller::UserInputMapper>();
|
||||
userInputMapper->registerDevice(_inputDevice);
|
||||
_registeredWithInputMapper = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -253,6 +252,17 @@ void ViveControllerManager::InputDevice::update(float deltaTime, const controlle
|
|||
handleHandController(deltaTime, leftHandDeviceIndex, inputCalibrationData, true);
|
||||
handleHandController(deltaTime, rightHandDeviceIndex, inputCalibrationData, false);
|
||||
|
||||
// handle haptics
|
||||
{
|
||||
Locker locker(_lock);
|
||||
if (_leftHapticDuration > 0.0f) {
|
||||
hapticsHelper(deltaTime, true);
|
||||
}
|
||||
if (_rightHapticDuration > 0.0f) {
|
||||
hapticsHelper(deltaTime, false);
|
||||
}
|
||||
}
|
||||
|
||||
int numTrackedControllers = 0;
|
||||
if (leftHandDeviceIndex != vr::k_unTrackedDeviceIndexInvalid) {
|
||||
numTrackedControllers++;
|
||||
|
@ -354,7 +364,7 @@ void ViveControllerManager::InputDevice::handleButtonEvent(float deltaTime, uint
|
|||
if (button == vr::k_EButton_ApplicationMenu) {
|
||||
_buttonPressedMap.insert(isLeftHand ? LEFT_APP_MENU : RIGHT_APP_MENU);
|
||||
} else if (button == vr::k_EButton_Grip) {
|
||||
_buttonPressedMap.insert(isLeftHand ? LEFT_GRIP : RIGHT_GRIP);
|
||||
_axisStateMap[isLeftHand ? LEFT_GRIP : RIGHT_GRIP] = 1.0f;
|
||||
} else if (button == vr::k_EButton_SteamVR_Trigger) {
|
||||
_buttonPressedMap.insert(isLeftHand ? LT : RT);
|
||||
} else if (button == vr::k_EButton_SteamVR_Touchpad) {
|
||||
|
@ -454,6 +464,55 @@ void ViveControllerManager::InputDevice::handlePoseEvent(float deltaTime, const
|
|||
_poseStateMap[isLeftHand ? controller::LEFT_HAND : controller::RIGHT_HAND] = avatarPose.transform(controllerToAvatar);
|
||||
}
|
||||
|
||||
bool ViveControllerManager::InputDevice::triggerHapticPulse(float strength, float duration, controller::Hand hand) {
|
||||
Locker locker(_lock);
|
||||
if (hand == controller::BOTH || hand == controller::LEFT) {
|
||||
if (strength == 0.0f) {
|
||||
_leftHapticStrength = 0.0f;
|
||||
_leftHapticDuration = 0.0f;
|
||||
} else {
|
||||
_leftHapticStrength = (duration > _leftHapticDuration) ? strength : _leftHapticStrength;
|
||||
_leftHapticDuration = std::max(duration, _leftHapticDuration);
|
||||
}
|
||||
}
|
||||
if (hand == controller::BOTH || hand == controller::RIGHT) {
|
||||
if (strength == 0.0f) {
|
||||
_rightHapticStrength = 0.0f;
|
||||
_rightHapticDuration = 0.0f;
|
||||
} else {
|
||||
_rightHapticStrength = (duration > _rightHapticDuration) ? strength : _rightHapticStrength;
|
||||
_rightHapticDuration = std::max(duration, _rightHapticDuration);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ViveControllerManager::InputDevice::hapticsHelper(float deltaTime, bool leftHand) {
|
||||
auto handRole = leftHand ? vr::TrackedControllerRole_LeftHand : vr::TrackedControllerRole_RightHand;
|
||||
auto deviceIndex = _system->GetTrackedDeviceIndexForControllerRole(handRole);
|
||||
|
||||
if (_system->IsTrackedDeviceConnected(deviceIndex) &&
|
||||
_system->GetTrackedDeviceClass(deviceIndex) == vr::TrackedDeviceClass_Controller &&
|
||||
_trackedDevicePose[deviceIndex].bPoseIsValid) {
|
||||
float strength = leftHand ? _leftHapticStrength : _rightHapticStrength;
|
||||
float duration = leftHand ? _leftHapticDuration : _rightHapticDuration;
|
||||
|
||||
// Vive Controllers only support duration up to 4 ms, which is short enough that any variation feels more like strength
|
||||
const float MAX_HAPTIC_TIME = 3999.0f; // in microseconds
|
||||
float hapticTime = strength * MAX_HAPTIC_TIME;
|
||||
if (hapticTime < duration * 1000.0f) {
|
||||
_system->TriggerHapticPulse(deviceIndex, 0, hapticTime);
|
||||
}
|
||||
|
||||
float remainingHapticTime = duration - (hapticTime / 1000.0f + deltaTime * 1000.0f); // in milliseconds
|
||||
if (leftHand) {
|
||||
_leftHapticDuration = remainingHapticTime;
|
||||
} else {
|
||||
_rightHapticDuration = remainingHapticTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
controller::Input::NamedVector ViveControllerManager::InputDevice::getAvailableInputs() const {
|
||||
using namespace controller;
|
||||
QVector<Input::NamedPair> availableInputs{
|
||||
|
|
|
@ -56,6 +56,9 @@ private:
|
|||
void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override;
|
||||
void focusOutEvent() override;
|
||||
|
||||
bool triggerHapticPulse(float strength, float duration, controller::Hand hand) override;
|
||||
void hapticsHelper(float deltaTime, bool leftHand);
|
||||
|
||||
void handleHandController(float deltaTime, uint32_t deviceIndex, const controller::InputCalibrationData& inputCalibrationData, bool isLeftHand);
|
||||
void handleButtonEvent(float deltaTime, uint32_t button, bool pressed, bool touched, bool isLeftHand);
|
||||
void handleAxisEvent(float deltaTime, uint32_t axis, float x, float y, bool isLeftHand);
|
||||
|
@ -90,8 +93,19 @@ private:
|
|||
FilteredStick _filteredLeftStick;
|
||||
FilteredStick _filteredRightStick;
|
||||
|
||||
// perform an action when the InputDevice mutex is acquired.
|
||||
using Locker = std::unique_lock<std::recursive_mutex>;
|
||||
template <typename F>
|
||||
void withLock(F&& f) { Locker locker(_lock); f(); }
|
||||
|
||||
int _trackedControllers { 0 };
|
||||
vr::IVRSystem*& _system;
|
||||
float _leftHapticStrength { 0.0f };
|
||||
float _leftHapticDuration { 0.0f };
|
||||
float _rightHapticStrength { 0.0f };
|
||||
float _rightHapticDuration { 0.0f };
|
||||
mutable std::recursive_mutex _lock;
|
||||
|
||||
friend class ViveControllerManager;
|
||||
};
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ Column {
|
|||
"Roughness",
|
||||
"Metallic",
|
||||
"Emissive",
|
||||
"Shaded/Lightmapped/Unlit",
|
||||
"Unlit",
|
||||
"Occlusion",
|
||||
"Lightmap",
|
||||
"Lighting",
|
||||
|
|
|
@ -265,9 +265,11 @@ eventMapping.from(Controller.Standard.RightSecondaryThumb).peek().to(goActive);
|
|||
eventMapping.from(Controller.Standard.LT).peek().to(goActive);
|
||||
eventMapping.from(Controller.Standard.LB).peek().to(goActive);
|
||||
eventMapping.from(Controller.Standard.LS).peek().to(goActive);
|
||||
eventMapping.from(Controller.Standard.LeftGrip).peek().to(goActive);
|
||||
eventMapping.from(Controller.Standard.RT).peek().to(goActive);
|
||||
eventMapping.from(Controller.Standard.RB).peek().to(goActive);
|
||||
eventMapping.from(Controller.Standard.RS).peek().to(goActive);
|
||||
eventMapping.from(Controller.Standard.RightGrip).peek().to(goActive);
|
||||
eventMapping.from(Controller.Standard.Back).peek().to(goActive);
|
||||
eventMapping.from(Controller.Standard.Start).peek().to(goActive);
|
||||
Controller.enableMapping(eventMappingName);
|
||||
|
|
|
@ -330,7 +330,7 @@ function MyController(hand) {
|
|||
|
||||
this.triggerValue = 0; // rolling average of trigger value
|
||||
this.rawTriggerValue = 0;
|
||||
this.rawBumperValue = 0;
|
||||
this.rawSecondaryValue = 0;
|
||||
this.rawThumbValue = 0;
|
||||
|
||||
//for visualizations
|
||||
|
@ -568,10 +568,10 @@ function MyController(hand) {
|
|||
var searchSphereLocation = Vec3.sum(distantPickRay.origin,
|
||||
Vec3.multiply(distantPickRay.direction, this.searchSphereDistance));
|
||||
this.searchSphereOn(searchSphereLocation, SEARCH_SPHERE_SIZE * this.searchSphereDistance,
|
||||
(this.triggerSmoothedGrab() || this.bumperSqueezed()) ? INTERSECT_COLOR : NO_INTERSECT_COLOR);
|
||||
(this.triggerSmoothedGrab() || this.secondarySqueezed()) ? INTERSECT_COLOR : NO_INTERSECT_COLOR);
|
||||
if ((USE_OVERLAY_LINES_FOR_SEARCHING === true) && PICK_WITH_HAND_RAY) {
|
||||
this.overlayLineOn(handPosition, searchSphereLocation,
|
||||
(this.triggerSmoothedGrab() || this.bumperSqueezed()) ? INTERSECT_COLOR : NO_INTERSECT_COLOR);
|
||||
(this.triggerSmoothedGrab() || this.secondarySqueezed()) ? INTERSECT_COLOR : NO_INTERSECT_COLOR);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -818,8 +818,8 @@ function MyController(hand) {
|
|||
_this.rawTriggerValue = value;
|
||||
};
|
||||
|
||||
this.bumperPress = function(value) {
|
||||
_this.rawBumperValue = value;
|
||||
this.secondaryPress = function(value) {
|
||||
_this.rawSecondaryValue = value;
|
||||
};
|
||||
|
||||
this.updateSmoothedTrigger = function() {
|
||||
|
@ -841,20 +841,20 @@ function MyController(hand) {
|
|||
return this.triggerValue < TRIGGER_OFF_VALUE;
|
||||
};
|
||||
|
||||
this.bumperSqueezed = function() {
|
||||
return _this.rawBumperValue > BUMPER_ON_VALUE;
|
||||
this.secondarySqueezed = function() {
|
||||
return _this.rawSecondaryValue > BUMPER_ON_VALUE;
|
||||
};
|
||||
|
||||
this.bumperReleased = function() {
|
||||
return _this.rawBumperValue < BUMPER_ON_VALUE;
|
||||
this.secondaryReleased = function() {
|
||||
return _this.rawSecondaryValue < BUMPER_ON_VALUE;
|
||||
};
|
||||
|
||||
// this.triggerOrBumperSqueezed = function() {
|
||||
// return triggerSmoothedSqueezed() || bumperSqueezed();
|
||||
// this.triggerOrsecondarySqueezed = function() {
|
||||
// return triggerSmoothedSqueezed() || secondarySqueezed();
|
||||
// }
|
||||
|
||||
// this.triggerAndBumperReleased = function() {
|
||||
// return triggerSmoothedReleased() && bumperReleased();
|
||||
// this.triggerAndSecondaryReleased = function() {
|
||||
// return triggerSmoothedReleased() && secondaryReleased();
|
||||
// }
|
||||
|
||||
this.thumbPress = function(value) {
|
||||
|
@ -870,13 +870,13 @@ function MyController(hand) {
|
|||
};
|
||||
|
||||
this.off = function() {
|
||||
if (this.triggerSmoothedSqueezed() || this.bumperSqueezed()) {
|
||||
if (this.triggerSmoothedSqueezed() || this.secondarySqueezed()) {
|
||||
this.lastPickTime = 0;
|
||||
var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand;
|
||||
this.startingHandRotation = Controller.getPoseValue(controllerHandInput).rotation;
|
||||
if (this.triggerSmoothedSqueezed()) {
|
||||
this.setState(STATE_SEARCHING);
|
||||
} else if (this.bumperSqueezed()) {
|
||||
} else if (this.secondarySqueezed()) {
|
||||
this.setState(STATE_HOLD_SEARCHING);
|
||||
}
|
||||
}
|
||||
|
@ -944,7 +944,7 @@ function MyController(hand) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (this.state == STATE_HOLD_SEARCHING && this.bumperReleased()) {
|
||||
if (this.state == STATE_HOLD_SEARCHING && this.secondaryReleased()) {
|
||||
this.setState(STATE_RELEASE);
|
||||
return;
|
||||
}
|
||||
|
@ -1108,7 +1108,7 @@ function MyController(hand) {
|
|||
grabbableData = grabbableDataForCandidate;
|
||||
}
|
||||
}
|
||||
if ((this.grabbedEntity !== null) && (this.triggerSmoothedGrab() || this.bumperSqueezed())) {
|
||||
if ((this.grabbedEntity !== null) && (this.triggerSmoothedGrab() || this.secondarySqueezed())) {
|
||||
// We are squeezing enough to grab, and we've found an entity that we'll try to do something with.
|
||||
var near = (nearPickedCandidateEntities.indexOf(this.grabbedEntity) >= 0) || minDistance <= NEAR_PICK_MAX_DISTANCE;
|
||||
var isPhysical = propsArePhysical(props);
|
||||
|
@ -1274,7 +1274,7 @@ function MyController(hand) {
|
|||
};
|
||||
|
||||
this.continueDistanceHolding = function() {
|
||||
if (this.triggerSmoothedReleased() && this.bumperReleased()) {
|
||||
if (this.triggerSmoothedReleased() && this.secondaryReleased()) {
|
||||
this.setState(STATE_RELEASE);
|
||||
this.callEntityMethodOnGrabbed("releaseGrab");
|
||||
return;
|
||||
|
@ -1498,7 +1498,7 @@ function MyController(hand) {
|
|||
this.callEntityMethodOnGrabbed("releaseGrab");
|
||||
return;
|
||||
}
|
||||
if (this.state == STATE_HOLD && this.bumperReleased()) {
|
||||
if (this.state == STATE_HOLD && this.secondaryReleased()) {
|
||||
this.setState(STATE_RELEASE);
|
||||
this.callEntityMethodOnGrabbed("releaseGrab");
|
||||
return;
|
||||
|
@ -1612,7 +1612,7 @@ function MyController(hand) {
|
|||
this.callEntityMethodOnGrabbed("releaseGrab");
|
||||
return;
|
||||
}
|
||||
if (this.state == STATE_CONTINUE_HOLD && this.bumperReleased()) {
|
||||
if (this.state == STATE_CONTINUE_HOLD && this.secondaryReleased()) {
|
||||
this.setState(STATE_RELEASE);
|
||||
this.callEntityMethodOnGrabbed("releaseEquip");
|
||||
return;
|
||||
|
@ -1741,7 +1741,7 @@ function MyController(hand) {
|
|||
};
|
||||
|
||||
this.nearTrigger = function() {
|
||||
if (this.triggerSmoothedReleased() && this.bumperReleased()) {
|
||||
if (this.triggerSmoothedReleased() && this.secondaryReleased()) {
|
||||
this.setState(STATE_RELEASE);
|
||||
this.callEntityMethodOnGrabbed("stopNearTrigger");
|
||||
return;
|
||||
|
@ -1751,7 +1751,7 @@ function MyController(hand) {
|
|||
};
|
||||
|
||||
this.farTrigger = function() {
|
||||
if (this.triggerSmoothedReleased() && this.bumperReleased()) {
|
||||
if (this.triggerSmoothedReleased() && this.secondaryReleased()) {
|
||||
this.setState(STATE_RELEASE);
|
||||
this.callEntityMethodOnGrabbed("stopFarTrigger");
|
||||
return;
|
||||
|
@ -1761,7 +1761,7 @@ function MyController(hand) {
|
|||
};
|
||||
|
||||
this.continueNearTrigger = function() {
|
||||
if (this.triggerSmoothedReleased() && this.bumperReleased()) {
|
||||
if (this.triggerSmoothedReleased() && this.secondaryReleased()) {
|
||||
this.setState(STATE_RELEASE);
|
||||
this.callEntityMethodOnGrabbed("stopNearTrigger");
|
||||
return;
|
||||
|
@ -1770,7 +1770,7 @@ function MyController(hand) {
|
|||
};
|
||||
|
||||
this.continueFarTrigger = function() {
|
||||
if (this.triggerSmoothedReleased() && this.bumperReleased()) {
|
||||
if (this.triggerSmoothedReleased() && this.secondaryReleased()) {
|
||||
this.setState(STATE_RELEASE);
|
||||
this.callEntityMethodOnGrabbed("stopFarTrigger");
|
||||
return;
|
||||
|
@ -2050,8 +2050,10 @@ var mapping = Controller.newMapping(MAPPING_NAME);
|
|||
mapping.from([Controller.Standard.RT]).peek().to(rightController.triggerPress);
|
||||
mapping.from([Controller.Standard.LT]).peek().to(leftController.triggerPress);
|
||||
|
||||
mapping.from([Controller.Standard.RB]).peek().to(rightController.bumperPress);
|
||||
mapping.from([Controller.Standard.LB]).peek().to(leftController.bumperPress);
|
||||
mapping.from([Controller.Standard.RB]).peek().to(rightController.secondaryPress);
|
||||
mapping.from([Controller.Standard.LB]).peek().to(leftController.secondaryPress);
|
||||
mapping.from([Controller.Standard.LeftGrip]).peek().to(leftController.secondaryPress);
|
||||
mapping.from([Controller.Standard.RightGrip]).peek().to(rightController.secondaryPress);
|
||||
|
||||
mapping.from([Controller.Standard.LeftPrimaryThumb]).peek().to(leftController.thumbPress);
|
||||
mapping.from([Controller.Standard.RightPrimaryThumb]).peek().to(rightController.thumbPress);
|
||||
|
|
Loading…
Reference in a new issue