diff --git a/assignment-client/src/octree/OctreeQueryNode.cpp b/assignment-client/src/octree/OctreeQueryNode.cpp index 10d30ad1ae..ede6dee5e3 100644 --- a/assignment-client/src/octree/OctreeQueryNode.cpp +++ b/assignment-client/src/octree/OctreeQueryNode.cpp @@ -158,10 +158,11 @@ bool OctreeQueryNode::shouldSuppressDuplicatePacket() { void OctreeQueryNode::init() { _myPacketType = getMyPacketType(); - resetOctreePacket(true); // don't bump sequence + resetOctreePacket(); // don't bump sequence } -void OctreeQueryNode::resetOctreePacket(bool lastWasSurpressed) { + +void OctreeQueryNode::resetOctreePacket() { // if shutting down, return immediately if (_isShuttingDown) { return; @@ -196,15 +197,12 @@ void OctreeQueryNode::resetOctreePacket(bool lastWasSurpressed) { *flagsAt = flags; _octreePacketAt += sizeof(OCTREE_PACKET_FLAGS); _octreePacketAvailableBytes -= sizeof(OCTREE_PACKET_FLAGS); - + // pack in sequence number OCTREE_PACKET_SEQUENCE* sequenceAt = (OCTREE_PACKET_SEQUENCE*)_octreePacketAt; *sequenceAt = _sequenceNumber; _octreePacketAt += sizeof(OCTREE_PACKET_SEQUENCE); _octreePacketAvailableBytes -= sizeof(OCTREE_PACKET_SEQUENCE); - if (!(lastWasSurpressed || _lastOctreePacketLength == (numBytesPacketHeader + OCTREE_PACKET_EXTRA_HEADERS_SIZE))) { - _sequenceNumber++; - } // pack in timestamp OCTREE_PACKET_SENT_TIME now = usecTimestampNow(); @@ -365,3 +363,6 @@ void OctreeQueryNode::dumpOutOfView() { } } +void OctreeQueryNode::incrementSequenceNumber() { + _sequenceNumber++; +} \ No newline at end of file diff --git a/assignment-client/src/octree/OctreeQueryNode.h b/assignment-client/src/octree/OctreeQueryNode.h index 7b42208f16..1bb3a72a0c 100644 --- a/assignment-client/src/octree/OctreeQueryNode.h +++ b/assignment-client/src/octree/OctreeQueryNode.h @@ -35,7 +35,7 @@ public: void init(); // called after creation to set up some virtual items virtual PacketType getMyPacketType() const = 0; - void resetOctreePacket(bool lastWasSurpressed = false); // resets octree packet to after "V" header + void resetOctreePacket(); // resets octree packet to after "V" header void writeToPacket(const unsigned char* buffer, unsigned int bytes); // writes to end of packet @@ -99,6 +99,8 @@ public: void nodeKilled(); void forceNodeShutdown(); bool isShuttingDown() const { return _isShuttingDown; } + + void incrementSequenceNumber(); private slots: void sendThreadFinished(); @@ -135,8 +137,9 @@ private: float _lastClientOctreeSizeScale; bool _lodChanged; bool _lodInitialized; - + OCTREE_PACKET_SEQUENCE _sequenceNumber; + quint64 _lastRootTimestamp; PacketType _myPacketType; diff --git a/assignment-client/src/octree/OctreeSendThread.cpp b/assignment-client/src/octree/OctreeSendThread.cpp index d8a9f3d1ea..8d6803adac 100644 --- a/assignment-client/src/octree/OctreeSendThread.cpp +++ b/assignment-client/src/octree/OctreeSendThread.cpp @@ -9,8 +9,6 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include - #include #include #include @@ -139,7 +137,7 @@ int OctreeSendThread::handlePacketSend(OctreeQueryNode* nodeData, int& trueBytes // obscure the packet and not send it. This allows the callers and upper level logic to not need to know about // this rate control savings. if (nodeData->shouldSuppressDuplicatePacket()) { - nodeData->resetOctreePacket(true); // we still need to reset it though! + nodeData->resetOctreePacket(); // we still need to reset it though! return packetsSent; // without sending... } @@ -244,6 +242,7 @@ int OctreeSendThread::handlePacketSend(OctreeQueryNode* nodeData, int& trueBytes trueBytesSent += nodeData->getPacketLength(); truePacketsSent++; packetsSent++; + nodeData->incrementSequenceNumber(); nodeData->resetOctreePacket(); } diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 9ad36e6956..f979de64c0 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -218,6 +218,8 @@ bool DomainServer::optionallySetupAssignmentPayment() { } } + qDebug() << "Assignments will be paid for via" << qPrintable(_oauthProviderURL.toString()); + // assume that the fact we are authing against HF data server means we will pay for assignments // setup a timer to send transactions to pay assigned nodes every 30 seconds QTimer* creditSetupTimer = new QTimer(this); @@ -731,10 +733,11 @@ void DomainServer::setupPendingAssignmentCredits() { qint64 elapsedMsecsSinceLastPayment = nodeData->getPaymentIntervalTimer().elapsed(); nodeData->getPaymentIntervalTimer().restart(); - const float CREDITS_PER_HOUR = 3; + const float CREDITS_PER_HOUR = 0.10f; const float CREDITS_PER_MSEC = CREDITS_PER_HOUR / (60 * 60 * 1000); + const int SATOSHIS_PER_MSEC = CREDITS_PER_MSEC * powf(10.0f, 8.0f); - float pendingCredits = elapsedMsecsSinceLastPayment * CREDITS_PER_MSEC; + float pendingCredits = elapsedMsecsSinceLastPayment * SATOSHIS_PER_MSEC; if (existingTransaction) { existingTransaction->incrementAmount(pendingCredits); diff --git a/examples/hydraMove.js b/examples/hydraMove.js index 60823ffb36..ed6a5a4f44 100644 --- a/examples/hydraMove.js +++ b/examples/hydraMove.js @@ -26,6 +26,7 @@ var THRUST_INCREASE_RATE = 1.05; var MAX_THRUST_MULTIPLIER = 75.0; var thrustMultiplier = INITIAL_THRUST_MULTPLIER; var grabDelta = { x: 0, y: 0, z: 0}; +var grabStartPosition = { x: 0, y: 0, z: 0}; var grabDeltaVelocity = { x: 0, y: 0, z: 0}; var grabStartRotation = { x: 0, y: 0, z: 0, w: 1}; var grabCurrentRotation = { x: 0, y: 0, z: 0, w: 1}; @@ -50,7 +51,7 @@ var JOYSTICK_PITCH_MAG = PITCH_MAG * 0.5; var LEFT_PALM = 0; -var LEFT_BUTTON_4 = 5; +var LEFT_BUTTON_4 = 4; var LEFT_BUTTON_FWD = 5; var RIGHT_PALM = 2; var RIGHT_BUTTON_4 = 10; @@ -63,6 +64,63 @@ function printVector(text, v, decimals) { } var debug = false; +var RED_COLOR = { red: 255, green: 0, blue: 0 }; +var GRAY_COLOR = { red: 25, green: 25, blue: 25 }; +var defaultPosition = { x: 0, y: 0, z: 0}; +var RADIUS = 0.05; +var greenSphere = -1; +var redSphere = -1; + +function createDebugOverlay() { + + if (greenSphere == -1) { + greenSphere = Overlays.addOverlay("sphere", { + position: defaultPosition, + size: RADIUS, + color: GRAY_COLOR, + alpha: 1, + visible: true, + solid: true, + anchor: "MyAvatar" + }); + redSphere = Overlays.addOverlay("sphere", { + position: defaultPosition, + size: RADIUS, + color: RED_COLOR, + alpha: 1, + visible: true, + solid: true, + anchor: "MyAvatar" + }); + } +} + +function destroyDebugOverlay() { + if (greenSphere != -1) { + Overlays.deleteOverlay(greenSphere); + Overlays.deleteOverlay(redSphere); + greenSphere = -1; + redSphere = -1; + } +} + +function displayDebug() { + if (!(grabbingWithRightHand || grabbingWithLeftHand)) { + if (greenSphere != -1) { + destroyDebugOverlay(); + } + } else { + // update debug indicator + if (greenSphere == -1) { + createDebugOverlay(); + } + + var displayOffset = { x:0, y:0.5, z:-0.5 }; + + Overlays.editOverlay(greenSphere, { position: Vec3.sum(grabStartPosition, displayOffset) } ); + Overlays.editOverlay(redSphere, { position: Vec3.sum(Vec3.sum(grabStartPosition, grabDelta), displayOffset), size: RADIUS + (0.25 * Vec3.length(grabDelta)) } ); + } +} function getJoystickPosition(palm) { // returns CONTROLLER_ID position in avatar local frame @@ -220,6 +278,8 @@ function flyWithHydra(deltaTime) { MyAvatar.headPitch = newPitch; } handleGrabBehavior(deltaTime); + displayDebug(); + } Script.update.connect(flyWithHydra); diff --git a/examples/lookWithMouse.js b/examples/lookWithMouse.js index 256a3ea67c..2fe12cce24 100644 --- a/examples/lookWithMouse.js +++ b/examples/lookWithMouse.js @@ -11,7 +11,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -var alwaysLook = true; // if you want the mouse look to happen only when you click, change this to false +var alwaysLook = false; // if you want the mouse look to happen only when you click, change this to false var isMouseDown = false; var lastX = 0; var lastY = 0; diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index fd09e530c1..07309fab85 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -237,12 +237,12 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : connect(&nodeList->getDomainHandler(), SIGNAL(connectedToDomain(const QString&)), SLOT(connectedToDomain(const QString&))); // update our location every 5 seconds in the data-server, assuming that we are authenticated with one - const float DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5.0f * 1000.0f; + const qint64 DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5 * 1000; QTimer* locationUpdateTimer = new QTimer(this); connect(locationUpdateTimer, &QTimer::timeout, this, &Application::updateLocationInServer); locationUpdateTimer->start(DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS); - + connect(nodeList, &NodeList::nodeAdded, this, &Application::nodeAdded); connect(nodeList, &NodeList::nodeKilled, this, &Application::nodeKilled); connect(nodeList, SIGNAL(nodeKilled(SharedNodePointer)), SLOT(nodeKilled(SharedNodePointer))); @@ -251,9 +251,18 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : connect(nodeList, &NodeList::uuidChanged, this, &Application::updateWindowTitle); connect(nodeList, SIGNAL(uuidChanged(const QUuid&)), _myAvatar, SLOT(setSessionUUID(const QUuid&))); connect(nodeList, &NodeList::limitOfSilentDomainCheckInsReached, nodeList, &NodeList::reset); - + // connect to appropriate slots on AccountManager AccountManager& accountManager = AccountManager::getInstance(); + + const qint64 BALANCE_UPDATE_INTERVAL_MSECS = 5 * 1000; + + QTimer* balanceUpdateTimer = new QTimer(this); + connect(balanceUpdateTimer, &QTimer::timeout, &accountManager, &AccountManager::updateBalance); + balanceUpdateTimer->start(BALANCE_UPDATE_INTERVAL_MSECS); + + connect(&accountManager, &AccountManager::balanceChanged, this, &Application::updateWindowTitle); + connect(&accountManager, &AccountManager::authRequired, Menu::getInstance(), &Menu::loginForCurrentDomain); connect(&accountManager, &AccountManager::usernameChanged, this, &Application::updateWindowTitle); @@ -3094,7 +3103,18 @@ void Application::updateWindowTitle(){ QString username = AccountManager::getInstance().getAccountInfo().getUsername(); QString title = QString() + (!username.isEmpty() ? username + " @ " : QString()) + nodeList->getDomainHandler().getHostname() + buildVersion; - qDebug("Application title set to: %s", title.toStdString().c_str()); + + AccountManager& accountManager = AccountManager::getInstance(); + if (accountManager.getAccountInfo().hasBalance()) { + float creditBalance = accountManager.getAccountInfo().getBalance() * pow(10.0f, -8.0f); + + QString creditBalanceString; + creditBalanceString.sprintf("%.8f", creditBalance); + + title += " - ₵" + creditBalanceString; + } + + qDebug("Application title set to: %s", title.toStdString().c_str()); _window->setWindowTitle(title); } diff --git a/interface/src/AudioReflector.cpp b/interface/src/AudioReflector.cpp index e66735c403..52d23b4fee 100644 --- a/interface/src/AudioReflector.cpp +++ b/interface/src/AudioReflector.cpp @@ -17,7 +17,7 @@ const float MINIMUM_ATTENUATION_TO_REFLECT = 1.0f / 256.0f; const float DEFAULT_DISTANCE_SCALING_FACTOR = 2.0f; const float MAXIMUM_DELAY_MS = 1000.0 * 20.0f; // stop reflecting after path is this long const int DEFAULT_DIFFUSION_FANOUT = 5; -const int ABSOLUTE_MAXIMUM_BOUNCE_COUNT = 10; +const unsigned int ABSOLUTE_MAXIMUM_BOUNCE_COUNT = 10; const float DEFAULT_LOCAL_ATTENUATION_FACTOR = 0.125; const float DEFAULT_COMB_FILTER_WINDOW = 0.05f; //ms delay differential to avoid diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 85c1734e56..042d2cdc7c 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -430,8 +430,6 @@ void MyAvatar::setGravity(const glm::vec3& gravity) { AnimationHandlePointer MyAvatar::addAnimationHandle() { AnimationHandlePointer handle = _skeletonModel.createAnimationHandle(); - handle->setLoop(true); - handle->start(); _animationHandles.append(handle); return handle; } @@ -441,10 +439,12 @@ void MyAvatar::removeAnimationHandle(const AnimationHandlePointer& handle) { _animationHandles.removeOne(handle); } -void MyAvatar::startAnimation(const QString& url, float fps, float priority, bool loop, const QStringList& maskedJoints) { +void MyAvatar::startAnimation(const QString& url, float fps, float priority, + bool loop, bool hold, int firstFrame, int lastFrame, const QStringList& maskedJoints) { if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "startAnimation", Q_ARG(const QString&, url), - Q_ARG(float, fps), Q_ARG(float, priority), Q_ARG(bool, loop), Q_ARG(const QStringList&, maskedJoints)); + QMetaObject::invokeMethod(this, "startAnimation", Q_ARG(const QString&, url), Q_ARG(float, fps), + Q_ARG(float, priority), Q_ARG(bool, loop), Q_ARG(bool, hold), Q_ARG(int, firstFrame), + Q_ARG(int, lastFrame), Q_ARG(const QStringList&, maskedJoints)); return; } AnimationHandlePointer handle = _skeletonModel.createAnimationHandle(); @@ -452,10 +452,54 @@ void MyAvatar::startAnimation(const QString& url, float fps, float priority, boo handle->setFPS(fps); handle->setPriority(priority); handle->setLoop(loop); + handle->setHold(hold); + handle->setFirstFrame(firstFrame); + handle->setLastFrame(lastFrame); handle->setMaskedJoints(maskedJoints); handle->start(); } +void MyAvatar::startAnimationByRole(const QString& role, const QString& url, float fps, float priority, + bool loop, bool hold, int firstFrame, int lastFrame, const QStringList& maskedJoints) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "startAnimationByRole", Q_ARG(const QString&, role), Q_ARG(const QString&, url), + Q_ARG(float, fps), Q_ARG(float, priority), Q_ARG(bool, loop), Q_ARG(bool, hold), Q_ARG(int, firstFrame), + Q_ARG(int, lastFrame), Q_ARG(const QStringList&, maskedJoints)); + return; + } + // check for a configured animation for the role + foreach (const AnimationHandlePointer& handle, _animationHandles) { + if (handle->getRole() == role) { + handle->start(); + return; + } + } + // no joy; use the parameters provided + AnimationHandlePointer handle = _skeletonModel.createAnimationHandle(); + handle->setRole(role); + handle->setURL(url); + handle->setFPS(fps); + handle->setPriority(priority); + handle->setLoop(loop); + handle->setHold(hold); + handle->setFirstFrame(firstFrame); + handle->setLastFrame(lastFrame); + handle->setMaskedJoints(maskedJoints); + handle->start(); +} + +void MyAvatar::stopAnimationByRole(const QString& role) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "stopAnimationByRole", Q_ARG(const QString&, role)); + return; + } + foreach (const AnimationHandlePointer& handle, _skeletonModel.getRunningAnimations()) { + if (handle->getRole() == role) { + handle->stop(); + } + } +} + void MyAvatar::stopAnimation(const QString& url) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "stopAnimation", Q_ARG(const QString&, url)); @@ -464,7 +508,6 @@ void MyAvatar::stopAnimation(const QString& url) { foreach (const AnimationHandlePointer& handle, _skeletonModel.getRunningAnimations()) { if (handle->getURL() == url) { handle->stop(); - return; } } } @@ -511,9 +554,15 @@ void MyAvatar::saveData(QSettings* settings) { for (int i = 0; i < _animationHandles.size(); i++) { settings->setArrayIndex(i); const AnimationHandlePointer& pointer = _animationHandles.at(i); + settings->setValue("role", pointer->getRole()); settings->setValue("url", pointer->getURL()); settings->setValue("fps", pointer->getFPS()); settings->setValue("priority", pointer->getPriority()); + settings->setValue("loop", pointer->getLoop()); + settings->setValue("hold", pointer->getHold()); + settings->setValue("startAutomatically", pointer->getStartAutomatically()); + settings->setValue("firstFrame", pointer->getFirstFrame()); + settings->setValue("lastFrame", pointer->getLastFrame()); settings->setValue("maskedJoints", pointer->getMaskedJoints()); } settings->endArray(); @@ -578,9 +627,15 @@ void MyAvatar::loadData(QSettings* settings) { for (int i = 0; i < animationCount; i++) { settings->setArrayIndex(i); const AnimationHandlePointer& handle = _animationHandles.at(i); + handle->setRole(settings->value("role", "idle").toString()); handle->setURL(settings->value("url").toUrl()); handle->setFPS(loadSetting(settings, "fps", 30.0f)); handle->setPriority(loadSetting(settings, "priority", 1.0f)); + handle->setLoop(settings->value("loop", true).toBool()); + handle->setHold(settings->value("hold", false).toBool()); + handle->setStartAutomatically(settings->value("startAutomatically", true).toBool()); + handle->setFirstFrame(settings->value("firstFrame", 0).toInt()); + handle->setLastFrame(settings->value("lastFrame", INT_MAX).toInt()); handle->setMaskedJoints(settings->value("maskedJoints").toStringList()); } settings->endArray(); @@ -698,17 +753,19 @@ glm::vec3 MyAvatar::getUprightHeadPosition() const { return _position + getWorldAlignedOrientation() * glm::vec3(0.0f, getPelvisToHeadLength(), 0.0f); } +const float JOINT_PRIORITY = 2.0f; + void MyAvatar::setJointData(int index, const glm::quat& rotation) { Avatar::setJointData(index, rotation); if (QThread::currentThread() == thread()) { - _skeletonModel.setJointState(index, true, rotation); + _skeletonModel.setJointState(index, true, rotation, JOINT_PRIORITY); } } void MyAvatar::clearJointData(int index) { Avatar::clearJointData(index); if (QThread::currentThread() == thread()) { - _skeletonModel.setJointState(index, false); + _skeletonModel.setJointState(index, false, glm::quat(), JOINT_PRIORITY); } } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 9d6f22264f..a8eb3babd0 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -67,12 +67,21 @@ public: void removeAnimationHandle(const AnimationHandlePointer& handle); /// Allows scripts to run animations. - Q_INVOKABLE void startAnimation(const QString& url, float fps = 30.0f, - float priority = 1.0f, bool loop = false, const QStringList& maskedJoints = QStringList()); + Q_INVOKABLE void startAnimation(const QString& url, float fps = 30.0f, float priority = 1.0f, bool loop = false, + bool hold = false, int firstFrame = 0, int lastFrame = INT_MAX, const QStringList& maskedJoints = QStringList()); /// Stops an animation as identified by a URL. Q_INVOKABLE void stopAnimation(const QString& url); + /// Starts an animation by its role, using the provided URL and parameters if the avatar doesn't have a custom + /// animation for the role. + Q_INVOKABLE void startAnimationByRole(const QString& role, const QString& url = QString(), float fps = 30.0f, + float priority = 1.0f, bool loop = false, bool hold = false, int firstFrame = 0, + int lastFrame = INT_MAX, const QStringList& maskedJoints = QStringList()); + + /// Stops an animation identified by its role. + Q_INVOKABLE void stopAnimationByRole(const QString& role); + // get/set avatar data void saveData(QSettings* settings); void loadData(QSettings* settings); diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 09871ad38b..429ab1cf30 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -21,6 +21,8 @@ SkeletonModel::SkeletonModel(Avatar* owningAvatar) : _owningAvatar(owningAvatar) { } +const float PALM_PRIORITY = 3.0f; + void SkeletonModel::simulate(float deltaTime, bool fullUpdate) { setTranslation(_owningAvatar->getPosition()); setRotation(_owningAvatar->getOrientation() * glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f))); @@ -43,7 +45,7 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) { } int jointIndex = geometry.humanIKJointIndices.at(humanIKJointIndex); if (jointIndex != -1) { - setJointRotation(jointIndex, _rotation * prioVR->getJointRotations().at(i), true); + setJointRotation(jointIndex, _rotation * prioVR->getJointRotations().at(i), true, PALM_PRIORITY); } } return; @@ -58,16 +60,16 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) { if (leftPalmIndex == -1) { // palms are not yet set, use mouse if (_owningAvatar->getHandState() == HAND_STATE_NULL) { - restoreRightHandPosition(HAND_RESTORATION_RATE); + restoreRightHandPosition(HAND_RESTORATION_RATE, PALM_PRIORITY); } else { applyHandPosition(geometry.rightHandJointIndex, _owningAvatar->getHandPosition()); } - restoreLeftHandPosition(HAND_RESTORATION_RATE); + restoreLeftHandPosition(HAND_RESTORATION_RATE, PALM_PRIORITY); } else if (leftPalmIndex == rightPalmIndex) { // right hand only applyPalmData(geometry.rightHandJointIndex, hand->getPalms()[leftPalmIndex]); - restoreLeftHandPosition(HAND_RESTORATION_RATE); + restoreLeftHandPosition(HAND_RESTORATION_RATE, PALM_PRIORITY); } else { applyPalmData(geometry.leftHandJointIndex, hand->getPalms()[leftPalmIndex]); @@ -132,7 +134,7 @@ void SkeletonModel::applyHandPosition(int jointIndex, const glm::vec3& position) if (jointIndex == -1) { return; } - setJointPosition(jointIndex, position); + setJointPosition(jointIndex, position, glm::quat(), false, -1, false, glm::vec3(0.0f, -1.0f, 0.0f), PALM_PRIORITY); const FBXGeometry& geometry = _geometry->getFBXGeometry(); glm::vec3 handPosition, elbowPosition; @@ -148,7 +150,8 @@ void SkeletonModel::applyHandPosition(int jointIndex, const glm::vec3& position) // align hand with forearm float sign = (jointIndex == geometry.rightHandJointIndex) ? 1.0f : -1.0f; - applyRotationDelta(jointIndex, rotationBetween(handRotation * glm::vec3(-sign, 0.0f, 0.0f), forearmVector)); + applyRotationDelta(jointIndex, rotationBetween(handRotation * glm::vec3(-sign, 0.0f, 0.0f), + forearmVector), true, PALM_PRIORITY); } void SkeletonModel::applyPalmData(int jointIndex, PalmData& palm) { @@ -183,12 +186,14 @@ void SkeletonModel::applyPalmData(int jointIndex, PalmData& palm) { } else if (Menu::getInstance()->isOptionChecked(MenuOption::AlignForearmsWithWrists)) { glm::vec3 forearmVector = palmRotation * glm::vec3(sign, 0.0f, 0.0f); setJointPosition(parentJointIndex, palm.getPosition() + forearmVector * - geometry.joints.at(jointIndex).distanceToParent * extractUniformScale(_scale)); - setJointRotation(parentJointIndex, palmRotation, true); + geometry.joints.at(jointIndex).distanceToParent * extractUniformScale(_scale), + glm::quat(), false, -1, false, glm::vec3(0.0f, -1.0f, 0.0f), PALM_PRIORITY); + setJointRotation(parentJointIndex, palmRotation, true, PALM_PRIORITY); _jointStates[jointIndex].rotation = glm::quat(); } else { - setJointPosition(jointIndex, palm.getPosition(), palmRotation, true); + setJointPosition(jointIndex, palm.getPosition(), palmRotation, + true, -1, false, glm::vec3(0.0f, -1.0f, 0.0f), PALM_PRIORITY); } } @@ -335,11 +340,11 @@ void SkeletonModel::setHandPosition(int jointIndex, const glm::vec3& position, c glm::vec3 forwardVector(rightHand ? -1.0f : 1.0f, 0.0f, 0.0f); glm::quat shoulderRotation = rotationBetween(forwardVector, elbowPosition - shoulderPosition); - setJointRotation(shoulderJointIndex, shoulderRotation, true); + setJointRotation(shoulderJointIndex, shoulderRotation, true, PALM_PRIORITY); setJointRotation(elbowJointIndex, rotationBetween(shoulderRotation * forwardVector, - wristPosition - elbowPosition) * shoulderRotation, true); + wristPosition - elbowPosition) * shoulderRotation, true, PALM_PRIORITY); - setJointRotation(jointIndex, rotation, true); + setJointRotation(jointIndex, rotation, true, PALM_PRIORITY); } diff --git a/interface/src/devices/SixenseManager.cpp b/interface/src/devices/SixenseManager.cpp index 547642a1be..8cd3cc059e 100644 --- a/interface/src/devices/SixenseManager.cpp +++ b/interface/src/devices/SixenseManager.cpp @@ -120,7 +120,6 @@ void SixenseManager::update(float deltaTime) { // Rotation of Palm glm::quat rotation(data->rot_quat[3], -data->rot_quat[0], data->rot_quat[1], -data->rot_quat[2]); rotation = glm::angleAxis(PI, glm::vec3(0.f, 1.f, 0.f)) * _orbRotation * rotation; - palm->setRawRotation(rotation); // Compute current velocity from position change glm::vec3 rawVelocity; @@ -130,7 +129,12 @@ void SixenseManager::update(float deltaTime) { rawVelocity = glm::vec3(0.0f); } palm->setRawVelocity(rawVelocity); // meters/sec - palm->setRawPosition(position); + + // Use a velocity sensitive filter to damp small motions and preserve large ones with + // no latency. + float velocityFilter = glm::clamp(1.0f - glm::length(rawVelocity), 0.0f, 1.0f); + palm->setRawPosition(palm->getRawPosition() * velocityFilter + position * (1.0f - velocityFilter)); + palm->setRawRotation(safeMix(palm->getRawRotation(), rotation, 1.0f - velocityFilter)); // use the velocity to determine whether there's any movement (if the hand isn't new) const float MOVEMENT_DISTANCE_THRESHOLD = 0.003f; diff --git a/interface/src/devices/Visage.cpp b/interface/src/devices/Visage.cpp index 70cdaf2b01..119d89654a 100644 --- a/interface/src/devices/Visage.cpp +++ b/interface/src/devices/Visage.cpp @@ -41,8 +41,14 @@ Visage::Visage() : _headOrigin(DEFAULT_HEAD_ORIGIN) { #ifdef HAVE_VISAGE - _tracker = NULL; - _data = NULL; +#ifdef WIN32 + QByteArray licensePath = Application::resourcesPath().toLatin1() + "visage"; +#else + QByteArray licensePath = Application::resourcesPath().toLatin1() + "visage/license.vlc"; +#endif + initializeLicenseManager(licensePath.data()); + _tracker = new VisageTracker2(Application::resourcesPath().toLatin1() + "visage/tracker.cfg"); + _data = new FaceData(); #endif } @@ -173,19 +179,6 @@ void Visage::setEnabled(bool enabled) { return; } if ((_enabled = enabled)) { - if(_tracker == NULL && _data == NULL){ - - #ifdef WIN32 - QByteArray licensePath = Application::resourcesPath().toLatin1() + "visage"; - #else - QByteArray licensePath = Application::resourcesPath().toLatin1() + "visage/license.vlc"; - #endif - - initializeLicenseManager(licensePath.data()); - _tracker = new VisageTracker2(Application::resourcesPath().toLatin1() + "visage/tracker.cfg"); - _data = new FaceData(); - } - _tracker->trackFromCam(); } else { _tracker->stop(); diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 2b50b6d19c..d18abce677 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -118,7 +118,7 @@ QVector Model::createJointStates(const FBXGeometry& geometry) JointState state; state.translation = joint.translation; state.rotation = joint.rotation; - state.animationDisabled = false; + state.animationPriority = 0.0f; jointStates.append(state); } @@ -473,9 +473,18 @@ bool Model::getJointState(int index, glm::quat& rotation) const { glm::abs(rotation.w - defaultRotation.w) >= EPSILON; } -void Model::setJointState(int index, bool valid, const glm::quat& rotation) { +void Model::setJointState(int index, bool valid, const glm::quat& rotation, float priority) { if (index != -1 && index < _jointStates.size()) { - _jointStates[index].rotation = valid ? rotation : _geometry->getFBXGeometry().joints.at(index).rotation; + JointState& state = _jointStates[index]; + if (priority >= state.animationPriority) { + if (valid) { + state.rotation = rotation; + state.animationPriority = priority; + } else if (priority == state.animationPriority) { + state.rotation = _geometry->getFBXGeometry().joints.at(index).rotation; + state.animationPriority = 0.0f; + } + } } } @@ -535,8 +544,8 @@ bool Model::getRightHandRotation(glm::quat& rotation) const { return getJointRotation(getRightHandJointIndex(), rotation); } -bool Model::restoreLeftHandPosition(float percent) { - return restoreJointPosition(getLeftHandJointIndex(), percent); +bool Model::restoreLeftHandPosition(float percent, float priority) { + return restoreJointPosition(getLeftHandJointIndex(), percent, priority); } bool Model::getLeftShoulderPosition(glm::vec3& position) const { @@ -547,8 +556,8 @@ float Model::getLeftArmLength() const { return getLimbLength(getLeftHandJointIndex()); } -bool Model::restoreRightHandPosition(float percent) { - return restoreJointPosition(getRightHandJointIndex(), percent); +bool Model::restoreRightHandPosition(float percent, float priority) { + return restoreJointPosition(getRightHandJointIndex(), percent, priority); } bool Model::getRightShoulderPosition(glm::vec3& position) const { @@ -1114,7 +1123,7 @@ void Model::maybeUpdateEyeRotation(const JointState& parentState, const FBXJoint } bool Model::setJointPosition(int jointIndex, const glm::vec3& translation, const glm::quat& rotation, bool useRotation, - int lastFreeIndex, bool allIntermediatesFree, const glm::vec3& alignment) { + int lastFreeIndex, bool allIntermediatesFree, const glm::vec3& alignment, float priority) { if (jointIndex == -1 || _jointStates.isEmpty()) { return false; } @@ -1137,7 +1146,7 @@ bool Model::setJointPosition(int jointIndex, const glm::vec3& translation, const glm::quat endRotation; if (useRotation) { getJointRotation(jointIndex, endRotation, true); - applyRotationDelta(jointIndex, rotation * glm::inverse(endRotation)); + applyRotationDelta(jointIndex, rotation * glm::inverse(endRotation), priority); getJointRotation(jointIndex, endRotation, true); } @@ -1181,7 +1190,7 @@ bool Model::setJointPosition(int jointIndex, const glm::vec3& translation, const 1.0f / (combinedWeight + 1.0f)); } } - applyRotationDelta(index, combinedDelta); + applyRotationDelta(index, combinedDelta, priority); glm::quat actualDelta = state.combinedRotation * glm::inverse(oldCombinedRotation); endPosition = actualDelta * jointVector + jointPosition; if (useRotation) { @@ -1199,15 +1208,17 @@ bool Model::setJointPosition(int jointIndex, const glm::vec3& translation, const return true; } -bool Model::setJointRotation(int jointIndex, const glm::quat& rotation, bool fromBind) { +bool Model::setJointRotation(int jointIndex, const glm::quat& rotation, bool fromBind, float priority) { if (jointIndex == -1 || _jointStates.isEmpty()) { return false; } JointState& state = _jointStates[jointIndex]; - state.rotation = state.rotation * glm::inverse(state.combinedRotation) * rotation * - glm::inverse(fromBind ? _geometry->getFBXGeometry().joints.at(jointIndex).inverseBindRotation : - _geometry->getFBXGeometry().joints.at(jointIndex).inverseDefaultRotation); - state.animationDisabled = true; + if (priority >= state.animationPriority) { + state.rotation = state.rotation * glm::inverse(state.combinedRotation) * rotation * + glm::inverse(fromBind ? _geometry->getFBXGeometry().joints.at(jointIndex).inverseBindRotation : + _geometry->getFBXGeometry().joints.at(jointIndex).inverseDefaultRotation); + state.animationPriority = priority; + } return true; } @@ -1228,7 +1239,7 @@ void Model::setJointTranslation(int jointIndex, const glm::vec3& translation) { state.translation = glm::vec3(glm::inverse(parentTransform) * glm::vec4(translation, 1.0f)) - preTranslation; } -bool Model::restoreJointPosition(int jointIndex, float percent) { +bool Model::restoreJointPosition(int jointIndex, float percent, float priority) { if (jointIndex == -1 || _jointStates.isEmpty()) { return false; } @@ -1237,10 +1248,12 @@ bool Model::restoreJointPosition(int jointIndex, float percent) { foreach (int index, freeLineage) { JointState& state = _jointStates[index]; - const FBXJoint& joint = geometry.joints.at(index); - state.rotation = safeMix(state.rotation, joint.rotation, percent); - state.translation = glm::mix(state.translation, joint.translation, percent); - state.animationDisabled = false; + if (priority == state.animationPriority) { + const FBXJoint& joint = geometry.joints.at(index); + state.rotation = safeMix(state.rotation, joint.rotation, percent); + state.translation = glm::mix(state.translation, joint.translation, percent); + state.animationPriority = 0.0f; + } } return true; } @@ -1259,8 +1272,12 @@ float Model::getLimbLength(int jointIndex) const { return length; } -void Model::applyRotationDelta(int jointIndex, const glm::quat& delta, bool constrain) { +void Model::applyRotationDelta(int jointIndex, const glm::quat& delta, bool constrain, float priority) { JointState& state = _jointStates[jointIndex]; + if (priority < state.animationPriority) { + return; + } + state.animationPriority = priority; const FBXJoint& joint = _geometry->getFBXGeometry().joints[jointIndex]; if (!constrain || (joint.rotationMin == glm::vec3(-PI, -PI, -PI) && joint.rotationMax == glm::vec3(PI, PI, PI))) { @@ -1274,7 +1291,6 @@ void Model::applyRotationDelta(int jointIndex, const glm::quat& delta, bool cons glm::quat newRotation = glm::quat(glm::clamp(eulers, joint.rotationMin, joint.rotationMax)); state.combinedRotation = state.combinedRotation * glm::inverse(state.rotation) * newRotation; state.rotation = newRotation; - state.animationDisabled = true; } const int BALL_SUBDIVISIONS = 10; @@ -1665,7 +1681,7 @@ void AnimationHandle::setURL(const QUrl& url) { static void insertSorted(QList& handles, const AnimationHandlePointer& handle) { for (QList::iterator it = handles.begin(); it != handles.end(); it++) { - if (handle->getPriority() < (*it)->getPriority()) { + if (handle->getPriority() > (*it)->getPriority()) { handles.insert(it, handle); return; } @@ -1674,12 +1690,25 @@ static void insertSorted(QList& handles, const Animation } void AnimationHandle::setPriority(float priority) { - if (_priority != priority) { - _priority = priority; - if (_running) { - _model->_runningAnimations.removeOne(_self); - insertSorted(_model->_runningAnimations, _self); + if (_priority == priority) { + return; + } + if (_running) { + _model->_runningAnimations.removeOne(_self); + if (priority < _priority) { + replaceMatchingPriorities(priority); } + _priority = priority; + insertSorted(_model->_runningAnimations, _self); + + } else { + _priority = priority; + } +} + +void AnimationHandle::setStartAutomatically(bool startAutomatically) { + if ((_startAutomatically = startAutomatically) && !_running) { + start(); } } @@ -1689,15 +1718,24 @@ void AnimationHandle::setMaskedJoints(const QStringList& maskedJoints) { } void AnimationHandle::setRunning(bool running) { + if (_running == running) { + if (running) { + // move back to the beginning + _frameIndex = _firstFrame; + } + return; + } if ((_running = running)) { if (!_model->_runningAnimations.contains(_self)) { insertSorted(_model->_runningAnimations, _self); } - _frameIndex = 0.0f; + _frameIndex = _firstFrame; } else { _model->_runningAnimations.removeOne(_self); + replaceMatchingPriorities(0.0f); } + emit runningChanged(_running); } AnimationHandle::AnimationHandle(Model* model) : @@ -1706,6 +1744,10 @@ AnimationHandle::AnimationHandle(Model* model) : _fps(30.0f), _priority(1.0f), _loop(false), + _hold(false), + _startAutomatically(false), + _firstFrame(0), + _lastFrame(INT_MAX), _running(false) { } @@ -1736,36 +1778,55 @@ void AnimationHandle::simulate(float deltaTime) { stop(); return; } - int ceilFrameIndex = (int)glm::ceil(_frameIndex); - if (!_loop && ceilFrameIndex >= animationGeometry.animationFrames.size()) { + int lastFrameIndex = qMin(_lastFrame, animationGeometry.animationFrames.size() - 1); + int firstFrameIndex = qMin(_firstFrame, lastFrameIndex); + if ((!_loop && _frameIndex >= lastFrameIndex) || firstFrameIndex == lastFrameIndex) { // passed the end; apply the last frame - const FBXAnimationFrame& frame = animationGeometry.animationFrames.last(); + const FBXAnimationFrame& frame = animationGeometry.animationFrames.at(lastFrameIndex); for (int i = 0; i < _jointMappings.size(); i++) { int mapping = _jointMappings.at(i); if (mapping != -1) { Model::JointState& state = _model->_jointStates[mapping]; - if (!state.animationDisabled) { + if (_priority >= state.animationPriority) { state.rotation = frame.rotations.at(i); + state.animationPriority = _priority; } } } - stop(); + if (!_hold) { + stop(); + } return; } + int frameCount = lastFrameIndex - firstFrameIndex + 1; + _frameIndex = firstFrameIndex + glm::mod(qMax(_frameIndex - firstFrameIndex, 0.0f), (float)frameCount); + // blend between the closest two frames const FBXAnimationFrame& ceilFrame = animationGeometry.animationFrames.at( - ceilFrameIndex % animationGeometry.animationFrames.size()); + firstFrameIndex + ((int)glm::ceil(_frameIndex) - firstFrameIndex) % frameCount); const FBXAnimationFrame& floorFrame = animationGeometry.animationFrames.at( - (int)glm::floor(_frameIndex) % animationGeometry.animationFrames.size()); + firstFrameIndex + ((int)glm::floor(_frameIndex) - firstFrameIndex) % frameCount); float frameFraction = glm::fract(_frameIndex); for (int i = 0; i < _jointMappings.size(); i++) { int mapping = _jointMappings.at(i); if (mapping != -1) { Model::JointState& state = _model->_jointStates[mapping]; - if (!state.animationDisabled) { + if (_priority >= state.animationPriority) { state.rotation = safeMix(floorFrame.rotations.at(i), ceilFrame.rotations.at(i), frameFraction); + state.animationPriority = _priority; } } } } +void AnimationHandle::replaceMatchingPriorities(float newPriority) { + for (int i = 0; i < _jointMappings.size(); i++) { + int mapping = _jointMappings.at(i); + if (mapping != -1) { + Model::JointState& state = _model->_jointStates[mapping]; + if (_priority == state.animationPriority) { + state.animationPriority = newPriority; + } + } + } +} diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 59ec50cac1..1a6642dfc6 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -113,7 +113,7 @@ public: bool getJointState(int index, glm::quat& rotation) const; /// Sets the joint state at the specified index. - void setJointState(int index, bool valid, const glm::quat& rotation = glm::quat()); + void setJointState(int index, bool valid, const glm::quat& rotation = glm::quat(), float priority = 1.0f); /// Returns the index of the left hand joint, or -1 if not found. int getLeftHandJointIndex() const { return isActive() ? _geometry->getFBXGeometry().leftHandJointIndex : -1; } @@ -166,7 +166,7 @@ public: /// Restores some percentage of the default position of the left hand. /// \param percent the percentage of the default position to restore /// \return whether or not the left hand joint was found - bool restoreLeftHandPosition(float percent = 1.0f); + bool restoreLeftHandPosition(float percent = 1.0f, float priority = 1.0f); /// Gets the position of the left shoulder. /// \return whether or not the left shoulder joint was found @@ -178,7 +178,7 @@ public: /// Restores some percentage of the default position of the right hand. /// \param percent the percentage of the default position to restore /// \return whether or not the right hand joint was found - bool restoreRightHandPosition(float percent = 1.0f); + bool restoreRightHandPosition(float percent = 1.0f, float priority = 1.0f); /// Gets the position of the right shoulder. /// \return whether or not the right shoulder joint was found @@ -254,7 +254,7 @@ protected: glm::quat rotation; // rotation relative to parent glm::mat4 transform; // rotation to world frame + translation in model frame glm::quat combinedRotation; // rotation from joint local to world frame - bool animationDisabled; // if true, animations do not affect this joint + float animationPriority; // the priority of the animation affecting this joint }; bool _shapesAreDirty; @@ -290,8 +290,8 @@ protected: bool setJointPosition(int jointIndex, const glm::vec3& translation, const glm::quat& rotation = glm::quat(), bool useRotation = false, int lastFreeIndex = -1, bool allIntermediatesFree = false, - const glm::vec3& alignment = glm::vec3(0.0f, -1.0f, 0.0f)); - bool setJointRotation(int jointIndex, const glm::quat& rotation, bool fromBind = false); + const glm::vec3& alignment = glm::vec3(0.0f, -1.0f, 0.0f), float priority = 1.0f); + bool setJointRotation(int jointIndex, const glm::quat& rotation, bool fromBind = false, float priority = 1.0f); void setJointTranslation(int jointIndex, const glm::vec3& translation); @@ -299,13 +299,13 @@ protected: /// \param percent the percentage of the default position to apply (i.e., 0.25f to slerp one fourth of the way to /// the original position /// \return true if the joint was found - bool restoreJointPosition(int jointIndex, float percent = 1.0f); + bool restoreJointPosition(int jointIndex, float percent = 1.0f, float priority = 0.0f); /// Computes and returns the extended length of the limb terminating at the specified joint and starting at the joint's /// first free ancestor. float getLimbLength(int jointIndex) const; - void applyRotationDelta(int jointIndex, const glm::quat& delta, bool constrain = true); + void applyRotationDelta(int jointIndex, const glm::quat& delta, bool constrain = true, float priority = 1.0f); void computeBoundingShape(const FBXGeometry& geometry); @@ -381,6 +381,9 @@ class AnimationHandle : public QObject { public: + void setRole(const QString& role) { _role = role; } + const QString& getRole() const { return _role; } + void setURL(const QUrl& url); const QUrl& getURL() const { return _url; } @@ -393,12 +396,30 @@ public: void setLoop(bool loop) { _loop = loop; } bool getLoop() const { return _loop; } + void setHold(bool hold) { _hold = hold; } + bool getHold() const { return _hold; } + + void setStartAutomatically(bool startAutomatically); + bool getStartAutomatically() const { return _startAutomatically; } + + void setFirstFrame(int firstFrame) { _firstFrame = firstFrame; } + int getFirstFrame() const { return _firstFrame; } + + void setLastFrame(int lastFrame) { _lastFrame = lastFrame; } + int getLastFrame() const { return _lastFrame; } + void setMaskedJoints(const QStringList& maskedJoints); const QStringList& getMaskedJoints() const { return _maskedJoints; } void setRunning(bool running); bool isRunning() const { return _running; } + +signals: + void runningChanged(bool running); + +public slots: + void start() { setRunning(true); } void stop() { setRunning(false); } @@ -409,14 +430,20 @@ private: AnimationHandle(Model* model); void simulate(float deltaTime); - + void replaceMatchingPriorities(float newPriority); + Model* _model; WeakAnimationHandlePointer _self; AnimationPointer _animation; + QString _role; QUrl _url; float _fps; float _priority; bool _loop; + bool _hold; + bool _startAutomatically; + int _firstFrame; + int _lastFrame; QStringList _maskedJoints; bool _running; QVector _jointMappings; diff --git a/interface/src/ui/AnimationsDialog.cpp b/interface/src/ui/AnimationsDialog.cpp index 29837f67be..2456b589da 100644 --- a/interface/src/ui/AnimationsDialog.cpp +++ b/interface/src/ui/AnimationsDialog.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include +#include #include #include #include @@ -79,6 +81,13 @@ AnimationPanel::AnimationPanel(AnimationsDialog* dialog, const AnimationHandlePo layout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); setLayout(layout); + layout->addRow("Role:", _role = new QComboBox()); + _role->addItem("idle"); + _role->addItem("sit"); + _role->setEditable(true); + _role->setCurrentText(handle->getRole()); + connect(_role, SIGNAL(currentTextChanged(const QString&)), SLOT(updateHandle())); + QHBoxLayout* urlBox = new QHBoxLayout(); layout->addRow("URL:", urlBox); urlBox->addWidget(_url = new QLineEdit(handle->getURL().toString()), 1); @@ -107,9 +116,40 @@ AnimationPanel::AnimationPanel(AnimationsDialog* dialog, const AnimationHandlePo maskedJointBox->addWidget(_chooseMaskedJoints = new QPushButton("Choose")); connect(_chooseMaskedJoints, SIGNAL(clicked(bool)), SLOT(chooseMaskedJoints())); + layout->addRow("Loop:", _loop = new QCheckBox()); + _loop->setChecked(handle->getLoop()); + connect(_loop, SIGNAL(toggled(bool)), SLOT(updateHandle())); + + layout->addRow("Hold:", _hold = new QCheckBox()); + _hold->setChecked(handle->getHold()); + connect(_hold, SIGNAL(toggled(bool)), SLOT(updateHandle())); + + layout->addRow("Start Automatically:", _startAutomatically = new QCheckBox()); + _startAutomatically->setChecked(handle->getStartAutomatically()); + connect(_startAutomatically, SIGNAL(toggled(bool)), SLOT(updateHandle())); + + layout->addRow("First Frame:", _firstFrame = new QSpinBox()); + _firstFrame->setMaximum(INT_MAX); + _firstFrame->setValue(handle->getFirstFrame()); + connect(_firstFrame, SIGNAL(valueChanged(int)), SLOT(updateHandle())); + + layout->addRow("Last Frame:", _lastFrame = new QSpinBox()); + _lastFrame->setMaximum(INT_MAX); + _lastFrame->setValue(handle->getLastFrame()); + connect(_lastFrame, SIGNAL(valueChanged(int)), SLOT(updateHandle())); + + QHBoxLayout* buttons = new QHBoxLayout(); + layout->addRow(buttons); + buttons->addWidget(_start = new QPushButton("Start")); + _handle->connect(_start, SIGNAL(clicked(bool)), SLOT(start())); + buttons->addWidget(_stop = new QPushButton("Stop")); + _handle->connect(_stop, SIGNAL(clicked(bool)), SLOT(stop())); QPushButton* remove = new QPushButton("Delete"); - layout->addRow(remove); + buttons->addWidget(remove); connect(remove, SIGNAL(clicked(bool)), SLOT(removeHandle())); + + _stop->connect(_handle.data(), SIGNAL(runningChanged(bool)), SLOT(setEnabled(bool))); + _stop->setEnabled(_handle->isRunning()); } void AnimationPanel::chooseURL() { @@ -146,9 +186,15 @@ void AnimationPanel::chooseMaskedJoints() { } void AnimationPanel::updateHandle() { + _handle->setRole(_role->currentText()); _handle->setURL(_url->text()); _handle->setFPS(_fps->value()); _handle->setPriority(_priority->value()); + _handle->setLoop(_loop->isChecked()); + _handle->setHold(_hold->isChecked()); + _handle->setStartAutomatically(_startAutomatically->isChecked()); + _handle->setFirstFrame(_firstFrame->value()); + _handle->setLastFrame(_lastFrame->value()); _handle->setMaskedJoints(_maskedJoints->text().split(QRegExp("\\s*,\\s*"))); } diff --git a/interface/src/ui/AnimationsDialog.h b/interface/src/ui/AnimationsDialog.h index 7693a1da97..dd3865741e 100644 --- a/interface/src/ui/AnimationsDialog.h +++ b/interface/src/ui/AnimationsDialog.h @@ -17,9 +17,12 @@ #include "avatar/MyAvatar.h" +class QCheckBox; +class QComboBox; class QDoubleSpinner; class QLineEdit; class QPushButton; +class QSpinBox; class QVBoxLayout; /// Allows users to edit the avatar animations. @@ -61,11 +64,19 @@ private: AnimationsDialog* _dialog; AnimationHandlePointer _handle; + QComboBox* _role; QLineEdit* _url; QDoubleSpinBox* _fps; QDoubleSpinBox* _priority; + QCheckBox* _loop; + QCheckBox* _hold; + QCheckBox* _startAutomatically; + QSpinBox* _firstFrame; + QSpinBox* _lastFrame; QLineEdit* _maskedJoints; QPushButton* _chooseMaskedJoints; + QPushButton* _start; + QPushButton* _stop; bool _applying; }; diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index aad2cfb386..7d27332a57 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -61,6 +61,8 @@ AccountManager::AccountManager() : qRegisterMetaType("QNetworkAccessManager::Operation"); qRegisterMetaType("JSONCallbackParameters"); + + connect(&_accountInfo, &DataServerAccountInfo::balanceChanged, this, &AccountManager::accountInfoBalanceChanged); } const QString DOUBLE_SLASH_SUBSTITUTE = "slashslash"; @@ -69,6 +71,9 @@ void AccountManager::logout() { // a logout means we want to delete the DataServerAccountInfo we currently have for this URL, in-memory and in file _accountInfo = DataServerAccountInfo(); + emit balanceChanged(0); + connect(&_accountInfo, &DataServerAccountInfo::balanceChanged, this, &AccountManager::accountInfoBalanceChanged); + QSettings settings; settings.beginGroup(ACCOUNTS_GROUP); @@ -82,6 +87,21 @@ void AccountManager::logout() { emit usernameChanged(QString()); } +void AccountManager::updateBalance() { + if (hasValidAccessToken()) { + // ask our auth endpoint for our balance + JSONCallbackParameters callbackParameters; + callbackParameters.jsonCallbackReceiver = &_accountInfo; + callbackParameters.jsonCallbackMethod = "setBalanceFromJSON"; + + authenticatedRequest("/api/v1/wallets/mine", QNetworkAccessManager::GetOperation, callbackParameters); + } +} + +void AccountManager::accountInfoBalanceChanged(qint64 newBalance) { + emit balanceChanged(newBalance); +} + void AccountManager::setAuthURL(const QUrl& authURL) { if (_authURL != authURL) { _authURL = authURL; diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index 6bdf5d76d8..628b084ea8 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -63,6 +63,8 @@ public slots: void requestFinished(); void requestError(QNetworkReply::NetworkError error); void logout(); + void updateBalance(); + void accountInfoBalanceChanged(qint64 newBalance); signals: void authRequired(); void authEndpointChanged(); @@ -71,6 +73,7 @@ signals: void loginComplete(const QUrl& authURL); void loginFailed(); void logoutComplete(); + void balanceChanged(qint64 newBalance); private slots: void processReply(); private: diff --git a/libraries/networking/src/DataServerAccountInfo.cpp b/libraries/networking/src/DataServerAccountInfo.cpp index 0fdb5ff4b1..b3607200fe 100644 --- a/libraries/networking/src/DataServerAccountInfo.cpp +++ b/libraries/networking/src/DataServerAccountInfo.cpp @@ -17,7 +17,9 @@ DataServerAccountInfo::DataServerAccountInfo() : _accessToken(), _username(), _xmppPassword(), - _discourseApiKey() + _discourseApiKey(), + _balance(0), + _hasBalance(false) { } @@ -25,7 +27,9 @@ DataServerAccountInfo::DataServerAccountInfo() : DataServerAccountInfo::DataServerAccountInfo(const QJsonObject& jsonObject) : _accessToken(jsonObject), _username(), - _xmppPassword() + _xmppPassword(), + _balance(0), + _hasBalance(false) { QJsonObject userJSONObject = jsonObject["user"].toObject(); setUsername(userJSONObject["username"].toString()); @@ -38,6 +42,8 @@ DataServerAccountInfo::DataServerAccountInfo(const DataServerAccountInfo& otherI _username = otherInfo._username; _xmppPassword = otherInfo._xmppPassword; _discourseApiKey = otherInfo._discourseApiKey; + _balance = otherInfo._balance; + _hasBalance = otherInfo._hasBalance; } DataServerAccountInfo& DataServerAccountInfo::operator=(const DataServerAccountInfo& otherInfo) { @@ -53,6 +59,8 @@ void DataServerAccountInfo::swap(DataServerAccountInfo& otherInfo) { swap(_username, otherInfo._username); swap(_xmppPassword, otherInfo._xmppPassword); swap(_discourseApiKey, otherInfo._discourseApiKey); + swap(_balance, otherInfo._balance); + swap(_hasBalance, otherInfo._hasBalance); } void DataServerAccountInfo::setUsername(const QString& username) { @@ -75,6 +83,22 @@ void DataServerAccountInfo::setDiscourseApiKey(const QString& discourseApiKey) { } } +void DataServerAccountInfo::setBalance(quint64 balance) { + if (!_hasBalance || _balance != balance) { + _balance = balance; + _hasBalance = true; + + emit balanceChanged(_balance); + } +} + +void DataServerAccountInfo::setBalanceFromJSON(const QJsonObject& jsonObject) { + if (jsonObject["status"].toString() == "success") { + qint64 balanceInSatoshis = jsonObject["data"].toObject()["wallet"].toObject()["balance"].toInt(); + setBalance(balanceInSatoshis); + } +} + QDataStream& operator<<(QDataStream &out, const DataServerAccountInfo& info) { out << info._accessToken << info._username << info._xmppPassword << info._discourseApiKey; return out; diff --git a/libraries/networking/src/DataServerAccountInfo.h b/libraries/networking/src/DataServerAccountInfo.h index a7d1fa9cb0..fd135f922b 100644 --- a/libraries/networking/src/DataServerAccountInfo.h +++ b/libraries/networking/src/DataServerAccountInfo.h @@ -34,9 +34,17 @@ public: const QString& getDiscourseApiKey() const { return _discourseApiKey; } void setDiscourseApiKey(const QString& discourseApiKey); + + quint64 getBalance() const { return _balance; } + void setBalance(quint64 balance); + bool hasBalance() const { return _hasBalance; } + void setHasBalance(bool hasBalance) { _hasBalance = hasBalance; } + Q_INVOKABLE void setBalanceFromJSON(const QJsonObject& jsonObject); friend QDataStream& operator<<(QDataStream &out, const DataServerAccountInfo& info); friend QDataStream& operator>>(QDataStream &in, DataServerAccountInfo& info); +signals: + qint64 balanceChanged(qint64 newBalance); private: void swap(DataServerAccountInfo& otherInfo); @@ -44,6 +52,8 @@ private: QString _username; QString _xmppPassword; QString _discourseApiKey; + quint64 _balance; + bool _hasBalance; }; #endif // hifi_DataServerAccountInfo_h