mirror of
https://github.com/Armored-Dragon/overte.git
synced 2025-03-11 16:13:16 +01:00
Merge branch 'master' of https://github.com/highfidelity/hifi into spectator-camera
This commit is contained in:
commit
5d8d2fd83e
153 changed files with 3942 additions and 1128 deletions
|
@ -1 +1,2 @@
|
|||
set(GRAPHVIZ_EXTERNAL_LIBS FALSE)
|
||||
set(GRAPHVIZ_EXTERNAL_LIBS FALSE)
|
||||
set(GRAPHVIZ_IGNORE_TARGETS "shared;networking")
|
|
@ -7,11 +7,13 @@ if (APPLE)
|
|||
set_target_properties(${TARGET_NAME} PROPERTIES INSTALL_RPATH "@executable_path/../Frameworks")
|
||||
endif ()
|
||||
|
||||
setup_memory_debugger()
|
||||
|
||||
# link in the shared libraries
|
||||
link_hifi_libraries(
|
||||
audio avatars octree gpu model fbx entities
|
||||
networking animation recording shared script-engine embedded-webserver
|
||||
controllers physics plugins
|
||||
physics plugins
|
||||
)
|
||||
|
||||
if (WIN32)
|
||||
|
|
|
@ -166,19 +166,6 @@ void AudioMixer::handleMuteEnvironmentPacket(QSharedPointer<ReceivedMessage> mes
|
|||
}
|
||||
}
|
||||
|
||||
DisplayPluginList getDisplayPlugins() {
|
||||
DisplayPluginList result;
|
||||
return result;
|
||||
}
|
||||
|
||||
InputPluginList getInputPlugins() {
|
||||
InputPluginList result;
|
||||
return result;
|
||||
}
|
||||
|
||||
// must be here to satisfy a reference in PluginManager::saveSettings()
|
||||
void saveInputPluginSettings(const InputPluginList& plugins) {}
|
||||
|
||||
const std::pair<QString, CodecPluginPointer> AudioMixer::negotiateCodec(std::vector<QString> codecs) {
|
||||
QString selectedCodecName;
|
||||
CodecPluginPointer selectedCodec;
|
||||
|
|
|
@ -51,6 +51,11 @@ public:
|
|||
static const QVector<ReverbSettings>& getReverbSettings() { return _zoneReverbSettings; }
|
||||
static const std::pair<QString, CodecPluginPointer> negotiateCodec(std::vector<QString> codecs);
|
||||
|
||||
static bool shouldReplicateTo(const Node& from, const Node& to) {
|
||||
return to.getType() == NodeType::DownstreamAudioMixer &&
|
||||
to.getPublicSocket() != from.getPublicSocket() &&
|
||||
to.getLocalSocket() != from.getLocalSocket();
|
||||
}
|
||||
public slots:
|
||||
void run() override;
|
||||
void sendStatsPacket() override;
|
||||
|
|
|
@ -67,13 +67,9 @@ void AudioMixerClientData::processPackets() {
|
|||
case PacketType::MicrophoneAudioNoEcho:
|
||||
case PacketType::MicrophoneAudioWithEcho:
|
||||
case PacketType::InjectAudio:
|
||||
case PacketType::SilentAudioFrame:
|
||||
case PacketType::ReplicatedMicrophoneAudioNoEcho:
|
||||
case PacketType::ReplicatedMicrophoneAudioWithEcho:
|
||||
case PacketType::ReplicatedInjectAudio:
|
||||
case PacketType::ReplicatedSilentAudioFrame: {
|
||||
case PacketType::SilentAudioFrame: {
|
||||
|
||||
if (node->isUpstream() && !_hasSetupCodecForUpstreamNode) {
|
||||
if (node->isUpstream()) {
|
||||
setupCodecForReplicatedAgent(packet);
|
||||
}
|
||||
|
||||
|
@ -146,7 +142,7 @@ void AudioMixerClientData::optionallyReplicatePacket(ReceivedMessage& message, c
|
|||
|
||||
// enumerate the downstream audio mixers and send them the replicated version of this packet
|
||||
nodeList->unsafeEachNode([&](const SharedNodePointer& downstreamNode) {
|
||||
if (downstreamNode->getType() == NodeType::DownstreamAudioMixer) {
|
||||
if (AudioMixer::shouldReplicateTo(node, *downstreamNode)) {
|
||||
// construct the packet only once, if we have any downstream audio mixers to send to
|
||||
if (!packet) {
|
||||
// construct an NLPacket to send to the replicant that has the contents of the received packet
|
||||
|
@ -692,14 +688,14 @@ void AudioMixerClientData::setupCodecForReplicatedAgent(QSharedPointer<ReceivedM
|
|||
// pull the codec string from the packet
|
||||
auto codecString = message->readString();
|
||||
|
||||
qDebug() << "Manually setting codec for replicated agent" << uuidStringWithoutCurlyBraces(getNodeID())
|
||||
if (codecString != _selectedCodecName) {
|
||||
qDebug() << "Manually setting codec for replicated agent" << uuidStringWithoutCurlyBraces(getNodeID())
|
||||
<< "-" << codecString;
|
||||
|
||||
const std::pair<QString, CodecPluginPointer> codec = AudioMixer::negotiateCodec({ codecString });
|
||||
setupCodec(codec.second, codec.first);
|
||||
const std::pair<QString, CodecPluginPointer> codec = AudioMixer::negotiateCodec({ codecString });
|
||||
setupCodec(codec.second, codec.first);
|
||||
|
||||
_hasSetupCodecForUpstreamNode = true;
|
||||
|
||||
// seek back to the beginning of the message so other readers are in the right place
|
||||
message->seek(0);
|
||||
// seek back to the beginning of the message so other readers are in the right place
|
||||
message->seek(0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -184,8 +184,6 @@ private:
|
|||
|
||||
bool _shouldMuteClient { false };
|
||||
bool _requestsDomainListData { false };
|
||||
|
||||
bool _hasSetupCodecForUpstreamNode { false };
|
||||
};
|
||||
|
||||
#endif // hifi_AudioMixerClientData_h
|
||||
|
|
|
@ -143,8 +143,8 @@ void AvatarMixer::optionallyReplicatePacket(ReceivedMessage& message, const Node
|
|||
std::unique_ptr<NLPacket> packet;
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
nodeList->eachMatchingNode([&](const SharedNodePointer& node) {
|
||||
return node->getType() == NodeType::DownstreamAvatarMixer;
|
||||
nodeList->eachMatchingNode([&](const SharedNodePointer& downstreamNode) {
|
||||
return shouldReplicateTo(node, *downstreamNode);
|
||||
}, [&](const SharedNodePointer& node) {
|
||||
if (!packet) {
|
||||
// construct an NLPacket to send to the replicant that has the contents of the received packet
|
||||
|
@ -229,7 +229,7 @@ void AvatarMixer::start() {
|
|||
auto start = usecTimestampNow();
|
||||
nodeList->nestedEach([&](NodeList::const_iterator cbegin, NodeList::const_iterator cend) {
|
||||
std::for_each(cbegin, cend, [&](const SharedNodePointer& node) {
|
||||
if (node->getType() == NodeType::Agent && !node->isUpstream()) {
|
||||
if (node->getType() == NodeType::Agent) {
|
||||
manageIdentityData(node);
|
||||
}
|
||||
|
||||
|
@ -325,8 +325,8 @@ void AvatarMixer::manageIdentityData(const SharedNodePointer& node) {
|
|||
sendIdentity = true;
|
||||
}
|
||||
}
|
||||
if (sendIdentity) {
|
||||
|
||||
if (sendIdentity && !node->isUpstream()) {
|
||||
sendIdentityPacket(nodeData, node); // Tell node whose name changed about its new session display name or avatar.
|
||||
// since this packet includes a change to either the skeleton model URL or the display name
|
||||
// it needs a new sequence number
|
||||
nodeData->getAvatar().pushIdentitySequenceNumber();
|
||||
|
@ -424,8 +424,8 @@ void AvatarMixer::nodeKilled(SharedNodePointer killedNode) {
|
|||
nodeList->eachMatchingNode([&](const SharedNodePointer& node) {
|
||||
// we relay avatar kill packets to agents that are not upstream
|
||||
// and downstream avatar mixers, if the node that was just killed was being replicated
|
||||
return (node->getType() == NodeType::Agent && !node->isUpstream())
|
||||
|| (killedNode->isReplicated() && node->getType() == NodeType::DownstreamAvatarMixer);
|
||||
return (node->getType() == NodeType::Agent && !node->isUpstream()) ||
|
||||
(killedNode->isReplicated() && shouldReplicateTo(*killedNode, *node));
|
||||
}, [&](const SharedNodePointer& node) {
|
||||
if (node->getType() == NodeType::Agent) {
|
||||
if (!killPacket) {
|
||||
|
|
|
@ -28,6 +28,13 @@ class AvatarMixer : public ThreadedAssignment {
|
|||
Q_OBJECT
|
||||
public:
|
||||
AvatarMixer(ReceivedMessage& message);
|
||||
|
||||
static bool shouldReplicateTo(const Node& from, const Node& to) {
|
||||
return to.getType() == NodeType::DownstreamAvatarMixer &&
|
||||
to.getPublicSocket() != from.getPublicSocket() &&
|
||||
to.getLocalSocket() != from.getLocalSocket();
|
||||
}
|
||||
|
||||
public slots:
|
||||
/// runs the avatar mixer
|
||||
void run() override;
|
||||
|
|
|
@ -79,13 +79,13 @@ int AvatarMixerSlave::sendIdentityPacket(const AvatarMixerClientData* nodeData,
|
|||
}
|
||||
}
|
||||
|
||||
int AvatarMixerSlave::sendReplicatedIdentityPacket(const AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode) {
|
||||
if (destinationNode->getType() == NodeType::DownstreamAvatarMixer) {
|
||||
QByteArray individualData = nodeData->getConstAvatarData()->identityByteArray();
|
||||
int AvatarMixerSlave::sendReplicatedIdentityPacket(const Node& agentNode, const AvatarMixerClientData* nodeData, const Node& destinationNode) {
|
||||
if (AvatarMixer::shouldReplicateTo(agentNode, destinationNode)) {
|
||||
QByteArray individualData = nodeData->getConstAvatarData()->identityByteArray(true);
|
||||
individualData.replace(0, NUM_BYTES_RFC4122_UUID, nodeData->getNodeID().toRfc4122()); // FIXME, this looks suspicious
|
||||
auto identityPacket = NLPacketList::create(PacketType::ReplicatedAvatarIdentity, QByteArray(), true, true);
|
||||
identityPacket->write(individualData);
|
||||
DependencyManager::get<NodeList>()->sendPacketList(std::move(identityPacket), *destinationNode);
|
||||
DependencyManager::get<NodeList>()->sendPacketList(std::move(identityPacket), destinationNode);
|
||||
_stats.numIdentityPackets++;
|
||||
return individualData.size();
|
||||
} else {
|
||||
|
@ -453,6 +453,10 @@ void AvatarMixerSlave::broadcastAvatarDataToDownstreamMixer(const SharedNodePoin
|
|||
nodeData->resetNumAvatarsSentLastFrame();
|
||||
|
||||
std::for_each(_begin, _end, [&](const SharedNodePointer& agentNode) {
|
||||
if (!AvatarMixer::shouldReplicateTo(*agentNode, *node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// collect agents that we have avatar data for that we are supposed to replicate
|
||||
if (agentNode->getType() == NodeType::Agent && agentNode->getLinkedData() && agentNode->isReplicated()) {
|
||||
const AvatarMixerClientData* agentNodeData = reinterpret_cast<const AvatarMixerClientData*>(agentNode->getLinkedData());
|
||||
|
@ -479,7 +483,7 @@ void AvatarMixerSlave::broadcastAvatarDataToDownstreamMixer(const SharedNodePoin
|
|||
auto lastBroadcastTime = nodeData->getLastBroadcastTime(agentNode->getUUID());
|
||||
if (lastBroadcastTime <= agentNodeData->getIdentityChangeTimestamp()
|
||||
|| (start - lastBroadcastTime) >= REBROADCAST_IDENTITY_TO_DOWNSTREAM_EVERY_US) {
|
||||
sendReplicatedIdentityPacket(agentNodeData, node);
|
||||
sendReplicatedIdentityPacket(*agentNode, agentNodeData, *node);
|
||||
nodeData->setLastBroadcastTime(agentNode->getUUID(), start);
|
||||
}
|
||||
|
||||
|
|
|
@ -95,7 +95,7 @@ public:
|
|||
|
||||
private:
|
||||
int sendIdentityPacket(const AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode);
|
||||
int sendReplicatedIdentityPacket(const AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode);
|
||||
int sendReplicatedIdentityPacket(const Node& agentNode, const AvatarMixerClientData* nodeData, const Node& destinationNode);
|
||||
|
||||
void broadcastAvatarDataToAgent(const SharedNodePointer& node);
|
||||
void broadcastAvatarDataToDownstreamMixer(const SharedNodePointer& node);
|
||||
|
|
|
@ -504,6 +504,10 @@ Function PostInstallOptionsPage
|
|||
|
||||
; set the checkbox state depending on what is present in the registry
|
||||
!insertmacro SetPostInstallOption $LaunchServerNowCheckbox @SERVER_LAUNCH_NOW_REG_KEY@ ${BST_CHECKED}
|
||||
${StrContains} $substringResult "/forceNoLaunchServer" $CMDLINE
|
||||
${IfNot} $substringResult == ""
|
||||
${NSD_SetState} $LaunchServerNowCheckbox ${BST_UNCHECKED}
|
||||
${EndIf}
|
||||
|
||||
IntOp $CurrentOffset $CurrentOffset + 15
|
||||
${EndIf}
|
||||
|
@ -514,6 +518,10 @@ Function PostInstallOptionsPage
|
|||
|
||||
; set the checkbox state depending on what is present in the registry
|
||||
!insertmacro SetPostInstallOption $LaunchClientNowCheckbox @CLIENT_LAUNCH_NOW_REG_KEY@ ${BST_CHECKED}
|
||||
${StrContains} $substringResult "/forceNoLaunchClient" $CMDLINE
|
||||
${IfNot} $substringResult == ""
|
||||
${NSD_SetState} $LaunchClientNowCheckbox ${BST_UNCHECKED}
|
||||
${EndIf}
|
||||
${EndIf}
|
||||
|
||||
${If} @PR_BUILD@ == 1
|
||||
|
|
|
@ -14,6 +14,8 @@ if (APPLE)
|
|||
set_target_properties(${TARGET_NAME} PROPERTIES INSTALL_RPATH "@executable_path/../Frameworks")
|
||||
endif ()
|
||||
|
||||
setup_memory_debugger()
|
||||
|
||||
# TODO: find a solution that will handle web file changes in resources on windows without a re-build.
|
||||
# Currently the resources are only copied on post-build. If one is changed but the domain-server is not, they will
|
||||
# not be re-copied. This is worked-around on OS X/UNIX by using a symlink.
|
||||
|
|
|
@ -87,7 +87,7 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
qDebug() << "[VERSION] VERSION:" << BuildInfo::VERSION;
|
||||
qDebug() << "[VERSION] BUILD_BRANCH:" << BuildInfo::BUILD_BRANCH;
|
||||
qDebug() << "[VERSION] BUILD_GLOBAL_SERVICES:" << BuildInfo::BUILD_GLOBAL_SERVICES;
|
||||
qDebug() << "[VERSION] We will be using this default ICE server:" << ICE_SERVER_DEFAULT_HOSTNAME;
|
||||
qDebug() << "[VERSION] We will be using this name to find ICE servers:" << _iceServerAddr;
|
||||
|
||||
|
||||
// make sure we have a fresh AccountManager instance
|
||||
|
@ -1552,7 +1552,7 @@ void DomainServer::sendHeartbeatToIceServer() {
|
|||
|
||||
} else {
|
||||
qDebug() << "Not sending ice-server heartbeat since there is no selected ice-server.";
|
||||
qDebug() << "Waiting for" << ICE_SERVER_DEFAULT_HOSTNAME << "host lookup response";
|
||||
qDebug() << "Waiting for" << _iceServerAddr << "host lookup response";
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -2683,7 +2683,7 @@ void DomainServer::handleICEHostInfo(const QHostInfo& hostInfo) {
|
|||
_iceAddressLookupID = -1;
|
||||
|
||||
if (hostInfo.error() != QHostInfo::NoError) {
|
||||
qWarning() << "IP address lookup failed for" << ICE_SERVER_DEFAULT_HOSTNAME << ":" << hostInfo.errorString();
|
||||
qWarning() << "IP address lookup failed for" << _iceServerAddr << ":" << hostInfo.errorString();
|
||||
|
||||
// if we don't have an ICE server to use yet, trigger a retry
|
||||
if (_iceServerSocket.isNull()) {
|
||||
|
@ -2698,7 +2698,7 @@ void DomainServer::handleICEHostInfo(const QHostInfo& hostInfo) {
|
|||
_iceServerAddresses = hostInfo.addresses();
|
||||
|
||||
if (countBefore == 0) {
|
||||
qInfo() << "Found" << _iceServerAddresses.count() << "ice-server IP addresses for" << ICE_SERVER_DEFAULT_HOSTNAME;
|
||||
qInfo() << "Found" << _iceServerAddresses.count() << "ice-server IP addresses for" << _iceServerAddr;
|
||||
}
|
||||
|
||||
if (_iceServerSocket.isNull()) {
|
||||
|
@ -2733,7 +2733,7 @@ void DomainServer::randomizeICEServerAddress(bool shouldTriggerHostLookup) {
|
|||
// so clear the set of failed addresses and start going through them again
|
||||
|
||||
qWarning() << "All current ice-server addresses have failed - re-attempting all current addresses for"
|
||||
<< ICE_SERVER_DEFAULT_HOSTNAME;
|
||||
<< _iceServerAddr;
|
||||
|
||||
_failedIceServerAddresses.clear();
|
||||
candidateICEAddresses = _iceServerAddresses;
|
||||
|
|
|
@ -18,5 +18,7 @@ endif ()
|
|||
|
||||
include_directories(SYSTEM "${OPENSSL_INCLUDE_DIR}")
|
||||
|
||||
setup_memory_debugger()
|
||||
|
||||
# append OpenSSL to our list of libraries to link
|
||||
target_link_libraries(${TARGET_NAME} ${OPENSSL_LIBRARIES})
|
||||
|
|
|
@ -4,6 +4,8 @@ project(${TARGET_NAME})
|
|||
# set a default root dir for each of our optional externals if it was not passed
|
||||
set(OPTIONAL_EXTERNALS "LeapMotion")
|
||||
|
||||
setup_memory_debugger()
|
||||
|
||||
foreach(EXTERNAL ${OPTIONAL_EXTERNALS})
|
||||
string(TOUPPER ${EXTERNAL} ${EXTERNAL}_UPPERCASE)
|
||||
if (NOT ${${EXTERNAL}_UPPERCASE}_ROOT_DIR)
|
||||
|
|
|
@ -68,7 +68,10 @@
|
|||
"typeVar": "rightHandType",
|
||||
"weightVar": "rightHandWeight",
|
||||
"weight": 1.0,
|
||||
"flexCoefficients": [1, 0.5, 0.5, 0.2, 0.01, 0.005, 0.001, 0.0, 0.0]
|
||||
"flexCoefficients": [1, 0.5, 0.5, 0.2, 0.01, 0.005, 0.001, 0.0, 0.0],
|
||||
"poleVectorEnabledVar": "rightHandPoleVectorEnabled",
|
||||
"poleReferenceVectorVar": "rightHandPoleReferenceVector",
|
||||
"poleVectorVar": "rightHandPoleVector"
|
||||
},
|
||||
{
|
||||
"jointName": "LeftHand",
|
||||
|
@ -77,7 +80,10 @@
|
|||
"typeVar": "leftHandType",
|
||||
"weightVar": "leftHandWeight",
|
||||
"weight": 1.0,
|
||||
"flexCoefficients": [1, 0.5, 0.5, 0.2, 0.01, 0.005, 0.001, 0.0, 0.0]
|
||||
"flexCoefficients": [1, 0.5, 0.5, 0.2, 0.01, 0.005, 0.001, 0.0, 0.0],
|
||||
"poleVectorEnabledVar": "leftHandPoleVectorEnabled",
|
||||
"poleReferenceVectorVar": "leftHandPoleReferenceVector",
|
||||
"poleVectorVar": "leftHandPoleVector"
|
||||
},
|
||||
{
|
||||
"jointName": "RightFoot",
|
||||
|
@ -86,7 +92,10 @@
|
|||
"typeVar": "rightFootType",
|
||||
"weightVar": "rightFootWeight",
|
||||
"weight": 1.0,
|
||||
"flexCoefficients": [1, 0.45, 0.45]
|
||||
"flexCoefficients": [1, 0.45, 0.45],
|
||||
"poleVectorEnabledVar": "rightFootPoleVectorEnabled",
|
||||
"poleReferenceVectorVar": "rightFootPoleReferenceVector",
|
||||
"poleVectorVar": "rightFootPoleVector"
|
||||
},
|
||||
{
|
||||
"jointName": "LeftFoot",
|
||||
|
@ -95,7 +104,10 @@
|
|||
"typeVar": "leftFootType",
|
||||
"weightVar": "leftFootWeight",
|
||||
"weight": 1.0,
|
||||
"flexCoefficients": [1, 0.45, 0.45]
|
||||
"flexCoefficients": [1, 0.45, 0.45],
|
||||
"poleVectorEnabledVar": "leftFootPoleVectorEnabled",
|
||||
"poleReferenceVectorVar": "leftFootPoleReferenceVector",
|
||||
"poleVectorVar": "leftFootPoleVector"
|
||||
},
|
||||
{
|
||||
"jointName": "Spine2",
|
||||
|
|
|
@ -217,7 +217,7 @@ FocusScope {
|
|||
anchors.leftMargin: hifi.dimensions.textPadding
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
id: popupText
|
||||
text: listView.model[index] ? listView.model[index] : (listView.model.get(index).text ? listView.model.get(index).text : "")
|
||||
text: listView.model[index] ? listView.model[index] : (listView.model.get && listView.model.get(index).text ? listView.model.get(index).text : "")
|
||||
size: hifi.fontSizes.textFieldInput
|
||||
color: hifi.colors.baseGray
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@ Item {
|
|||
property bool selected: false
|
||||
property bool isAdmin: false
|
||||
property bool isPresent: true
|
||||
property bool isReplicated: false
|
||||
property string placeName: ""
|
||||
property string profilePicBorderColor: (connectionStatus == "connection" ? hifi.colors.indigoAccent : (connectionStatus == "friend" ? hifi.colors.greenHighlight : "transparent"))
|
||||
property alias avImage: avatarImage
|
||||
|
|
|
@ -473,6 +473,7 @@ Rectangle {
|
|||
visible: !isCheckBox && !isButton && !isAvgAudio;
|
||||
uuid: model ? model.sessionId : "";
|
||||
selected: styleData.selected;
|
||||
isReplicated: model.isReplicated;
|
||||
isAdmin: model && model.admin;
|
||||
isPresent: model && model.isPresent;
|
||||
// Size
|
||||
|
@ -553,6 +554,7 @@ Rectangle {
|
|||
id: actionButton;
|
||||
color: 2; // Red
|
||||
visible: isButton;
|
||||
enabled: !nameCard.isReplicated;
|
||||
anchors.centerIn: parent;
|
||||
width: 32;
|
||||
height: 32;
|
||||
|
|
|
@ -39,10 +39,26 @@ ScrollingWindow {
|
|||
property alias x: root.x
|
||||
property alias y: root.y
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: refreshTimer
|
||||
interval: 100
|
||||
repeat: false
|
||||
running: false
|
||||
onTriggered: updateRunningScripts();
|
||||
}
|
||||
|
||||
Component {
|
||||
id: listModelBuilder
|
||||
ListModel { }
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: ScriptDiscoveryService
|
||||
onScriptCountChanged: updateRunningScripts();
|
||||
onScriptCountChanged: {
|
||||
runningScriptsModel = listModelBuilder.createObject(root);
|
||||
refreshTimer.restart();
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
|
@ -65,10 +81,16 @@ ScrollingWindow {
|
|||
b = simplify(b.path);
|
||||
return a < b ? -1 : a > b ? 1 : 0;
|
||||
});
|
||||
runningScriptsModel.clear()
|
||||
// Calling `runningScriptsModel.clear()` here instead of creating a new object
|
||||
// triggers some kind of weird heap corruption deep inside Qt. So instead of
|
||||
// modifying the model in place, possibly triggering behaviors in the table
|
||||
// instead we create a new `ListModel`, populate it and update the
|
||||
// existing model atomically.
|
||||
var newRunningScriptsModel = listModelBuilder.createObject(root);
|
||||
for (var i = 0; i < runningScripts.length; ++i) {
|
||||
runningScriptsModel.append(runningScripts[i]);
|
||||
newRunningScriptsModel.append(runningScripts[i]);
|
||||
}
|
||||
runningScriptsModel = newRunningScriptsModel;
|
||||
}
|
||||
|
||||
function loadScript(script) {
|
||||
|
|
|
@ -23,11 +23,13 @@ import "../../../windows"
|
|||
import "../../../dialogs/fileDialog"
|
||||
|
||||
//FIXME implement shortcuts for favorite location
|
||||
Item {
|
||||
Rectangle {
|
||||
id: root
|
||||
anchors.top: parent.top
|
||||
anchors.top: parent ? parent.top : undefined
|
||||
HifiConstants { id: hifi }
|
||||
|
||||
color: hifi.colors.baseGray;
|
||||
|
||||
Settings {
|
||||
category: "FileDialog"
|
||||
property alias width: root.width
|
||||
|
|
|
@ -37,8 +37,6 @@
|
|||
#include <QtQml/QQmlEngine>
|
||||
#include <QtQuick/QQuickWindow>
|
||||
|
||||
#include <QtWebEngineWidgets/QWebEngineProfile>
|
||||
|
||||
#include <QtWidgets/QDesktopWidget>
|
||||
#include <QtWidgets/QMessageBox>
|
||||
|
||||
|
@ -99,8 +97,8 @@
|
|||
#include <OctalCode.h>
|
||||
#include <OctreeSceneStats.h>
|
||||
#include <OffscreenUi.h>
|
||||
#include <gl/OffscreenQmlSurfaceCache.h>
|
||||
#include <gl/OffscreenGLCanvas.h>
|
||||
#include <ui/OffscreenQmlSurfaceCache.h>
|
||||
#include <PathUtils.h>
|
||||
#include <PerfStat.h>
|
||||
#include <PhysicsEngine.h>
|
||||
|
@ -125,7 +123,8 @@
|
|||
#include <ScriptEngines.h>
|
||||
#include <ScriptCache.h>
|
||||
#include <SoundCache.h>
|
||||
#include <TabletScriptingInterface.h>
|
||||
#include <ui/TabletScriptingInterface.h>
|
||||
#include <ui/ToolbarScriptingInterface.h>
|
||||
#include <Tooltip.h>
|
||||
#include <udt/PacketHeaders.h>
|
||||
#include <UserActivityLogger.h>
|
||||
|
@ -166,7 +165,6 @@
|
|||
#include "scripting/SettingsScriptingInterface.h"
|
||||
#include "scripting/WindowScriptingInterface.h"
|
||||
#include "scripting/ControllerScriptingInterface.h"
|
||||
#include "scripting/ToolbarScriptingInterface.h"
|
||||
#include "scripting/RatesScriptingInterface.h"
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
|
||||
#include "SpeechRecognizer.h"
|
||||
|
@ -441,6 +439,11 @@ static const QString STATE_ADVANCED_MOVEMENT_CONTROLS = "AdvancedMovement";
|
|||
static const QString STATE_GROUNDED = "Grounded";
|
||||
static const QString STATE_NAV_FOCUSED = "NavigationFocused";
|
||||
|
||||
// Statically provided display and input plugins
|
||||
extern DisplayPluginList getDisplayPlugins();
|
||||
extern InputPluginList getInputPlugins();
|
||||
extern void saveInputPluginSettings(const InputPluginList& plugins);
|
||||
|
||||
bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
|
||||
const char** constArgv = const_cast<const char**>(argv);
|
||||
|
||||
|
@ -480,7 +483,12 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
|
|||
|
||||
Setting::init();
|
||||
|
||||
if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) {
|
||||
// Tell the plugin manager about our statically linked plugins
|
||||
auto pluginManager = PluginManager::getInstance();
|
||||
pluginManager->setInputPluginProvider([] { return getInputPlugins(); });
|
||||
pluginManager->setDisplayPluginProvider([] { return getDisplayPlugins(); });
|
||||
pluginManager->setInputPluginSettingsPersister([](const InputPluginList& plugins) { saveInputPluginSettings(plugins); });
|
||||
if (auto steamClient = pluginManager->getSteamClientPlugin()) {
|
||||
steamClient->init();
|
||||
}
|
||||
|
||||
|
@ -713,9 +721,13 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
updateHeartbeat();
|
||||
|
||||
// setup a timer for domain-server check ins
|
||||
QTimer* domainCheckInTimer = new QTimer(nodeList.data());
|
||||
QTimer* domainCheckInTimer = new QTimer(this);
|
||||
connect(domainCheckInTimer, &QTimer::timeout, nodeList.data(), &NodeList::sendDomainServerCheckIn);
|
||||
domainCheckInTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS);
|
||||
connect(this, &QCoreApplication::aboutToQuit, [domainCheckInTimer] {
|
||||
domainCheckInTimer->stop();
|
||||
domainCheckInTimer->deleteLater();
|
||||
});
|
||||
|
||||
|
||||
auto audioIO = DependencyManager::get<AudioClient>();
|
||||
|
@ -903,6 +915,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
_saveAvatarOverrideUrl = true;
|
||||
}
|
||||
|
||||
QString defaultScriptsLocation = getCmdOption(argc, constArgv, "--scripts");
|
||||
if (!defaultScriptsLocation.isEmpty()) {
|
||||
PathUtils::defaultScriptsLocation(defaultScriptsLocation);
|
||||
}
|
||||
|
||||
_glWidget = new GLCanvas();
|
||||
getApplicationCompositor().setRenderingWidget(_glWidget);
|
||||
_window->setCentralWidget(_glWidget);
|
||||
|
@ -1167,7 +1184,15 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
// force the model the look at the correct directory (weird order of operations issue)
|
||||
scriptEngines->setScriptsLocation(scriptEngines->getScriptsLocation());
|
||||
// do this as late as possible so that all required subsystems are initialized
|
||||
scriptEngines->loadScripts();
|
||||
// If we've overridden the default scripts location, just load default scripts
|
||||
// otherwise, load 'em all
|
||||
if (!defaultScriptsLocation.isEmpty()) {
|
||||
scriptEngines->loadDefaultScripts();
|
||||
scriptEngines->defaultScriptsLocationOverridden(true);
|
||||
} else {
|
||||
scriptEngines->loadScripts();
|
||||
}
|
||||
|
||||
// Make sure we don't time out during slow operations at startup
|
||||
updateHeartbeat();
|
||||
|
||||
|
@ -1792,6 +1817,8 @@ void Application::cleanupBeforeQuit() {
|
|||
#endif
|
||||
|
||||
// stop QML
|
||||
DependencyManager::destroy<TabletScriptingInterface>();
|
||||
DependencyManager::destroy<ToolbarScriptingInterface>();
|
||||
DependencyManager::destroy<OffscreenUi>();
|
||||
|
||||
DependencyManager::destroy<OffscreenQmlSurfaceCache>();
|
||||
|
@ -4165,7 +4192,7 @@ void Application::updateMyAvatarLookAtPosition() {
|
|||
lookAtSpot = transformPoint(worldHeadMat, glm::vec3(0.0f, 0.0f, TREE_SCALE));
|
||||
} else {
|
||||
lookAtSpot = myAvatar->getHead()->getEyePosition() +
|
||||
(myAvatar->getHead()->getFinalOrientationInWorldFrame() * glm::vec3(0.0f, 0.0f, TREE_SCALE));
|
||||
(myAvatar->getHead()->getFinalOrientationInWorldFrame() * glm::vec3(0.0f, 0.0f, -TREE_SCALE));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5535,8 +5562,16 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
|
|||
|
||||
scriptEngine->registerGlobalObject("OffscreenFlags", DependencyManager::get<OffscreenUi>()->getFlags());
|
||||
scriptEngine->registerGlobalObject("Desktop", DependencyManager::get<DesktopScriptingInterface>().data());
|
||||
|
||||
qScriptRegisterMetaType(scriptEngine, wrapperToScriptValue<ToolbarProxy>, wrapperFromScriptValue<ToolbarProxy>);
|
||||
qScriptRegisterMetaType(scriptEngine, wrapperToScriptValue<ToolbarButtonProxy>, wrapperFromScriptValue<ToolbarButtonProxy>);
|
||||
scriptEngine->registerGlobalObject("Toolbars", DependencyManager::get<ToolbarScriptingInterface>().data());
|
||||
|
||||
qScriptRegisterMetaType(scriptEngine, wrapperToScriptValue<TabletProxy>, wrapperFromScriptValue<TabletProxy>);
|
||||
qScriptRegisterMetaType(scriptEngine, wrapperToScriptValue<TabletButtonProxy>, wrapperFromScriptValue<TabletButtonProxy>);
|
||||
scriptEngine->registerGlobalObject("Tablet", DependencyManager::get<TabletScriptingInterface>().data());
|
||||
|
||||
|
||||
DependencyManager::get<TabletScriptingInterface>().data()->setToolbarScriptingInterface(DependencyManager::get<ToolbarScriptingInterface>().data());
|
||||
|
||||
scriptEngine->registerGlobalObject("Window", DependencyManager::get<WindowScriptingInterface>().data());
|
||||
|
@ -5610,7 +5645,6 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
|
|||
scriptEngine->registerGlobalObject("EntityScriptServerLog", entityScriptServerLog.data());
|
||||
scriptEngine->registerGlobalObject("AvatarInputs", AvatarInputs::getInstance());
|
||||
|
||||
|
||||
qScriptRegisterMetaType(scriptEngine, OverlayIDtoScriptValue, OverlayIDfromScriptValue);
|
||||
|
||||
// connect this script engines printedMessage signal to the global ScriptEngines these various messages
|
||||
|
@ -5619,6 +5653,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
|
|||
connect(scriptEngine, &ScriptEngine::warningMessage, DependencyManager::get<ScriptEngines>().data(), &ScriptEngines::onWarningMessage);
|
||||
connect(scriptEngine, &ScriptEngine::infoMessage, DependencyManager::get<ScriptEngines>().data(), &ScriptEngines::onInfoMessage);
|
||||
connect(scriptEngine, &ScriptEngine::clearDebugWindow, DependencyManager::get<ScriptEngines>().data(), &ScriptEngines::onClearDebugWindow);
|
||||
|
||||
}
|
||||
|
||||
bool Application::canAcceptURL(const QString& urlString) const {
|
||||
|
@ -5855,7 +5890,7 @@ void Application::showDialog(const QUrl& widgetUrl, const QUrl& tabletUrl, const
|
|||
|
||||
void Application::showScriptLogs() {
|
||||
auto scriptEngines = DependencyManager::get<ScriptEngines>();
|
||||
QUrl defaultScriptsLoc = defaultScriptsLocation();
|
||||
QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation();
|
||||
defaultScriptsLoc.setPath(defaultScriptsLoc.path() + "developer/debugging/debugWindow.js");
|
||||
scriptEngines->loadScript(defaultScriptsLoc.toString());
|
||||
}
|
||||
|
|
|
@ -297,6 +297,7 @@ public:
|
|||
void setAvatarOverrideUrl(const QUrl& url, bool save);
|
||||
QUrl getAvatarOverrideUrl() { return _avatarOverrideUrl; }
|
||||
bool getSaveAvatarOverrideUrl() { return _saveAvatarOverrideUrl; }
|
||||
void setCacheOverrideDir(const QString& dirName) { _cacheDir = dirName; }
|
||||
|
||||
signals:
|
||||
void svoImportRequested(const QString& url);
|
||||
|
@ -688,5 +689,7 @@ private:
|
|||
|
||||
QUrl _avatarOverrideUrl;
|
||||
bool _saveAvatarOverrideUrl { false };
|
||||
|
||||
QString _cacheDir;
|
||||
};
|
||||
#endif // hifi_Application_h
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
#include <AudioClient.h>
|
||||
#include <CrashHelpers.h>
|
||||
#include <DependencyManager.h>
|
||||
#include <TabletScriptingInterface.h>
|
||||
#include <ui/TabletScriptingInterface.h>
|
||||
#include <display-plugins/DisplayPlugin.h>
|
||||
#include <PathUtils.h>
|
||||
#include <SettingHandle.h>
|
||||
|
@ -121,9 +121,14 @@ Menu::Menu() {
|
|||
QAction::NoRole, UNSPECIFIED_POSITION, "Advanced");
|
||||
|
||||
// Edit > Reload All Scripts... [advanced]
|
||||
addActionToQMenuAndActionHash(editMenu, MenuOption::ReloadAllScripts, Qt::CTRL | Qt::Key_R,
|
||||
scriptEngines.data(), SLOT(reloadAllScripts()),
|
||||
action = addActionToQMenuAndActionHash(editMenu, MenuOption::ReloadAllScripts, Qt::CTRL | Qt::Key_R,
|
||||
nullptr, nullptr,
|
||||
QAction::NoRole, UNSPECIFIED_POSITION, "Advanced");
|
||||
connect(action, &QAction::triggered, [] {
|
||||
DependencyManager::get<ScriptEngines>()->reloadAllScripts();
|
||||
DependencyManager::get<OffscreenUi>()->clearCache();
|
||||
});
|
||||
|
||||
|
||||
// Edit > Console... [advanced]
|
||||
addActionToQMenuAndActionHash(editMenu, MenuOption::Console, Qt::CTRL | Qt::ALT | Qt::Key_J,
|
||||
|
@ -303,7 +308,7 @@ Menu::Menu() {
|
|||
// Settings > Avatar...
|
||||
action = addActionToQMenuAndActionHash(settingsMenu, "Avatar...");
|
||||
connect(action, &QAction::triggered, [] {
|
||||
qApp->showDialog(QString("hifi/dialogs/AvatarPreferencesDialog.qml"),
|
||||
qApp->showDialog(QString("hifi/dialogs/AvatarPreferencesDialog.qml"),
|
||||
QString("../../hifi/tablet/TabletAvatarPreferences.qml"), "AvatarPreferencesDialog");
|
||||
});
|
||||
|
||||
|
@ -629,7 +634,7 @@ Menu::Menu() {
|
|||
action = addActionToQMenuAndActionHash(audioDebugMenu, "Stats...");
|
||||
connect(action, &QAction::triggered, [] {
|
||||
auto scriptEngines = DependencyManager::get<ScriptEngines>();
|
||||
QUrl defaultScriptsLoc = defaultScriptsLocation();
|
||||
QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation();
|
||||
defaultScriptsLoc.setPath(defaultScriptsLoc.path() + "developer/utilities/audio/stats.js");
|
||||
scriptEngines->loadScript(defaultScriptsLoc.toString());
|
||||
});
|
||||
|
|
|
@ -47,110 +47,113 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
|||
|
||||
MyAvatar* myAvatar = static_cast<MyAvatar*>(_owningAvatar);
|
||||
|
||||
Rig::HeadParameters headParams;
|
||||
Rig::ControllerParameters params;
|
||||
|
||||
AnimPose avatarToRigPose(glm::vec3(1.0f), Quaternions::Y_180, glm::vec3(0.0f));
|
||||
|
||||
// input action is the highest priority source for head orientation.
|
||||
auto avatarHeadPose = myAvatar->getHeadControllerPoseInAvatarFrame();
|
||||
if (avatarHeadPose.isValid()) {
|
||||
glm::mat4 rigHeadMat = Matrices::Y_180 *
|
||||
createMatFromQuatAndPos(avatarHeadPose.getRotation(), avatarHeadPose.getTranslation());
|
||||
headParams.rigHeadPosition = extractTranslation(rigHeadMat);
|
||||
headParams.rigHeadOrientation = glmExtractRotation(rigHeadMat);
|
||||
headParams.headEnabled = true;
|
||||
AnimPose pose(avatarHeadPose.getRotation(), avatarHeadPose.getTranslation());
|
||||
params.controllerPoses[Rig::ControllerType_Head] = avatarToRigPose * pose;
|
||||
params.controllerActiveFlags[Rig::ControllerType_Head] = true;
|
||||
} else {
|
||||
// even though full head IK is disabled, the rig still needs the head orientation to rotate the head up and
|
||||
// down in desktop mode.
|
||||
// preMult 180 is necessary to convert from avatar to rig coordinates.
|
||||
// postMult 180 is necessary to convert head from -z forward to z forward.
|
||||
headParams.rigHeadOrientation = Quaternions::Y_180 * head->getFinalOrientationInLocalFrame() * Quaternions::Y_180;
|
||||
headParams.headEnabled = false;
|
||||
glm::quat headRot = Quaternions::Y_180 * head->getFinalOrientationInLocalFrame() * Quaternions::Y_180;
|
||||
params.controllerPoses[Rig::ControllerType_Head] = AnimPose(glm::vec3(1.0f), headRot, glm::vec3(0.0f));
|
||||
params.controllerActiveFlags[Rig::ControllerType_Head] = false;
|
||||
}
|
||||
|
||||
auto avatarHipsPose = myAvatar->getHipsControllerPoseInAvatarFrame();
|
||||
if (avatarHipsPose.isValid()) {
|
||||
glm::mat4 rigHipsMat = Matrices::Y_180 * createMatFromQuatAndPos(avatarHipsPose.getRotation(), avatarHipsPose.getTranslation());
|
||||
headParams.hipsMatrix = rigHipsMat;
|
||||
headParams.hipsEnabled = true;
|
||||
AnimPose pose(avatarHipsPose.getRotation(), avatarHipsPose.getTranslation());
|
||||
params.controllerPoses[Rig::ControllerType_Hips] = avatarToRigPose * pose;
|
||||
params.controllerActiveFlags[Rig::ControllerType_Hips] = true;
|
||||
} else {
|
||||
headParams.hipsEnabled = false;
|
||||
params.controllerPoses[Rig::ControllerType_Hips] = AnimPose::identity;
|
||||
params.controllerActiveFlags[Rig::ControllerType_Hips] = false;
|
||||
}
|
||||
|
||||
auto avatarSpine2Pose = myAvatar->getSpine2ControllerPoseInAvatarFrame();
|
||||
if (avatarSpine2Pose.isValid()) {
|
||||
glm::mat4 rigSpine2Mat = Matrices::Y_180 * createMatFromQuatAndPos(avatarSpine2Pose.getRotation(), avatarSpine2Pose.getTranslation());
|
||||
headParams.spine2Matrix = rigSpine2Mat;
|
||||
headParams.spine2Enabled = true;
|
||||
AnimPose pose(avatarSpine2Pose.getRotation(), avatarSpine2Pose.getTranslation());
|
||||
params.controllerPoses[Rig::ControllerType_Spine2] = avatarToRigPose * pose;
|
||||
params.controllerActiveFlags[Rig::ControllerType_Spine2] = true;
|
||||
} else {
|
||||
headParams.spine2Enabled = false;
|
||||
params.controllerPoses[Rig::ControllerType_Spine2] = AnimPose::identity;
|
||||
params.controllerActiveFlags[Rig::ControllerType_Spine2] = false;
|
||||
}
|
||||
|
||||
auto avatarRightArmPose = myAvatar->getRightArmControllerPoseInAvatarFrame();
|
||||
if (avatarRightArmPose.isValid()) {
|
||||
glm::mat4 rightArmMat = Matrices::Y_180 * createMatFromQuatAndPos(avatarRightArmPose.getRotation(), avatarRightArmPose.getTranslation());
|
||||
headParams.rightArmPosition = extractTranslation(rightArmMat);
|
||||
headParams.rightArmRotation = glmExtractRotation(rightArmMat);
|
||||
headParams.rightArmEnabled = true;
|
||||
AnimPose pose(avatarRightArmPose.getRotation(), avatarRightArmPose.getTranslation());
|
||||
params.controllerPoses[Rig::ControllerType_RightArm] = avatarToRigPose * pose;
|
||||
params.controllerActiveFlags[Rig::ControllerType_RightArm] = true;
|
||||
} else {
|
||||
headParams.rightArmEnabled = false;
|
||||
params.controllerPoses[Rig::ControllerType_RightArm] = AnimPose::identity;
|
||||
params.controllerActiveFlags[Rig::ControllerType_RightArm] = false;
|
||||
}
|
||||
|
||||
|
||||
auto avatarLeftArmPose = myAvatar->getLeftArmControllerPoseInAvatarFrame();
|
||||
if (avatarLeftArmPose.isValid()) {
|
||||
glm::mat4 leftArmMat = Matrices::Y_180 * createMatFromQuatAndPos(avatarLeftArmPose.getRotation(), avatarLeftArmPose.getTranslation());
|
||||
headParams.leftArmPosition = extractTranslation(leftArmMat);
|
||||
headParams.leftArmRotation = glmExtractRotation(leftArmMat);
|
||||
headParams.leftArmEnabled = true;
|
||||
AnimPose pose(avatarLeftArmPose.getRotation(), avatarLeftArmPose.getTranslation());
|
||||
params.controllerPoses[Rig::ControllerType_LeftArm] = avatarToRigPose * pose;
|
||||
params.controllerActiveFlags[Rig::ControllerType_LeftArm] = true;
|
||||
} else {
|
||||
headParams.leftArmEnabled = false;
|
||||
params.controllerPoses[Rig::ControllerType_LeftArm] = AnimPose::identity;
|
||||
params.controllerActiveFlags[Rig::ControllerType_LeftArm] = false;
|
||||
}
|
||||
|
||||
headParams.isTalking = head->getTimeWithoutTalking() <= 1.5f;
|
||||
|
||||
_rig.updateFromHeadParameters(headParams, deltaTime);
|
||||
|
||||
Rig::HandAndFeetParameters handAndFeetParams;
|
||||
|
||||
auto leftPose = myAvatar->getLeftHandControllerPoseInAvatarFrame();
|
||||
if (leftPose.isValid()) {
|
||||
handAndFeetParams.isLeftEnabled = true;
|
||||
handAndFeetParams.leftPosition = Quaternions::Y_180 * leftPose.getTranslation();
|
||||
handAndFeetParams.leftOrientation = Quaternions::Y_180 * leftPose.getRotation();
|
||||
auto avatarLeftHandPose = myAvatar->getLeftHandControllerPoseInAvatarFrame();
|
||||
if (avatarLeftHandPose.isValid()) {
|
||||
AnimPose pose(avatarLeftHandPose.getRotation(), avatarLeftHandPose.getTranslation());
|
||||
params.controllerPoses[Rig::ControllerType_LeftHand] = avatarToRigPose * pose;
|
||||
params.controllerActiveFlags[Rig::ControllerType_LeftHand] = true;
|
||||
} else {
|
||||
handAndFeetParams.isLeftEnabled = false;
|
||||
params.controllerPoses[Rig::ControllerType_LeftHand] = AnimPose::identity;
|
||||
params.controllerActiveFlags[Rig::ControllerType_LeftHand] = false;
|
||||
}
|
||||
|
||||
auto rightPose = myAvatar->getRightHandControllerPoseInAvatarFrame();
|
||||
if (rightPose.isValid()) {
|
||||
handAndFeetParams.isRightEnabled = true;
|
||||
handAndFeetParams.rightPosition = Quaternions::Y_180 * rightPose.getTranslation();
|
||||
handAndFeetParams.rightOrientation = Quaternions::Y_180 * rightPose.getRotation();
|
||||
auto avatarRightHandPose = myAvatar->getRightHandControllerPoseInAvatarFrame();
|
||||
if (avatarRightHandPose.isValid()) {
|
||||
AnimPose pose(avatarRightHandPose.getRotation(), avatarRightHandPose.getTranslation());
|
||||
params.controllerPoses[Rig::ControllerType_RightHand] = avatarToRigPose * pose;
|
||||
params.controllerActiveFlags[Rig::ControllerType_RightHand] = true;
|
||||
} else {
|
||||
handAndFeetParams.isRightEnabled = false;
|
||||
params.controllerPoses[Rig::ControllerType_RightHand] = AnimPose::identity;
|
||||
params.controllerActiveFlags[Rig::ControllerType_RightHand] = false;
|
||||
}
|
||||
|
||||
auto leftFootPose = myAvatar->getLeftFootControllerPoseInAvatarFrame();
|
||||
if (leftFootPose.isValid()) {
|
||||
handAndFeetParams.isLeftFootEnabled = true;
|
||||
handAndFeetParams.leftFootPosition = Quaternions::Y_180 * leftFootPose.getTranslation();
|
||||
handAndFeetParams.leftFootOrientation = Quaternions::Y_180 * leftFootPose.getRotation();
|
||||
auto avatarLeftFootPose = myAvatar->getLeftFootControllerPoseInAvatarFrame();
|
||||
if (avatarLeftFootPose.isValid()) {
|
||||
AnimPose pose(avatarLeftFootPose.getRotation(), avatarLeftFootPose.getTranslation());
|
||||
params.controllerPoses[Rig::ControllerType_LeftFoot] = avatarToRigPose * pose;
|
||||
params.controllerActiveFlags[Rig::ControllerType_LeftFoot] = true;
|
||||
} else {
|
||||
handAndFeetParams.isLeftFootEnabled = false;
|
||||
params.controllerPoses[Rig::ControllerType_LeftFoot] = AnimPose::identity;
|
||||
params.controllerActiveFlags[Rig::ControllerType_LeftFoot] = false;
|
||||
}
|
||||
|
||||
auto rightFootPose = myAvatar->getRightFootControllerPoseInAvatarFrame();
|
||||
if (rightFootPose.isValid()) {
|
||||
handAndFeetParams.isRightFootEnabled = true;
|
||||
handAndFeetParams.rightFootPosition = Quaternions::Y_180 * rightFootPose.getTranslation();
|
||||
handAndFeetParams.rightFootOrientation = Quaternions::Y_180 * rightFootPose.getRotation();
|
||||
auto avatarRightFootPose = myAvatar->getRightFootControllerPoseInAvatarFrame();
|
||||
if (avatarRightFootPose.isValid()) {
|
||||
AnimPose pose(avatarRightFootPose.getRotation(), avatarRightFootPose.getTranslation());
|
||||
params.controllerPoses[Rig::ControllerType_RightFoot] = avatarToRigPose * pose;
|
||||
params.controllerActiveFlags[Rig::ControllerType_RightFoot] = true;
|
||||
} else {
|
||||
handAndFeetParams.isRightFootEnabled = false;
|
||||
params.controllerPoses[Rig::ControllerType_RightFoot] = AnimPose::identity;
|
||||
params.controllerActiveFlags[Rig::ControllerType_RightFoot] = false;
|
||||
}
|
||||
|
||||
handAndFeetParams.bodyCapsuleRadius = myAvatar->getCharacterController()->getCapsuleRadius();
|
||||
handAndFeetParams.bodyCapsuleHalfHeight = myAvatar->getCharacterController()->getCapsuleHalfHeight();
|
||||
handAndFeetParams.bodyCapsuleLocalOffset = myAvatar->getCharacterController()->getCapsuleLocalOffset();
|
||||
params.bodyCapsuleRadius = myAvatar->getCharacterController()->getCapsuleRadius();
|
||||
params.bodyCapsuleHalfHeight = myAvatar->getCharacterController()->getCapsuleHalfHeight();
|
||||
params.bodyCapsuleLocalOffset = myAvatar->getCharacterController()->getCapsuleLocalOffset();
|
||||
|
||||
_rig.updateFromHandAndFeetParameters(handAndFeetParams, deltaTime);
|
||||
params.isTalking = head->getTimeWithoutTalking() <= 1.5f;
|
||||
|
||||
_rig.updateFromControllerParameters(params, deltaTime);
|
||||
|
||||
Rig::CharacterControllerState ccState = convertCharacterControllerState(myAvatar->getCharacterController()->getState());
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include <gl/OpenGLVersionChecker.h>
|
||||
#include <SandboxUtils.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <NetworkAccessManager.h>
|
||||
|
||||
#include "AddressManager.h"
|
||||
#include "Application.h"
|
||||
|
@ -71,15 +72,17 @@ int main(int argc, const char* argv[]) {
|
|||
QCommandLineOption runServerOption("runServer", "Whether to run the server");
|
||||
QCommandLineOption serverContentPathOption("serverContentPath", "Where to find server content", "serverContentPath");
|
||||
QCommandLineOption allowMultipleInstancesOption("allowMultipleInstances", "Allow multiple instances to run");
|
||||
QCommandLineOption overrideAppLocalDataPathOption("cache", "set test cache <dir>", "dir");
|
||||
parser.addOption(urlOption);
|
||||
parser.addOption(noUpdaterOption);
|
||||
parser.addOption(checkMinSpecOption);
|
||||
parser.addOption(runServerOption);
|
||||
parser.addOption(serverContentPathOption);
|
||||
parser.addOption(overrideAppLocalDataPathOption);
|
||||
parser.addOption(allowMultipleInstancesOption);
|
||||
parser.parse(arguments);
|
||||
|
||||
|
||||
|
||||
const QString& applicationName = getInterfaceSharedMemoryName();
|
||||
bool instanceMightBeRunning = true;
|
||||
#ifdef Q_OS_WIN
|
||||
|
@ -96,6 +99,19 @@ int main(int argc, const char* argv[]) {
|
|||
if (allowMultipleInstances) {
|
||||
instanceMightBeRunning = false;
|
||||
}
|
||||
if (parser.isSet(overrideAppLocalDataPathOption)) {
|
||||
// get dir to use for cache
|
||||
QString cacheDir = parser.value(overrideAppLocalDataPathOption);
|
||||
if (!cacheDir.isEmpty()) {
|
||||
// tell everyone to use the right cache location
|
||||
//
|
||||
// this handles data8 and prepared
|
||||
ResourceManager::setCacheDir(cacheDir);
|
||||
|
||||
// this does the ktx_cache
|
||||
PathUtils::getAppLocalDataPath(cacheDir);
|
||||
}
|
||||
}
|
||||
|
||||
if (instanceMightBeRunning) {
|
||||
// Try to connect and send message to existing interface instance
|
||||
|
@ -179,7 +195,7 @@ int main(int argc, const char* argv[]) {
|
|||
QString openvrDllPath = appPath + "/plugins/openvr.dll";
|
||||
HMODULE openvrDll;
|
||||
CHECKMINSPECPROC checkMinSpecPtr;
|
||||
if ((openvrDll = LoadLibrary(openvrDllPath.toLocal8Bit().data())) &&
|
||||
if ((openvrDll = LoadLibrary(openvrDllPath.toLocal8Bit().data())) &&
|
||||
(checkMinSpecPtr = (CHECKMINSPECPROC)GetProcAddress(openvrDll, "CheckMinSpec"))) {
|
||||
if (!checkMinSpecPtr()) {
|
||||
return -1;
|
||||
|
|
|
@ -9,18 +9,30 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "AudioDevices.h"
|
||||
|
||||
#include "Application.h"
|
||||
#include "AudioClient.h"
|
||||
#include "Audio.h"
|
||||
|
||||
#include "UserActivityLogger.h"
|
||||
|
||||
using namespace scripting;
|
||||
|
||||
Setting::Handle<QString> inputDeviceDesktop { QStringList { Audio::AUDIO, Audio::DESKTOP, "INPUT" }};
|
||||
Setting::Handle<QString> outputDeviceDesktop { QStringList { Audio::AUDIO, Audio::DESKTOP, "OUTPUT" }};
|
||||
Setting::Handle<QString> inputDeviceHMD { QStringList { Audio::AUDIO, Audio::HMD, "INPUT" }};
|
||||
Setting::Handle<QString> outputDeviceHMD { QStringList { Audio::AUDIO, Audio::HMD, "OUTPUT" }};
|
||||
static Setting::Handle<QString> desktopInputDeviceSetting { QStringList { Audio::AUDIO, Audio::DESKTOP, "INPUT" }};
|
||||
static Setting::Handle<QString> desktopOutputDeviceSetting { QStringList { Audio::AUDIO, Audio::DESKTOP, "OUTPUT" }};
|
||||
static Setting::Handle<QString> hmdInputDeviceSetting { QStringList { Audio::AUDIO, Audio::HMD, "INPUT" }};
|
||||
static Setting::Handle<QString> hmdOutputDeviceSetting { QStringList { Audio::AUDIO, Audio::HMD, "OUTPUT" }};
|
||||
|
||||
Setting::Handle<QString>& getSetting(bool contextIsHMD, QAudio::Mode mode) {
|
||||
if (mode == QAudio::AudioInput) {
|
||||
return contextIsHMD ? hmdInputDeviceSetting : desktopInputDeviceSetting;
|
||||
} else { // if (mode == QAudio::AudioOutput)
|
||||
return contextIsHMD ? hmdOutputDeviceSetting : desktopOutputDeviceSetting;
|
||||
}
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> AudioDeviceList::_roles {
|
||||
{ Qt::DisplayRole, "display" },
|
||||
|
@ -43,32 +55,37 @@ QVariant AudioDeviceList::data(const QModelIndex& index, int role) const {
|
|||
}
|
||||
|
||||
bool AudioDeviceList::setData(const QModelIndex& index, const QVariant& value, int role) {
|
||||
if (!index.isValid() || index.row() >= _devices.size()) {
|
||||
if (!index.isValid() || index.row() >= _devices.size() || role != Qt::CheckStateRole) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// only allow switching to a new device, not deactivating an in-use device
|
||||
auto selected = value.toBool();
|
||||
if (!selected) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return setDevice(index.row(), true);
|
||||
}
|
||||
|
||||
bool AudioDeviceList::setDevice(int row, bool fromUser) {
|
||||
bool success = false;
|
||||
auto& device = _devices[row];
|
||||
|
||||
if (role == Qt::CheckStateRole) {
|
||||
auto selected = value.toBool();
|
||||
auto& device = _devices[index.row()];
|
||||
// skip if already selected
|
||||
if (!device.selected) {
|
||||
auto client = DependencyManager::get<AudioClient>();
|
||||
QMetaObject::invokeMethod(client.data(), "switchAudioDevice", Qt::BlockingQueuedConnection,
|
||||
Q_RETURN_ARG(bool, success),
|
||||
Q_ARG(QAudio::Mode, _mode),
|
||||
Q_ARG(const QAudioDeviceInfo&, device.info));
|
||||
|
||||
// only allow switching to a new device, not deactivating an in-use device
|
||||
if (selected
|
||||
// skip if already selected
|
||||
&& selected != device.selected) {
|
||||
|
||||
auto client = DependencyManager::get<AudioClient>();
|
||||
QMetaObject::invokeMethod(client.data(), "switchAudioDevice", Qt::BlockingQueuedConnection,
|
||||
Q_RETURN_ARG(bool, success),
|
||||
Q_ARG(QAudio::Mode, _mode),
|
||||
Q_ARG(const QAudioDeviceInfo&, device.info));
|
||||
|
||||
if (success) {
|
||||
device.selected = true;
|
||||
emit deviceSelected(device.info);
|
||||
emit deviceChanged(device.info);
|
||||
if (success) {
|
||||
device.selected = true;
|
||||
if (fromUser) {
|
||||
emit deviceSelected(device.info, _selectedDevice);
|
||||
}
|
||||
emit deviceChanged(device.info);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -88,12 +105,12 @@ void AudioDeviceList::resetDevice(bool contextIsHMD, const QString& device) {
|
|||
}
|
||||
}
|
||||
if (i < rowCount()) {
|
||||
success = setData(createIndex(i, 0), true, Qt::CheckStateRole);
|
||||
success = setDevice(i, false);
|
||||
}
|
||||
|
||||
// the selection failed - reset it
|
||||
if (!success) {
|
||||
emit deviceSelected(QAudioDeviceInfo());
|
||||
emit deviceSelected();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -167,48 +184,55 @@ AudioDevices::AudioDevices(bool& contextIsHMD) : _contextIsHMD(contextIsHMD) {
|
|||
_inputs.onDevicesChanged(client->getAudioDevices(QAudio::AudioInput));
|
||||
_outputs.onDevicesChanged(client->getAudioDevices(QAudio::AudioOutput));
|
||||
|
||||
connect(&_inputs, &AudioDeviceList::deviceSelected, this, &AudioDevices::onInputDeviceSelected);
|
||||
connect(&_outputs, &AudioDeviceList::deviceSelected, this, &AudioDevices::onOutputDeviceSelected);
|
||||
connect(&_inputs, &AudioDeviceList::deviceSelected, [&](const QAudioDeviceInfo& device, const QAudioDeviceInfo& previousDevice) {
|
||||
onDeviceSelected(QAudio::AudioInput, device, previousDevice);
|
||||
});
|
||||
connect(&_outputs, &AudioDeviceList::deviceSelected, [&](const QAudioDeviceInfo& device, const QAudioDeviceInfo& previousDevice) {
|
||||
onDeviceSelected(QAudio::AudioOutput, device, previousDevice);
|
||||
});
|
||||
}
|
||||
|
||||
void AudioDevices::onContextChanged(const QString& context) {
|
||||
QString input;
|
||||
QString output;
|
||||
if (_contextIsHMD) {
|
||||
input = inputDeviceHMD.get();
|
||||
output = outputDeviceHMD.get();
|
||||
} else {
|
||||
input = inputDeviceDesktop.get();
|
||||
output = outputDeviceDesktop.get();
|
||||
}
|
||||
auto input = getSetting(_contextIsHMD, QAudio::AudioInput).get();
|
||||
auto output = getSetting(_contextIsHMD, QAudio::AudioOutput).get();
|
||||
|
||||
_inputs.resetDevice(_contextIsHMD, input);
|
||||
_outputs.resetDevice(_contextIsHMD, output);
|
||||
}
|
||||
|
||||
void AudioDevices::onInputDeviceSelected(const QAudioDeviceInfo& device) {
|
||||
QString deviceName;
|
||||
void AudioDevices::onDeviceSelected(QAudio::Mode mode, const QAudioDeviceInfo& device, const QAudioDeviceInfo& previousDevice) {
|
||||
QString deviceName = device.isNull() ? QString() : device.deviceName();
|
||||
|
||||
auto& setting = getSetting(_contextIsHMD, mode);
|
||||
|
||||
// check for a previous device
|
||||
auto wasDefault = setting.get().isNull();
|
||||
|
||||
// store the selected device
|
||||
setting.set(deviceName);
|
||||
|
||||
// log the selected device
|
||||
if (!device.isNull()) {
|
||||
deviceName = device.deviceName();
|
||||
}
|
||||
QJsonObject data;
|
||||
|
||||
if (_contextIsHMD) {
|
||||
inputDeviceHMD.set(deviceName);
|
||||
} else {
|
||||
inputDeviceDesktop.set(deviceName);
|
||||
}
|
||||
}
|
||||
const QString MODE = "audio_mode";
|
||||
const QString INPUT = "INPUT";
|
||||
const QString OUTPUT = "OUTPUT"; data[MODE] = mode == QAudio::AudioInput ? INPUT : OUTPUT;
|
||||
|
||||
void AudioDevices::onOutputDeviceSelected(const QAudioDeviceInfo& device) {
|
||||
QString deviceName;
|
||||
if (!device.isNull()) {
|
||||
deviceName = device.deviceName();
|
||||
}
|
||||
const QString CONTEXT = "display_mode";
|
||||
data[CONTEXT] = _contextIsHMD ? Audio::HMD : Audio::DESKTOP;
|
||||
|
||||
if (_contextIsHMD) {
|
||||
outputDeviceHMD.set(deviceName);
|
||||
} else {
|
||||
outputDeviceDesktop.set(deviceName);
|
||||
const QString DISPLAY = "display_device";
|
||||
data[DISPLAY] = qApp->getActiveDisplayPlugin()->getName();
|
||||
|
||||
const QString DEVICE = "device";
|
||||
const QString PREVIOUS_DEVICE = "previous_device";
|
||||
const QString WAS_DEFAULT = "was_default";
|
||||
data[DEVICE] = deviceName;
|
||||
data[PREVIOUS_DEVICE] = previousDevice.deviceName();
|
||||
data[WAS_DEFAULT] = wasDefault;
|
||||
|
||||
UserActivityLogger::getInstance().logAction("selected_audio_device", data);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -239,4 +263,4 @@ void AudioDevices::onDevicesChanged(QAudio::Mode mode, const QList<QAudioDeviceI
|
|||
static std::once_flag outputFlag;
|
||||
std::call_once(outputFlag, initialize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,8 @@ public:
|
|||
void resetDevice(bool contextIsHMD, const QString& device);
|
||||
|
||||
signals:
|
||||
void deviceSelected(const QAudioDeviceInfo& device);
|
||||
void deviceSelected(const QAudioDeviceInfo& device = QAudioDeviceInfo(),
|
||||
const QAudioDeviceInfo& previousDevice = QAudioDeviceInfo());
|
||||
void deviceChanged(const QAudioDeviceInfo& device);
|
||||
|
||||
private slots:
|
||||
|
@ -53,6 +54,8 @@ private slots:
|
|||
private:
|
||||
friend class AudioDevices;
|
||||
|
||||
bool setDevice(int index, bool fromUser);
|
||||
|
||||
static QHash<int, QByteArray> _roles;
|
||||
static Qt::ItemFlags _flags;
|
||||
|
||||
|
@ -76,8 +79,7 @@ signals:
|
|||
|
||||
private slots:
|
||||
void onContextChanged(const QString& context);
|
||||
void onInputDeviceSelected(const QAudioDeviceInfo& device);
|
||||
void onOutputDeviceSelected(const QAudioDeviceInfo& device);
|
||||
void onDeviceSelected(QAudio::Mode mode, const QAudioDeviceInfo& device, const QAudioDeviceInfo& previousDevice);
|
||||
void onDeviceChanged(QAudio::Mode mode, const QAudioDeviceInfo& device);
|
||||
void onDevicesChanged(QAudio::Mode mode, const QList<QAudioDeviceInfo>& devices);
|
||||
|
||||
|
|
|
@ -1,63 +0,0 @@
|
|||
//
|
||||
// Created by Anthony J. Thibault on 2016-12-12
|
||||
// Copyright 2013-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_QmlWrapper_h
|
||||
#define hifi_QmlWrapper_h
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <OffscreenUi.h>
|
||||
#include <DependencyManager.h>
|
||||
|
||||
class QmlWrapper : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
QmlWrapper(QObject* qmlObject, QObject* parent = nullptr)
|
||||
: QObject(parent), _qmlObject(qmlObject) {
|
||||
}
|
||||
|
||||
Q_INVOKABLE void writeProperty(QString propertyName, QVariant propertyValue) {
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
offscreenUi->executeOnUiThread([=] {
|
||||
_qmlObject->setProperty(propertyName.toStdString().c_str(), propertyValue);
|
||||
});
|
||||
}
|
||||
|
||||
Q_INVOKABLE void writeProperties(QVariant propertyMap) {
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
offscreenUi->executeOnUiThread([=] {
|
||||
QVariantMap map = propertyMap.toMap();
|
||||
for (const QString& key : map.keys()) {
|
||||
_qmlObject->setProperty(key.toStdString().c_str(), map[key]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Q_INVOKABLE QVariant readProperty(const QString& propertyName) {
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
return offscreenUi->returnFromUiThread([&]()->QVariant {
|
||||
return _qmlObject->property(propertyName.toStdString().c_str());
|
||||
});
|
||||
}
|
||||
|
||||
Q_INVOKABLE QVariant readProperties(const QVariant& propertyList) {
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
return offscreenUi->returnFromUiThread([&]()->QVariant {
|
||||
QVariantMap result;
|
||||
for (const QVariant& property : propertyList.toList()) {
|
||||
QString propertyString = property.toString();
|
||||
result.insert(propertyString, _qmlObject->property(propertyString.toStdString().c_str()));
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
protected:
|
||||
QObject* _qmlObject{ nullptr };
|
||||
};
|
||||
|
||||
#endif
|
|
@ -1,104 +0,0 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2016-06-16
|
||||
// Copyright 2013-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 "ToolbarScriptingInterface.h"
|
||||
|
||||
|
||||
#include <QtCore/QThread>
|
||||
|
||||
#include <OffscreenUi.h>
|
||||
#include "QmlWrapper.h"
|
||||
|
||||
class ToolbarButtonProxy : public QmlWrapper {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ToolbarButtonProxy(QObject* qmlObject, QObject* parent = nullptr) : QmlWrapper(qmlObject, parent) {
|
||||
std::lock_guard<std::mutex> guard(_mutex);
|
||||
_qmlButton = qobject_cast<QQuickItem*>(qmlObject);
|
||||
connect(qmlObject, SIGNAL(clicked()), this, SIGNAL(clicked()));
|
||||
}
|
||||
|
||||
Q_INVOKABLE void editProperties(QVariantMap properties) {
|
||||
std::lock_guard<std::mutex> guard(_mutex);
|
||||
QVariantMap::const_iterator iter = properties.constBegin();
|
||||
while (iter != properties.constEnd()) {
|
||||
_properties[iter.key()] = iter.value();
|
||||
if (_qmlButton) {
|
||||
// [01/25 14:26:20] [WARNING] [default] QMetaObject::invokeMethod: No such method ToolbarButton_QMLTYPE_195::changeProperty(QVariant,QVariant)
|
||||
|
||||
QMetaObject::invokeMethod(_qmlButton, "changeProperty", Qt::AutoConnection,
|
||||
Q_ARG(QVariant, QVariant(iter.key())), Q_ARG(QVariant, iter.value()));
|
||||
}
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
|
||||
signals:
|
||||
void clicked();
|
||||
|
||||
protected:
|
||||
mutable std::mutex _mutex;
|
||||
QQuickItem* _qmlButton { nullptr };
|
||||
QVariantMap _properties;
|
||||
};
|
||||
|
||||
class ToolbarProxy : public QmlWrapper {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ToolbarProxy(QObject* qmlObject, QObject* parent = nullptr) : QmlWrapper(qmlObject, parent) { }
|
||||
|
||||
Q_INVOKABLE QObject* addButton(const QVariant& properties) {
|
||||
QVariant resultVar;
|
||||
Qt::ConnectionType connectionType = Qt::AutoConnection;
|
||||
if (QThread::currentThread() != _qmlObject->thread()) {
|
||||
connectionType = Qt::BlockingQueuedConnection;
|
||||
}
|
||||
bool invokeResult = QMetaObject::invokeMethod(_qmlObject, "addButton", connectionType, Q_RETURN_ARG(QVariant, resultVar), Q_ARG(QVariant, properties));
|
||||
if (!invokeResult) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QObject* rawButton = qvariant_cast<QObject *>(resultVar);
|
||||
if (!rawButton) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return new ToolbarButtonProxy(rawButton, this);
|
||||
}
|
||||
|
||||
Q_INVOKABLE void removeButton(const QVariant& name) {
|
||||
QMetaObject::invokeMethod(_qmlObject, "removeButton", Qt::AutoConnection, Q_ARG(QVariant, name));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
QObject* ToolbarScriptingInterface::getToolbar(const QString& toolbarId) {
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
auto desktop = offscreenUi->getDesktop();
|
||||
Qt::ConnectionType connectionType = Qt::AutoConnection;
|
||||
if (QThread::currentThread() != desktop->thread()) {
|
||||
connectionType = Qt::BlockingQueuedConnection;
|
||||
}
|
||||
QVariant resultVar;
|
||||
bool invokeResult = QMetaObject::invokeMethod(desktop, "getToolbar", connectionType, Q_RETURN_ARG(QVariant, resultVar), Q_ARG(QVariant, toolbarId));
|
||||
if (!invokeResult) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QObject* rawToolbar = qvariant_cast<QObject *>(resultVar);
|
||||
if (!rawToolbar) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return new ToolbarProxy(rawToolbar);
|
||||
}
|
||||
|
||||
|
||||
#include "ToolbarScriptingInterface.moc"
|
|
@ -1,26 +0,0 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2016-06-16
|
||||
// Copyright 2013-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_ToolbarScriptingInterface_h
|
||||
#define hifi_ToolbarScriptingInterface_h
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include <QtCore/QObject>
|
||||
|
||||
#include <DependencyManager.h>
|
||||
|
||||
class ToolbarProxy;
|
||||
|
||||
class ToolbarScriptingInterface : public QObject, public Dependency {
|
||||
Q_OBJECT
|
||||
public:
|
||||
Q_INVOKABLE QObject* getToolbar(const QString& toolbarId);
|
||||
};
|
||||
|
||||
#endif // hifi_ToolbarScriptingInterface_h
|
|
@ -17,6 +17,7 @@
|
|||
#include <Application.h>
|
||||
#include <MainWindow.h>
|
||||
#include <PathUtils.h>
|
||||
#include <ui/TabletScriptingInterface.h>
|
||||
|
||||
#include "AddressBarDialog.h"
|
||||
#include "ConnectionFailureDialog.h"
|
||||
|
@ -28,7 +29,6 @@
|
|||
#include "PreferencesDialog.h"
|
||||
#include "UpdateDialog.h"
|
||||
|
||||
#include "TabletScriptingInterface.h"
|
||||
#include "scripting/HMDScriptingInterface.h"
|
||||
|
||||
static const QVariant TABLET_ADDRESS_DIALOG = "TabletAddressDialog.qml";
|
||||
|
|
|
@ -19,13 +19,13 @@
|
|||
#include <NetworkingConstants.h>
|
||||
#include <plugins/PluginManager.h>
|
||||
#include <plugins/SteamClientPlugin.h>
|
||||
#include <ui/TabletScriptingInterface.h>
|
||||
|
||||
#include "AccountManager.h"
|
||||
#include "DependencyManager.h"
|
||||
#include "Menu.h"
|
||||
|
||||
#include "Application.h"
|
||||
#include "TabletScriptingInterface.h"
|
||||
#include "scripting/HMDScriptingInterface.h"
|
||||
|
||||
HIFI_QML_DEF(LoginDialog)
|
||||
|
|
|
@ -25,16 +25,15 @@
|
|||
#include <GeometryCache.h>
|
||||
#include <GeometryUtil.h>
|
||||
#include <scripting/HMDScriptingInterface.h>
|
||||
#include <gl/OffscreenQmlSurface.h>
|
||||
#include <ui/OffscreenQmlSurface.h>
|
||||
#include <ui/OffscreenQmlSurfaceCache.h>
|
||||
#include <ui/TabletScriptingInterface.h>
|
||||
#include <PathUtils.h>
|
||||
#include <RegisteredMetaTypes.h>
|
||||
#include <TabletScriptingInterface.h>
|
||||
#include <TextureCache.h>
|
||||
#include <UsersScriptingInterface.h>
|
||||
#include <UserActivityLoggerScriptingInterface.h>
|
||||
#include <AbstractViewStateInterface.h>
|
||||
#include <gl/OffscreenQmlSurface.h>
|
||||
#include <gl/OffscreenQmlSurfaceCache.h>
|
||||
#include <AddressManager.h>
|
||||
#include "scripting/AccountScriptingInterface.h"
|
||||
#include "scripting/HMDScriptingInterface.h"
|
||||
|
@ -87,7 +86,7 @@ Web3DOverlay::~Web3DOverlay() {
|
|||
|
||||
if (rootItem && rootItem->objectName() == "tabletRoot") {
|
||||
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
||||
tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", nullptr, nullptr);
|
||||
tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", nullptr);
|
||||
}
|
||||
|
||||
// Fix for crash in QtWebEngineCore when rapidly switching domains
|
||||
|
@ -206,7 +205,7 @@ void Web3DOverlay::loadSourceURL() {
|
|||
_webSurface->getSurfaceContext()->setContextProperty("SoundCache", DependencyManager::get<SoundCache>().data());
|
||||
|
||||
_webSurface->getSurfaceContext()->setContextProperty("pathToFonts", "../../");
|
||||
tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface->getRootItem(), _webSurface.data());
|
||||
tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface.data());
|
||||
|
||||
// mark the TabletProxy object as cpp ownership.
|
||||
QObject* tablet = tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system");
|
||||
|
|
|
@ -23,13 +23,40 @@
|
|||
#include "CubicHermiteSpline.h"
|
||||
#include "AnimUtil.h"
|
||||
|
||||
static void lookupJointChainInfo(AnimInverseKinematics::JointChainInfo* jointChainInfos, size_t numJointChainInfos,
|
||||
int indexA, int indexB,
|
||||
AnimInverseKinematics::JointChainInfo** jointChainInfoA,
|
||||
AnimInverseKinematics::JointChainInfo** jointChainInfoB) {
|
||||
*jointChainInfoA = nullptr;
|
||||
*jointChainInfoB = nullptr;
|
||||
for (size_t i = 0; i < numJointChainInfos; i++) {
|
||||
if (jointChainInfos[i].jointIndex == indexA) {
|
||||
*jointChainInfoA = jointChainInfos + i;
|
||||
}
|
||||
if (jointChainInfos[i].jointIndex == indexB) {
|
||||
*jointChainInfoB = jointChainInfos + i;
|
||||
}
|
||||
if (*jointChainInfoA && *jointChainInfoB) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static float easeOutExpo(float t) {
|
||||
return 1.0f - powf(2, -10.0f * t);
|
||||
}
|
||||
|
||||
AnimInverseKinematics::IKTargetVar::IKTargetVar(const QString& jointNameIn, const QString& positionVarIn, const QString& rotationVarIn,
|
||||
const QString& typeVarIn, const QString& weightVarIn, float weightIn, const std::vector<float>& flexCoefficientsIn) :
|
||||
const QString& typeVarIn, const QString& weightVarIn, float weightIn, const std::vector<float>& flexCoefficientsIn,
|
||||
const QString& poleVectorEnabledVarIn, const QString& poleReferenceVectorVarIn, const QString& poleVectorVarIn) :
|
||||
jointName(jointNameIn),
|
||||
positionVar(positionVarIn),
|
||||
rotationVar(rotationVarIn),
|
||||
typeVar(typeVarIn),
|
||||
weightVar(weightVarIn),
|
||||
poleVectorEnabledVar(poleVectorEnabledVarIn),
|
||||
poleReferenceVectorVar(poleReferenceVectorVarIn),
|
||||
poleVectorVar(poleVectorVarIn),
|
||||
weight(weightIn),
|
||||
numFlexCoefficients(flexCoefficientsIn.size()),
|
||||
jointIndex(-1)
|
||||
|
@ -46,6 +73,9 @@ AnimInverseKinematics::IKTargetVar::IKTargetVar(const IKTargetVar& orig) :
|
|||
rotationVar(orig.rotationVar),
|
||||
typeVar(orig.typeVar),
|
||||
weightVar(orig.weightVar),
|
||||
poleVectorEnabledVar(orig.poleVectorEnabledVar),
|
||||
poleReferenceVectorVar(orig.poleReferenceVectorVar),
|
||||
poleVectorVar(orig.poleVectorVar),
|
||||
weight(orig.weight),
|
||||
numFlexCoefficients(orig.numFlexCoefficients),
|
||||
jointIndex(orig.jointIndex)
|
||||
|
@ -99,8 +129,9 @@ void AnimInverseKinematics::computeAbsolutePoses(AnimPoseVec& absolutePoses) con
|
|||
}
|
||||
|
||||
void AnimInverseKinematics::setTargetVars(const QString& jointName, const QString& positionVar, const QString& rotationVar,
|
||||
const QString& typeVar, const QString& weightVar, float weight, const std::vector<float>& flexCoefficients) {
|
||||
IKTargetVar targetVar(jointName, positionVar, rotationVar, typeVar, weightVar, weight, flexCoefficients);
|
||||
const QString& typeVar, const QString& weightVar, float weight, const std::vector<float>& flexCoefficients,
|
||||
const QString& poleVectorEnabledVar, const QString& poleReferenceVectorVar, const QString& poleVectorVar) {
|
||||
IKTargetVar targetVar(jointName, positionVar, rotationVar, typeVar, weightVar, weight, flexCoefficients, poleVectorEnabledVar, poleReferenceVectorVar, poleVectorVar);
|
||||
|
||||
// if there are dups, last one wins.
|
||||
bool found = false;
|
||||
|
@ -138,9 +169,9 @@ void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std::
|
|||
IKTarget target;
|
||||
target.setType(animVars.lookup(targetVar.typeVar, (int)IKTarget::Type::RotationAndPosition));
|
||||
if (target.getType() != IKTarget::Type::Unknown) {
|
||||
AnimPose defaultPose = _skeleton->getAbsolutePose(targetVar.jointIndex, underPoses);
|
||||
glm::quat rotation = animVars.lookupRigToGeometry(targetVar.rotationVar, defaultPose.rot());
|
||||
glm::vec3 translation = animVars.lookupRigToGeometry(targetVar.positionVar, defaultPose.trans());
|
||||
AnimPose absPose = _skeleton->getAbsolutePose(targetVar.jointIndex, underPoses);
|
||||
glm::quat rotation = animVars.lookupRigToGeometry(targetVar.rotationVar, absPose.rot());
|
||||
glm::vec3 translation = animVars.lookupRigToGeometry(targetVar.positionVar, absPose.trans());
|
||||
float weight = animVars.lookup(targetVar.weightVar, targetVar.weight);
|
||||
|
||||
target.setPose(rotation, translation);
|
||||
|
@ -148,6 +179,15 @@ void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std::
|
|||
target.setWeight(weight);
|
||||
target.setFlexCoefficients(targetVar.numFlexCoefficients, targetVar.flexCoefficients);
|
||||
|
||||
bool poleVectorEnabled = animVars.lookup(targetVar.poleVectorEnabledVar, false);
|
||||
target.setPoleVectorEnabled(poleVectorEnabled);
|
||||
|
||||
glm::vec3 poleVector = animVars.lookupRigToGeometryVector(targetVar.poleVectorVar, Vectors::UNIT_Z);
|
||||
target.setPoleVector(glm::normalize(poleVector));
|
||||
|
||||
glm::vec3 poleReferenceVector = animVars.lookupRigToGeometryVector(targetVar.poleReferenceVectorVar, Vectors::UNIT_Z);
|
||||
target.setPoleReferenceVector(glm::normalize(poleReferenceVector));
|
||||
|
||||
targets.push_back(target);
|
||||
|
||||
if (targetVar.jointIndex > _maxTargetIndex) {
|
||||
|
@ -298,7 +338,8 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const
|
|||
// the tip's parent-relative as we proceed up the chain
|
||||
glm::quat tipParentOrientation = absolutePoses[pivotIndex].rot();
|
||||
|
||||
std::map<int, DebugJoint> debugJointMap;
|
||||
const size_t MAX_CHAIN_DEPTH = 30;
|
||||
JointChainInfo jointChainInfos[MAX_CHAIN_DEPTH];
|
||||
|
||||
// NOTE: if this code is removed, the head will remain rigid, causing the spine/hips to thrust forward backward
|
||||
// as the head is nodded.
|
||||
|
@ -326,15 +367,8 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const
|
|||
}
|
||||
}
|
||||
|
||||
// store the relative rotation change in the accumulator
|
||||
_rotationAccumulators[tipIndex].add(tipRelativeRotation, target.getWeight());
|
||||
|
||||
glm::vec3 tipRelativeTranslation = _relativePoses[target.getIndex()].trans();
|
||||
_translationAccumulators[tipIndex].add(tipRelativeTranslation);
|
||||
|
||||
if (debug) {
|
||||
debugJointMap[tipIndex] = DebugJoint(tipRelativeRotation, tipRelativeTranslation, constrained);
|
||||
}
|
||||
jointChainInfos[chainDepth] = { tipRelativeRotation, tipRelativeTranslation, target.getWeight(), tipIndex, constrained };
|
||||
}
|
||||
|
||||
// cache tip absolute position
|
||||
|
@ -344,6 +378,9 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const
|
|||
|
||||
// descend toward root, pivoting each joint to get tip closer to target position
|
||||
while (pivotIndex != _hipsIndex && pivotsParentIndex != -1) {
|
||||
|
||||
assert(chainDepth < MAX_CHAIN_DEPTH);
|
||||
|
||||
// compute the two lines that should be aligned
|
||||
glm::vec3 jointPosition = absolutePoses[pivotIndex].trans();
|
||||
glm::vec3 leverArm = tipPosition - jointPosition;
|
||||
|
@ -357,6 +394,7 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const
|
|||
const float MIN_AXIS_LENGTH = 1.0e-4f;
|
||||
RotationConstraint* constraint = getConstraint(pivotIndex);
|
||||
|
||||
|
||||
// only allow swing on lowerSpine if there is a hips IK target.
|
||||
if (_hipsTargetIndex < 0 && constraint && constraint->isLowerSpine() && tipIndex != _headIndex) {
|
||||
// for these types of targets we only allow twist at the lower-spine
|
||||
|
@ -382,6 +420,7 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const
|
|||
float cosAngle = glm::clamp(glm::dot(leverArm, targetLine) / (glm::length(leverArm) * glm::length(targetLine)), -1.0f, 1.0f);
|
||||
float angle = acosf(cosAngle);
|
||||
const float MIN_ADJUSTMENT_ANGLE = 1.0e-4f;
|
||||
|
||||
if (angle > MIN_ADJUSTMENT_ANGLE) {
|
||||
// reduce angle by a flexCoefficient
|
||||
angle *= target.getFlexCoefficient(chainDepth);
|
||||
|
@ -440,15 +479,8 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const
|
|||
}
|
||||
}
|
||||
|
||||
// store the relative rotation change in the accumulator
|
||||
_rotationAccumulators[pivotIndex].add(newRot, target.getWeight());
|
||||
|
||||
glm::vec3 newTrans = _relativePoses[pivotIndex].trans();
|
||||
_translationAccumulators[pivotIndex].add(newTrans);
|
||||
|
||||
if (debug) {
|
||||
debugJointMap[pivotIndex] = DebugJoint(newRot, newTrans, constrained);
|
||||
}
|
||||
jointChainInfos[chainDepth] = { newRot, newTrans, target.getWeight(), pivotIndex, constrained };
|
||||
|
||||
// keep track of tip's new transform as we descend towards root
|
||||
tipPosition = jointPosition + deltaRotation * (tipPosition - jointPosition);
|
||||
|
@ -461,8 +493,127 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const
|
|||
chainDepth++;
|
||||
}
|
||||
|
||||
if (target.getPoleVectorEnabled()) {
|
||||
int topJointIndex = target.getIndex();
|
||||
int midJointIndex = _skeleton->getParentIndex(topJointIndex);
|
||||
if (midJointIndex != -1) {
|
||||
int baseJointIndex = _skeleton->getParentIndex(midJointIndex);
|
||||
if (baseJointIndex != -1) {
|
||||
int baseParentJointIndex = _skeleton->getParentIndex(baseJointIndex);
|
||||
AnimPose topPose, midPose, basePose;
|
||||
int topChainIndex = -1, baseChainIndex = -1;
|
||||
AnimPose postAbsPoses[MAX_CHAIN_DEPTH];
|
||||
AnimPose accum = absolutePoses[_hipsIndex];
|
||||
AnimPose baseParentPose = absolutePoses[_hipsIndex];
|
||||
for (int i = (int)chainDepth - 1; i >= 0; i--) {
|
||||
accum = accum * AnimPose(glm::vec3(1.0f), jointChainInfos[i].relRot, jointChainInfos[i].relTrans);
|
||||
postAbsPoses[i] = accum;
|
||||
if (jointChainInfos[i].jointIndex == topJointIndex) {
|
||||
topChainIndex = i;
|
||||
topPose = accum;
|
||||
}
|
||||
if (jointChainInfos[i].jointIndex == midJointIndex) {
|
||||
midPose = accum;
|
||||
}
|
||||
if (jointChainInfos[i].jointIndex == baseJointIndex) {
|
||||
baseChainIndex = i;
|
||||
basePose = accum;
|
||||
}
|
||||
if (jointChainInfos[i].jointIndex == baseParentJointIndex) {
|
||||
baseParentPose = accum;
|
||||
}
|
||||
}
|
||||
|
||||
glm::quat poleRot = Quaternions::IDENTITY;
|
||||
glm::vec3 d = basePose.trans() - topPose.trans();
|
||||
float dLen = glm::length(d);
|
||||
if (dLen > EPSILON) {
|
||||
glm::vec3 dUnit = d / dLen;
|
||||
glm::vec3 e = midPose.xformVector(target.getPoleReferenceVector());
|
||||
glm::vec3 eProj = e - glm::dot(e, dUnit) * dUnit;
|
||||
float eProjLen = glm::length(eProj);
|
||||
|
||||
const float MIN_EPROJ_LEN = 0.5f;
|
||||
if (eProjLen < MIN_EPROJ_LEN) {
|
||||
glm::vec3 midPoint = topPose.trans() + d * 0.5f;
|
||||
e = midPose.trans() - midPoint;
|
||||
eProj = e - glm::dot(e, dUnit) * dUnit;
|
||||
eProjLen = glm::length(eProj);
|
||||
}
|
||||
|
||||
glm::vec3 p = target.getPoleVector();
|
||||
glm::vec3 pProj = p - glm::dot(p, dUnit) * dUnit;
|
||||
float pProjLen = glm::length(pProj);
|
||||
|
||||
if (eProjLen > EPSILON && pProjLen > EPSILON) {
|
||||
// as pProjLen become orthognal to d, reduce the amount of rotation.
|
||||
float magnitude = easeOutExpo(pProjLen);
|
||||
float dot = glm::clamp(glm::dot(eProj / eProjLen, pProj / pProjLen), 0.0f, 1.0f);
|
||||
float theta = acosf(dot);
|
||||
glm::vec3 cross = glm::cross(eProj, pProj);
|
||||
const float MIN_ADJUSTMENT_ANGLE = 0.001745f; // 0.1 degree
|
||||
if (theta > MIN_ADJUSTMENT_ANGLE) {
|
||||
glm::vec3 axis = dUnit;
|
||||
if (glm::dot(cross, dUnit) < 0) {
|
||||
axis = -dUnit;
|
||||
}
|
||||
poleRot = glm::angleAxis(magnitude * theta, axis);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
const vec4 RED(1.0f, 0.0f, 0.0f, 1.0f);
|
||||
const vec4 GREEN(0.0f, 1.0f, 0.0f, 1.0f);
|
||||
const vec4 BLUE(0.0f, 0.0f, 1.0f, 1.0f);
|
||||
const vec4 YELLOW(1.0f, 1.0f, 0.0f, 1.0f);
|
||||
const vec4 WHITE(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
|
||||
AnimPose geomToWorldPose = AnimPose(context.getRigToWorldMatrix() * context.getGeometryToRigMatrix());
|
||||
|
||||
glm::vec3 dUnit = d / dLen;
|
||||
glm::vec3 e = midPose.xformVector(target.getPoleReferenceVector());
|
||||
glm::vec3 eProj = e - glm::dot(e, dUnit) * dUnit;
|
||||
float eProjLen = glm::length(eProj);
|
||||
const float MIN_EPROJ_LEN = 0.5f;
|
||||
if (eProjLen < MIN_EPROJ_LEN) {
|
||||
glm::vec3 midPoint = topPose.trans() + d * 0.5f;
|
||||
e = midPose.trans() - midPoint;
|
||||
eProj = e - glm::dot(e, dUnit) * dUnit;
|
||||
eProjLen = glm::length(eProj);
|
||||
}
|
||||
|
||||
glm::vec3 p = target.getPoleVector();
|
||||
const float PROJ_VECTOR_LEN = 10.0f;
|
||||
const float POLE_VECTOR_LEN = 100.0f;
|
||||
glm::vec3 midPoint = (basePose.trans() + topPose.trans()) * 0.5f;
|
||||
DebugDraw::getInstance().drawRay(geomToWorldPose.xformPoint(basePose.trans()),
|
||||
geomToWorldPose.xformPoint(topPose.trans()),
|
||||
YELLOW);
|
||||
DebugDraw::getInstance().drawRay(geomToWorldPose.xformPoint(midPoint),
|
||||
geomToWorldPose.xformPoint(midPoint + PROJ_VECTOR_LEN * glm::normalize(e)),
|
||||
RED);
|
||||
DebugDraw::getInstance().drawRay(geomToWorldPose.xformPoint(midPoint),
|
||||
geomToWorldPose.xformPoint(midPoint + POLE_VECTOR_LEN * glm::normalize(p)),
|
||||
BLUE);
|
||||
}
|
||||
|
||||
glm::quat newBaseRelRot = glm::inverse(baseParentPose.rot()) * poleRot * basePose.rot();
|
||||
jointChainInfos[baseChainIndex].relRot = newBaseRelRot;
|
||||
|
||||
glm::quat newTopRelRot = glm::inverse(midPose.rot()) * glm::inverse(poleRot) * topPose.rot();
|
||||
jointChainInfos[topChainIndex].relRot = newTopRelRot;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < chainDepth; i++) {
|
||||
_rotationAccumulators[jointChainInfos[i].jointIndex].add(jointChainInfos[i].relRot, jointChainInfos[i].weight);
|
||||
_translationAccumulators[jointChainInfos[i].jointIndex].add(jointChainInfos[i].relTrans, jointChainInfos[i].weight);
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
debugDrawIKChain(debugJointMap, context);
|
||||
debugDrawIKChain(jointChainInfos, chainDepth, context);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -548,7 +699,8 @@ const std::vector<AnimInverseKinematics::SplineJointInfo>* AnimInverseKinematics
|
|||
|
||||
void AnimInverseKinematics::solveTargetWithSpline(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug) {
|
||||
|
||||
std::map<int, DebugJoint> debugJointMap;
|
||||
const size_t MAX_CHAIN_DEPTH = 30;
|
||||
JointChainInfo jointChainInfos[MAX_CHAIN_DEPTH];
|
||||
|
||||
const int baseIndex = _hipsIndex;
|
||||
|
||||
|
@ -608,7 +760,6 @@ void AnimInverseKinematics::solveTargetWithSpline(const AnimContext& context, co
|
|||
::blend(1, &absolutePoses[splineJointInfo.jointIndex], &desiredAbsPose, target.getFlexCoefficient(i), &flexedAbsPose);
|
||||
|
||||
AnimPose relPose = parentAbsPose.inverse() * flexedAbsPose;
|
||||
_rotationAccumulators[splineJointInfo.jointIndex].add(relPose.rot(), target.getWeight());
|
||||
|
||||
bool constrained = false;
|
||||
if (splineJointInfo.jointIndex != _hipsIndex) {
|
||||
|
@ -632,18 +783,19 @@ void AnimInverseKinematics::solveTargetWithSpline(const AnimContext& context, co
|
|||
}
|
||||
}
|
||||
|
||||
_translationAccumulators[splineJointInfo.jointIndex].add(relPose.trans(), target.getWeight());
|
||||
|
||||
if (debug) {
|
||||
debugJointMap[splineJointInfo.jointIndex] = DebugJoint(relPose.rot(), relPose.trans(), constrained);
|
||||
}
|
||||
jointChainInfos[i] = { relPose.rot(), relPose.trans(), target.getWeight(), splineJointInfo.jointIndex, constrained };
|
||||
|
||||
parentAbsPose = flexedAbsPose;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < splineJointInfoVec->size(); i++) {
|
||||
_rotationAccumulators[jointChainInfos[i].jointIndex].add(jointChainInfos[i].relRot, jointChainInfos[i].weight);
|
||||
_translationAccumulators[jointChainInfos[i].jointIndex].add(jointChainInfos[i].relTrans, jointChainInfos[i].weight);
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
debugDrawIKChain(debugJointMap, context);
|
||||
debugDrawIKChain(jointChainInfos, splineJointInfoVec->size(), context);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -654,6 +806,7 @@ const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVar
|
|||
return _relativePoses;
|
||||
}
|
||||
|
||||
|
||||
//virtual
|
||||
const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) {
|
||||
// allows solutionSource to be overridden by an animVar
|
||||
|
@ -767,6 +920,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
|
|||
|
||||
{
|
||||
PROFILE_RANGE_EX(simulation_animation, "ik/ccd", 0xffff00ff, 0);
|
||||
preconditionRelativePosesToAvoidLimbLock(context, targets);
|
||||
solve(context, targets);
|
||||
}
|
||||
|
||||
|
@ -921,7 +1075,7 @@ void AnimInverseKinematics::initConstraints() {
|
|||
// y |
|
||||
// | |
|
||||
// | O---O---O RightUpLeg
|
||||
// z | | Hips2 |
|
||||
// z | | Hips |
|
||||
// \ | | |
|
||||
// \| | |
|
||||
// x -----+ O O RightLeg
|
||||
|
@ -966,7 +1120,9 @@ void AnimInverseKinematics::initConstraints() {
|
|||
if (0 == baseName.compare("Arm", Qt::CaseSensitive)) {
|
||||
SwingTwistConstraint* stConstraint = new SwingTwistConstraint();
|
||||
stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot());
|
||||
stConstraint->setTwistLimits(-PI / 2.0f, PI / 2.0f);
|
||||
//stConstraint->setTwistLimits(-PI / 2.0f, PI / 2.0f);
|
||||
const float TWIST_LIMIT = 5.0f * PI / 8.0f;
|
||||
stConstraint->setTwistLimits(-TWIST_LIMIT, TWIST_LIMIT);
|
||||
|
||||
/* KEEP THIS CODE for future experimentation
|
||||
// these directions are approximate swing limits in root-frame
|
||||
|
@ -992,7 +1148,7 @@ void AnimInverseKinematics::initConstraints() {
|
|||
|
||||
// simple cone
|
||||
std::vector<float> minDots;
|
||||
const float MAX_HAND_SWING = PI / 2.0f;
|
||||
const float MAX_HAND_SWING = 5.0f * PI / 8.0f;
|
||||
minDots.push_back(cosf(MAX_HAND_SWING));
|
||||
stConstraint->setSwingLimits(minDots);
|
||||
|
||||
|
@ -1000,7 +1156,7 @@ void AnimInverseKinematics::initConstraints() {
|
|||
} else if (0 == baseName.compare("UpLeg", Qt::CaseSensitive)) {
|
||||
SwingTwistConstraint* stConstraint = new SwingTwistConstraint();
|
||||
stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot());
|
||||
stConstraint->setTwistLimits(-PI / 4.0f, PI / 4.0f);
|
||||
stConstraint->setTwistLimits(-PI / 2.0f, PI / 2.0f);
|
||||
|
||||
std::vector<glm::vec3> swungDirections;
|
||||
float deltaTheta = PI / 4.0f;
|
||||
|
@ -1142,7 +1298,7 @@ void AnimInverseKinematics::initConstraints() {
|
|||
// we determine the max/min angles by rotating the swing limit lines from parent- to child-frame
|
||||
// then measure the angles to swing the yAxis into alignment
|
||||
glm::vec3 hingeAxis = - mirror * Vectors::UNIT_Z;
|
||||
const float MIN_ELBOW_ANGLE = 0.05f;
|
||||
const float MIN_ELBOW_ANGLE = 0.0f;
|
||||
const float MAX_ELBOW_ANGLE = 11.0f * PI / 12.0f;
|
||||
glm::quat invReferenceRotation = glm::inverse(referenceRotation);
|
||||
glm::vec3 minSwingAxis = invReferenceRotation * glm::angleAxis(MIN_ELBOW_ANGLE, hingeAxis) * Vectors::UNIT_Y;
|
||||
|
@ -1173,8 +1329,8 @@ void AnimInverseKinematics::initConstraints() {
|
|||
|
||||
// we determine the max/min angles by rotating the swing limit lines from parent- to child-frame
|
||||
// then measure the angles to swing the yAxis into alignment
|
||||
const float MIN_KNEE_ANGLE = 0.097f; // ~5 deg
|
||||
const float MAX_KNEE_ANGLE = 7.0f * PI / 8.0f;
|
||||
const float MIN_KNEE_ANGLE = 0.0f;
|
||||
const float MAX_KNEE_ANGLE = 7.0f * PI / 8.0f; // 157.5 deg
|
||||
glm::quat invReferenceRotation = glm::inverse(referenceRotation);
|
||||
glm::vec3 minSwingAxis = invReferenceRotation * glm::angleAxis(MIN_KNEE_ANGLE, hingeAxis) * Vectors::UNIT_Y;
|
||||
glm::vec3 maxSwingAxis = invReferenceRotation * glm::angleAxis(MAX_KNEE_ANGLE, hingeAxis) * Vectors::UNIT_Y;
|
||||
|
@ -1308,6 +1464,7 @@ void AnimInverseKinematics::debugDrawRelativePoses(const AnimContext& context) c
|
|||
// convert relative poses to absolute
|
||||
_skeleton->convertRelativePosesToAbsolute(poses);
|
||||
|
||||
|
||||
mat4 geomToWorldMatrix = context.getRigToWorldMatrix() * context.getGeometryToRigMatrix();
|
||||
|
||||
const vec4 RED(1.0f, 0.0f, 0.0f, 1.0f);
|
||||
|
@ -1338,13 +1495,14 @@ void AnimInverseKinematics::debugDrawRelativePoses(const AnimContext& context) c
|
|||
}
|
||||
}
|
||||
|
||||
void AnimInverseKinematics::debugDrawIKChain(std::map<int, DebugJoint>& debugJointMap, const AnimContext& context) const {
|
||||
void AnimInverseKinematics::debugDrawIKChain(JointChainInfo* jointChainInfos, size_t numJointChainInfos, const AnimContext& context) const {
|
||||
AnimPoseVec poses = _relativePoses;
|
||||
|
||||
// copy debug joint rotations into the relative poses
|
||||
for (auto& debugJoint : debugJointMap) {
|
||||
poses[debugJoint.first].rot() = debugJoint.second.relRot;
|
||||
poses[debugJoint.first].trans() = debugJoint.second.relTrans;
|
||||
for (size_t i = 0; i < numJointChainInfos; i++) {
|
||||
const JointChainInfo& info = jointChainInfos[i];
|
||||
poses[info.jointIndex].rot() = info.relRot;
|
||||
poses[info.jointIndex].trans() = info.relTrans;
|
||||
}
|
||||
|
||||
// convert relative poses to absolute
|
||||
|
@ -1360,11 +1518,11 @@ void AnimInverseKinematics::debugDrawIKChain(std::map<int, DebugJoint>& debugJoi
|
|||
|
||||
// draw each pose
|
||||
for (int i = 0; i < (int)poses.size(); i++) {
|
||||
|
||||
// only draw joints that are actually in debugJointMap, or their parents
|
||||
auto iter = debugJointMap.find(i);
|
||||
auto parentIter = debugJointMap.find(_skeleton->getParentIndex(i));
|
||||
if (iter != debugJointMap.end() || parentIter != debugJointMap.end()) {
|
||||
int parentIndex = _skeleton->getParentIndex(i);
|
||||
JointChainInfo* jointInfo = nullptr;
|
||||
JointChainInfo* parentJointInfo = nullptr;
|
||||
lookupJointChainInfo(jointChainInfos, numJointChainInfos, i, parentIndex, &jointInfo, &parentJointInfo);
|
||||
if (jointInfo && parentJointInfo) {
|
||||
|
||||
// transform local axes into world space.
|
||||
auto pose = poses[i];
|
||||
|
@ -1377,13 +1535,12 @@ void AnimInverseKinematics::debugDrawIKChain(std::map<int, DebugJoint>& debugJoi
|
|||
DebugDraw::getInstance().drawRay(pos, pos + AXIS_LENGTH * zAxis, BLUE);
|
||||
|
||||
// draw line to parent
|
||||
int parentIndex = _skeleton->getParentIndex(i);
|
||||
if (parentIndex != -1) {
|
||||
glm::vec3 parentPos = transformPoint(geomToWorldMatrix, poses[parentIndex].trans());
|
||||
glm::vec4 color = GRAY;
|
||||
|
||||
// draw constrained joints with a RED link to their parent.
|
||||
if (parentIter != debugJointMap.end() && parentIter->second.constrained) {
|
||||
if (parentJointInfo->constrained) {
|
||||
color = RED;
|
||||
}
|
||||
DebugDraw::getInstance().drawRay(pos, parentPos, color);
|
||||
|
@ -1486,7 +1643,7 @@ void AnimInverseKinematics::debugDrawConstraints(const AnimContext& context) con
|
|||
glm::vec3 worldSwungAxis = transformVectorFast(geomToWorldMatrix, parentAbsRot * refRot * swungAxis);
|
||||
glm::vec3 swingTip = pos + SWING_LENGTH * worldSwungAxis;
|
||||
|
||||
float prevPhi = acos(swingTwistConstraint->getMinDots()[j]);
|
||||
float prevPhi = acosf(swingTwistConstraint->getMinDots()[j]);
|
||||
float prevTheta = theta - D_THETA;
|
||||
glm::vec3 prevSwungAxis = sphericalToCartesian(prevPhi, prevTheta - PI_2);
|
||||
glm::vec3 prevWorldSwungAxis = transformVectorFast(geomToWorldMatrix, parentAbsRot * refRot * prevSwungAxis);
|
||||
|
@ -1521,6 +1678,50 @@ void AnimInverseKinematics::blendToPoses(const AnimPoseVec& targetPoses, const A
|
|||
}
|
||||
}
|
||||
|
||||
void AnimInverseKinematics::preconditionRelativePosesToAvoidLimbLock(const AnimContext& context, const std::vector<IKTarget>& targets) {
|
||||
const int NUM_LIMBS = 4;
|
||||
std::pair<int, int> limbs[NUM_LIMBS] = {
|
||||
{_skeleton->nameToJointIndex("LeftHand"), _skeleton->nameToJointIndex("LeftArm")},
|
||||
{_skeleton->nameToJointIndex("RightHand"), _skeleton->nameToJointIndex("RightArm")},
|
||||
{_skeleton->nameToJointIndex("LeftFoot"), _skeleton->nameToJointIndex("LeftUpLeg")},
|
||||
{_skeleton->nameToJointIndex("RightFoot"), _skeleton->nameToJointIndex("RightUpLeg")}
|
||||
};
|
||||
const float MIN_AXIS_LENGTH = 1.0e-4f;
|
||||
|
||||
for (auto& target : targets) {
|
||||
if (target.getIndex() != -1) {
|
||||
for (int i = 0; i < NUM_LIMBS; i++) {
|
||||
if (limbs[i].first == target.getIndex()) {
|
||||
int tipIndex = limbs[i].first;
|
||||
int baseIndex = limbs[i].second;
|
||||
|
||||
// TODO: as an optimization, these poses can be computed in one pass down the chain, instead of three.
|
||||
AnimPose tipPose = _skeleton->getAbsolutePose(tipIndex, _relativePoses);
|
||||
AnimPose basePose = _skeleton->getAbsolutePose(baseIndex, _relativePoses);
|
||||
AnimPose baseParentPose = _skeleton->getAbsolutePose(_skeleton->getParentIndex(baseIndex), _relativePoses);
|
||||
|
||||
// to help reduce limb locking, and to help the CCD solver converge faster
|
||||
// rotate the limbs leverArm over the targetLine.
|
||||
glm::vec3 targetLine = target.getTranslation() - basePose.trans();
|
||||
glm::vec3 leverArm = tipPose.trans() - basePose.trans();
|
||||
glm::vec3 axis = glm::cross(leverArm, targetLine);
|
||||
float axisLength = glm::length(axis);
|
||||
if (axisLength > MIN_AXIS_LENGTH) {
|
||||
// compute angle of rotation that brings tip to target
|
||||
axis /= axisLength;
|
||||
float cosAngle = glm::clamp(glm::dot(leverArm, targetLine) / (glm::length(leverArm) * glm::length(targetLine)), -1.0f, 1.0f);
|
||||
float angle = acosf(cosAngle);
|
||||
glm::quat newBaseRotation = glm::angleAxis(angle, axis) * basePose.rot();
|
||||
|
||||
// convert base rotation into relative space of base.
|
||||
_relativePoses[baseIndex].rot() = glm::inverse(baseParentPose.rot()) * newBaseRotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AnimInverseKinematics::initRelativePosesFromSolutionSource(SolutionSource solutionSource, const AnimPoseVec& underPoses) {
|
||||
const float RELAX_BLEND_FACTOR = (1.0f / 16.0f);
|
||||
const float COPY_BLEND_FACTOR = 1.0f;
|
||||
|
@ -1540,7 +1741,7 @@ void AnimInverseKinematics::initRelativePosesFromSolutionSource(SolutionSource s
|
|||
break;
|
||||
case SolutionSource::LimitCenterPoses:
|
||||
// essentially copy limitCenterPoses over to _relativePoses.
|
||||
blendToPoses(_limitCenterPoses, underPoses, COPY_BLEND_FACTOR);
|
||||
blendToPoses(underPoses, _limitCenterPoses, COPY_BLEND_FACTOR);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,14 @@ class RotationConstraint;
|
|||
class AnimInverseKinematics : public AnimNode {
|
||||
public:
|
||||
|
||||
struct JointChainInfo {
|
||||
glm::quat relRot;
|
||||
glm::vec3 relTrans;
|
||||
float weight;
|
||||
int jointIndex;
|
||||
bool constrained;
|
||||
};
|
||||
|
||||
explicit AnimInverseKinematics(const QString& id);
|
||||
virtual ~AnimInverseKinematics() override;
|
||||
|
||||
|
@ -34,7 +42,8 @@ public:
|
|||
void computeAbsolutePoses(AnimPoseVec& absolutePoses) const;
|
||||
|
||||
void setTargetVars(const QString& jointName, const QString& positionVar, const QString& rotationVar,
|
||||
const QString& typeVar, const QString& weightVar, float weight, const std::vector<float>& flexCoefficients);
|
||||
const QString& typeVar, const QString& weightVar, float weight, const std::vector<float>& flexCoefficients,
|
||||
const QString& poleVectorEnabledVar, const QString& poleReferenceVectorVar, const QString& poleVectorVar);
|
||||
|
||||
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimNode::Triggers& triggersOut) override;
|
||||
virtual const AnimPoseVec& overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) override;
|
||||
|
@ -67,19 +76,13 @@ protected:
|
|||
void solveTargetWithCCD(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug);
|
||||
void solveTargetWithSpline(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug);
|
||||
virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) override;
|
||||
struct DebugJoint {
|
||||
DebugJoint() : relRot(), constrained(false) {}
|
||||
DebugJoint(const glm::quat& relRotIn, const glm::vec3& relTransIn, bool constrainedIn) : relRot(relRotIn), relTrans(relTransIn), constrained(constrainedIn) {}
|
||||
glm::quat relRot;
|
||||
glm::vec3 relTrans;
|
||||
bool constrained;
|
||||
};
|
||||
void debugDrawIKChain(std::map<int, DebugJoint>& debugJointMap, const AnimContext& context) const;
|
||||
void debugDrawIKChain(JointChainInfo* jointChainInfos, size_t numJointChainInfos, const AnimContext& context) const;
|
||||
void debugDrawRelativePoses(const AnimContext& context) const;
|
||||
void debugDrawConstraints(const AnimContext& context) const;
|
||||
void debugDrawSpineSplines(const AnimContext& context, const std::vector<IKTarget>& targets) const;
|
||||
void initRelativePosesFromSolutionSource(SolutionSource solutionSource, const AnimPoseVec& underPose);
|
||||
void blendToPoses(const AnimPoseVec& targetPoses, const AnimPoseVec& underPose, float blendFactor);
|
||||
void preconditionRelativePosesToAvoidLimbLock(const AnimContext& context, const std::vector<IKTarget>& targets);
|
||||
|
||||
// used to pre-compute information about each joint influeced by a spline IK target.
|
||||
struct SplineJointInfo {
|
||||
|
@ -107,7 +110,8 @@ protected:
|
|||
enum FlexCoefficients { MAX_FLEX_COEFFICIENTS = 10 };
|
||||
struct IKTargetVar {
|
||||
IKTargetVar(const QString& jointNameIn, const QString& positionVarIn, const QString& rotationVarIn,
|
||||
const QString& typeVarIn, const QString& weightVarIn, float weightIn, const std::vector<float>& flexCoefficientsIn);
|
||||
const QString& typeVarIn, const QString& weightVarIn, float weightIn, const std::vector<float>& flexCoefficientsIn,
|
||||
const QString& poleVectorEnabledVar, const QString& poleReferenceVectorVar, const QString& poleVectorVar);
|
||||
IKTargetVar(const IKTargetVar& orig);
|
||||
|
||||
QString jointName;
|
||||
|
@ -115,6 +119,9 @@ protected:
|
|||
QString rotationVar;
|
||||
QString typeVar;
|
||||
QString weightVar;
|
||||
QString poleVectorEnabledVar;
|
||||
QString poleReferenceVectorVar;
|
||||
QString poleVectorVar;
|
||||
float weight;
|
||||
float flexCoefficients[MAX_FLEX_COEFFICIENTS];
|
||||
size_t numFlexCoefficients;
|
||||
|
|
|
@ -479,6 +479,9 @@ AnimNode::Pointer loadInverseKinematicsNode(const QJsonObject& jsonObj, const QS
|
|||
READ_OPTIONAL_STRING(typeVar, targetObj);
|
||||
READ_OPTIONAL_STRING(weightVar, targetObj);
|
||||
READ_OPTIONAL_FLOAT(weight, targetObj, 1.0f);
|
||||
READ_OPTIONAL_STRING(poleVectorEnabledVar, targetObj);
|
||||
READ_OPTIONAL_STRING(poleReferenceVectorVar, targetObj);
|
||||
READ_OPTIONAL_STRING(poleVectorVar, targetObj);
|
||||
|
||||
auto flexCoefficientsValue = targetObj.value("flexCoefficients");
|
||||
if (!flexCoefficientsValue.isArray()) {
|
||||
|
@ -491,7 +494,7 @@ AnimNode::Pointer loadInverseKinematicsNode(const QJsonObject& jsonObj, const QS
|
|||
flexCoefficients.push_back((float)value.toDouble());
|
||||
}
|
||||
|
||||
node->setTargetVars(jointName, positionVar, rotationVar, typeVar, weightVar, weight, flexCoefficients);
|
||||
node->setTargetVars(jointName, positionVar, rotationVar, typeVar, weightVar, weight, flexCoefficients, poleVectorEnabledVar, poleReferenceVectorVar, poleVectorVar);
|
||||
};
|
||||
|
||||
READ_OPTIONAL_STRING(solutionSource, jsonObj);
|
||||
|
|
|
@ -21,6 +21,8 @@ class AnimPose {
|
|||
public:
|
||||
AnimPose() {}
|
||||
explicit AnimPose(const glm::mat4& mat);
|
||||
explicit AnimPose(const glm::quat& rotIn) : _scale(1.0f), _rot(rotIn), _trans(0.0f) {}
|
||||
AnimPose(const glm::quat& rotIn, const glm::vec3& transIn) : _scale(1.0f), _rot(rotIn), _trans(transIn) {}
|
||||
AnimPose(const glm::vec3& scaleIn, const glm::quat& rotIn, const glm::vec3& transIn) : _scale(scaleIn), _rot(rotIn), _trans(transIn) {}
|
||||
static const AnimPose identity;
|
||||
|
||||
|
|
|
@ -76,11 +76,11 @@ const QString& AnimSkeleton::getJointName(int jointIndex) const {
|
|||
return _joints[jointIndex].name;
|
||||
}
|
||||
|
||||
AnimPose AnimSkeleton::getAbsolutePose(int jointIndex, const AnimPoseVec& poses) const {
|
||||
if (jointIndex < 0 || jointIndex >= (int)poses.size() || jointIndex >= _jointsSize) {
|
||||
AnimPose AnimSkeleton::getAbsolutePose(int jointIndex, const AnimPoseVec& relativePoses) const {
|
||||
if (jointIndex < 0 || jointIndex >= (int)relativePoses.size() || jointIndex >= _jointsSize) {
|
||||
return AnimPose::identity;
|
||||
} else {
|
||||
return getAbsolutePose(_joints[jointIndex].parentIndex, poses) * poses[jointIndex];
|
||||
return getAbsolutePose(_joints[jointIndex].parentIndex, relativePoses) * relativePoses[jointIndex];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ public:
|
|||
|
||||
int getParentIndex(int jointIndex) const;
|
||||
|
||||
AnimPose getAbsolutePose(int jointIndex, const AnimPoseVec& poses) const;
|
||||
AnimPose getAbsolutePose(int jointIndex, const AnimPoseVec& relativePoses) const;
|
||||
|
||||
void convertRelativePosesToAbsolute(AnimPoseVec& poses) const;
|
||||
void convertAbsolutePosesToRelative(AnimPoseVec& poses) const;
|
||||
|
|
|
@ -30,10 +30,16 @@ public:
|
|||
const glm::vec3& getTranslation() const { return _pose.trans(); }
|
||||
const glm::quat& getRotation() const { return _pose.rot(); }
|
||||
const AnimPose& getPose() const { return _pose; }
|
||||
glm::vec3 getPoleVector() const { return _poleVector; }
|
||||
glm::vec3 getPoleReferenceVector() const { return _poleReferenceVector; }
|
||||
bool getPoleVectorEnabled() const { return _poleVectorEnabled; }
|
||||
int getIndex() const { return _index; }
|
||||
Type getType() const { return _type; }
|
||||
|
||||
void setPose(const glm::quat& rotation, const glm::vec3& translation);
|
||||
void setPoleVector(const glm::vec3& poleVector) { _poleVector = poleVector; }
|
||||
void setPoleReferenceVector(const glm::vec3& poleReferenceVector) { _poleReferenceVector = poleReferenceVector; }
|
||||
void setPoleVectorEnabled(bool poleVectorEnabled) { _poleVectorEnabled = poleVectorEnabled; }
|
||||
void setIndex(int index) { _index = index; }
|
||||
void setType(int);
|
||||
void setFlexCoefficients(size_t numFlexCoefficientsIn, const float* flexCoefficientsIn);
|
||||
|
@ -46,8 +52,11 @@ public:
|
|||
|
||||
private:
|
||||
AnimPose _pose;
|
||||
int _index{-1};
|
||||
Type _type{Type::RotationAndPosition};
|
||||
glm::vec3 _poleVector;
|
||||
glm::vec3 _poleReferenceVector;
|
||||
bool _poleVectorEnabled { false };
|
||||
int _index { -1 };
|
||||
Type _type { Type::RotationAndPosition };
|
||||
float _weight;
|
||||
float _flexCoefficients[MAX_FLEX_COEFFICIENTS];
|
||||
size_t _numFlexCoefficients;
|
||||
|
|
|
@ -479,12 +479,6 @@ bool Rig::getAbsoluteJointPoseInRigFrame(int jointIndex, AnimPose& returnPose) c
|
|||
}
|
||||
}
|
||||
|
||||
bool Rig::getJointCombinedRotation(int jointIndex, glm::quat& result, const glm::quat& rotation) const {
|
||||
// AJT: TODO: used by attachments
|
||||
ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
void Rig::calcAnimAlpha(float speed, const std::vector<float>& referenceSpeeds, float* alphaOut) const {
|
||||
|
||||
ASSERT(referenceSpeeds.size() > 0);
|
||||
|
@ -950,6 +944,7 @@ void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, cons
|
|||
|
||||
// evaluate the animation
|
||||
AnimNode::Triggers triggersOut;
|
||||
|
||||
_internalPoseSet._relativePoses = _animNode->evaluate(_animVars, context, deltaTime, triggersOut);
|
||||
if ((int)_internalPoseSet._relativePoses.size() != _animSkeleton->getNumJoints()) {
|
||||
// animations haven't fully loaded yet.
|
||||
|
@ -1015,46 +1010,6 @@ glm::quat Rig::getJointDefaultRotationInParentFrame(int jointIndex) {
|
|||
return glm::quat();
|
||||
}
|
||||
|
||||
void Rig::updateFromHeadParameters(const HeadParameters& params, float dt) {
|
||||
updateHeadAnimVars(params);
|
||||
|
||||
_animVars.set("isTalking", params.isTalking);
|
||||
_animVars.set("notIsTalking", !params.isTalking);
|
||||
|
||||
if (params.hipsEnabled) {
|
||||
_animVars.set("solutionSource", (int)AnimInverseKinematics::SolutionSource::RelaxToLimitCenterPoses);
|
||||
_animVars.set("hipsType", (int)IKTarget::Type::RotationAndPosition);
|
||||
_animVars.set("hipsPosition", extractTranslation(params.hipsMatrix));
|
||||
_animVars.set("hipsRotation", glmExtractRotation(params.hipsMatrix));
|
||||
} else {
|
||||
_animVars.set("solutionSource", (int)AnimInverseKinematics::SolutionSource::RelaxToUnderPoses);
|
||||
_animVars.set("hipsType", (int)IKTarget::Type::Unknown);
|
||||
}
|
||||
|
||||
if (params.hipsEnabled && params.spine2Enabled) {
|
||||
_animVars.set("spine2Type", (int)IKTarget::Type::Spline);
|
||||
_animVars.set("spine2Position", extractTranslation(params.spine2Matrix));
|
||||
_animVars.set("spine2Rotation", glmExtractRotation(params.spine2Matrix));
|
||||
} else {
|
||||
_animVars.set("spine2Type", (int)IKTarget::Type::Unknown);
|
||||
}
|
||||
|
||||
if (params.leftArmEnabled) {
|
||||
_animVars.set("leftArmType", (int)IKTarget::Type::RotationAndPosition);
|
||||
_animVars.set("leftArmPosition", params.leftArmPosition);
|
||||
_animVars.set("leftArmRotation", params.leftArmRotation);
|
||||
} else {
|
||||
_animVars.set("leftArmType", (int)IKTarget::Type::Unknown);
|
||||
}
|
||||
|
||||
if (params.rightArmEnabled) {
|
||||
_animVars.set("rightArmType", (int)IKTarget::Type::RotationAndPosition);
|
||||
_animVars.set("rightArmPosition", params.rightArmPosition);
|
||||
_animVars.set("rightArmRotation", params.rightArmRotation);
|
||||
} else {
|
||||
_animVars.set("rightArmType", (int)IKTarget::Type::Unknown);
|
||||
}
|
||||
}
|
||||
|
||||
void Rig::updateFromEyeParameters(const EyeParameters& params) {
|
||||
updateEyeJoint(params.leftEyeJointIndex, params.modelTranslation, params.modelRotation, params.eyeLookAt, params.eyeSaccade);
|
||||
|
@ -1086,12 +1041,12 @@ void Rig::computeHeadFromHMD(const AnimPose& hmdPose, glm::vec3& headPositionOut
|
|||
headOrientationOut = hmdOrientation;
|
||||
}
|
||||
|
||||
void Rig::updateHeadAnimVars(const HeadParameters& params) {
|
||||
void Rig::updateHead(bool headEnabled, bool hipsEnabled, const AnimPose& headPose) {
|
||||
if (_animSkeleton) {
|
||||
if (params.headEnabled) {
|
||||
_animVars.set("headPosition", params.rigHeadPosition);
|
||||
_animVars.set("headRotation", params.rigHeadOrientation);
|
||||
if (params.hipsEnabled) {
|
||||
if (headEnabled) {
|
||||
_animVars.set("headPosition", headPose.trans());
|
||||
_animVars.set("headRotation", headPose.rot());
|
||||
if (hipsEnabled) {
|
||||
// Since there is an explicit hips ik target, switch the head to use the more flexible Spline IK chain type.
|
||||
// this will allow the spine to compress/expand and bend more natrually, ensuring that it can reach the head target position.
|
||||
_animVars.set("headType", (int)IKTarget::Type::Spline);
|
||||
|
@ -1104,12 +1059,271 @@ void Rig::updateHeadAnimVars(const HeadParameters& params) {
|
|||
}
|
||||
} else {
|
||||
_animVars.unset("headPosition");
|
||||
_animVars.set("headRotation", params.rigHeadOrientation);
|
||||
_animVars.set("headRotation", headPose.rot());
|
||||
_animVars.set("headType", (int)IKTarget::Type::RotationOnly);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnabled, bool leftArmEnabled, bool rightArmEnabled, float dt,
|
||||
const AnimPose& leftHandPose, const AnimPose& rightHandPose,
|
||||
float bodyCapsuleRadius, float bodyCapsuleHalfHeight, const glm::vec3& bodyCapsuleLocalOffset) {
|
||||
|
||||
// Use this capsule to represent the avatar body.
|
||||
int hipsIndex = indexOfJoint("Hips");
|
||||
glm::vec3 hipsTrans;
|
||||
if (hipsIndex >= 0) {
|
||||
hipsTrans = _internalPoseSet._absolutePoses[hipsIndex].trans();
|
||||
}
|
||||
|
||||
const glm::vec3 bodyCapsuleCenter = hipsTrans - bodyCapsuleLocalOffset;
|
||||
const glm::vec3 bodyCapsuleStart = bodyCapsuleCenter - glm::vec3(0, bodyCapsuleHalfHeight, 0);
|
||||
const glm::vec3 bodyCapsuleEnd = bodyCapsuleCenter + glm::vec3(0, bodyCapsuleHalfHeight, 0);
|
||||
|
||||
const float HAND_RADIUS = 0.05f;
|
||||
|
||||
const float RELAX_DURATION = 0.6f;
|
||||
const float CONTROL_DURATION = 0.4f;
|
||||
const bool TO_CONTROLLED = true;
|
||||
const bool FROM_CONTROLLED = false;
|
||||
const bool LEFT_HAND = true;
|
||||
const bool RIGHT_HAND = false;
|
||||
|
||||
const float ELBOW_POLE_VECTOR_BLEND_FACTOR = 0.95f;
|
||||
|
||||
if (leftHandEnabled) {
|
||||
if (!_isLeftHandControlled) {
|
||||
_leftHandControlTimeRemaining = CONTROL_DURATION;
|
||||
_isLeftHandControlled = true;
|
||||
}
|
||||
|
||||
glm::vec3 handPosition = leftHandPose.trans();
|
||||
glm::quat handRotation = leftHandPose.rot();
|
||||
|
||||
if (_leftHandControlTimeRemaining > 0.0f) {
|
||||
// Move hand from non-controlled position to controlled position.
|
||||
_leftHandControlTimeRemaining = std::max(_leftHandControlTimeRemaining - dt, 0.0f);
|
||||
AnimPose handPose(Vectors::ONE, handRotation, handPosition);
|
||||
if (transitionHandPose(_leftHandControlTimeRemaining, CONTROL_DURATION, handPose,
|
||||
LEFT_HAND, TO_CONTROLLED, handPose)) {
|
||||
handPosition = handPose.trans();
|
||||
handRotation = handPose.rot();
|
||||
}
|
||||
}
|
||||
|
||||
if (!hipsEnabled) {
|
||||
// prevent the hand IK targets from intersecting the body capsule
|
||||
glm::vec3 displacement;
|
||||
if (findSphereCapsulePenetration(handPosition, HAND_RADIUS, bodyCapsuleStart, bodyCapsuleEnd, bodyCapsuleRadius, displacement)) {
|
||||
handPosition -= displacement;
|
||||
}
|
||||
}
|
||||
|
||||
_animVars.set("leftHandPosition", handPosition);
|
||||
_animVars.set("leftHandRotation", handRotation);
|
||||
_animVars.set("leftHandType", (int)IKTarget::Type::RotationAndPosition);
|
||||
|
||||
_lastLeftHandControlledPose = AnimPose(Vectors::ONE, handRotation, handPosition);
|
||||
_isLeftHandControlled = true;
|
||||
|
||||
// compute pole vector
|
||||
int handJointIndex = _animSkeleton->nameToJointIndex("LeftHand");
|
||||
int armJointIndex = _animSkeleton->nameToJointIndex("LeftArm");
|
||||
int elbowJointIndex = _animSkeleton->nameToJointIndex("LeftForeArm");
|
||||
if (!leftArmEnabled && elbowJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0) {
|
||||
glm::vec3 poleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, hipsIndex, true);
|
||||
|
||||
// smooth toward desired pole vector from previous pole vector... to reduce jitter
|
||||
if (!_prevLeftHandPoleVectorValid) {
|
||||
_prevLeftHandPoleVectorValid = true;
|
||||
_prevLeftHandPoleVector = poleVector;
|
||||
}
|
||||
glm::quat deltaRot = rotationBetween(_prevLeftHandPoleVector, poleVector);
|
||||
glm::quat smoothDeltaRot = safeMix(deltaRot, Quaternions::IDENTITY, ELBOW_POLE_VECTOR_BLEND_FACTOR);
|
||||
_prevLeftHandPoleVector = smoothDeltaRot * _prevLeftHandPoleVector;
|
||||
|
||||
_animVars.set("leftHandPoleVectorEnabled", true);
|
||||
_animVars.set("leftHandPoleReferenceVector", Vectors::UNIT_X);
|
||||
_animVars.set("leftHandPoleVector", _prevLeftHandPoleVector);
|
||||
} else {
|
||||
_prevLeftHandPoleVectorValid = false;
|
||||
_animVars.set("leftHandPoleVectorEnabled", false);
|
||||
}
|
||||
} else {
|
||||
_prevLeftHandPoleVectorValid = false;
|
||||
_animVars.set("leftHandPoleVectorEnabled", false);
|
||||
|
||||
if (_isLeftHandControlled) {
|
||||
_leftHandRelaxTimeRemaining = RELAX_DURATION;
|
||||
_isLeftHandControlled = false;
|
||||
}
|
||||
|
||||
if (_leftHandRelaxTimeRemaining > 0.0f) {
|
||||
// Move hand from controlled position to non-controlled position.
|
||||
_leftHandRelaxTimeRemaining = std::max(_leftHandRelaxTimeRemaining - dt, 0.0f);
|
||||
AnimPose handPose;
|
||||
if (transitionHandPose(_leftHandRelaxTimeRemaining, RELAX_DURATION, _lastLeftHandControlledPose,
|
||||
LEFT_HAND, FROM_CONTROLLED, handPose)) {
|
||||
_animVars.set("leftHandPosition", handPose.trans());
|
||||
_animVars.set("leftHandRotation", handPose.rot());
|
||||
_animVars.set("leftHandType", (int)IKTarget::Type::RotationAndPosition);
|
||||
}
|
||||
} else {
|
||||
_animVars.unset("leftHandPosition");
|
||||
_animVars.unset("leftHandRotation");
|
||||
_animVars.set("leftHandType", (int)IKTarget::Type::HipsRelativeRotationAndPosition);
|
||||
}
|
||||
}
|
||||
|
||||
if (rightHandEnabled) {
|
||||
if (!_isRightHandControlled) {
|
||||
_rightHandControlTimeRemaining = CONTROL_DURATION;
|
||||
_isRightHandControlled = true;
|
||||
}
|
||||
|
||||
glm::vec3 handPosition = rightHandPose.trans();
|
||||
glm::quat handRotation = rightHandPose.rot();
|
||||
|
||||
if (_rightHandControlTimeRemaining > 0.0f) {
|
||||
// Move hand from non-controlled position to controlled position.
|
||||
_rightHandControlTimeRemaining = std::max(_rightHandControlTimeRemaining - dt, 0.0f);
|
||||
AnimPose handPose(Vectors::ONE, handRotation, handPosition);
|
||||
if (transitionHandPose(_rightHandControlTimeRemaining, CONTROL_DURATION, handPose, RIGHT_HAND, TO_CONTROLLED, handPose)) {
|
||||
handPosition = handPose.trans();
|
||||
handRotation = handPose.rot();
|
||||
}
|
||||
}
|
||||
|
||||
if (!hipsEnabled) {
|
||||
// prevent the hand IK targets from intersecting the body capsule
|
||||
glm::vec3 displacement;
|
||||
if (findSphereCapsulePenetration(handPosition, HAND_RADIUS, bodyCapsuleStart, bodyCapsuleEnd, bodyCapsuleRadius, displacement)) {
|
||||
handPosition -= displacement;
|
||||
}
|
||||
}
|
||||
|
||||
_animVars.set("rightHandPosition", handPosition);
|
||||
_animVars.set("rightHandRotation", handRotation);
|
||||
_animVars.set("rightHandType", (int)IKTarget::Type::RotationAndPosition);
|
||||
|
||||
_lastRightHandControlledPose = AnimPose(Vectors::ONE, handRotation, handPosition);
|
||||
_isRightHandControlled = true;
|
||||
|
||||
// compute pole vector
|
||||
int handJointIndex = _animSkeleton->nameToJointIndex("RightHand");
|
||||
int armJointIndex = _animSkeleton->nameToJointIndex("RightArm");
|
||||
int elbowJointIndex = _animSkeleton->nameToJointIndex("RightForeArm");
|
||||
if (!rightArmEnabled && elbowJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0) {
|
||||
glm::vec3 poleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, hipsIndex, false);
|
||||
|
||||
// smooth toward desired pole vector from previous pole vector... to reduce jitter
|
||||
if (!_prevRightHandPoleVectorValid) {
|
||||
_prevRightHandPoleVectorValid = true;
|
||||
_prevRightHandPoleVector = poleVector;
|
||||
}
|
||||
glm::quat deltaRot = rotationBetween(_prevRightHandPoleVector, poleVector);
|
||||
glm::quat smoothDeltaRot = safeMix(deltaRot, Quaternions::IDENTITY, ELBOW_POLE_VECTOR_BLEND_FACTOR);
|
||||
_prevRightHandPoleVector = smoothDeltaRot * _prevRightHandPoleVector;
|
||||
|
||||
_animVars.set("rightHandPoleVectorEnabled", true);
|
||||
_animVars.set("rightHandPoleReferenceVector", -Vectors::UNIT_X);
|
||||
_animVars.set("rightHandPoleVector", _prevRightHandPoleVector);
|
||||
} else {
|
||||
_prevRightHandPoleVectorValid = false;
|
||||
_animVars.set("rightHandPoleVectorEnabled", false);
|
||||
}
|
||||
} else {
|
||||
_prevRightHandPoleVectorValid = false;
|
||||
_animVars.set("rightHandPoleVectorEnabled", false);
|
||||
|
||||
if (_isRightHandControlled) {
|
||||
_rightHandRelaxTimeRemaining = RELAX_DURATION;
|
||||
_isRightHandControlled = false;
|
||||
}
|
||||
|
||||
if (_rightHandRelaxTimeRemaining > 0.0f) {
|
||||
// Move hand from controlled position to non-controlled position.
|
||||
_rightHandRelaxTimeRemaining = std::max(_rightHandRelaxTimeRemaining - dt, 0.0f);
|
||||
AnimPose handPose;
|
||||
if (transitionHandPose(_rightHandRelaxTimeRemaining, RELAX_DURATION, _lastRightHandControlledPose, RIGHT_HAND, FROM_CONTROLLED, handPose)) {
|
||||
_animVars.set("rightHandPosition", handPose.trans());
|
||||
_animVars.set("rightHandRotation", handPose.rot());
|
||||
_animVars.set("rightHandType", (int)IKTarget::Type::RotationAndPosition);
|
||||
}
|
||||
} else {
|
||||
_animVars.unset("rightHandPosition");
|
||||
_animVars.unset("rightHandRotation");
|
||||
_animVars.set("rightHandType", (int)IKTarget::Type::HipsRelativeRotationAndPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Rig::updateFeet(bool leftFootEnabled, bool rightFootEnabled, const AnimPose& leftFootPose, const AnimPose& rightFootPose) {
|
||||
|
||||
const float KNEE_POLE_VECTOR_BLEND_FACTOR = 0.95f;
|
||||
|
||||
int hipsIndex = indexOfJoint("Hips");
|
||||
|
||||
if (leftFootEnabled) {
|
||||
_animVars.set("leftFootPosition", leftFootPose.trans());
|
||||
_animVars.set("leftFootRotation", leftFootPose.rot());
|
||||
_animVars.set("leftFootType", (int)IKTarget::Type::RotationAndPosition);
|
||||
|
||||
int footJointIndex = _animSkeleton->nameToJointIndex("LeftFoot");
|
||||
int kneeJointIndex = _animSkeleton->nameToJointIndex("LeftLeg");
|
||||
int upLegJointIndex = _animSkeleton->nameToJointIndex("LeftUpLeg");
|
||||
glm::vec3 poleVector = calculateKneePoleVector(footJointIndex, kneeJointIndex, upLegJointIndex, hipsIndex, leftFootPose);
|
||||
|
||||
// smooth toward desired pole vector from previous pole vector... to reduce jitter
|
||||
if (!_prevLeftFootPoleVectorValid) {
|
||||
_prevLeftFootPoleVectorValid = true;
|
||||
_prevLeftFootPoleVector = poleVector;
|
||||
}
|
||||
glm::quat deltaRot = rotationBetween(_prevLeftFootPoleVector, poleVector);
|
||||
glm::quat smoothDeltaRot = safeMix(deltaRot, Quaternions::IDENTITY, KNEE_POLE_VECTOR_BLEND_FACTOR);
|
||||
_prevLeftFootPoleVector = smoothDeltaRot * _prevLeftFootPoleVector;
|
||||
|
||||
_animVars.set("leftFootPoleVectorEnabled", true);
|
||||
_animVars.set("leftFootPoleReferenceVector", Vectors::UNIT_Z);
|
||||
_animVars.set("leftFootPoleVector", _prevLeftFootPoleVector);
|
||||
} else {
|
||||
_animVars.unset("leftFootPosition");
|
||||
_animVars.unset("leftFootRotation");
|
||||
_animVars.set("leftFootType", (int)IKTarget::Type::RotationAndPosition);
|
||||
_animVars.set("leftFootPoleVectorEnabled", false);
|
||||
_prevLeftFootPoleVectorValid = false;
|
||||
}
|
||||
|
||||
if (rightFootEnabled) {
|
||||
_animVars.set("rightFootPosition", rightFootPose.trans());
|
||||
_animVars.set("rightFootRotation", rightFootPose.rot());
|
||||
_animVars.set("rightFootType", (int)IKTarget::Type::RotationAndPosition);
|
||||
|
||||
int footJointIndex = _animSkeleton->nameToJointIndex("RightFoot");
|
||||
int kneeJointIndex = _animSkeleton->nameToJointIndex("RightLeg");
|
||||
int upLegJointIndex = _animSkeleton->nameToJointIndex("RightUpLeg");
|
||||
glm::vec3 poleVector = calculateKneePoleVector(footJointIndex, kneeJointIndex, upLegJointIndex, hipsIndex, rightFootPose);
|
||||
|
||||
// smooth toward desired pole vector from previous pole vector... to reduce jitter
|
||||
if (!_prevRightFootPoleVectorValid) {
|
||||
_prevRightFootPoleVectorValid = true;
|
||||
_prevRightFootPoleVector = poleVector;
|
||||
}
|
||||
glm::quat deltaRot = rotationBetween(_prevRightFootPoleVector, poleVector);
|
||||
glm::quat smoothDeltaRot = safeMix(deltaRot, Quaternions::IDENTITY, KNEE_POLE_VECTOR_BLEND_FACTOR);
|
||||
_prevRightFootPoleVector = smoothDeltaRot * _prevRightFootPoleVector;
|
||||
|
||||
_animVars.set("rightFootPoleVectorEnabled", true);
|
||||
_animVars.set("rightFootPoleReferenceVector", Vectors::UNIT_Z);
|
||||
_animVars.set("rightFootPoleVector", _prevRightFootPoleVector);
|
||||
} else {
|
||||
_animVars.unset("rightFootPosition");
|
||||
_animVars.unset("rightFootRotation");
|
||||
_animVars.set("rightFootPoleVectorEnabled", false);
|
||||
_animVars.set("rightFootType", (int)IKTarget::Type::RotationAndPosition);
|
||||
}
|
||||
}
|
||||
|
||||
void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm::quat& modelRotation, const glm::vec3& lookAtSpot, const glm::vec3& saccade) {
|
||||
|
||||
// TODO: does not properly handle avatar scale.
|
||||
|
@ -1145,162 +1359,138 @@ void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm
|
|||
}
|
||||
}
|
||||
|
||||
void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, float dt) {
|
||||
if (_animSkeleton && _animNode) {
|
||||
const float HAND_RADIUS = 0.05f;
|
||||
int hipsIndex = indexOfJoint("Hips");
|
||||
glm::vec3 hipsTrans;
|
||||
if (hipsIndex >= 0) {
|
||||
hipsTrans = _internalPoseSet._absolutePoses[hipsIndex].trans();
|
||||
}
|
||||
static glm::quat quatLerp(const glm::quat& q1, const glm::quat& q2, float alpha) {
|
||||
float dot = glm::dot(q1, q2);
|
||||
glm::quat temp;
|
||||
if (dot < 0.0f) {
|
||||
temp = -q2;
|
||||
} else {
|
||||
temp = q2;
|
||||
}
|
||||
return glm::normalize(glm::lerp(q1, temp, alpha));
|
||||
}
|
||||
|
||||
// Use this capsule to represent the avatar body.
|
||||
const float bodyCapsuleRadius = params.bodyCapsuleRadius;
|
||||
const glm::vec3 bodyCapsuleCenter = hipsTrans - params.bodyCapsuleLocalOffset;
|
||||
const glm::vec3 bodyCapsuleStart = bodyCapsuleCenter - glm::vec3(0, params.bodyCapsuleHalfHeight, 0);
|
||||
const glm::vec3 bodyCapsuleEnd = bodyCapsuleCenter + glm::vec3(0, params.bodyCapsuleHalfHeight, 0);
|
||||
glm::vec3 Rig::calculateElbowPoleVector(int handIndex, int elbowIndex, int armIndex, int hipsIndex, bool isLeft) const {
|
||||
AnimPose hipsPose = _externalPoseSet._absolutePoses[hipsIndex];
|
||||
AnimPose handPose = _externalPoseSet._absolutePoses[handIndex];
|
||||
AnimPose elbowPose = _externalPoseSet._absolutePoses[elbowIndex];
|
||||
AnimPose armPose = _externalPoseSet._absolutePoses[armIndex];
|
||||
|
||||
// TODO: add isHipsEnabled
|
||||
bool bodySensorTrackingEnabled = params.isLeftFootEnabled || params.isRightFootEnabled;
|
||||
// ray from hand to arm.
|
||||
glm::vec3 d = glm::normalize(handPose.trans() - armPose.trans());
|
||||
|
||||
const float RELAX_DURATION = 0.6f;
|
||||
const float CONTROL_DURATION = 0.4f;
|
||||
const bool TO_CONTROLLED = true;
|
||||
const bool FROM_CONTROLLED = false;
|
||||
const bool LEFT_HAND = true;
|
||||
const bool RIGHT_HAND = false;
|
||||
float sign = isLeft ? 1.0f : -1.0f;
|
||||
|
||||
if (params.isLeftEnabled) {
|
||||
if (!_isLeftHandControlled) {
|
||||
_leftHandControlTimeRemaining = CONTROL_DURATION;
|
||||
_isLeftHandControlled = true;
|
||||
}
|
||||
// form a plane normal to the hips x-axis.
|
||||
glm::vec3 n = hipsPose.rot() * Vectors::UNIT_X;
|
||||
glm::vec3 y = hipsPose.rot() * Vectors::UNIT_Y;
|
||||
|
||||
glm::vec3 handPosition = params.leftPosition;
|
||||
glm::quat handRotation = params.leftOrientation;
|
||||
// project d onto this plane
|
||||
glm::vec3 dProj = d - glm::dot(d, n) * n;
|
||||
|
||||
if (_leftHandControlTimeRemaining > 0.0f) {
|
||||
// Move hand from non-controlled position to controlled position.
|
||||
_leftHandControlTimeRemaining = std::max(_leftHandControlTimeRemaining - dt, 0.0f);
|
||||
AnimPose handPose(Vectors::ONE, handRotation, handPosition);
|
||||
if (transitionHandPose(_leftHandControlTimeRemaining, CONTROL_DURATION, handPose, LEFT_HAND, TO_CONTROLLED,
|
||||
handPose)) {
|
||||
handPosition = handPose.trans();
|
||||
handRotation = handPose.rot();
|
||||
}
|
||||
}
|
||||
// give dProj a bit of offset away from the body.
|
||||
float avatarScale = extractUniformScale(_modelOffset);
|
||||
const float LATERAL_OFFSET = 1.0f * avatarScale;
|
||||
const float VERTICAL_OFFSET = -0.333f * avatarScale;
|
||||
glm::vec3 dProjWithOffset = dProj + sign * LATERAL_OFFSET * n + y * VERTICAL_OFFSET;
|
||||
|
||||
if (!bodySensorTrackingEnabled) {
|
||||
// prevent the hand IK targets from intersecting the body capsule
|
||||
glm::vec3 displacement;
|
||||
if (findSphereCapsulePenetration(handPosition, HAND_RADIUS, bodyCapsuleStart, bodyCapsuleEnd, bodyCapsuleRadius, displacement)) {
|
||||
handPosition -= displacement;
|
||||
}
|
||||
}
|
||||
// rotate dProj by 90 degrees to get the poleVector.
|
||||
glm::vec3 poleVector = glm::angleAxis(PI / 2.0f, n) * dProjWithOffset;
|
||||
|
||||
_animVars.set("leftHandPosition", handPosition);
|
||||
_animVars.set("leftHandRotation", handRotation);
|
||||
_animVars.set("leftHandType", (int)IKTarget::Type::RotationAndPosition);
|
||||
// blend the wrist oreintation into the pole vector to reduce the painfully bent wrist problem.
|
||||
glm::quat elbowToHandDelta = handPose.rot() * glm::inverse(elbowPose.rot());
|
||||
const float WRIST_POLE_ADJUST_FACTOR = 0.5f;
|
||||
glm::quat poleAdjust = quatLerp(Quaternions::IDENTITY, elbowToHandDelta, WRIST_POLE_ADJUST_FACTOR);
|
||||
|
||||
_lastLeftHandControlledPose = AnimPose(Vectors::ONE, handRotation, handPosition);
|
||||
} else {
|
||||
if (_isLeftHandControlled) {
|
||||
_leftHandRelaxTimeRemaining = RELAX_DURATION;
|
||||
_isLeftHandControlled = false;
|
||||
}
|
||||
return glm::normalize(poleAdjust * poleVector);
|
||||
}
|
||||
|
||||
if (_leftHandRelaxTimeRemaining > 0.0f) {
|
||||
// Move hand from controlled position to non-controlled position.
|
||||
_leftHandRelaxTimeRemaining = std::max(_leftHandRelaxTimeRemaining - dt, 0.0f);
|
||||
AnimPose handPose;
|
||||
if (transitionHandPose(_leftHandRelaxTimeRemaining, RELAX_DURATION, _lastLeftHandControlledPose, LEFT_HAND,
|
||||
FROM_CONTROLLED, handPose)) {
|
||||
_animVars.set("leftHandPosition", handPose.trans());
|
||||
_animVars.set("leftHandRotation", handPose.rot());
|
||||
_animVars.set("leftHandType", (int)IKTarget::Type::RotationAndPosition);
|
||||
}
|
||||
} else {
|
||||
_animVars.unset("leftHandPosition");
|
||||
_animVars.unset("leftHandRotation");
|
||||
_animVars.set("leftHandType", (int)IKTarget::Type::HipsRelativeRotationAndPosition);
|
||||
}
|
||||
}
|
||||
glm::vec3 Rig::calculateKneePoleVector(int footJointIndex, int kneeIndex, int upLegIndex, int hipsIndex, const AnimPose& targetFootPose) const {
|
||||
|
||||
if (params.isRightEnabled) {
|
||||
if (!_isRightHandControlled) {
|
||||
_rightHandControlTimeRemaining = CONTROL_DURATION;
|
||||
_isRightHandControlled = true;
|
||||
}
|
||||
AnimPose hipsPose = _externalPoseSet._absolutePoses[hipsIndex];
|
||||
AnimPose footPose = targetFootPose;
|
||||
AnimPose kneePose = _externalPoseSet._absolutePoses[kneeIndex];
|
||||
AnimPose upLegPose = _externalPoseSet._absolutePoses[upLegIndex];
|
||||
|
||||
glm::vec3 handPosition = params.rightPosition;
|
||||
glm::quat handRotation = params.rightOrientation;
|
||||
// ray from foot to upLeg
|
||||
glm::vec3 d = glm::normalize(footPose.trans() - upLegPose.trans());
|
||||
|
||||
if (_rightHandControlTimeRemaining > 0.0f) {
|
||||
// Move hand from non-controlled position to controlled position.
|
||||
_rightHandControlTimeRemaining = std::max(_rightHandControlTimeRemaining - dt, 0.0f);
|
||||
AnimPose handPose(Vectors::ONE, handRotation, handPosition);
|
||||
if (transitionHandPose(_rightHandControlTimeRemaining, CONTROL_DURATION, handPose, RIGHT_HAND, TO_CONTROLLED,
|
||||
handPose)) {
|
||||
handPosition = handPose.trans();
|
||||
handRotation = handPose.rot();
|
||||
}
|
||||
}
|
||||
// form a plane normal to the hips x-axis
|
||||
glm::vec3 n = hipsPose.rot() * Vectors::UNIT_X;
|
||||
|
||||
if (!bodySensorTrackingEnabled) {
|
||||
// prevent the hand IK targets from intersecting the body capsule
|
||||
glm::vec3 displacement;
|
||||
if (findSphereCapsulePenetration(handPosition, HAND_RADIUS, bodyCapsuleStart, bodyCapsuleEnd, bodyCapsuleRadius, displacement)) {
|
||||
handPosition -= displacement;
|
||||
}
|
||||
}
|
||||
// project d onto this plane
|
||||
glm::vec3 dProj = d - glm::dot(d, n) * n;
|
||||
|
||||
_animVars.set("rightHandPosition", handPosition);
|
||||
_animVars.set("rightHandRotation", handRotation);
|
||||
_animVars.set("rightHandType", (int)IKTarget::Type::RotationAndPosition);
|
||||
// rotate dProj by 90 degrees to get the poleVector.
|
||||
glm::vec3 poleVector = glm::angleAxis(-PI / 2.0f, n) * dProj;
|
||||
|
||||
_lastRightHandControlledPose = AnimPose(Vectors::ONE, handRotation, handPosition);
|
||||
} else {
|
||||
if (_isRightHandControlled) {
|
||||
_rightHandRelaxTimeRemaining = RELAX_DURATION;
|
||||
_isRightHandControlled = false;
|
||||
}
|
||||
// blend the foot oreintation into the pole vector
|
||||
glm::quat kneeToFootDelta = footPose.rot() * glm::inverse(kneePose.rot());
|
||||
const float WRIST_POLE_ADJUST_FACTOR = 0.5f;
|
||||
glm::quat poleAdjust = quatLerp(Quaternions::IDENTITY, kneeToFootDelta, WRIST_POLE_ADJUST_FACTOR);
|
||||
|
||||
if (_rightHandRelaxTimeRemaining > 0.0f) {
|
||||
// Move hand from controlled position to non-controlled position.
|
||||
_rightHandRelaxTimeRemaining = std::max(_rightHandRelaxTimeRemaining - dt, 0.0f);
|
||||
AnimPose handPose;
|
||||
if (transitionHandPose(_rightHandRelaxTimeRemaining, RELAX_DURATION, _lastRightHandControlledPose, RIGHT_HAND,
|
||||
FROM_CONTROLLED, handPose)) {
|
||||
_animVars.set("rightHandPosition", handPose.trans());
|
||||
_animVars.set("rightHandRotation", handPose.rot());
|
||||
_animVars.set("rightHandType", (int)IKTarget::Type::RotationAndPosition);
|
||||
}
|
||||
} else {
|
||||
_animVars.unset("rightHandPosition");
|
||||
_animVars.unset("rightHandRotation");
|
||||
_animVars.set("rightHandType", (int)IKTarget::Type::HipsRelativeRotationAndPosition);
|
||||
}
|
||||
}
|
||||
return glm::normalize(poleAdjust * poleVector);
|
||||
}
|
||||
|
||||
if (params.isLeftFootEnabled) {
|
||||
_animVars.set("leftFootPosition", params.leftFootPosition);
|
||||
_animVars.set("leftFootRotation", params.leftFootOrientation);
|
||||
_animVars.set("leftFootType", (int)IKTarget::Type::RotationAndPosition);
|
||||
} else {
|
||||
_animVars.unset("leftFootPosition");
|
||||
_animVars.unset("leftFootRotation");
|
||||
_animVars.set("leftFootType", (int)IKTarget::Type::RotationAndPosition);
|
||||
}
|
||||
void Rig::updateFromControllerParameters(const ControllerParameters& params, float dt) {
|
||||
if (!_animSkeleton || !_animNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (params.isRightFootEnabled) {
|
||||
_animVars.set("rightFootPosition", params.rightFootPosition);
|
||||
_animVars.set("rightFootRotation", params.rightFootOrientation);
|
||||
_animVars.set("rightFootType", (int)IKTarget::Type::RotationAndPosition);
|
||||
} else {
|
||||
_animVars.unset("rightFootPosition");
|
||||
_animVars.unset("rightFootRotation");
|
||||
_animVars.set("rightFootType", (int)IKTarget::Type::RotationAndPosition);
|
||||
}
|
||||
_animVars.set("isTalking", params.isTalking);
|
||||
_animVars.set("notIsTalking", !params.isTalking);
|
||||
|
||||
bool headEnabled = params.controllerActiveFlags[ControllerType_Head];
|
||||
bool leftHandEnabled = params.controllerActiveFlags[ControllerType_LeftHand];
|
||||
bool rightHandEnabled = params.controllerActiveFlags[ControllerType_RightHand];
|
||||
bool hipsEnabled = params.controllerActiveFlags[ControllerType_Hips];
|
||||
bool leftFootEnabled = params.controllerActiveFlags[ControllerType_LeftFoot];
|
||||
bool rightFootEnabled = params.controllerActiveFlags[ControllerType_RightFoot];
|
||||
bool leftArmEnabled = params.controllerActiveFlags[ControllerType_LeftArm];
|
||||
bool rightArmEnabled = params.controllerActiveFlags[ControllerType_RightArm];
|
||||
bool spine2Enabled = params.controllerActiveFlags[ControllerType_Spine2];
|
||||
|
||||
updateHead(headEnabled, hipsEnabled, params.controllerPoses[ControllerType_Head]);
|
||||
|
||||
updateHands(leftHandEnabled, rightHandEnabled, hipsEnabled, leftArmEnabled, rightArmEnabled, dt,
|
||||
params.controllerPoses[ControllerType_LeftHand], params.controllerPoses[ControllerType_RightHand],
|
||||
params.bodyCapsuleRadius, params.bodyCapsuleHalfHeight, params.bodyCapsuleLocalOffset);
|
||||
|
||||
updateFeet(leftFootEnabled, rightFootEnabled,
|
||||
params.controllerPoses[ControllerType_LeftFoot], params.controllerPoses[ControllerType_RightFoot]);
|
||||
|
||||
if (hipsEnabled) {
|
||||
_animVars.set("solutionSource", (int)AnimInverseKinematics::SolutionSource::RelaxToLimitCenterPoses);
|
||||
_animVars.set("hipsType", (int)IKTarget::Type::RotationAndPosition);
|
||||
_animVars.set("hipsPosition", params.controllerPoses[ControllerType_Hips].trans());
|
||||
_animVars.set("hipsRotation", params.controllerPoses[ControllerType_Hips].rot());
|
||||
} else {
|
||||
_animVars.set("solutionSource", (int)AnimInverseKinematics::SolutionSource::RelaxToUnderPoses);
|
||||
_animVars.set("hipsType", (int)IKTarget::Type::Unknown);
|
||||
}
|
||||
|
||||
if (hipsEnabled && spine2Enabled) {
|
||||
_animVars.set("spine2Type", (int)IKTarget::Type::Spline);
|
||||
_animVars.set("spine2Position", params.controllerPoses[ControllerType_Spine2].trans());
|
||||
_animVars.set("spine2Rotation", params.controllerPoses[ControllerType_Spine2].rot());
|
||||
} else {
|
||||
_animVars.set("spine2Type", (int)IKTarget::Type::Unknown);
|
||||
}
|
||||
|
||||
if (leftArmEnabled) {
|
||||
_animVars.set("leftArmType", (int)IKTarget::Type::RotationAndPosition);
|
||||
_animVars.set("leftArmPosition", params.controllerPoses[ControllerType_LeftArm].trans());
|
||||
_animVars.set("leftArmRotation", params.controllerPoses[ControllerType_LeftArm].rot());
|
||||
} else {
|
||||
_animVars.set("leftArmType", (int)IKTarget::Type::Unknown);
|
||||
}
|
||||
|
||||
if (rightArmEnabled) {
|
||||
_animVars.set("rightArmType", (int)IKTarget::Type::RotationAndPosition);
|
||||
_animVars.set("rightArmPosition", params.controllerPoses[ControllerType_RightArm].trans());
|
||||
_animVars.set("rightArmRotation", params.controllerPoses[ControllerType_RightArm].rot());
|
||||
} else {
|
||||
_animVars.set("rightArmType", (int)IKTarget::Type::Unknown);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1486,22 +1676,18 @@ void Rig::computeAvatarBoundingCapsule(
|
|||
AnimInverseKinematics ikNode("boundingShape");
|
||||
ikNode.setSkeleton(_animSkeleton);
|
||||
|
||||
ikNode.setTargetVars("LeftHand",
|
||||
"leftHandPosition",
|
||||
"leftHandRotation",
|
||||
"leftHandType", "leftHandWeight", 1.0f, {});
|
||||
ikNode.setTargetVars("RightHand",
|
||||
"rightHandPosition",
|
||||
"rightHandRotation",
|
||||
"rightHandType", "rightHandWeight", 1.0f, {});
|
||||
ikNode.setTargetVars("LeftFoot",
|
||||
"leftFootPosition",
|
||||
"leftFootRotation",
|
||||
"leftFootType", "leftFootWeight", 1.0f, {});
|
||||
ikNode.setTargetVars("RightFoot",
|
||||
"rightFootPosition",
|
||||
"rightFootRotation",
|
||||
"rightFootType", "rightFootWeight", 1.0f, {});
|
||||
ikNode.setTargetVars("LeftHand", "leftHandPosition", "leftHandRotation",
|
||||
"leftHandType", "leftHandWeight", 1.0f, {},
|
||||
QString(), QString(), QString());
|
||||
ikNode.setTargetVars("RightHand", "rightHandPosition", "rightHandRotation",
|
||||
"rightHandType", "rightHandWeight", 1.0f, {},
|
||||
QString(), QString(), QString());
|
||||
ikNode.setTargetVars("LeftFoot", "leftFootPosition", "leftFootRotation",
|
||||
"leftFootType", "leftFootWeight", 1.0f, {},
|
||||
QString(), QString(), QString());
|
||||
ikNode.setTargetVars("RightFoot", "rightFootPosition", "rightFootRotation",
|
||||
"rightFootType", "rightFootWeight", 1.0f, {},
|
||||
QString(), QString(), QString());
|
||||
|
||||
AnimPose geometryToRig = _modelOffset * _geometryOffset;
|
||||
|
||||
|
|
|
@ -41,21 +41,26 @@ public:
|
|||
bool useNames;
|
||||
};
|
||||
|
||||
struct HeadParameters {
|
||||
glm::mat4 hipsMatrix = glm::mat4(); // rig space
|
||||
glm::mat4 spine2Matrix = glm::mat4(); // rig space
|
||||
glm::quat rigHeadOrientation = glm::quat(); // rig space (-z forward)
|
||||
glm::vec3 rigHeadPosition = glm::vec3(); // rig space
|
||||
glm::vec3 rightArmPosition = glm::vec3(); // rig space
|
||||
glm::quat rightArmRotation = glm::quat(); // rig space
|
||||
glm::vec3 leftArmPosition = glm::vec3(); // rig space
|
||||
glm::quat leftArmRotation = glm::quat(); // rig space
|
||||
bool hipsEnabled = false;
|
||||
bool headEnabled = false;
|
||||
bool spine2Enabled = false;
|
||||
bool leftArmEnabled = false;
|
||||
bool rightArmEnabled = false;
|
||||
bool isTalking = false;
|
||||
enum ControllerType {
|
||||
ControllerType_Head = 0,
|
||||
ControllerType_LeftHand,
|
||||
ControllerType_RightHand,
|
||||
ControllerType_Hips,
|
||||
ControllerType_LeftFoot,
|
||||
ControllerType_RightFoot,
|
||||
ControllerType_LeftArm,
|
||||
ControllerType_RightArm,
|
||||
ControllerType_Spine2,
|
||||
NumControllerTypes
|
||||
};
|
||||
|
||||
struct ControllerParameters {
|
||||
AnimPose controllerPoses[NumControllerTypes]; // rig space
|
||||
bool controllerActiveFlags[NumControllerTypes];
|
||||
bool isTalking;
|
||||
float bodyCapsuleRadius;
|
||||
float bodyCapsuleHalfHeight;
|
||||
glm::vec3 bodyCapsuleLocalOffset;
|
||||
};
|
||||
|
||||
struct EyeParameters {
|
||||
|
@ -67,25 +72,6 @@ public:
|
|||
int rightEyeJointIndex = -1;
|
||||
};
|
||||
|
||||
struct HandAndFeetParameters {
|
||||
bool isLeftEnabled;
|
||||
bool isRightEnabled;
|
||||
float bodyCapsuleRadius;
|
||||
float bodyCapsuleHalfHeight;
|
||||
glm::vec3 bodyCapsuleLocalOffset;
|
||||
glm::vec3 leftPosition = glm::vec3(); // rig space
|
||||
glm::quat leftOrientation = glm::quat(); // rig space (z forward)
|
||||
glm::vec3 rightPosition = glm::vec3(); // rig space
|
||||
glm::quat rightOrientation = glm::quat(); // rig space (z forward)
|
||||
|
||||
bool isLeftFootEnabled;
|
||||
bool isRightFootEnabled;
|
||||
glm::vec3 leftFootPosition = glm::vec3(); // rig space
|
||||
glm::quat leftFootOrientation = glm::quat(); // rig space (z forward)
|
||||
glm::vec3 rightFootPosition = glm::vec3(); // rig space
|
||||
glm::quat rightFootOrientation = glm::quat(); // rig space (z forward)
|
||||
};
|
||||
|
||||
enum class CharacterControllerState {
|
||||
Ground = 0,
|
||||
Takeoff,
|
||||
|
@ -153,9 +139,6 @@ public:
|
|||
bool getAbsoluteJointTranslationInRigFrame(int jointIndex, glm::vec3& translation) const;
|
||||
bool getAbsoluteJointPoseInRigFrame(int jointIndex, AnimPose& returnPose) const;
|
||||
|
||||
// legacy
|
||||
bool getJointCombinedRotation(int jointIndex, glm::quat& result, const glm::quat& rotation) const;
|
||||
|
||||
// rig space
|
||||
glm::mat4 getJointTransform(int jointIndex) const;
|
||||
|
||||
|
@ -195,9 +178,8 @@ public:
|
|||
// legacy
|
||||
void clearJointStatePriorities();
|
||||
|
||||
void updateFromHeadParameters(const HeadParameters& params, float dt);
|
||||
void updateFromControllerParameters(const ControllerParameters& params, float dt);
|
||||
void updateFromEyeParameters(const EyeParameters& params);
|
||||
void updateFromHandAndFeetParameters(const HandAndFeetParameters& params, float dt);
|
||||
|
||||
void initAnimGraph(const QUrl& url);
|
||||
|
||||
|
@ -247,11 +229,18 @@ protected:
|
|||
void applyOverridePoses();
|
||||
void buildAbsoluteRigPoses(const AnimPoseVec& relativePoses, AnimPoseVec& absolutePosesOut);
|
||||
|
||||
void updateHeadAnimVars(const HeadParameters& params);
|
||||
void updateHead(bool headEnabled, bool hipsEnabled, const AnimPose& headMatrix);
|
||||
void updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnabled, bool leftArmEnabled, bool rightArmEnabled, float dt,
|
||||
const AnimPose& leftHandPose, const AnimPose& rightHandPose,
|
||||
float bodyCapsuleRadius, float bodyCapsuleHalfHeight, const glm::vec3& bodyCapsuleLocalOffset);
|
||||
void updateFeet(bool leftFootEnabled, bool rightFootEnabled, const AnimPose& leftFootPose, const AnimPose& rightFootPose);
|
||||
|
||||
void updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm::quat& modelRotation, const glm::vec3& lookAt, const glm::vec3& saccade);
|
||||
void calcAnimAlpha(float speed, const std::vector<float>& referenceSpeeds, float* alphaOut) const;
|
||||
|
||||
glm::vec3 calculateElbowPoleVector(int handIndex, int elbowIndex, int armIndex, int hipsIndex, bool isLeft) const;
|
||||
glm::vec3 calculateKneePoleVector(int footJointIndex, int kneeJoint, int upLegIndex, int hipsIndex, const AnimPose& targetFootPose) const;
|
||||
|
||||
AnimPose _modelOffset; // model to rig space
|
||||
AnimPose _geometryOffset; // geometry to model space (includes unit offset & fst offsets)
|
||||
AnimPose _invGeometryOffset;
|
||||
|
@ -347,13 +336,12 @@ protected:
|
|||
bool _enableDebugDrawIKConstraints { false };
|
||||
bool _enableDebugDrawIKChains { false };
|
||||
|
||||
private:
|
||||
QMap<int, StateHandler> _stateHandlers;
|
||||
int _nextStateHandlerId { 0 };
|
||||
QMutex _stateMutex;
|
||||
|
||||
bool transitionHandPose(float deltaTime, float totalDuration, AnimPose& controlledHandPose, bool isLeftHand,
|
||||
bool isToControlled, AnimPose& returnHandPose);
|
||||
bool transitionHandPose(float deltaTime, float totalDuration, AnimPose& controlledHandPose, bool isLeftHand,
|
||||
bool isToControlled, AnimPose& returnHandPose);
|
||||
|
||||
bool _isLeftHandControlled { false };
|
||||
bool _isRightHandControlled { false };
|
||||
|
@ -363,6 +351,18 @@ private:
|
|||
float _rightHandRelaxTimeRemaining { 0.0f };
|
||||
AnimPose _lastLeftHandControlledPose;
|
||||
AnimPose _lastRightHandControlledPose;
|
||||
|
||||
glm::vec3 _prevRightFootPoleVector { Vectors::UNIT_Z };
|
||||
bool _prevRightFootPoleVectorValid { false };
|
||||
|
||||
glm::vec3 _prevLeftFootPoleVector { Vectors::UNIT_Z };
|
||||
bool _prevLeftFootPoleVectorValid { false };
|
||||
|
||||
glm::vec3 _prevRightHandPoleVector { -Vectors::UNIT_Z };
|
||||
bool _prevRightHandPoleVectorValid { false };
|
||||
|
||||
glm::vec3 _prevLeftHandPoleVector { -Vectors::UNIT_Z };
|
||||
bool _prevLeftHandPoleVectorValid { false };
|
||||
};
|
||||
|
||||
#endif /* defined(__hifi__Rig__) */
|
||||
|
|
|
@ -1509,6 +1509,7 @@ void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& ide
|
|||
>> identity.attachmentData
|
||||
>> identity.displayName
|
||||
>> identity.sessionDisplayName
|
||||
>> identity.isReplicated
|
||||
>> identity.avatarEntityData;
|
||||
|
||||
// set the store identity sequence number to match the incoming identity
|
||||
|
@ -1531,6 +1532,11 @@ void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& ide
|
|||
}
|
||||
maybeUpdateSessionDisplayNameFromTransport(identity.sessionDisplayName);
|
||||
|
||||
if (identity.isReplicated != _isReplicated) {
|
||||
_isReplicated = identity.isReplicated;
|
||||
identityChanged = true;
|
||||
}
|
||||
|
||||
if (identity.attachmentData != _attachmentData) {
|
||||
setAttachmentData(identity.attachmentData);
|
||||
identityChanged = true;
|
||||
|
@ -1561,7 +1567,7 @@ void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& ide
|
|||
}
|
||||
}
|
||||
|
||||
QByteArray AvatarData::identityByteArray() const {
|
||||
QByteArray AvatarData::identityByteArray(bool setIsReplicated) const {
|
||||
QByteArray identityData;
|
||||
QDataStream identityStream(&identityData, QIODevice::Append);
|
||||
const QUrl& urlToSend = cannonicalSkeletonModelURL(emptyURL); // depends on _skeletonModelURL
|
||||
|
@ -1576,6 +1582,7 @@ QByteArray AvatarData::identityByteArray() const {
|
|||
<< _attachmentData
|
||||
<< _displayName
|
||||
<< getSessionDisplayNameForTransport() // depends on _sessionDisplayName
|
||||
<< (_isReplicated || setIsReplicated)
|
||||
<< _avatarEntityData;
|
||||
});
|
||||
|
||||
|
|
|
@ -531,6 +531,7 @@ public:
|
|||
QVector<AttachmentData> attachmentData;
|
||||
QString displayName;
|
||||
QString sessionDisplayName;
|
||||
bool isReplicated;
|
||||
AvatarEntityMap avatarEntityData;
|
||||
};
|
||||
|
||||
|
@ -539,7 +540,7 @@ public:
|
|||
void processAvatarIdentity(const QByteArray& identityData, bool& identityChanged,
|
||||
bool& displayNameChanged, bool& skeletonModelUrlChanged);
|
||||
|
||||
QByteArray identityByteArray() const;
|
||||
QByteArray identityByteArray(bool setIsReplicated = false) const;
|
||||
|
||||
const QUrl& getSkeletonModelURL() const { return _skeletonModelURL; }
|
||||
const QString& getDisplayName() const { return _displayName; }
|
||||
|
@ -629,6 +630,8 @@ public:
|
|||
|
||||
float getDensity() const { return _density; }
|
||||
|
||||
bool getIsReplicated() const { return _isReplicated; }
|
||||
|
||||
signals:
|
||||
void displayNameChanged();
|
||||
|
||||
|
@ -665,6 +668,10 @@ protected:
|
|||
bool hasParent() const { return !getParentID().isNull(); }
|
||||
bool hasFaceTracker() const { return _headData ? _headData->_isFaceTrackerConnected : false; }
|
||||
|
||||
// isReplicated will be true on downstream Avatar Mixers and their clients, but false on the upstream "master"
|
||||
// Audio Mixer that the replicated avatar is connected to.
|
||||
bool _isReplicated{ false };
|
||||
|
||||
glm::vec3 _handPosition;
|
||||
virtual const QString& getSessionDisplayNameForTransport() const { return _sessionDisplayName; }
|
||||
virtual void maybeUpdateSessionDisplayNameFromTransport(const QString& sessionDisplayName) { } // No-op in AvatarMixer
|
||||
|
|
|
@ -152,6 +152,15 @@ QString ScriptAvatarData::getSessionDisplayName() const {
|
|||
return QString();
|
||||
}
|
||||
}
|
||||
|
||||
bool ScriptAvatarData::getIsReplicated() const {
|
||||
if (AvatarSharedPointer sharedAvatarData = _avatarData.lock()) {
|
||||
return sharedAvatarData->getIsReplicated();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// IDENTIFIER PROPERTIES
|
||||
// END
|
||||
|
|
|
@ -45,6 +45,7 @@ class ScriptAvatarData : public QObject {
|
|||
Q_PROPERTY(QUuid sessionUUID READ getSessionUUID)
|
||||
Q_PROPERTY(QString displayName READ getDisplayName NOTIFY displayNameChanged)
|
||||
Q_PROPERTY(QString sessionDisplayName READ getSessionDisplayName)
|
||||
Q_PROPERTY(bool isReplicated READ getIsReplicated)
|
||||
|
||||
//
|
||||
// ATTACHMENT AND JOINT PROPERTIES
|
||||
|
@ -95,6 +96,7 @@ public:
|
|||
QUuid getSessionUUID() const;
|
||||
QString getDisplayName() const;
|
||||
QString getSessionDisplayName() const;
|
||||
bool getIsReplicated() const;
|
||||
|
||||
//
|
||||
// ATTACHMENT AND JOINT PROPERTIES
|
||||
|
|
|
@ -9,9 +9,14 @@
|
|||
#include "Input.h"
|
||||
|
||||
namespace controller {
|
||||
const Input Input::INVALID_INPUT = Input(0x7fffffff);
|
||||
const Input Input::INVALID_INPUT = invalidInput();
|
||||
const uint16_t Input::INVALID_DEVICE = Input::INVALID_INPUT.device;
|
||||
const uint16_t Input::INVALID_CHANNEL = Input::INVALID_INPUT.channel;
|
||||
const uint16_t Input::INVALID_TYPE = Input::INVALID_INPUT.type;
|
||||
|
||||
const Input& Input::invalidInput() {
|
||||
static const Input INVALID_INPUT = Input(0x7fffffff);
|
||||
return INVALID_INPUT;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -83,6 +83,8 @@ struct Input {
|
|||
|
||||
using NamedPair = QPair<Input, QString>;
|
||||
using NamedVector = QVector<NamedPair>;
|
||||
|
||||
static const Input& invalidInput();
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -47,8 +47,8 @@
|
|||
|
||||
namespace controller {
|
||||
const uint16_t UserInputMapper::STANDARD_DEVICE = 0;
|
||||
const uint16_t UserInputMapper::ACTIONS_DEVICE = Input::INVALID_DEVICE - 0x00FF;
|
||||
const uint16_t UserInputMapper::STATE_DEVICE = Input::INVALID_DEVICE - 0x0100;
|
||||
const uint16_t UserInputMapper::ACTIONS_DEVICE = Input::invalidInput().device - 0x00FF;
|
||||
const uint16_t UserInputMapper::STATE_DEVICE = Input::invalidInput().device - 0x0100;
|
||||
}
|
||||
|
||||
// Default contruct allocate the poutput size with the current hardcoded action channels
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
set(TARGET_NAME entities-renderer)
|
||||
AUTOSCRIBE_SHADER_LIB(gpu model procedural render render-utils)
|
||||
setup_hifi_library(Widgets Network Script)
|
||||
link_hifi_libraries(shared gpu procedural model model-networking script-engine render render-utils image)
|
||||
link_hifi_libraries(shared gpu procedural model model-networking script-engine render render-utils image ui)
|
||||
include_hifi_library_headers(networking)
|
||||
include_hifi_library_headers(gl)
|
||||
include_hifi_library_headers(ktx)
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
#include <PathUtils.h>
|
||||
#include <TextureCache.h>
|
||||
#include <gpu/Context.h>
|
||||
#include <TabletScriptingInterface.h>
|
||||
#include <ui/TabletScriptingInterface.h>
|
||||
|
||||
#include "EntityTreeRenderer.h"
|
||||
#include "EntitiesRendererLogging.h"
|
||||
|
@ -74,19 +74,6 @@ bool RenderableWebEntityItem::buildWebSurface(QSharedPointer<EntityTreeRenderer>
|
|||
qWarning() << "Too many concurrent web views to create new view";
|
||||
return false;
|
||||
}
|
||||
QString javaScriptToInject;
|
||||
QFile webChannelFile(":qtwebchannel/qwebchannel.js");
|
||||
QFile createGlobalEventBridgeFile(PathUtils::resourcesPath() + "/html/createGlobalEventBridge.js");
|
||||
if (webChannelFile.open(QFile::ReadOnly | QFile::Text) &&
|
||||
createGlobalEventBridgeFile.open(QFile::ReadOnly | QFile::Text)) {
|
||||
QString webChannelStr = QTextStream(&webChannelFile).readAll();
|
||||
QString createGlobalEventBridgeStr = QTextStream(&createGlobalEventBridgeFile).readAll();
|
||||
|
||||
// concatenate these js files
|
||||
_javaScriptToInject = webChannelStr + createGlobalEventBridgeStr;
|
||||
} else {
|
||||
qCWarning(entitiesrenderer) << "unable to find qwebchannel.js or createGlobalEventBridge.js";
|
||||
}
|
||||
|
||||
// Save the original GL context, because creating a QML surface will create a new context
|
||||
QOpenGLContext* currentContext = QOpenGLContext::currentContext();
|
||||
|
@ -266,10 +253,7 @@ void RenderableWebEntityItem::loadSourceURL() {
|
|||
_webSurface->setMaxFps(DEFAULT_MAX_FPS);
|
||||
}
|
||||
|
||||
_webSurface->load("WebEntityView.qml", [&](QQmlContext* context, QObject* obj) {
|
||||
context->setContextProperty("eventBridgeJavaScriptToInject", QVariant(_javaScriptToInject));
|
||||
});
|
||||
|
||||
_webSurface->load("WebEntityView.qml");
|
||||
_webSurface->getRootItem()->setProperty("url", _sourceUrl);
|
||||
_webSurface->getSurfaceContext()->setContextProperty("desktop", QVariant());
|
||||
|
||||
|
@ -280,8 +264,7 @@ void RenderableWebEntityItem::loadSourceURL() {
|
|||
|
||||
if (_webSurface->getRootItem() && _webSurface->getRootItem()->objectName() == "tabletRoot") {
|
||||
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
||||
tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system",
|
||||
_webSurface->getRootItem(), _webSurface.data());
|
||||
tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface.data());
|
||||
}
|
||||
}
|
||||
_webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(getPosition()));
|
||||
|
@ -386,7 +369,7 @@ void RenderableWebEntityItem::destroyWebSurface() {
|
|||
|
||||
if (rootItem && rootItem->objectName() == "tabletRoot") {
|
||||
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
||||
tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", nullptr, nullptr);
|
||||
tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", nullptr);
|
||||
}
|
||||
|
||||
// Fix for crash in QtWebEngineCore when rapidly switching domains
|
||||
|
|
|
@ -13,10 +13,9 @@
|
|||
#include <QMouseEvent>
|
||||
#include <QTouchEvent>
|
||||
#include <PointerEvent.h>
|
||||
#include <gl/OffscreenQmlSurface.h>
|
||||
#include <ui/OffscreenQmlSurface.h>
|
||||
|
||||
#include <WebEntityItem.h>
|
||||
#include <gl/OffscreenQmlSurface.h>
|
||||
|
||||
#include "RenderableEntityItem.h"
|
||||
|
||||
|
@ -74,8 +73,6 @@ private:
|
|||
QMetaObject::Connection _mouseMoveConnection;
|
||||
QMetaObject::Connection _hoverLeaveConnection;
|
||||
|
||||
QString _javaScriptToInject;
|
||||
|
||||
enum contentType {
|
||||
htmlContent,
|
||||
qmlContent
|
||||
|
|
|
@ -178,6 +178,11 @@ void GLBackend::init() {
|
|||
int swapInterval = wglGetSwapIntervalEXT();
|
||||
qCDebug(gpugllogging, "V-Sync is %s\n", (swapInterval > 0 ? "ON" : "OFF"));
|
||||
}*/
|
||||
#endif
|
||||
#if THREADED_TEXTURE_BUFFERING
|
||||
// This has to happen on the main thread in order to give the thread
|
||||
// pool a reasonable parent object
|
||||
GLVariableAllocationSupport::TransferJob::startBufferingThread();
|
||||
#endif
|
||||
});
|
||||
}
|
||||
|
|
|
@ -461,11 +461,6 @@ void GLVariableAllocationSupport::updateMemoryPressure() {
|
|||
|
||||
if (newState != _memoryPressureState) {
|
||||
_memoryPressureState = newState;
|
||||
#if THREADED_TEXTURE_BUFFERING
|
||||
if (MemoryPressureState::Transfer == _memoryPressureState) {
|
||||
TransferJob::startBufferingThread();
|
||||
}
|
||||
#endif
|
||||
// Clear the existing queue
|
||||
_transferQueue = WorkQueue();
|
||||
_promoteQueue = WorkQueue();
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
set(TARGET_NAME networking)
|
||||
setup_hifi_library(Network WebEngine)
|
||||
setup_hifi_library(Network)
|
||||
link_hifi_libraries(shared)
|
||||
|
||||
target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}/includes")
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
|
||||
MessageID AssetClient::_currentID = 0;
|
||||
|
||||
AssetClient::AssetClient() {
|
||||
AssetClient::AssetClient(const QString& cacheDir) : _cacheDir(cacheDir) {
|
||||
setCustomDeleter([](Dependency* dependency){
|
||||
static_cast<AssetClient*>(dependency)->deleteLater();
|
||||
});
|
||||
|
@ -55,14 +55,15 @@ void AssetClient::init() {
|
|||
// Setup disk cache if not already
|
||||
auto& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
if (!networkAccessManager.cache()) {
|
||||
QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
|
||||
cachePath = !cachePath.isEmpty() ? cachePath : "interfaceCache";
|
||||
|
||||
if (_cacheDir.isEmpty()) {
|
||||
QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
|
||||
_cacheDir = !cachePath.isEmpty() ? cachePath : "interfaceCache";
|
||||
}
|
||||
QNetworkDiskCache* cache = new QNetworkDiskCache();
|
||||
cache->setMaximumCacheSize(MAXIMUM_CACHE_SIZE);
|
||||
cache->setCacheDirectory(cachePath);
|
||||
cache->setCacheDirectory(_cacheDir);
|
||||
networkAccessManager.setCache(cache);
|
||||
qInfo() << "ResourceManager disk cache setup at" << cachePath
|
||||
qInfo() << "ResourceManager disk cache setup at" << _cacheDir
|
||||
<< "(size:" << MAXIMUM_CACHE_SIZE / BYTES_PER_GIGABYTES << "GB)";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ using ProgressCallback = std::function<void(qint64 totalReceived, qint64 total)>
|
|||
class AssetClient : public QObject, public Dependency {
|
||||
Q_OBJECT
|
||||
public:
|
||||
AssetClient();
|
||||
AssetClient(const QString& cacheDir="");
|
||||
|
||||
Q_INVOKABLE GetMappingRequest* createGetMappingRequest(const AssetPath& path);
|
||||
Q_INVOKABLE GetAllMappingsRequest* createGetAllMappingsRequest();
|
||||
|
@ -109,6 +109,8 @@ private:
|
|||
std::unordered_map<SharedNodePointer, std::unordered_map<MessageID, GetInfoCallback>> _pendingInfoRequests;
|
||||
std::unordered_map<SharedNodePointer, std::unordered_map<MessageID, UploadResultCallback>> _pendingUploads;
|
||||
|
||||
QString _cacheDir;
|
||||
|
||||
friend class AssetRequest;
|
||||
friend class AssetUpload;
|
||||
friend class MappingRequest;
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
QThread ResourceManager::_thread;
|
||||
ResourceManager::PrefixMap ResourceManager::_prefixMap;
|
||||
QMutex ResourceManager::_prefixMapLock;
|
||||
|
||||
QString ResourceManager::_cacheDir;
|
||||
|
||||
void ResourceManager::setUrlPrefixOverride(const QString& prefix, const QString& replacement) {
|
||||
QMutexLocker locker(&_prefixMapLock);
|
||||
|
@ -78,7 +78,7 @@ QUrl ResourceManager::normalizeURL(const QUrl& originalUrl) {
|
|||
void ResourceManager::init() {
|
||||
_thread.setObjectName("Resource Manager Thread");
|
||||
|
||||
auto assetClient = DependencyManager::set<AssetClient>();
|
||||
auto assetClient = DependencyManager::set<AssetClient>(_cacheDir);
|
||||
assetClient->moveToThread(&_thread);
|
||||
QObject::connect(&_thread, &QThread::started, assetClient.data(), &AssetClient::init);
|
||||
|
||||
|
@ -164,3 +164,7 @@ bool ResourceManager::resourceExists(const QUrl& url) {
|
|||
return false;
|
||||
}
|
||||
|
||||
void ResourceManager::setCacheDir(const QString& cacheDir) {
|
||||
// TODO: check for existence?
|
||||
_cacheDir = cacheDir;
|
||||
}
|
||||
|
|
|
@ -40,6 +40,9 @@ public:
|
|||
// to return to the calling thread so that events can still be processed.
|
||||
static bool resourceExists(const QUrl& url);
|
||||
|
||||
// adjust where we persist the cache
|
||||
static void setCacheDir(const QString& cacheDir);
|
||||
|
||||
private:
|
||||
static QThread _thread;
|
||||
|
||||
|
@ -47,6 +50,8 @@ private:
|
|||
|
||||
static PrefixMap _prefixMap;
|
||||
static QMutex _prefixMapLock;
|
||||
|
||||
static QString _cacheDir;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -69,7 +69,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
|
|||
case PacketType::AvatarData:
|
||||
case PacketType::BulkAvatarData:
|
||||
case PacketType::KillAvatar:
|
||||
return static_cast<PacketVersion>(AvatarMixerPacketVersion::AvatarIdentitySequenceFront);
|
||||
return static_cast<PacketVersion>(AvatarMixerPacketVersion::IsReplicatedInAvatarIdentity);
|
||||
case PacketType::MessagesData:
|
||||
return static_cast<PacketVersion>(MessageDataVersion::TextOrBinaryData);
|
||||
case PacketType::ICEServerHeartbeat:
|
||||
|
|
|
@ -247,7 +247,8 @@ enum class AvatarMixerPacketVersion : PacketVersion {
|
|||
IdentityPacketsIncludeUpdateTime,
|
||||
AvatarIdentitySequenceId,
|
||||
MannequinDefaultAvatar,
|
||||
AvatarIdentitySequenceFront
|
||||
AvatarIdentitySequenceFront,
|
||||
IsReplicatedInAvatarIdentity
|
||||
};
|
||||
|
||||
enum class DomainConnectRequestVersion : PacketVersion {
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
|
||||
enum class PluginType {
|
||||
DISPLAY_PLUGIN,
|
||||
|
@ -26,8 +27,12 @@ class PluginManager;
|
|||
|
||||
using DisplayPluginPointer = std::shared_ptr<DisplayPlugin>;
|
||||
using DisplayPluginList = std::vector<DisplayPluginPointer>;
|
||||
using DisplayPluginProvider = std::function<DisplayPluginList()>;
|
||||
using InputPluginPointer = std::shared_ptr<InputPlugin>;
|
||||
using InputPluginList = std::vector<InputPluginPointer>;
|
||||
using InputPluginProvider = std::function<InputPluginList()>;
|
||||
using CodecPluginPointer = std::shared_ptr<CodecPlugin>;
|
||||
using CodecPluginList = std::vector<CodecPluginPointer>;
|
||||
using CodecPluginProvider = std::function<CodecPluginList()>;
|
||||
using SteamClientPluginPointer = std::shared_ptr<SteamClientPlugin>;
|
||||
using InputPluginSettingsPersister = std::function<void(const InputPluginList&)>;
|
||||
|
|
|
@ -24,6 +24,22 @@
|
|||
#include "PluginLogging.h"
|
||||
|
||||
|
||||
void PluginManager::setDisplayPluginProvider(const DisplayPluginProvider& provider) {
|
||||
_displayPluginProvider = provider;
|
||||
}
|
||||
|
||||
void PluginManager::setInputPluginProvider(const InputPluginProvider& provider) {
|
||||
_inputPluginProvider = provider;
|
||||
}
|
||||
|
||||
void PluginManager::setCodecPluginProvider(const CodecPluginProvider& provider) {
|
||||
_codecPluginProvider = provider;
|
||||
}
|
||||
|
||||
void PluginManager::setInputPluginSettingsPersister(const InputPluginSettingsPersister& persister) {
|
||||
_inputSettingsPersister = persister;
|
||||
}
|
||||
|
||||
PluginManager* PluginManager::getInstance() {
|
||||
static PluginManager _manager;
|
||||
return &_manager;
|
||||
|
@ -117,12 +133,12 @@ const LoaderList& getLoadedPlugins() {
|
|||
PluginManager::PluginManager() {
|
||||
}
|
||||
|
||||
extern CodecPluginList getCodecPlugins();
|
||||
|
||||
const CodecPluginList& PluginManager::getCodecPlugins() {
|
||||
static CodecPluginList codecPlugins;
|
||||
static std::once_flag once;
|
||||
std::call_once(once, [&] {
|
||||
codecPlugins = _codecPluginProvider();
|
||||
|
||||
// Now grab the dynamic plugins
|
||||
for (auto loader : getLoadedPlugins()) {
|
||||
CodecProvider* codecProvider = qobject_cast<CodecProvider*>(loader->instance());
|
||||
|
@ -163,11 +179,6 @@ const SteamClientPluginPointer PluginManager::getSteamClientPlugin() {
|
|||
|
||||
#ifndef Q_OS_ANDROID
|
||||
|
||||
// TODO migrate to a DLL model where plugins are discovered and loaded at runtime by the PluginManager class
|
||||
extern DisplayPluginList getDisplayPlugins();
|
||||
extern InputPluginList getInputPlugins();
|
||||
|
||||
extern void saveInputPluginSettings(const InputPluginList& plugins);
|
||||
static DisplayPluginList displayPlugins;
|
||||
|
||||
const DisplayPluginList& PluginManager::getDisplayPlugins() {
|
||||
|
@ -183,7 +194,7 @@ const DisplayPluginList& PluginManager::getDisplayPlugins() {
|
|||
|
||||
std::call_once(once, [&] {
|
||||
// Grab the built in plugins
|
||||
displayPlugins = ::getDisplayPlugins();
|
||||
displayPlugins = _displayPluginProvider();
|
||||
|
||||
|
||||
// Now grab the dynamic plugins
|
||||
|
@ -229,7 +240,7 @@ const InputPluginList& PluginManager::getInputPlugins() {
|
|||
};
|
||||
|
||||
std::call_once(once, [&] {
|
||||
inputPlugins = ::getInputPlugins();
|
||||
inputPlugins = _inputPluginProvider();
|
||||
|
||||
// Now grab the dynamic plugins
|
||||
for (auto loader : getLoadedPlugins()) {
|
||||
|
@ -288,7 +299,7 @@ void PluginManager::disableInputs(const QStringList& inputs) {
|
|||
}
|
||||
|
||||
void PluginManager::saveSettings() {
|
||||
saveInputPluginSettings(getInputPlugins());
|
||||
_inputSettingsPersister(getInputPlugins());
|
||||
}
|
||||
|
||||
void PluginManager::shutdown() {
|
||||
|
|
|
@ -31,6 +31,17 @@ public:
|
|||
void setContainer(PluginContainer* container) { _container = container; }
|
||||
|
||||
void shutdown();
|
||||
|
||||
// Application that have statically linked plugins can expose them to the plugin manager with these function
|
||||
void setDisplayPluginProvider(const DisplayPluginProvider& provider);
|
||||
void setInputPluginProvider(const InputPluginProvider& provider);
|
||||
void setCodecPluginProvider(const CodecPluginProvider& provider);
|
||||
void setInputPluginSettingsPersister(const InputPluginSettingsPersister& persister);
|
||||
|
||||
private:
|
||||
DisplayPluginProvider _displayPluginProvider { []()->DisplayPluginList { return {}; } };
|
||||
InputPluginProvider _inputPluginProvider { []()->InputPluginList { return {}; } };
|
||||
CodecPluginProvider _codecPluginProvider { []()->CodecPluginList { return {}; } };
|
||||
InputPluginSettingsPersister _inputSettingsPersister { [](const InputPluginList& list) {} };
|
||||
PluginContainer* _container { nullptr };
|
||||
};
|
||||
|
|
|
@ -867,10 +867,6 @@ bool Model::getRelativeDefaultJointTranslation(int jointIndex, glm::vec3& transl
|
|||
return _rig.getRelativeDefaultJointTranslation(jointIndex, translationOut);
|
||||
}
|
||||
|
||||
bool Model::getJointCombinedRotation(int jointIndex, glm::quat& rotation) const {
|
||||
return _rig.getJointCombinedRotation(jointIndex, rotation, _rotation);
|
||||
}
|
||||
|
||||
QStringList Model::getJointNames() const {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QStringList result;
|
||||
|
|
|
@ -177,7 +177,6 @@ public:
|
|||
int getJointStateCount() const { return (int)_rig.getJointStateCount(); }
|
||||
bool getJointPositionInWorldFrame(int jointIndex, glm::vec3& position) const;
|
||||
bool getJointRotationInWorldFrame(int jointIndex, glm::quat& rotation) const;
|
||||
bool getJointCombinedRotation(int jointIndex, glm::quat& rotation) const;
|
||||
|
||||
/// \param jointIndex index of joint in model structure
|
||||
/// \param rotation[out] rotation of joint in model-frame
|
||||
|
|
|
@ -16,6 +16,6 @@ if (NOT ANDROID)
|
|||
|
||||
endif ()
|
||||
|
||||
link_hifi_libraries(shared networking octree gpu ui procedural model model-networking ktx recording avatars fbx entities controllers animation audio physics image)
|
||||
link_hifi_libraries(shared networking octree gpu procedural model model-networking ktx recording avatars fbx entities controllers animation audio physics image)
|
||||
# ui includes gl, but link_hifi_libraries does not use transitive includes, so gl must be explicit
|
||||
include_hifi_library_headers(gl)
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
|
||||
#include <QtScript/QScriptEngine>
|
||||
|
||||
#include <ui/Menu.h>
|
||||
#include "KeyEvent.h"
|
||||
|
||||
|
||||
|
@ -35,7 +34,7 @@ public:
|
|||
QKeySequence shortcutKeySequence; // this is what we actually use, it's set from one of the above
|
||||
|
||||
// location related items: in order of priority
|
||||
int position { ui::Menu::UNSPECIFIED_POSITION };
|
||||
int position { UNSPECIFIED_POSITION };
|
||||
QString beforeItem;
|
||||
QString afterItem;
|
||||
|
||||
|
@ -45,6 +44,9 @@ public:
|
|||
bool isSeparator { false };
|
||||
|
||||
QString grouping; /// Either: "", "Advanced", or "Developer"
|
||||
|
||||
private:
|
||||
static const int UNSPECIFIED_POSITION = -1;
|
||||
};
|
||||
Q_DECLARE_METATYPE(MenuItemProperties)
|
||||
QScriptValue menuItemPropertiesToScriptValue(QScriptEngine* engine, const MenuItemProperties& props);
|
||||
|
|
|
@ -8,9 +8,17 @@
|
|||
|
||||
#include "RecordingScriptingInterface.h"
|
||||
|
||||
#include <QStandardPaths>
|
||||
#include <QtCore/QThread>
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtScript/QScriptValue>
|
||||
#include <QtWidgets/QFileDialog>
|
||||
|
||||
#include <AssetClient.h>
|
||||
#include <AssetUpload.h>
|
||||
#include <BuildInfo.h>
|
||||
#include <NumericalConstants.h>
|
||||
#include <PathUtils.h>
|
||||
#include <Transform.h>
|
||||
#include <recording/Deck.h>
|
||||
#include <recording/Recorder.h>
|
||||
|
@ -18,13 +26,6 @@
|
|||
#include <recording/Frame.h>
|
||||
#include <recording/ClipCache.h>
|
||||
|
||||
|
||||
#include <QtScript/QScriptValue>
|
||||
#include <AssetClient.h>
|
||||
#include <AssetUpload.h>
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtWidgets/QFileDialog>
|
||||
|
||||
#include "ScriptEngineLogging.h"
|
||||
|
||||
using namespace recording;
|
||||
|
@ -188,6 +189,14 @@ void RecordingScriptingInterface::stopRecording() {
|
|||
_lastClip->seek(0);
|
||||
}
|
||||
|
||||
QString RecordingScriptingInterface::getDefaultRecordingSaveDirectory() {
|
||||
QString directory = PathUtils::getAppLocalDataPath() + "Avatar Recordings/";
|
||||
if (!QDir(directory).exists()) {
|
||||
QDir().mkdir(directory);
|
||||
}
|
||||
return directory;
|
||||
}
|
||||
|
||||
void RecordingScriptingInterface::saveRecording(const QString& filename) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "saveRecording", Qt::BlockingQueuedConnection,
|
||||
|
|
|
@ -65,6 +65,7 @@ public slots:
|
|||
|
||||
float recorderElapsed() const;
|
||||
|
||||
QString getDefaultRecordingSaveDirectory();
|
||||
void saveRecording(const QString& filename);
|
||||
bool saveRecordingToAsset(QScriptValue getClipAtpUrl);
|
||||
void loadLastRecording();
|
||||
|
|
|
@ -24,6 +24,8 @@
|
|||
|
||||
#include <QtWidgets/QMainWindow>
|
||||
#include <QtWidgets/QApplication>
|
||||
#include <QtWidgets/QMenuBar>
|
||||
#include <QtWidgets/QMenu>
|
||||
|
||||
#include <QtNetwork/QNetworkRequest>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
|
@ -48,7 +50,6 @@
|
|||
#include <ScriptAvatarData.h>
|
||||
#include <udt/PacketHeaders.h>
|
||||
#include <UUID.h>
|
||||
#include <ui/Menu.h>
|
||||
|
||||
#include <controllers/ScriptingInterface.h>
|
||||
#include <AnimationObject.h>
|
||||
|
@ -70,7 +71,6 @@
|
|||
#include "WebSocketClass.h"
|
||||
#include "RecordingScriptingInterface.h"
|
||||
#include "ScriptEngines.h"
|
||||
#include "TabletScriptingInterface.h"
|
||||
#include "ModelScriptingInterface.h"
|
||||
|
||||
|
||||
|
@ -172,7 +172,7 @@ ScriptEngine::ScriptEngine(Context context, const QString& scriptContents, const
|
|||
emit unhandledException(exception);
|
||||
}
|
||||
}, Qt::DirectConnection);
|
||||
|
||||
|
||||
setProcessEventsInterval(MSECS_PER_SECOND);
|
||||
if (isEntityServerScript()) {
|
||||
qCDebug(scriptengine) << "isEntityServerScript() -- limiting maxRetries to 1";
|
||||
|
@ -281,7 +281,7 @@ void ScriptEngine::runDebuggable() {
|
|||
scriptDebugMenu = nullptr;
|
||||
}
|
||||
}
|
||||
disconnect(timer);
|
||||
disconnect(timer);
|
||||
});
|
||||
|
||||
connect(timer, &QTimer::timeout, [this, timer] {
|
||||
|
@ -340,7 +340,7 @@ void ScriptEngine::runInThread() {
|
|||
QThread* workerThread = new QThread();
|
||||
workerThread->setObjectName(QString("js:") + getFilename().replace("about:",""));
|
||||
moveToThread(workerThread);
|
||||
|
||||
|
||||
// NOTE: If you connect any essential signals for proper shutdown or cleanup of
|
||||
// the script engine, make sure to add code to "reconnect" them to the
|
||||
// disconnectNonEssentialSignals() method
|
||||
|
@ -697,8 +697,6 @@ void ScriptEngine::init() {
|
|||
// constants
|
||||
globalObject().setProperty("TREE_SCALE", newVariant(QVariant(TREE_SCALE)));
|
||||
|
||||
registerGlobalObject("Tablet", DependencyManager::get<TabletScriptingInterface>().data());
|
||||
qScriptRegisterMetaType(this, tabletToScriptValue, tabletFromScriptValue);
|
||||
registerGlobalObject("Assets", &_assetScriptingInterface);
|
||||
registerGlobalObject("Resources", DependencyManager::get<ResourceScriptingInterface>().data());
|
||||
|
||||
|
@ -1047,26 +1045,26 @@ void ScriptEngine::run() {
|
|||
auto beforeSleep = clock::now();
|
||||
|
||||
// Throttle to SCRIPT_FPS
|
||||
// We'd like to try to keep the script at a solid SCRIPT_FPS update rate. And so we will
|
||||
// We'd like to try to keep the script at a solid SCRIPT_FPS update rate. And so we will
|
||||
// calculate a sleepUntil to be the time from our start time until the original target
|
||||
// sleepUntil for this frame. This approach will allow us to "catch up" in the event
|
||||
// that some of our script udpates/frames take a little bit longer than the target average
|
||||
// sleepUntil for this frame. This approach will allow us to "catch up" in the event
|
||||
// that some of our script udpates/frames take a little bit longer than the target average
|
||||
// to execute.
|
||||
// NOTE: if we go to variable SCRIPT_FPS, then we will need to reconsider this approach
|
||||
const std::chrono::microseconds TARGET_SCRIPT_FRAME_DURATION(USECS_PER_SECOND / SCRIPT_FPS + 1);
|
||||
clock::time_point targetSleepUntil(startTime + (thisFrame++ * TARGET_SCRIPT_FRAME_DURATION));
|
||||
|
||||
// However, if our sleepUntil is not at least our average update and timer execution time
|
||||
// into the future it means our script is taking too long in its updates, and we want to
|
||||
// punish the script a little bit. So we will force the sleepUntil to be at least our
|
||||
// However, if our sleepUntil is not at least our average update and timer execution time
|
||||
// into the future it means our script is taking too long in its updates, and we want to
|
||||
// punish the script a little bit. So we will force the sleepUntil to be at least our
|
||||
// averageUpdate + averageTimerPerFrame time into the future.
|
||||
auto averageUpdate = totalUpdates / thisFrame;
|
||||
auto averageTimerPerFrame = _totalTimerExecution / thisFrame;
|
||||
auto averageTimerAndUpdate = averageUpdate + averageTimerPerFrame;
|
||||
auto sleepUntil = std::max(targetSleepUntil, beforeSleep + averageTimerAndUpdate);
|
||||
|
||||
// We don't want to actually sleep for too long, because it causes our scripts to hang
|
||||
// on shutdown and stop... so we want to loop and sleep until we've spent our time in
|
||||
// We don't want to actually sleep for too long, because it causes our scripts to hang
|
||||
// on shutdown and stop... so we want to loop and sleep until we've spent our time in
|
||||
// purgatory, constantly checking to see if our script was asked to end
|
||||
bool processedEvents = false;
|
||||
while (!_isFinished && clock::now() < sleepUntil) {
|
||||
|
@ -1311,6 +1309,7 @@ QObject* ScriptEngine::setupTimerWithInterval(const QScriptValue& function, int
|
|||
// make sure the timer stops when the script does
|
||||
connect(this, &ScriptEngine::scriptEnding, newTimer, &QTimer::stop);
|
||||
|
||||
|
||||
CallbackData timerData = { function, currentEntityIdentifier, currentSandboxURL };
|
||||
_timerFunctionMap.insert(newTimer, timerData);
|
||||
|
||||
|
@ -1394,12 +1393,21 @@ void ScriptEngine::print(const QString& message) {
|
|||
emit printedMessage(message, getFilename());
|
||||
}
|
||||
|
||||
|
||||
void ScriptEngine::beginProfileRange(const QString& label) const {
|
||||
PROFILE_SYNC_BEGIN(script, label.toStdString().c_str(), label.toStdString().c_str());
|
||||
}
|
||||
|
||||
void ScriptEngine::endProfileRange(const QString& label) const {
|
||||
PROFILE_SYNC_END(script, label.toStdString().c_str(), label.toStdString().c_str());
|
||||
}
|
||||
|
||||
// Script.require.resolve -- like resolvePath, but performs more validation and throws exceptions on invalid module identifiers (for consistency with Node.js)
|
||||
QString ScriptEngine::_requireResolve(const QString& moduleId, const QString& relativeTo) {
|
||||
if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {
|
||||
return QString();
|
||||
}
|
||||
QUrl defaultScriptsLoc = defaultScriptsLocation();
|
||||
QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation();
|
||||
QUrl url(moduleId);
|
||||
|
||||
auto displayId = moduleId;
|
||||
|
@ -1465,7 +1473,7 @@ QString ScriptEngine::_requireResolve(const QString& moduleId, const QString& re
|
|||
canonical.setPath(file.canonicalFilePath());
|
||||
}
|
||||
|
||||
bool disallowOutsideFiles = !defaultScriptsLocation().isParentOf(canonical) && !currentSandboxURL.isLocalFile();
|
||||
bool disallowOutsideFiles = !PathUtils::defaultScriptsLocation().isParentOf(canonical) && !currentSandboxURL.isLocalFile();
|
||||
if (disallowOutsideFiles && !PathUtils::isDescendantOf(canonical, currentSandboxURL)) {
|
||||
return throwResolveError(makeError(message.arg(
|
||||
QString("path '%1' outside of origin script '%2' '%3'")
|
||||
|
@ -1750,7 +1758,7 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac
|
|||
return;
|
||||
}
|
||||
if (DependencyManager::get<ScriptEngines>()->isStopped()) {
|
||||
scriptWarningMessage("Script.include() while shutting down is ignored... includeFiles:"
|
||||
scriptWarningMessage("Script.include() while shutting down is ignored... includeFiles:"
|
||||
+ includeFiles.join(",") + "parent script:" + getFilename());
|
||||
return; // bail early
|
||||
}
|
||||
|
@ -1762,7 +1770,7 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac
|
|||
bool isStandardLibrary = false;
|
||||
if (file.startsWith("/~/")) {
|
||||
thisURL = expandScriptUrl(QUrl::fromLocalFile(expandScriptPath(file)));
|
||||
QUrl defaultScriptsLoc = defaultScriptsLocation();
|
||||
QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation();
|
||||
if (!defaultScriptsLoc.isParentOf(thisURL)) {
|
||||
scriptWarningMessage("Script.include() -- skipping" + file + "-- outside of standard libraries");
|
||||
continue;
|
||||
|
@ -1774,7 +1782,7 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac
|
|||
|
||||
bool disallowOutsideFiles = thisURL.isLocalFile() && !isStandardLibrary && !currentSandboxURL.isLocalFile();
|
||||
if (disallowOutsideFiles && !PathUtils::isDescendantOf(thisURL, currentSandboxURL)) {
|
||||
scriptWarningMessage("Script.include() ignoring file path" + thisURL.toString()
|
||||
scriptWarningMessage("Script.include() ignoring file path" + thisURL.toString()
|
||||
+ "outside of original entity script" + currentSandboxURL.toString());
|
||||
} else {
|
||||
// We could also check here for CORS, but we don't yet.
|
||||
|
@ -1844,7 +1852,7 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac
|
|||
|
||||
void ScriptEngine::include(const QString& includeFile, QScriptValue callback) {
|
||||
if (DependencyManager::get<ScriptEngines>()->isStopped()) {
|
||||
scriptWarningMessage("Script.include() while shutting down is ignored... includeFile:"
|
||||
scriptWarningMessage("Script.include() while shutting down is ignored... includeFile:"
|
||||
+ includeFile + "parent script:" + getFilename());
|
||||
return; // bail early
|
||||
}
|
||||
|
@ -1862,12 +1870,12 @@ void ScriptEngine::load(const QString& loadFile) {
|
|||
return;
|
||||
}
|
||||
if (DependencyManager::get<ScriptEngines>()->isStopped()) {
|
||||
scriptWarningMessage("Script.load() while shutting down is ignored... loadFile:"
|
||||
scriptWarningMessage("Script.load() while shutting down is ignored... loadFile:"
|
||||
+ loadFile + "parent script:" + getFilename());
|
||||
return; // bail early
|
||||
}
|
||||
if (!currentEntityIdentifier.isInvalidID()) {
|
||||
scriptWarningMessage("Script.load() from entity script is ignored... loadFile:"
|
||||
scriptWarningMessage("Script.load() from entity script is ignored... loadFile:"
|
||||
+ loadFile + "parent script:" + getFilename() + "entity: " + currentEntityIdentifier.toString());
|
||||
return; // bail early
|
||||
}
|
||||
|
@ -2548,7 +2556,7 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS
|
|||
qCDebug(scriptengine) << "ScriptEngine::callEntityScriptMethod() called on correct thread [" << thread() << "] "
|
||||
"entityID:" << entityID << "methodName:" << methodName << "otherID:" << otherID << "collision: collision";
|
||||
#endif
|
||||
|
||||
|
||||
if (HIFI_AUTOREFRESH_FILE_SCRIPTS) {
|
||||
refreshFileScript(entityID);
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
#include "Vec3.h"
|
||||
#include "ConsoleScriptingInterface.h"
|
||||
#include "SettingHandle.h"
|
||||
#include "Profile.h"
|
||||
|
||||
class QScriptEngineDebugger;
|
||||
|
||||
|
@ -182,6 +183,8 @@ public:
|
|||
Q_INVOKABLE void print(const QString& message);
|
||||
Q_INVOKABLE QUrl resolvePath(const QString& path) const;
|
||||
Q_INVOKABLE QUrl resourcesPath() const;
|
||||
Q_INVOKABLE void beginProfileRange(const QString& label) const;
|
||||
Q_INVOKABLE void endProfileRange(const QString& label) const;
|
||||
|
||||
// Entity Script Related methods
|
||||
Q_INVOKABLE bool isEntityScriptRunning(const EntityItemID& entityID) {
|
||||
|
|
|
@ -16,8 +16,6 @@
|
|||
#include <UserActivityLogger.h>
|
||||
#include <PathUtils.h>
|
||||
|
||||
#include <OffscreenUi.h>
|
||||
|
||||
#include "ScriptEngine.h"
|
||||
#include "ScriptEngineLogging.h"
|
||||
|
||||
|
@ -74,7 +72,7 @@ ScriptEngines::ScriptEngines(ScriptEngine::Context context)
|
|||
QUrl normalizeScriptURL(const QUrl& rawScriptURL) {
|
||||
if (rawScriptURL.scheme() == "file") {
|
||||
QUrl fullNormal = rawScriptURL;
|
||||
QUrl defaultScriptLoc = defaultScriptsLocation();
|
||||
QUrl defaultScriptLoc = PathUtils::defaultScriptsLocation();
|
||||
|
||||
// if this url is something "beneath" the default script url, replace the local path with ~
|
||||
if (fullNormal.scheme() == defaultScriptLoc.scheme() &&
|
||||
|
@ -93,7 +91,7 @@ QUrl normalizeScriptURL(const QUrl& rawScriptURL) {
|
|||
|
||||
QString expandScriptPath(const QString& rawPath) {
|
||||
QStringList splitPath = rawPath.split("/");
|
||||
QUrl defaultScriptsLoc = defaultScriptsLocation();
|
||||
QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation();
|
||||
return defaultScriptsLoc.path() + "/" + splitPath.mid(2).join("/"); // 2 to skip the slashes in /~/
|
||||
}
|
||||
|
||||
|
@ -112,7 +110,7 @@ QUrl expandScriptUrl(const QUrl& rawScriptURL) {
|
|||
QFileInfo fileInfo(url.toLocalFile());
|
||||
url = QUrl::fromLocalFile(fileInfo.canonicalFilePath());
|
||||
|
||||
QUrl defaultScriptsLoc = defaultScriptsLocation();
|
||||
QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation();
|
||||
if (!defaultScriptsLoc.isParentOf(url)) {
|
||||
qCWarning(scriptengine) << "Script.include() ignoring file path" << rawScriptURL
|
||||
<< "-- outside of standard libraries: "
|
||||
|
@ -327,6 +325,13 @@ void ScriptEngines::saveScripts() {
|
|||
return;
|
||||
}
|
||||
|
||||
// don't save scripts if we started with --scripts, as we would overwrite
|
||||
// the scripts that the user expects to be there when launched without the
|
||||
// --scripts override.
|
||||
if (_defaultScriptsLocationOverridden) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Saves all currently running user-loaded scripts
|
||||
QVariantList list;
|
||||
|
||||
|
@ -439,7 +444,6 @@ void ScriptEngines::setScriptsLocation(const QString& scriptsLocation) {
|
|||
void ScriptEngines::reloadAllScripts() {
|
||||
qCDebug(scriptengine) << "reloadAllScripts -- clearing caches";
|
||||
DependencyManager::get<ScriptCache>()->clearCache();
|
||||
DependencyManager::get<OffscreenUi>()->clearCache();
|
||||
qCDebug(scriptengine) << "reloadAllScripts -- stopping all scripts";
|
||||
stopAllScripts(true);
|
||||
}
|
||||
|
@ -541,11 +545,11 @@ void ScriptEngines::launchScriptEngine(ScriptEngine* scriptEngine) {
|
|||
initializer(scriptEngine);
|
||||
}
|
||||
|
||||
// FIXME disabling 'shift key' debugging for now. If you start up the application with
|
||||
// the shift key held down, it triggers a deadlock because of script interfaces running
|
||||
// FIXME disabling 'shift key' debugging for now. If you start up the application with
|
||||
// the shift key held down, it triggers a deadlock because of script interfaces running
|
||||
// on the main thread
|
||||
auto const wantDebug = scriptEngine->isDebuggable(); // || (qApp->queryKeyboardModifiers() & Qt::ShiftModifier);
|
||||
|
||||
|
||||
if (HIFI_SCRIPT_DEBUGGABLES && wantDebug) {
|
||||
scriptEngine->runDebuggable();
|
||||
} else {
|
||||
|
@ -581,5 +585,5 @@ void ScriptEngines::onScriptEngineError(const QString& scriptFilename) {
|
|||
}
|
||||
|
||||
QString ScriptEngines::getDefaultScriptsLocation() const {
|
||||
return defaultScriptsLocation().toString();
|
||||
return PathUtils::defaultScriptsLocation().toString();
|
||||
}
|
||||
|
|
|
@ -66,6 +66,8 @@ public:
|
|||
|
||||
Q_PROPERTY(QString defaultScriptsPath READ getDefaultScriptsLocation)
|
||||
|
||||
void defaultScriptsLocationOverridden(bool overridden) { _defaultScriptsLocationOverridden = overridden; };
|
||||
|
||||
// Called at shutdown time
|
||||
void shutdownScripting();
|
||||
bool isStopped() const { return _isStopped; }
|
||||
|
@ -113,6 +115,7 @@ protected:
|
|||
ScriptsModelFilter _scriptsModelFilter;
|
||||
std::atomic<bool> _isStopped { false };
|
||||
std::atomic<bool> _isReloading { false };
|
||||
bool _defaultScriptsLocationOverridden { false };
|
||||
};
|
||||
|
||||
QUrl normalizeScriptURL(const QUrl& rawScriptURL);
|
||||
|
|
|
@ -125,15 +125,15 @@ int ScriptsModel::columnCount(const QModelIndex& parent) const {
|
|||
|
||||
void ScriptsModel::updateScriptsLocation(const QString& newPath) {
|
||||
_fsWatcher.removePath(_localDirectory.absolutePath());
|
||||
|
||||
|
||||
if (!newPath.isEmpty()) {
|
||||
_localDirectory.setPath(newPath);
|
||||
|
||||
|
||||
if (!_localDirectory.absolutePath().isEmpty()) {
|
||||
_fsWatcher.addPath(_localDirectory.absolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
reloadLocalFiles();
|
||||
}
|
||||
|
||||
|
@ -154,7 +154,7 @@ void ScriptsModel::reloadDefaultFiles() {
|
|||
}
|
||||
|
||||
void ScriptsModel::requestDefaultFiles(QString marker) {
|
||||
QUrl url(defaultScriptsLocation());
|
||||
QUrl url(PathUtils::defaultScriptsLocation());
|
||||
|
||||
// targets that don't have a scripts folder in the appropriate location will have an empty URL here
|
||||
if (!url.isEmpty()) {
|
||||
|
@ -244,7 +244,7 @@ bool ScriptsModel::parseXML(QByteArray xmlFile) {
|
|||
lastKey = xml.text().toString();
|
||||
if (jsRegex.exactMatch(xml.text().toString())) {
|
||||
QString localPath = lastKey.split("/").mid(1).join("/");
|
||||
QUrl fullPath = defaultScriptsLocation();
|
||||
QUrl fullPath = PathUtils::defaultScriptsLocation();
|
||||
fullPath.setPath(fullPath.path() + lastKey);
|
||||
const QString fullPathStr = normalizeScriptURL(fullPath).toString();
|
||||
_treeNodes.append(new TreeNodeScript(localPath, fullPathStr, SCRIPT_ORIGIN_DEFAULT));
|
||||
|
|
|
@ -34,7 +34,18 @@ QString PathUtils::getAppDataPath() {
|
|||
return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/";
|
||||
}
|
||||
|
||||
QString PathUtils::getAppLocalDataPath() {
|
||||
QString PathUtils::getAppLocalDataPath(const QString& overridePath /* = "" */) {
|
||||
static QString overriddenPath = "";
|
||||
// set the overridden path if one was passed in
|
||||
if (!overridePath.isEmpty()) {
|
||||
overriddenPath = overridePath;
|
||||
}
|
||||
// return overridden path if set
|
||||
if (!overriddenPath.isEmpty()) {
|
||||
return overriddenPath;
|
||||
}
|
||||
|
||||
// otherwise return standard path
|
||||
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/";
|
||||
}
|
||||
|
||||
|
@ -71,21 +82,33 @@ QString findMostRecentFileExtension(const QString& originalFileName, QVector<QSt
|
|||
return newestFileName;
|
||||
}
|
||||
|
||||
QUrl defaultScriptsLocation() {
|
||||
// return "http://s3.amazonaws.com/hifi-public";
|
||||
#ifdef Q_OS_WIN
|
||||
QString path = QCoreApplication::applicationDirPath() + "/scripts";
|
||||
#elif defined(Q_OS_OSX)
|
||||
QString path = QCoreApplication::applicationDirPath() + "/../Resources/scripts";
|
||||
#else
|
||||
QString path = QCoreApplication::applicationDirPath() + "/scripts";
|
||||
#endif
|
||||
QUrl PathUtils::defaultScriptsLocation(const QString& newDefaultPath) {
|
||||
static QString overriddenDefaultScriptsLocation = "";
|
||||
QString path;
|
||||
|
||||
// set overriddenDefaultScriptLocation if it was passed in
|
||||
if (!newDefaultPath.isEmpty()) {
|
||||
overriddenDefaultScriptsLocation = newDefaultPath;
|
||||
}
|
||||
|
||||
// use the overridden location if it is set
|
||||
if (!overriddenDefaultScriptsLocation.isEmpty()) {
|
||||
path = overriddenDefaultScriptsLocation;
|
||||
} else {
|
||||
#ifdef Q_OS_WIN
|
||||
path = QCoreApplication::applicationDirPath() + "/scripts";
|
||||
#elif defined(Q_OS_OSX)
|
||||
path = QCoreApplication::applicationDirPath() + "/../Resources/scripts";
|
||||
#else
|
||||
path = QCoreApplication::applicationDirPath() + "/scripts";
|
||||
#endif
|
||||
}
|
||||
|
||||
// turn the string into a legit QUrl
|
||||
QFileInfo fileInfo(path);
|
||||
return QUrl::fromLocalFile(fileInfo.canonicalFilePath());
|
||||
}
|
||||
|
||||
|
||||
QString PathUtils::stripFilename(const QUrl& url) {
|
||||
// Guard against meaningless query and fragment parts.
|
||||
// Do NOT use PreferLocalFile as its behavior is unpredictable (e.g., on defaultScriptsLocation())
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
#define hifi_PathUtils_h
|
||||
|
||||
#include <QtCore/QObject>
|
||||
|
||||
#include "DependencyManager.h"
|
||||
|
||||
/**jsdoc
|
||||
|
@ -29,7 +28,7 @@ public:
|
|||
static const QString& resourcesPath();
|
||||
|
||||
static QString getAppDataPath();
|
||||
static QString getAppLocalDataPath();
|
||||
static QString getAppLocalDataPath(const QString& overridePath = "");
|
||||
|
||||
static QString getAppDataFilePath(const QString& filename);
|
||||
static QString getAppLocalDataFilePath(const QString& filename);
|
||||
|
@ -38,11 +37,11 @@ public:
|
|||
static QString stripFilename(const QUrl& url);
|
||||
// note: this is FS-case-sensitive version of parentURL.isParentOf(childURL)
|
||||
static bool isDescendantOf(const QUrl& childURL, const QUrl& parentURL);
|
||||
static QUrl defaultScriptsLocation(const QString& newDefault = "");
|
||||
|
||||
};
|
||||
|
||||
QString fileNameWithoutExtension(const QString& fileName, const QVector<QString> possibleExtensions);
|
||||
QString findMostRecentFileExtension(const QString& originalFileName, QVector<QString> possibleExtensions);
|
||||
|
||||
QUrl defaultScriptsLocation();
|
||||
|
||||
#endif // hifi_PathUtils_h
|
||||
|
|
|
@ -46,6 +46,20 @@ private:
|
|||
const QLoggingCategory& _category;
|
||||
};
|
||||
|
||||
|
||||
inline void syncBegin(const QLoggingCategory& category, const QString& name, const QString& id, const QVariantMap& args = QVariantMap(), const QVariantMap& extra = QVariantMap()) {
|
||||
if (category.isDebugEnabled()) {
|
||||
tracing::traceEvent(category, name, tracing::DurationBegin, id, args, extra);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
inline void syncEnd(const QLoggingCategory& category, const QString& name, const QString& id, const QVariantMap& args = QVariantMap(), const QVariantMap& extra = QVariantMap()) {
|
||||
if (category.isDebugEnabled()) {
|
||||
tracing::traceEvent(category, name, tracing::DurationEnd, id, args, extra);
|
||||
}
|
||||
}
|
||||
|
||||
inline void asyncBegin(const QLoggingCategory& category, const QString& name, const QString& id, const QVariantMap& args = QVariantMap(), const QVariantMap& extra = QVariantMap()) {
|
||||
if (category.isDebugEnabled()) {
|
||||
tracing::traceEvent(category, name, tracing::AsyncNestableStart, id, args, extra);
|
||||
|
@ -80,6 +94,8 @@ inline void metadata(const QString& metadataType, const QVariantMap& args) {
|
|||
#define PROFILE_RANGE_EX(category, name, argbColor, payload, ...) Duration profileRangeThis(trace_##category(), name, argbColor, (uint64_t)payload, ##__VA_ARGS__);
|
||||
#define PROFILE_RANGE_BEGIN(category, rangeId, name, argbColor) rangeId = Duration::beginRange(trace_##category(), name, argbColor)
|
||||
#define PROFILE_RANGE_END(category, rangeId) Duration::endRange(trace_##category(), rangeId)
|
||||
#define PROFILE_SYNC_BEGIN(category, name, id, ...) syncBegin(trace_##category(), name, id, ##__VA_ARGS__);
|
||||
#define PROFILE_SYNC_END(category, name, id, ...) syncEnd(trace_##category(), name, id, ##__VA_ARGS__);
|
||||
#define PROFILE_ASYNC_BEGIN(category, name, id, ...) asyncBegin(trace_##category(), name, id, ##__VA_ARGS__);
|
||||
#define PROFILE_ASYNC_END(category, name, id, ...) asyncEnd(trace_##category(), name, id, ##__VA_ARGS__);
|
||||
#define PROFILE_COUNTER_IF_CHANGED(category, name, type, value) { static type lastValue = 0; type newValue = value; if (newValue != lastValue) { counter(trace_##category(), name, { { name, newValue }}); lastValue = newValue; } }
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
set(TARGET_NAME ui)
|
||||
setup_hifi_library(OpenGL Network Qml Quick Script WebChannel WebSockets XmlPatterns)
|
||||
link_hifi_libraries(shared networking gl script-engine)
|
||||
setup_hifi_library(OpenGL Network Qml Quick Script WebChannel WebEngine WebSockets XmlPatterns)
|
||||
link_hifi_libraries(shared networking gl audio)
|
||||
|
||||
if (NOT ANDROID)
|
||||
# Required for some low level GL interaction in the OffscreenQMLSurface
|
||||
target_glew()
|
||||
endif ()
|
||||
|
|
|
@ -20,7 +20,8 @@
|
|||
#include <AbstractUriHandler.h>
|
||||
#include <AccountManager.h>
|
||||
#include <DependencyManager.h>
|
||||
#include <TabletScriptingInterface.h>
|
||||
|
||||
#include "ui/TabletScriptingInterface.h"
|
||||
#include "FileDialogHelper.h"
|
||||
#include "VrMenu.h"
|
||||
|
||||
|
|
|
@ -21,9 +21,9 @@
|
|||
#include <QtWidgets/QMessageBox>
|
||||
#include <QtWidgets/QInputDialog>
|
||||
|
||||
#include <gl/OffscreenQmlSurface.h>
|
||||
#include <DependencyManager.h>
|
||||
|
||||
#include "ui/OffscreenQmlSurface.h"
|
||||
#include "OffscreenQmlElement.h"
|
||||
|
||||
class VrMenu;
|
||||
|
|
|
@ -6,7 +6,9 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
#include "OffscreenQmlSurface.h"
|
||||
#include "Config.h"
|
||||
|
||||
// Has to come before Qt GL includes
|
||||
#include <gl/Config.h>
|
||||
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
|
@ -33,14 +35,17 @@
|
|||
#include <NetworkAccessManager.h>
|
||||
#include <GLMHelpers.h>
|
||||
#include <shared/GlobalAppProperties.h>
|
||||
#include <FileTypeProfile.h>
|
||||
#include <HFWebEngineProfile.h>
|
||||
#include <HFTabletWebEngineProfile.h>
|
||||
|
||||
#include "OffscreenGLCanvas.h"
|
||||
#include "GLHelpers.h"
|
||||
#include "GLLogging.h"
|
||||
#include "Context.h"
|
||||
#include <gl/OffscreenGLCanvas.h>
|
||||
#include <gl/GLHelpers.h>
|
||||
#include <gl/Context.h>
|
||||
|
||||
#include "types/FileTypeProfile.h"
|
||||
#include "types/HFWebEngineProfile.h"
|
||||
#include "types/HFTabletWebEngineProfile.h"
|
||||
#include "types/SoundEffect.h"
|
||||
|
||||
#include "Logging.h"
|
||||
|
||||
Q_LOGGING_CATEGORY(trace_render_qml, "trace.render.qml")
|
||||
Q_LOGGING_CATEGORY(trace_render_qml_gl, "trace.render.qml.gl")
|
||||
|
@ -272,7 +277,7 @@ QString getEventBridgeJavascript() {
|
|||
QString createGlobalEventBridgeStr = QTextStream(&createGlobalEventBridgeFile).readAll();
|
||||
javaScriptToInject = webChannelStr + createGlobalEventBridgeStr;
|
||||
} else {
|
||||
qCWarning(glLogging) << "Unable to find qwebchannel.js or createGlobalEventBridge.js";
|
||||
qCWarning(uiLogging) << "Unable to find qwebchannel.js or createGlobalEventBridge.js";
|
||||
}
|
||||
return javaScriptToInject;
|
||||
}
|
||||
|
@ -297,6 +302,14 @@ private:
|
|||
|
||||
QQmlEngine* acquireEngine(QQuickWindow* window) {
|
||||
Q_ASSERT(QThread::currentThread() == qApp->thread());
|
||||
if (QThread::currentThread() != qApp->thread()) {
|
||||
qCWarning(uiLogging) << "Cannot acquire QML engine on any thread but the main thread";
|
||||
}
|
||||
static std::once_flag once;
|
||||
std::call_once(once, [] {
|
||||
qmlRegisterType<SoundEffect>("Hifi", 1, 0, "SoundEffect");
|
||||
});
|
||||
|
||||
if (!globalEngine) {
|
||||
Q_ASSERT(0 == globalEngineRefCount);
|
||||
globalEngine = new QQmlEngine();
|
||||
|
@ -325,8 +338,6 @@ QQmlEngine* acquireEngine(QQuickWindow* window) {
|
|||
rootContext->setContextProperty("FileTypeProfile", new FileTypeProfile(rootContext));
|
||||
rootContext->setContextProperty("HFWebEngineProfile", new HFWebEngineProfile(rootContext));
|
||||
rootContext->setContextProperty("HFTabletWebEngineProfile", new HFTabletWebEngineProfile(rootContext));
|
||||
|
||||
|
||||
}
|
||||
|
||||
++globalEngineRefCount;
|
||||
|
@ -457,7 +468,7 @@ void OffscreenQmlSurface::onAboutToQuit() {
|
|||
}
|
||||
|
||||
void OffscreenQmlSurface::create(QOpenGLContext* shareContext) {
|
||||
qCDebug(glLogging) << "Building QML surface";
|
||||
qCDebug(uiLogging) << "Building QML surface";
|
||||
|
||||
_renderControl = new QMyQuickRenderControl();
|
||||
connect(_renderControl, &QQuickRenderControl::renderRequested, this, [this] { _render = true; });
|
||||
|
@ -548,7 +559,7 @@ void OffscreenQmlSurface::resize(const QSize& newSize_, bool forceResize) {
|
|||
return;
|
||||
}
|
||||
|
||||
qCDebug(glLogging) << "Offscreen UI resizing to " << newSize.width() << "x" << newSize.height();
|
||||
qCDebug(uiLogging) << "Offscreen UI resizing to " << newSize.width() << "x" << newSize.height();
|
||||
gl::withSavedContext([&] {
|
||||
_canvas->makeCurrent();
|
||||
|
||||
|
@ -595,6 +606,9 @@ void OffscreenQmlSurface::setBaseUrl(const QUrl& baseUrl) {
|
|||
}
|
||||
|
||||
QObject* OffscreenQmlSurface::load(const QUrl& qmlSource, bool createNewContext, std::function<void(QQmlContext*, QObject*)> f) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
qCWarning(uiLogging) << "Called load on a non-surface thread";
|
||||
}
|
||||
// Synchronous loading may take a while; restart the deadlock timer
|
||||
QMetaObject::invokeMethod(qApp, "updateHeartbeat", Qt::DirectConnection);
|
||||
|
||||
|
@ -636,7 +650,7 @@ QObject* OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlCon
|
|||
disconnect(qmlComponent, &QQmlComponent::statusChanged, this, 0);
|
||||
if (qmlComponent->isError()) {
|
||||
for (const auto& error : qmlComponent->errors()) {
|
||||
qCWarning(glLogging) << error.url() << error.line() << error;
|
||||
qCWarning(uiLogging) << error.url() << error.line() << error;
|
||||
}
|
||||
qmlComponent->deleteLater();
|
||||
return nullptr;
|
||||
|
@ -646,7 +660,7 @@ QObject* OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlCon
|
|||
QObject* newObject = qmlComponent->beginCreate(qmlContext);
|
||||
if (qmlComponent->isError()) {
|
||||
for (const auto& error : qmlComponent->errors()) {
|
||||
qCWarning(glLogging) << error.url() << error.line() << error;
|
||||
qCWarning(uiLogging) << error.url() << error.line() << error;
|
||||
}
|
||||
if (!_rootItem) {
|
||||
qFatal("Unable to finish loading QML root");
|
59
libraries/ui/src/ui/QmlWrapper.cpp
Normal file
59
libraries/ui/src/ui/QmlWrapper.cpp
Normal file
|
@ -0,0 +1,59 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2017/06/22
|
||||
// Copyright 2013-2017 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 "QmlWrapper.h"
|
||||
|
||||
#include <QtCore/QThread>
|
||||
#include <QtCore/QCoreApplication>
|
||||
|
||||
QmlWrapper::QmlWrapper(QObject* qmlObject, QObject* parent)
|
||||
: QObject(parent), _qmlObject(qmlObject) {
|
||||
Q_ASSERT(QThread::currentThread() == qApp->thread());
|
||||
}
|
||||
|
||||
void QmlWrapper::writeProperty(QString propertyName, QVariant propertyValue) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "writeProperty", Q_ARG(QString, propertyName), Q_ARG(QVariant, propertyValue));
|
||||
}
|
||||
_qmlObject->setProperty(propertyName.toStdString().c_str(), propertyValue);
|
||||
}
|
||||
|
||||
void QmlWrapper::writeProperties(QVariant propertyMap) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "writeProperties", Q_ARG(QVariant, propertyMap));
|
||||
}
|
||||
QVariantMap map = propertyMap.toMap();
|
||||
for (const QString& key : map.keys()) {
|
||||
_qmlObject->setProperty(key.toStdString().c_str(), map[key]);
|
||||
}
|
||||
}
|
||||
|
||||
QVariant QmlWrapper::readProperty(const QString& propertyName) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QVariant result;
|
||||
QMetaObject::invokeMethod(this, "readProperty", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QVariant, result), Q_ARG(QString, propertyName));
|
||||
return result;
|
||||
}
|
||||
|
||||
return _qmlObject->property(propertyName.toStdString().c_str());
|
||||
}
|
||||
|
||||
QVariant QmlWrapper::readProperties(const QVariant& propertyList) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QVariant result;
|
||||
QMetaObject::invokeMethod(this, "readProperties", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QVariant, result), Q_ARG(QVariant, propertyList));
|
||||
return result;
|
||||
}
|
||||
|
||||
QVariantMap result;
|
||||
for (const QVariant& property : propertyList.toList()) {
|
||||
QString propertyString = property.toString();
|
||||
result.insert(propertyString, _qmlObject->property(propertyString.toStdString().c_str()));
|
||||
}
|
||||
return result;
|
||||
}
|
44
libraries/ui/src/ui/QmlWrapper.h
Normal file
44
libraries/ui/src/ui/QmlWrapper.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
//
|
||||
// Created by Anthony J. Thibault on 2016-12-12
|
||||
// Copyright 2013-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_QmlWrapper_h
|
||||
#define hifi_QmlWrapper_h
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QVariant>
|
||||
#include <QtScript/QScriptValue>
|
||||
#include <QtScript/QScriptEngine>
|
||||
|
||||
class QmlWrapper : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
QmlWrapper(QObject* qmlObject, QObject* parent = nullptr);
|
||||
|
||||
Q_INVOKABLE void writeProperty(QString propertyName, QVariant propertyValue);
|
||||
Q_INVOKABLE void writeProperties(QVariant propertyMap);
|
||||
Q_INVOKABLE QVariant readProperty(const QString& propertyName);
|
||||
Q_INVOKABLE QVariant readProperties(const QVariant& propertyList);
|
||||
|
||||
protected:
|
||||
QObject* _qmlObject{ nullptr };
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
QScriptValue wrapperToScriptValue(QScriptEngine* engine, T* const &in) {
|
||||
if (!in) {
|
||||
return engine->undefinedValue();
|
||||
}
|
||||
return engine->newQObject(in, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void wrapperFromScriptValue(const QScriptValue& value, T* &out) {
|
||||
out = qobject_cast<T*>(value.toQObject());
|
||||
}
|
||||
|
||||
#endif
|
|
@ -9,66 +9,51 @@
|
|||
#include "TabletScriptingInterface.h"
|
||||
|
||||
#include <QtCore/QThread>
|
||||
#include <QtQml/QQmlProperty>
|
||||
|
||||
#include <AccountManager.h>
|
||||
#include "DependencyManager.h"
|
||||
#include <PathUtils.h>
|
||||
#include <QmlWindowClass.h>
|
||||
#include <QQmlProperty>
|
||||
#include <DependencyManager.h>
|
||||
#include <AccountManager.h>
|
||||
#include <RegisteredMetaTypes.h>
|
||||
#include "ScriptEngineLogging.h"
|
||||
#include <OffscreenUi.h>
|
||||
#include <InfoView.h>
|
||||
#include "SoundEffect.h"
|
||||
|
||||
#include "../QmlWindowClass.h"
|
||||
#include "../OffscreenUi.h"
|
||||
#include "../InfoView.h"
|
||||
#include "ToolbarScriptingInterface.h"
|
||||
#include "Logging.h"
|
||||
|
||||
// FIXME move to global app properties
|
||||
const QString SYSTEM_TOOLBAR = "com.highfidelity.interface.toolbar.system";
|
||||
const QString SYSTEM_TABLET = "com.highfidelity.interface.tablet.system";
|
||||
|
||||
QScriptValue tabletToScriptValue(QScriptEngine* engine, TabletProxy* const &in) {
|
||||
return engine->newQObject(in, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects);
|
||||
}
|
||||
|
||||
void tabletFromScriptValue(const QScriptValue& value, TabletProxy* &out) {
|
||||
out = qobject_cast<TabletProxy*>(value.toQObject());
|
||||
}
|
||||
|
||||
TabletScriptingInterface::TabletScriptingInterface() {
|
||||
qmlRegisterType<SoundEffect>("Hifi", 1, 0, "SoundEffect");
|
||||
qCDebug(uiLogging) << "Building tablet scripting interface";
|
||||
}
|
||||
|
||||
QObject* TabletScriptingInterface::getSystemToolbarProxy() {
|
||||
Qt::ConnectionType connectionType = Qt::AutoConnection;
|
||||
if (QThread::currentThread() != _toolbarScriptingInterface->thread()) {
|
||||
connectionType = Qt::BlockingQueuedConnection;
|
||||
}
|
||||
TabletScriptingInterface::~TabletScriptingInterface() {
|
||||
qCDebug(uiLogging) << "Destroying tablet scripting interface";
|
||||
}
|
||||
|
||||
QObject* toolbarProxy = nullptr;
|
||||
bool hasResult = QMetaObject::invokeMethod(_toolbarScriptingInterface, "getToolbar", connectionType, Q_RETURN_ARG(QObject*, toolbarProxy), Q_ARG(QString, SYSTEM_TOOLBAR));
|
||||
if (hasResult) {
|
||||
return toolbarProxy;
|
||||
} else {
|
||||
qCWarning(scriptengine) << "ToolbarScriptingInterface getToolbar has no result";
|
||||
return nullptr;
|
||||
}
|
||||
ToolbarProxy* TabletScriptingInterface::getSystemToolbarProxy() {
|
||||
Q_ASSERT(QThread::currentThread() == qApp->thread());
|
||||
return _toolbarScriptingInterface->getToolbar(SYSTEM_TOOLBAR);
|
||||
}
|
||||
|
||||
TabletProxy* TabletScriptingInterface::getTablet(const QString& tabletId) {
|
||||
TabletProxy* tabletProxy = nullptr;
|
||||
{
|
||||
// the only thing guarded should be map mutation
|
||||
// this avoids a deadlock with the Main thread
|
||||
// from Qt::BlockingQueuedEvent invocations later in the call-tree
|
||||
std::lock_guard<std::mutex> guard(_mapMutex);
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "getTablet", Qt::BlockingQueuedConnection, Q_RETURN_ARG(TabletProxy*, tabletProxy), Q_ARG(QString, tabletId));
|
||||
return tabletProxy;
|
||||
}
|
||||
|
||||
auto iter = _tabletProxies.find(tabletId);
|
||||
if (iter != _tabletProxies.end()) {
|
||||
// tablet already exists
|
||||
return iter->second;
|
||||
} else {
|
||||
// tablet must be created
|
||||
tabletProxy = new TabletProxy(this, tabletId);
|
||||
_tabletProxies[tabletId] = tabletProxy;
|
||||
}
|
||||
auto iter = _tabletProxies.find(tabletId);
|
||||
if (iter != _tabletProxies.end()) {
|
||||
// tablet already exists
|
||||
return iter->second;
|
||||
} else {
|
||||
// tablet must be created
|
||||
tabletProxy = new TabletProxy(this, tabletId);
|
||||
_tabletProxies[tabletId] = tabletProxy;
|
||||
}
|
||||
|
||||
assert(tabletProxy);
|
||||
|
@ -78,42 +63,40 @@ TabletProxy* TabletScriptingInterface::getTablet(const QString& tabletId) {
|
|||
}
|
||||
|
||||
void TabletScriptingInterface::setToolbarMode(bool toolbarMode) {
|
||||
{
|
||||
// the only thing guarded should be _toolbarMode
|
||||
// this avoids a deadlock with the Main thread
|
||||
// from Qt::BlockingQueuedEvent invocations later in the call-tree
|
||||
std::lock_guard<std::mutex> guard(_mapMutex);
|
||||
_toolbarMode = toolbarMode;
|
||||
}
|
||||
|
||||
Q_ASSERT(QThread::currentThread() == qApp->thread());
|
||||
_toolbarMode = toolbarMode;
|
||||
for (auto& iter : _tabletProxies) {
|
||||
iter.second->setToolbarMode(toolbarMode);
|
||||
}
|
||||
}
|
||||
|
||||
void TabletScriptingInterface::setQmlTabletRoot(QString tabletId, QQuickItem* qmlTabletRoot, QObject* qmlOffscreenSurface) {
|
||||
void TabletScriptingInterface::setQmlTabletRoot(QString tabletId, OffscreenQmlSurface* qmlOffscreenSurface) {
|
||||
Q_ASSERT(QThread::currentThread() == qApp->thread());
|
||||
TabletProxy* tablet = qobject_cast<TabletProxy*>(getTablet(tabletId));
|
||||
if (tablet) {
|
||||
tablet->setQmlTabletRoot(qmlTabletRoot, qmlOffscreenSurface);
|
||||
tablet->setQmlTabletRoot(qmlOffscreenSurface);
|
||||
} else {
|
||||
qCWarning(scriptengine) << "TabletScriptingInterface::setupTablet() bad tablet object";
|
||||
qCWarning(uiLogging) << "TabletScriptingInterface::setupTablet() bad tablet object";
|
||||
}
|
||||
}
|
||||
|
||||
QQuickWindow* TabletScriptingInterface::getTabletWindow() {
|
||||
Q_ASSERT(QThread::currentThread() == qApp->thread());
|
||||
TabletProxy* tablet = qobject_cast<TabletProxy*>(getTablet(SYSTEM_TABLET));
|
||||
QObject* qmlSurface = tablet->getTabletSurface();
|
||||
|
||||
OffscreenQmlSurface* surface = dynamic_cast<OffscreenQmlSurface*>(qmlSurface);
|
||||
|
||||
if (!surface) {
|
||||
if (!tablet) {
|
||||
return nullptr;
|
||||
}
|
||||
QQuickWindow* window = surface->getWindow();
|
||||
return window;
|
||||
|
||||
auto* qmlSurface = tablet->getTabletSurface();
|
||||
if (!qmlSurface) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return qmlSurface->getWindow();
|
||||
}
|
||||
|
||||
void TabletScriptingInterface::processMenuEvents(QObject* object, const QKeyEvent* event) {
|
||||
Q_ASSERT(QThread::currentThread() == qApp->thread());
|
||||
switch (event->key()) {
|
||||
case Qt::Key_Down:
|
||||
QMetaObject::invokeMethod(object, "nextItem");
|
||||
|
@ -141,6 +124,7 @@ void TabletScriptingInterface::processMenuEvents(QObject* object, const QKeyEven
|
|||
}
|
||||
|
||||
void TabletScriptingInterface::processTabletEvents(QObject* object, const QKeyEvent* event) {
|
||||
Q_ASSERT(QThread::currentThread() == qApp->thread());
|
||||
switch (event->key()) {
|
||||
case Qt::Key_Down:
|
||||
QMetaObject::invokeMethod(object, "downItem");
|
||||
|
@ -167,8 +151,8 @@ void TabletScriptingInterface::processTabletEvents(QObject* object, const QKeyEv
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
void TabletScriptingInterface::processEvent(const QKeyEvent* event) {
|
||||
Q_ASSERT(QThread::currentThread() == qApp->thread());
|
||||
TabletProxy* tablet = qobject_cast<TabletProxy*>(getTablet(SYSTEM_TABLET));
|
||||
QObject* qmlTablet = tablet->getQmlTablet();
|
||||
QObject* qmlMenu = tablet->getQmlMenu();
|
||||
|
@ -180,8 +164,8 @@ void TabletScriptingInterface::processEvent(const QKeyEvent* event) {
|
|||
}
|
||||
}
|
||||
|
||||
QObject* TabletScriptingInterface::getFlags()
|
||||
{
|
||||
QObject* TabletScriptingInterface::getFlags() {
|
||||
Q_ASSERT(QThread::currentThread() == qApp->thread());
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
return offscreenUi->getFlags();
|
||||
}
|
||||
|
@ -198,11 +182,24 @@ class TabletRootWindow : public QmlWindowClass {
|
|||
virtual QString qmlSource() const override { return "hifi/tablet/WindowRoot.qml"; }
|
||||
};
|
||||
|
||||
TabletProxy::TabletProxy(QObject* parent, QString name) : QObject(parent), _name(name) {
|
||||
TabletProxy::TabletProxy(QObject* parent, const QString& name) : QObject(parent), _name(name) {
|
||||
if (QThread::currentThread() != qApp->thread()) {
|
||||
qCWarning(uiLogging) << "Creating tablet proxy on wrong thread " << _name;
|
||||
}
|
||||
}
|
||||
|
||||
TabletProxy::~TabletProxy() {
|
||||
qCDebug(uiLogging) << "Destroying tablet proxy " << _name;
|
||||
if (QThread::currentThread() != thread()) {
|
||||
qCWarning(uiLogging) << "Destroying tablet proxy on wrong thread" << _name;
|
||||
}
|
||||
}
|
||||
|
||||
void TabletProxy::setToolbarMode(bool toolbarMode) {
|
||||
std::lock_guard<std::mutex> guard(_tabletMutex);
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "setToolbarMode", Q_ARG(bool, toolbarMode));
|
||||
return;
|
||||
}
|
||||
|
||||
if (toolbarMode == _toolbarMode) {
|
||||
return;
|
||||
|
@ -245,25 +242,23 @@ void TabletProxy::setToolbarMode(bool toolbarMode) {
|
|||
}
|
||||
|
||||
static void addButtonProxyToQmlTablet(QQuickItem* qmlTablet, TabletButtonProxy* buttonProxy) {
|
||||
QVariant resultVar;
|
||||
Qt::ConnectionType connectionType = Qt::AutoConnection;
|
||||
if (QThread::currentThread() != qmlTablet->thread()) {
|
||||
connectionType = Qt::BlockingQueuedConnection;
|
||||
Q_ASSERT(QThread::currentThread() == qApp->thread());
|
||||
if (buttonProxy == NULL){
|
||||
qCCritical(uiLogging) << "TabletScriptingInterface addButtonProxyToQmlTablet buttonProxy is NULL";
|
||||
return;
|
||||
}
|
||||
if (buttonProxy == NULL){
|
||||
qCCritical(scriptengine) << "TabletScriptingInterface addButtonProxyToQmlTablet buttonProxy is NULL";
|
||||
return;
|
||||
}
|
||||
bool hasResult = QMetaObject::invokeMethod(qmlTablet, "addButtonProxy", connectionType,
|
||||
|
||||
QVariant resultVar;
|
||||
bool hasResult = QMetaObject::invokeMethod(qmlTablet, "addButtonProxy", Qt::DirectConnection,
|
||||
Q_RETURN_ARG(QVariant, resultVar), Q_ARG(QVariant, buttonProxy->getProperties()));
|
||||
if (!hasResult) {
|
||||
qCWarning(scriptengine) << "TabletScriptingInterface addButtonProxyToQmlTablet has no result";
|
||||
qCWarning(uiLogging) << "TabletScriptingInterface addButtonProxyToQmlTablet has no result";
|
||||
return;
|
||||
}
|
||||
|
||||
QObject* qmlButton = qvariant_cast<QObject *>(resultVar);
|
||||
if (!qmlButton) {
|
||||
qCWarning(scriptengine) << "TabletScriptingInterface addButtonProxyToQmlTablet result not a QObject";
|
||||
qCWarning(uiLogging) << "TabletScriptingInterface addButtonProxyToQmlTablet result not a QObject";
|
||||
return;
|
||||
}
|
||||
QObject::connect(qmlButton, SIGNAL(clicked()), buttonProxy, SLOT(clickedSlot()));
|
||||
|
@ -281,6 +276,11 @@ static QString getUsername() {
|
|||
}
|
||||
|
||||
void TabletProxy::initialScreen(const QVariant& url) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "initialScreen", Q_ARG(QVariant, url));
|
||||
return;
|
||||
}
|
||||
|
||||
if (_qmlTabletRoot) {
|
||||
pushOntoStack(url);
|
||||
} else {
|
||||
|
@ -290,34 +290,49 @@ void TabletProxy::initialScreen(const QVariant& url) {
|
|||
}
|
||||
|
||||
bool TabletProxy::isMessageDialogOpen() {
|
||||
if (_qmlTabletRoot) {
|
||||
QVariant result;
|
||||
QMetaObject::invokeMethod(_qmlTabletRoot, "isDialogOpen",Qt::DirectConnection,
|
||||
Q_RETURN_ARG(QVariant, result));
|
||||
|
||||
return result.toBool();
|
||||
if (QThread::currentThread() != thread()) {
|
||||
bool result = false;
|
||||
QMetaObject::invokeMethod(this, "isMessageDialogOpen", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, result));
|
||||
return result;
|
||||
}
|
||||
|
||||
return false;
|
||||
if (!_qmlTabletRoot) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QVariant result;
|
||||
QMetaObject::invokeMethod(_qmlTabletRoot, "isDialogOpen",Qt::DirectConnection,
|
||||
Q_RETURN_ARG(QVariant, result));
|
||||
return result.toBool();
|
||||
}
|
||||
|
||||
void TabletProxy::emitWebEvent(QVariant msg) {
|
||||
void TabletProxy::emitWebEvent(const QVariant& msg) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "emitWebEvent", Q_ARG(QVariant, msg));
|
||||
return;
|
||||
}
|
||||
emit webEventReceived(msg);
|
||||
}
|
||||
|
||||
bool TabletProxy::isPathLoaded(QVariant path) {
|
||||
bool TabletProxy::isPathLoaded(const QVariant& path) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
bool result = false;
|
||||
QMetaObject::invokeMethod(this, "isPathLoaded", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, result), Q_ARG(QVariant, path));
|
||||
return result;
|
||||
}
|
||||
|
||||
return path.toString() == _currentPathLoaded.toString();
|
||||
}
|
||||
void TabletProxy::setQmlTabletRoot(QQuickItem* qmlTabletRoot, QObject* qmlOffscreenSurface) {
|
||||
std::lock_guard<std::mutex> guard(_tabletMutex);
|
||||
_qmlOffscreenSurface = qmlOffscreenSurface;
|
||||
_qmlTabletRoot = qmlTabletRoot;
|
||||
if (_qmlTabletRoot && _qmlOffscreenSurface) {
|
||||
|
||||
QObject::connect(_qmlOffscreenSurface, SIGNAL(webEventReceived(QVariant)), this, SLOT(emitWebEvent(QVariant)), Qt::DirectConnection);
|
||||
void TabletProxy::setQmlTabletRoot(OffscreenQmlSurface* qmlOffscreenSurface) {
|
||||
Q_ASSERT(QThread::currentThread() == qApp->thread());
|
||||
_qmlOffscreenSurface = qmlOffscreenSurface;
|
||||
_qmlTabletRoot = qmlOffscreenSurface ? qmlOffscreenSurface->getRootItem() : nullptr;
|
||||
if (_qmlTabletRoot && _qmlOffscreenSurface) {
|
||||
QObject::connect(_qmlOffscreenSurface, SIGNAL(webEventReceived(QVariant)), this, SLOT(emitWebEvent(QVariant)));
|
||||
|
||||
// forward qml surface events to interface js
|
||||
connect(dynamic_cast<OffscreenQmlSurface*>(_qmlOffscreenSurface), &OffscreenQmlSurface::fromQml, [this](QVariant message) {
|
||||
connect(_qmlOffscreenSurface, &OffscreenQmlSurface::fromQml, [this](QVariant message) {
|
||||
if (message.canConvert<QJSValue>()) {
|
||||
emit fromQml(qvariant_cast<QJSValue>(message).toVariant());
|
||||
} else if (message.canConvert<QString>()) {
|
||||
|
@ -330,7 +345,7 @@ void TabletProxy::setQmlTabletRoot(QQuickItem* qmlTabletRoot, QObject* qmlOffscr
|
|||
if (_toolbarMode) {
|
||||
// if someone creates the tablet in toolbar mode, make sure to display the home screen on the tablet.
|
||||
auto loader = _qmlTabletRoot->findChild<QQuickItem*>("loader");
|
||||
QObject::connect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToHomeScreen()), Qt::DirectConnection);
|
||||
QObject::connect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToHomeScreen()));
|
||||
QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(TABLET_SOURCE_URL)));
|
||||
}
|
||||
|
||||
|
@ -360,9 +375,18 @@ void TabletProxy::setQmlTabletRoot(QQuickItem* qmlTabletRoot, QObject* qmlOffscr
|
|||
}
|
||||
|
||||
void TabletProxy::gotoHomeScreen() {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "gotoHomeScreen");
|
||||
return;
|
||||
}
|
||||
loadHomeScreen(false);
|
||||
}
|
||||
|
||||
void TabletProxy::gotoMenuScreen(const QString& submenu) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "gotoMenuScreen", Q_ARG(QString, submenu));
|
||||
return;
|
||||
}
|
||||
|
||||
QObject* root = nullptr;
|
||||
if (!_toolbarMode && _qmlTabletRoot) {
|
||||
|
@ -385,6 +409,11 @@ void TabletProxy::gotoMenuScreen(const QString& submenu) {
|
|||
}
|
||||
|
||||
void TabletProxy::loadQMLOnTop(const QVariant& path) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "loadQMLOnTop", Q_ARG(QVariant, path));
|
||||
return;
|
||||
}
|
||||
|
||||
QObject* root = nullptr;
|
||||
if (!_toolbarMode && _qmlTabletRoot) {
|
||||
root = _qmlTabletRoot;
|
||||
|
@ -396,11 +425,16 @@ void TabletProxy::loadQMLOnTop(const QVariant& path) {
|
|||
QMetaObject::invokeMethod(root, "loadQMLOnTop", Q_ARG(const QVariant&, path));
|
||||
QMetaObject::invokeMethod(root, "setShown", Q_ARG(const QVariant&, QVariant(true)));
|
||||
} else {
|
||||
qCDebug(scriptengine) << "tablet cannot load QML because _qmlTabletRoot is null";
|
||||
qCDebug(uiLogging) << "tablet cannot load QML because _qmlTabletRoot is null";
|
||||
}
|
||||
}
|
||||
|
||||
void TabletProxy::returnToPreviousApp() {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "returnToPreviousApp");
|
||||
return;
|
||||
}
|
||||
|
||||
QObject* root = nullptr;
|
||||
if (!_toolbarMode && _qmlTabletRoot) {
|
||||
root = _qmlTabletRoot;
|
||||
|
@ -412,11 +446,15 @@ void TabletProxy::returnToPreviousApp() {
|
|||
QMetaObject::invokeMethod(root, "returnToPreviousApp");
|
||||
QMetaObject::invokeMethod(root, "setShown", Q_ARG(const QVariant&, QVariant(true)));
|
||||
} else {
|
||||
qCDebug(scriptengine) << "tablet cannot load QML because _qmlTabletRoot is null";
|
||||
qCDebug(uiLogging) << "tablet cannot load QML because _qmlTabletRoot is null";
|
||||
}
|
||||
}
|
||||
|
||||
void TabletProxy::loadQMLSource(const QVariant& path) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "loadQMLSource", Q_ARG(QVariant, path));
|
||||
return;
|
||||
}
|
||||
|
||||
QObject* root = nullptr;
|
||||
if (!_toolbarMode && _qmlTabletRoot) {
|
||||
|
@ -435,11 +473,17 @@ void TabletProxy::loadQMLSource(const QVariant& path) {
|
|||
QMetaObject::invokeMethod(root, "setShown", Q_ARG(const QVariant&, QVariant(true)));
|
||||
}
|
||||
} else {
|
||||
qCDebug(scriptengine) << "tablet cannot load QML because _qmlTabletRoot is null";
|
||||
qCDebug(uiLogging) << "tablet cannot load QML because _qmlTabletRoot is null";
|
||||
}
|
||||
}
|
||||
|
||||
bool TabletProxy::pushOntoStack(const QVariant& path) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
bool result = false;
|
||||
QMetaObject::invokeMethod(this, "pushOntoStack", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, result), Q_ARG(QVariant, path));
|
||||
return result;
|
||||
}
|
||||
|
||||
QObject* root = nullptr;
|
||||
if (!_toolbarMode && _qmlTabletRoot) {
|
||||
root = _qmlTabletRoot;
|
||||
|
@ -455,13 +499,18 @@ bool TabletProxy::pushOntoStack(const QVariant& path) {
|
|||
loadQMLSource(path);
|
||||
}
|
||||
} else {
|
||||
qCDebug(scriptengine) << "tablet cannot push QML because _qmlTabletRoot or _desktopWindow is null";
|
||||
qCDebug(uiLogging) << "tablet cannot push QML because _qmlTabletRoot or _desktopWindow is null";
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
void TabletProxy::popFromStack() {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "popFromStack");
|
||||
return;
|
||||
}
|
||||
|
||||
QObject* root = nullptr;
|
||||
if (!_toolbarMode && _qmlTabletRoot) {
|
||||
root = _qmlTabletRoot;
|
||||
|
@ -473,15 +522,20 @@ void TabletProxy::popFromStack() {
|
|||
auto stack = root->findChild<QQuickItem*>("stack");
|
||||
QMetaObject::invokeMethod(stack, "popSource");
|
||||
} else {
|
||||
qCDebug(scriptengine) << "tablet cannot pop QML because _qmlTabletRoot or _desktopWindow is null";
|
||||
qCDebug(uiLogging) << "tablet cannot pop QML because _qmlTabletRoot or _desktopWindow is null";
|
||||
}
|
||||
}
|
||||
|
||||
void TabletProxy::loadHomeScreen(bool forceOntoHomeScreen) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "loadHomeScreen", Q_ARG(bool, forceOntoHomeScreen));
|
||||
return;
|
||||
}
|
||||
|
||||
if ((_state != State::Home && _state != State::Uninitialized) || forceOntoHomeScreen) {
|
||||
if (!_toolbarMode && _qmlTabletRoot) {
|
||||
auto loader = _qmlTabletRoot->findChild<QQuickItem*>("loader");
|
||||
QObject::connect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToHomeScreen()), Qt::DirectConnection);
|
||||
QObject::connect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToHomeScreen()));
|
||||
QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(TABLET_SOURCE_URL)));
|
||||
QMetaObject::invokeMethod(_qmlTabletRoot, "playButtonClickSound");
|
||||
} else if (_toolbarMode && _desktopWindow) {
|
||||
|
@ -505,6 +559,11 @@ void TabletProxy::loadWebScreenOnTop(const QVariant& url) {
|
|||
}
|
||||
|
||||
void TabletProxy::loadWebScreenOnTop(const QVariant& url, const QString& injectJavaScriptUrl) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "loadWebScreenOnTop", Q_ARG(QVariant, url), Q_ARG(QString, injectJavaScriptUrl));
|
||||
return;
|
||||
}
|
||||
|
||||
QObject* root = nullptr;
|
||||
if (!_toolbarMode && _qmlTabletRoot) {
|
||||
root = _qmlTabletRoot;
|
||||
|
@ -521,6 +580,10 @@ void TabletProxy::loadWebScreenOnTop(const QVariant& url, const QString& injectJ
|
|||
}
|
||||
|
||||
void TabletProxy::gotoWebScreen(const QString& url, const QString& injectedJavaScriptUrl) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "gotoWebScreen", Q_ARG(QString, url), Q_ARG(QString, injectedJavaScriptUrl));
|
||||
return;
|
||||
}
|
||||
|
||||
QObject* root = nullptr;
|
||||
if (!_toolbarMode && _qmlTabletRoot) {
|
||||
|
@ -540,57 +603,59 @@ void TabletProxy::gotoWebScreen(const QString& url, const QString& injectedJavaS
|
|||
_currentPathLoaded = QVariant(url);
|
||||
}
|
||||
|
||||
QObject* TabletProxy::addButton(const QVariant& properties) {
|
||||
TabletButtonProxy* TabletProxy::addButton(const QVariant& properties) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
TabletButtonProxy* result = nullptr;
|
||||
QMetaObject::invokeMethod(this, "addButton", Qt::BlockingQueuedConnection, Q_RETURN_ARG(TabletButtonProxy*, result), Q_ARG(QVariant, properties));
|
||||
return result;
|
||||
}
|
||||
|
||||
auto tabletButtonProxy = QSharedPointer<TabletButtonProxy>(new TabletButtonProxy(properties.toMap()));
|
||||
std::unique_lock<std::mutex> guard(_tabletMutex);
|
||||
_tabletButtonProxies.push_back(tabletButtonProxy);
|
||||
if (!_toolbarMode && _qmlTabletRoot) {
|
||||
auto tablet = getQmlTablet();
|
||||
if (tablet) {
|
||||
addButtonProxyToQmlTablet(tablet, tabletButtonProxy.data());
|
||||
} else {
|
||||
qCCritical(scriptengine) << "Could not find tablet in TabletRoot.qml";
|
||||
qCCritical(uiLogging) << "Could not find tablet in TabletRoot.qml";
|
||||
}
|
||||
} else if (_toolbarMode) {
|
||||
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
||||
QObject* toolbarProxy = tabletScriptingInterface->getSystemToolbarProxy();
|
||||
|
||||
Qt::ConnectionType connectionType = Qt::AutoConnection;
|
||||
if (QThread::currentThread() != toolbarProxy->thread()) {
|
||||
connectionType = Qt::BlockingQueuedConnection;
|
||||
}
|
||||
|
||||
guard.unlock();
|
||||
|
||||
// copy properties from tablet button proxy to toolbar button proxy.
|
||||
QObject* toolbarButtonProxy = nullptr;
|
||||
bool hasResult = QMetaObject::invokeMethod(toolbarProxy, "addButton", connectionType, Q_RETURN_ARG(QObject*, toolbarButtonProxy), Q_ARG(QVariant, tabletButtonProxy->getProperties()));
|
||||
if (hasResult) {
|
||||
auto toolbarProxy = DependencyManager::get<TabletScriptingInterface>()->getSystemToolbarProxy();
|
||||
if (toolbarProxy) {
|
||||
// copy properties from tablet button proxy to toolbar button proxy.
|
||||
auto toolbarButtonProxy = toolbarProxy->addButton(tabletButtonProxy->getProperties());
|
||||
tabletButtonProxy->setToolbarButtonProxy(toolbarButtonProxy);
|
||||
} else {
|
||||
qCWarning(scriptengine) << "ToolbarProxy addButton has no result";
|
||||
}
|
||||
}
|
||||
return tabletButtonProxy.data();
|
||||
}
|
||||
|
||||
bool TabletProxy::onHomeScreen() {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
bool result = false;
|
||||
QMetaObject::invokeMethod(this, "onHomeScreen", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, result));
|
||||
return result;
|
||||
}
|
||||
|
||||
return _state == State::Home;
|
||||
}
|
||||
|
||||
void TabletProxy::removeButton(QObject* tabletButtonProxy) {
|
||||
std::unique_lock<std::mutex> guard(_tabletMutex);
|
||||
void TabletProxy::removeButton(TabletButtonProxy* tabletButtonProxy) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "removeButton", Q_ARG(TabletButtonProxy*, tabletButtonProxy));
|
||||
return;
|
||||
}
|
||||
|
||||
auto tablet = getQmlTablet();
|
||||
if (!tablet) {
|
||||
qCCritical(scriptengine) << "Could not find tablet in TabletRoot.qml";
|
||||
qCCritical(uiLogging) << "Could not find tablet in TabletRoot.qml";
|
||||
}
|
||||
|
||||
QSharedPointer<TabletButtonProxy> buttonProxy;
|
||||
{
|
||||
auto iter = std::find(_tabletButtonProxies.begin(), _tabletButtonProxies.end(), tabletButtonProxy);
|
||||
if (iter == _tabletButtonProxies.end()) {
|
||||
qCWarning(scriptengine) << "TabletProxy::removeButton() could not find button " << tabletButtonProxy;
|
||||
qCWarning(uiLogging) << "TabletProxy::removeButton() could not find button " << tabletButtonProxy;
|
||||
return;
|
||||
}
|
||||
buttonProxy = *iter;
|
||||
|
@ -600,21 +665,24 @@ void TabletProxy::removeButton(QObject* tabletButtonProxy) {
|
|||
if (!_toolbarMode && _qmlTabletRoot) {
|
||||
buttonProxy->setQmlButton(nullptr);
|
||||
if (tablet) {
|
||||
guard.unlock();
|
||||
QMetaObject::invokeMethod(tablet, "removeButtonProxy", Qt::AutoConnection, Q_ARG(QVariant, buttonProxy->getProperties()));
|
||||
}
|
||||
} else if (_toolbarMode) {
|
||||
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
||||
QObject* toolbarProxy = tabletScriptingInterface->getSystemToolbarProxy();
|
||||
|
||||
auto toolbarProxy = DependencyManager::get<TabletScriptingInterface>()->getSystemToolbarProxy();
|
||||
// remove button from toolbarProxy
|
||||
guard.unlock();
|
||||
QMetaObject::invokeMethod(toolbarProxy, "removeButton", Qt::AutoConnection, Q_ARG(QVariant, buttonProxy->getUuid().toString()));
|
||||
buttonProxy->setToolbarButtonProxy(nullptr);
|
||||
if (toolbarProxy) {
|
||||
toolbarProxy->removeButton(buttonProxy->getUuid().toString());
|
||||
buttonProxy->setToolbarButtonProxy(nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TabletProxy::emitScriptEvent(QVariant msg) {
|
||||
void TabletProxy::emitScriptEvent(const QVariant& msg) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "emitScriptEvent", Q_ARG(QVariant, msg));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_toolbarMode && _qmlOffscreenSurface) {
|
||||
QMetaObject::invokeMethod(_qmlOffscreenSurface, "emitScriptEvent", Qt::AutoConnection, Q_ARG(QVariant, msg));
|
||||
} else if (_toolbarMode && _desktopWindow) {
|
||||
|
@ -622,7 +690,12 @@ void TabletProxy::emitScriptEvent(QVariant msg) {
|
|||
}
|
||||
}
|
||||
|
||||
void TabletProxy::sendToQml(QVariant msg) {
|
||||
void TabletProxy::sendToQml(const QVariant& msg) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "sendToQml", Q_ARG(QVariant, msg));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_toolbarMode && _qmlOffscreenSurface) {
|
||||
QMetaObject::invokeMethod(_qmlOffscreenSurface, "sendToQml", Qt::AutoConnection, Q_ARG(QVariant, msg));
|
||||
} else if (_toolbarMode && _desktopWindow) {
|
||||
|
@ -635,8 +708,6 @@ void TabletProxy::addButtonsToHomeScreen() {
|
|||
if (!tablet || _toolbarMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
||||
for (auto& buttonProxy : _tabletButtonProxies) {
|
||||
addButtonProxyToQmlTablet(tablet, buttonProxy.data());
|
||||
}
|
||||
|
@ -644,7 +715,7 @@ void TabletProxy::addButtonsToHomeScreen() {
|
|||
QObject::disconnect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToHomeScreen()));
|
||||
}
|
||||
|
||||
QObject* TabletProxy::getTabletSurface() {
|
||||
OffscreenQmlSurface* TabletProxy::getTabletSurface() {
|
||||
return _qmlOffscreenSurface;
|
||||
}
|
||||
|
||||
|
@ -663,35 +734,23 @@ void TabletProxy::desktopWindowClosed() {
|
|||
}
|
||||
|
||||
void TabletProxy::addButtonsToToolbar() {
|
||||
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
||||
QObject* toolbarProxy = tabletScriptingInterface->getSystemToolbarProxy();
|
||||
|
||||
Qt::ConnectionType connectionType = Qt::AutoConnection;
|
||||
if (QThread::currentThread() != toolbarProxy->thread()) {
|
||||
connectionType = Qt::BlockingQueuedConnection;
|
||||
}
|
||||
|
||||
Q_ASSERT(QThread::currentThread() == thread());
|
||||
ToolbarProxy* toolbarProxy = DependencyManager::get<TabletScriptingInterface>()->getSystemToolbarProxy();
|
||||
for (auto& buttonProxy : _tabletButtonProxies) {
|
||||
// copy properties from tablet button proxy to toolbar button proxy.
|
||||
QObject* toolbarButtonProxy = nullptr;
|
||||
bool hasResult = QMetaObject::invokeMethod(toolbarProxy, "addButton", connectionType, Q_RETURN_ARG(QObject*, toolbarButtonProxy), Q_ARG(QVariant, buttonProxy->getProperties()));
|
||||
if (hasResult) {
|
||||
buttonProxy->setToolbarButtonProxy(toolbarButtonProxy);
|
||||
} else {
|
||||
qCWarning(scriptengine) << "ToolbarProxy addButton has no result";
|
||||
}
|
||||
buttonProxy->setToolbarButtonProxy(toolbarProxy->addButton(buttonProxy->getProperties()));
|
||||
}
|
||||
|
||||
// make the toolbar visible
|
||||
QMetaObject::invokeMethod(toolbarProxy, "writeProperty", Qt::AutoConnection, Q_ARG(QString, "visible"), Q_ARG(QVariant, QVariant(true)));
|
||||
toolbarProxy->writeProperty("visible", QVariant(true));
|
||||
}
|
||||
|
||||
void TabletProxy::removeButtonsFromToolbar() {
|
||||
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
||||
QObject* toolbarProxy = tabletScriptingInterface->getSystemToolbarProxy();
|
||||
Q_ASSERT(QThread::currentThread() == thread());
|
||||
ToolbarProxy* toolbarProxy = DependencyManager::get<TabletScriptingInterface>()->getSystemToolbarProxy();
|
||||
for (auto& buttonProxy : _tabletButtonProxies) {
|
||||
// remove button from toolbarProxy
|
||||
QMetaObject::invokeMethod(toolbarProxy, "removeButton", Qt::AutoConnection, Q_ARG(QVariant, buttonProxy->getUuid().toString()));
|
||||
toolbarProxy->removeButton(buttonProxy->getUuid().toString());
|
||||
buttonProxy->setToolbarButtonProxy(nullptr);
|
||||
}
|
||||
}
|
||||
|
@ -753,34 +812,56 @@ TabletButtonProxy::TabletButtonProxy(const QVariantMap& properties) :
|
|||
_properties[UUID_KEY] = _uuid;
|
||||
_properties[OBJECT_NAME_KEY] = _uuid.toString();
|
||||
_properties[STABLE_ORDER_KEY] = _stableOrder;
|
||||
if (QThread::currentThread() != qApp->thread()) {
|
||||
qCWarning(uiLogging) << "Creating tablet button proxy on wrong thread";
|
||||
}
|
||||
}
|
||||
|
||||
TabletButtonProxy::~TabletButtonProxy() {
|
||||
qCDebug(uiLogging) << "Destroying tablet button proxy " ;
|
||||
if (QThread::currentThread() != thread()) {
|
||||
qCWarning(uiLogging) << "Destroying tablet button proxy on wrong thread";
|
||||
}
|
||||
}
|
||||
|
||||
void TabletButtonProxy::setQmlButton(QQuickItem* qmlButton) {
|
||||
std::lock_guard<std::mutex> guard(_buttonMutex);
|
||||
Q_ASSERT(QThread::currentThread() == qApp->thread());
|
||||
_qmlButton = qmlButton;
|
||||
}
|
||||
|
||||
void TabletButtonProxy::setToolbarButtonProxy(QObject* toolbarButtonProxy) {
|
||||
std::lock_guard<std::mutex> guard(_buttonMutex);
|
||||
Q_ASSERT(QThread::currentThread() == thread());
|
||||
_toolbarButtonProxy = toolbarButtonProxy;
|
||||
if (_toolbarButtonProxy) {
|
||||
QObject::connect(_toolbarButtonProxy, SIGNAL(clicked()), this, SLOT(clickedSlot()));
|
||||
}
|
||||
}
|
||||
|
||||
QVariantMap TabletButtonProxy::getProperties() const {
|
||||
std::lock_guard<std::mutex> guard(_buttonMutex);
|
||||
QVariantMap TabletButtonProxy::getProperties() {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QVariantMap result;
|
||||
QMetaObject::invokeMethod(this, "getProperties", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QVariantMap, result));
|
||||
return result;
|
||||
}
|
||||
|
||||
return _properties;
|
||||
}
|
||||
|
||||
void TabletButtonProxy::editProperties(QVariantMap properties) {
|
||||
std::lock_guard<std::mutex> guard(_buttonMutex);
|
||||
void TabletButtonProxy::editProperties(const QVariantMap& properties) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "editProperties", Q_ARG(QVariantMap, properties));
|
||||
return;
|
||||
}
|
||||
|
||||
QVariantMap::const_iterator iter = properties.constBegin();
|
||||
while (iter != properties.constEnd()) {
|
||||
_properties[iter.key()] = iter.value();
|
||||
if (_qmlButton) {
|
||||
QMetaObject::invokeMethod(_qmlButton, "changeProperty", Qt::AutoConnection, Q_ARG(QVariant, QVariant(iter.key())), Q_ARG(QVariant, iter.value()));
|
||||
const auto& key = iter.key();
|
||||
const auto& value = iter.value();
|
||||
if (!_properties.contains(key) || _properties[key] != value) {
|
||||
_properties[iter.key()] = iter.value();
|
||||
if (_qmlButton) {
|
||||
QMetaObject::invokeMethod(_qmlButton, "changeProperty", Qt::AutoConnection, Q_ARG(QVariant, QVariant(iter.key())), Q_ARG(QVariant, iter.value()));
|
||||
}
|
||||
}
|
||||
++iter;
|
||||
}
|
||||
|
@ -789,5 +870,3 @@ void TabletButtonProxy::editProperties(QVariantMap properties) {
|
|||
QMetaObject::invokeMethod(_toolbarButtonProxy, "editProperties", Qt::AutoConnection, Q_ARG(QVariantMap, properties));
|
||||
}
|
||||
}
|
||||
|
||||
#include "TabletScriptingInterface.moc"
|
|
@ -26,9 +26,13 @@
|
|||
|
||||
#include <DependencyManager.h>
|
||||
|
||||
class ToolbarProxy;
|
||||
class ToolbarScriptingInterface;
|
||||
|
||||
class TabletProxy;
|
||||
class TabletButtonProxy;
|
||||
class QmlWindowClass;
|
||||
class OffscreenQmlSurface;
|
||||
|
||||
/**jsdoc
|
||||
* @namespace Tablet
|
||||
|
@ -37,9 +41,9 @@ class TabletScriptingInterface : public QObject, public Dependency {
|
|||
Q_OBJECT
|
||||
public:
|
||||
TabletScriptingInterface();
|
||||
~TabletScriptingInterface();
|
||||
|
||||
void setToolbarScriptingInterface(QObject* toolbarScriptingInterface) { _toolbarScriptingInterface = toolbarScriptingInterface; }
|
||||
QObject* getSystemToolbarProxy();
|
||||
void setToolbarScriptingInterface(ToolbarScriptingInterface* toolbarScriptingInterface) { _toolbarScriptingInterface = toolbarScriptingInterface; }
|
||||
|
||||
/**jsdoc
|
||||
* Creates or retruns a new TabletProxy and returns it.
|
||||
|
@ -51,7 +55,7 @@ public:
|
|||
|
||||
void setToolbarMode(bool toolbarMode);
|
||||
|
||||
void setQmlTabletRoot(QString tabletId, QQuickItem* qmlTabletRoot, QObject* qmlOffscreenSurface);
|
||||
void setQmlTabletRoot(QString tabletId, OffscreenQmlSurface* offscreenQmlSurface);
|
||||
|
||||
void processEvent(const QKeyEvent* event);
|
||||
|
||||
|
@ -67,13 +71,14 @@ signals:
|
|||
void tabletNotification();
|
||||
|
||||
private:
|
||||
friend class TabletProxy;
|
||||
void processMenuEvents(QObject* object, const QKeyEvent* event);
|
||||
void processTabletEvents(QObject* object, const QKeyEvent* event);
|
||||
ToolbarProxy* getSystemToolbarProxy();
|
||||
|
||||
protected:
|
||||
std::mutex _mapMutex;
|
||||
std::map<QString, TabletProxy*> _tabletProxies;
|
||||
QObject* _toolbarScriptingInterface { nullptr };
|
||||
ToolbarScriptingInterface* _toolbarScriptingInterface { nullptr };
|
||||
bool _toolbarMode { false };
|
||||
};
|
||||
|
||||
|
@ -90,19 +95,20 @@ class TabletProxy : public QObject {
|
|||
Q_PROPERTY(bool landscape READ getLandscape WRITE setLandscape)
|
||||
Q_PROPERTY(bool tabletShown MEMBER _tabletShown NOTIFY tabletShownChanged)
|
||||
public:
|
||||
TabletProxy(QObject* parent, QString name);
|
||||
TabletProxy(QObject* parent, const QString& name);
|
||||
~TabletProxy();
|
||||
|
||||
void setQmlTabletRoot(QQuickItem* qmlTabletRoot, QObject* qmlOffscreenSurface);
|
||||
|
||||
Q_INVOKABLE void gotoMenuScreen(const QString& submenu = "");
|
||||
|
||||
QString getName() const { return _name; }
|
||||
void setQmlTabletRoot(OffscreenQmlSurface* offscreenQmlSurface);
|
||||
|
||||
const QString getName() const { return _name; }
|
||||
bool getToolbarMode() const { return _toolbarMode; }
|
||||
void setToolbarMode(bool toolbarMode);
|
||||
|
||||
|
||||
Q_INVOKABLE void gotoMenuScreen(const QString& submenu = "");
|
||||
Q_INVOKABLE void initialScreen(const QVariant& url);
|
||||
|
||||
|
||||
/**jsdoc
|
||||
* transition to the home screen
|
||||
* @function TabletProxy#gotoHomeScreen
|
||||
|
@ -143,28 +149,28 @@ public:
|
|||
* @param properties {Object} button properties UI_TABLET_HACK: enumerate these when we figure out what they should be!
|
||||
* @returns {TabletButtonProxy}
|
||||
*/
|
||||
Q_INVOKABLE QObject* addButton(const QVariant& properties);
|
||||
Q_INVOKABLE TabletButtonProxy* addButton(const QVariant& properties);
|
||||
|
||||
/**jsdoc
|
||||
* removes button from the tablet
|
||||
* @function TabletProxy.removeButton
|
||||
* @param tabletButtonProxy {TabletButtonProxy} button to be removed
|
||||
*/
|
||||
Q_INVOKABLE void removeButton(QObject* tabletButtonProxy);
|
||||
Q_INVOKABLE void removeButton(TabletButtonProxy* tabletButtonProxy);
|
||||
|
||||
/**jsdoc
|
||||
* Used to send an event to the html/js embedded in the tablet
|
||||
* @function TabletProxy#emitScriptEvent
|
||||
* @param msg {object|string}
|
||||
*/
|
||||
Q_INVOKABLE void emitScriptEvent(QVariant msg);
|
||||
Q_INVOKABLE void emitScriptEvent(const QVariant& msg);
|
||||
|
||||
/**jsdoc
|
||||
* Used to send an event to the qml embedded in the tablet
|
||||
* @function TabletProxy#sendToQml
|
||||
* @param msg {object|string}
|
||||
*/
|
||||
Q_INVOKABLE void sendToQml(QVariant msg);
|
||||
Q_INVOKABLE void sendToQml(const QVariant& msg);
|
||||
|
||||
/**jsdoc
|
||||
* Check if the tablet is on the homescreen
|
||||
|
@ -180,11 +186,11 @@ public:
|
|||
Q_INVOKABLE void setLandscape(bool landscape) { _landscape = landscape; }
|
||||
Q_INVOKABLE bool getLandscape() { return _landscape; }
|
||||
|
||||
Q_INVOKABLE bool isPathLoaded(QVariant path);
|
||||
Q_INVOKABLE bool isPathLoaded(const QVariant& path);
|
||||
|
||||
QQuickItem* getTabletRoot() const { return _qmlTabletRoot; }
|
||||
|
||||
QObject* getTabletSurface();
|
||||
OffscreenQmlSurface* getTabletSurface();
|
||||
|
||||
QQuickItem* getQmlTablet() const;
|
||||
|
||||
|
@ -225,7 +231,7 @@ signals:
|
|||
protected slots:
|
||||
void addButtonsToHomeScreen();
|
||||
void desktopWindowClosed();
|
||||
void emitWebEvent(QVariant msg);
|
||||
void emitWebEvent(const QVariant& msg);
|
||||
protected:
|
||||
void removeButtonsFromHomeScreen();
|
||||
void loadHomeScreen(bool forceOntoHomeScreen);
|
||||
|
@ -236,10 +242,9 @@ protected:
|
|||
QVariant _initialPath { "" };
|
||||
QVariant _currentPathLoaded { "" };
|
||||
QString _name;
|
||||
std::mutex _tabletMutex;
|
||||
std::vector<QSharedPointer<TabletButtonProxy>> _tabletButtonProxies;
|
||||
QQuickItem* _qmlTabletRoot { nullptr };
|
||||
QObject* _qmlOffscreenSurface { nullptr };
|
||||
OffscreenQmlSurface* _qmlOffscreenSurface { nullptr };
|
||||
QmlWindowClass* _desktopWindow { nullptr };
|
||||
bool _toolbarMode { false };
|
||||
bool _tabletShown { false };
|
||||
|
@ -251,9 +256,6 @@ protected:
|
|||
|
||||
Q_DECLARE_METATYPE(TabletProxy*);
|
||||
|
||||
QScriptValue tabletToScriptValue(QScriptEngine* engine, TabletProxy* const &in);
|
||||
void tabletFromScriptValue(const QScriptValue& value, TabletProxy* &out);
|
||||
|
||||
/**jsdoc
|
||||
* @class TabletButtonProxy
|
||||
* @property uuid {QUuid} READ_ONLY: uniquely identifies this button
|
||||
|
@ -263,6 +265,7 @@ class TabletButtonProxy : public QObject {
|
|||
Q_PROPERTY(QUuid uuid READ getUuid)
|
||||
public:
|
||||
TabletButtonProxy(const QVariantMap& properties);
|
||||
~TabletButtonProxy();
|
||||
|
||||
void setQmlButton(QQuickItem* qmlButton);
|
||||
void setToolbarButtonProxy(QObject* toolbarButtonProxy);
|
||||
|
@ -274,14 +277,14 @@ public:
|
|||
* @function TabletButtonProxy#getProperties
|
||||
* @returns {ButtonProperties}
|
||||
*/
|
||||
Q_INVOKABLE QVariantMap getProperties() const;
|
||||
Q_INVOKABLE QVariantMap getProperties();
|
||||
|
||||
/**jsdoc
|
||||
* Replace the values of some of this button's properties
|
||||
* @function TabletButtonProxy#editProperties
|
||||
* @param {ButtonProperties} properties - set of properties to change
|
||||
*/
|
||||
Q_INVOKABLE void editProperties(QVariantMap properties);
|
||||
Q_INVOKABLE void editProperties(const QVariantMap& properties);
|
||||
|
||||
public slots:
|
||||
void clickedSlot() { emit clicked(); }
|
||||
|
@ -297,12 +300,13 @@ signals:
|
|||
protected:
|
||||
QUuid _uuid;
|
||||
int _stableOrder;
|
||||
mutable std::mutex _buttonMutex;
|
||||
QQuickItem* _qmlButton { nullptr };
|
||||
QObject* _toolbarButtonProxy { nullptr };
|
||||
QVariantMap _properties;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(TabletButtonProxy*);
|
||||
|
||||
/**jsdoc
|
||||
* @typedef TabletButtonProxy.ButtonProperties
|
||||
* @property {string} icon - url to button icon. (50 x 50)
|
124
libraries/ui/src/ui/ToolbarScriptingInterface.cpp
Normal file
124
libraries/ui/src/ui/ToolbarScriptingInterface.cpp
Normal file
|
@ -0,0 +1,124 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2016-06-16
|
||||
// Copyright 2013-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 "ToolbarScriptingInterface.h"
|
||||
|
||||
#include <QtCore/QThread>
|
||||
#include <QtQuick/QQuickItem>
|
||||
#include <QtScript/QScriptValue>
|
||||
#include <QtScript/QScriptEngine>
|
||||
#include "../OffscreenUi.h"
|
||||
|
||||
QScriptValue toolbarToScriptValue(QScriptEngine* engine, ToolbarProxy* const &in) {
|
||||
if (!in) {
|
||||
return engine->undefinedValue();
|
||||
}
|
||||
return engine->newQObject(in, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects);
|
||||
}
|
||||
|
||||
void toolbarFromScriptValue(const QScriptValue& value, ToolbarProxy* &out) {
|
||||
out = qobject_cast<ToolbarProxy*>(value.toQObject());
|
||||
}
|
||||
|
||||
QScriptValue toolbarButtonToScriptValue(QScriptEngine* engine, ToolbarButtonProxy* const &in) {
|
||||
if (!in) {
|
||||
return engine->undefinedValue();
|
||||
}
|
||||
return engine->newQObject(in, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects);
|
||||
}
|
||||
|
||||
void toolbarButtonFromScriptValue(const QScriptValue& value, ToolbarButtonProxy* &out) {
|
||||
out = qobject_cast<ToolbarButtonProxy*>(value.toQObject());
|
||||
}
|
||||
|
||||
|
||||
ToolbarButtonProxy::ToolbarButtonProxy(QObject* qmlObject, QObject* parent) : QmlWrapper(qmlObject, parent) {
|
||||
Q_ASSERT(QThread::currentThread() == qApp->thread());
|
||||
_qmlButton = qobject_cast<QQuickItem*>(qmlObject);
|
||||
connect(qmlObject, SIGNAL(clicked()), this, SIGNAL(clicked()));
|
||||
}
|
||||
|
||||
void ToolbarButtonProxy::editProperties(const QVariantMap& properties) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "editProperties", Q_ARG(QVariantMap, properties));
|
||||
return;
|
||||
}
|
||||
|
||||
QVariantMap::const_iterator iter = properties.constBegin();
|
||||
while (iter != properties.constEnd()) {
|
||||
_properties[iter.key()] = iter.value();
|
||||
if (_qmlButton) {
|
||||
// [01/25 14:26:20] [WARNING] [default] QMetaObject::invokeMethod: No such method ToolbarButton_QMLTYPE_195::changeProperty(QVariant,QVariant)
|
||||
QMetaObject::invokeMethod(_qmlButton, "changeProperty", Qt::AutoConnection,
|
||||
Q_ARG(QVariant, QVariant(iter.key())), Q_ARG(QVariant, iter.value()));
|
||||
}
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
|
||||
ToolbarProxy::ToolbarProxy(QObject* qmlObject, QObject* parent) : QmlWrapper(qmlObject, parent) {
|
||||
Q_ASSERT(QThread::currentThread() == qApp->thread());
|
||||
}
|
||||
|
||||
ToolbarButtonProxy* ToolbarProxy::addButton(const QVariant& properties) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
ToolbarButtonProxy* result = nullptr;
|
||||
QMetaObject::invokeMethod(this, "addButton", Qt::BlockingQueuedConnection, Q_RETURN_ARG(ToolbarButtonProxy*, result), Q_ARG(QVariant, properties));
|
||||
return result;
|
||||
}
|
||||
|
||||
QVariant resultVar;
|
||||
bool invokeResult = QMetaObject::invokeMethod(_qmlObject, "addButton", Q_RETURN_ARG(QVariant, resultVar), Q_ARG(QVariant, properties));
|
||||
if (!invokeResult) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QObject* rawButton = qvariant_cast<QObject *>(resultVar);
|
||||
if (!rawButton) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return new ToolbarButtonProxy(rawButton, this);
|
||||
}
|
||||
|
||||
void ToolbarProxy::removeButton(const QVariant& name) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "removeButton", Q_ARG(QVariant, name));
|
||||
return;
|
||||
}
|
||||
|
||||
QMetaObject::invokeMethod(_qmlObject, "removeButton", Q_ARG(QVariant, name));
|
||||
}
|
||||
|
||||
|
||||
ToolbarProxy* ToolbarScriptingInterface::getToolbar(const QString& toolbarId) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
ToolbarProxy* result = nullptr;
|
||||
QMetaObject::invokeMethod(this, "getToolbar", Qt::BlockingQueuedConnection, Q_RETURN_ARG(ToolbarProxy*, result), Q_ARG(QString, toolbarId));
|
||||
return result;
|
||||
}
|
||||
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
auto desktop = offscreenUi->getDesktop();
|
||||
Qt::ConnectionType connectionType = Qt::AutoConnection;
|
||||
if (QThread::currentThread() != desktop->thread()) {
|
||||
connectionType = Qt::BlockingQueuedConnection;
|
||||
}
|
||||
QVariant resultVar;
|
||||
bool invokeResult = QMetaObject::invokeMethod(desktop, "getToolbar", connectionType, Q_RETURN_ARG(QVariant, resultVar), Q_ARG(QVariant, toolbarId));
|
||||
if (!invokeResult) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QObject* rawToolbar = qvariant_cast<QObject *>(resultVar);
|
||||
if (!rawToolbar) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return new ToolbarProxy(rawToolbar);
|
||||
}
|
56
libraries/ui/src/ui/ToolbarScriptingInterface.h
Normal file
56
libraries/ui/src/ui/ToolbarScriptingInterface.h
Normal file
|
@ -0,0 +1,56 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2016-06-16
|
||||
// Copyright 2013-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_ToolbarScriptingInterface_h
|
||||
#define hifi_ToolbarScriptingInterface_h
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtScript/QScriptValue>
|
||||
|
||||
#include <DependencyManager.h>
|
||||
#include "QmlWrapper.h"
|
||||
|
||||
class QQuickItem;
|
||||
|
||||
class ToolbarButtonProxy : public QmlWrapper {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ToolbarButtonProxy(QObject* qmlObject, QObject* parent = nullptr);
|
||||
Q_INVOKABLE void editProperties(const QVariantMap& properties);
|
||||
|
||||
signals:
|
||||
void clicked();
|
||||
|
||||
protected:
|
||||
QQuickItem* _qmlButton { nullptr };
|
||||
QVariantMap _properties;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(ToolbarButtonProxy*);
|
||||
|
||||
class ToolbarProxy : public QmlWrapper {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ToolbarProxy(QObject* qmlObject, QObject* parent = nullptr);
|
||||
Q_INVOKABLE ToolbarButtonProxy* addButton(const QVariant& properties);
|
||||
Q_INVOKABLE void removeButton(const QVariant& name);
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(ToolbarProxy*);
|
||||
|
||||
class ToolbarScriptingInterface : public QObject, public Dependency {
|
||||
Q_OBJECT
|
||||
public:
|
||||
Q_INVOKABLE ToolbarProxy* getToolbar(const QString& toolbarId);
|
||||
};
|
||||
|
||||
|
||||
#endif // hifi_ToolbarScriptingInterface_h
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue