diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 72ac7ba62a..0f8fbbae56 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -60,6 +60,7 @@ #include #include #include +#include #include #include #include @@ -268,6 +269,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : // set the account manager's root URL and trigger a login request if we don't have the access token accountManager.setAuthURL(DEFAULT_NODE_AUTH_URL); + UserActivityLogger::getInstance().launch(applicationVersion()); // once the event loop has started, check and signal for an access token QMetaObject::invokeMethod(&accountManager, "checkAndSignalForAccessToken", Qt::QueuedConnection); @@ -396,7 +398,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : } Application::~Application() { - + int DELAY_TIME = 1000; + UserActivityLogger::getInstance().close(DELAY_TIME); + qInstallMessageHandler(NULL); // make sure we don't call the idle timer any more @@ -3577,6 +3581,7 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript _scriptEnginesHash.insertMulti(scriptURLString, scriptEngine); _runningScriptsWidget->setRunningScripts(getRunningScripts()); + UserActivityLogger::getInstance().loadedScript(scriptURLString); } // setup the packet senders and jurisdiction listeners of the script engine's scripting interfaces so diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index f5f148969c..df23fe9540 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -432,7 +432,7 @@ void Audio::handleAudioInput() { float inputToNetworkInputRatio = calculateDeviceToNetworkInputRatio(_numInputCallbackBytes); - unsigned int inputSamplesRequired = NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * inputToNetworkInputRatio; + int inputSamplesRequired = (int)((float)NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * inputToNetworkInputRatio); QByteArray inputByteArray = _inputDevice->readAll(); diff --git a/interface/src/avatar/FaceModel.cpp b/interface/src/avatar/FaceModel.cpp index c3f31ff6e7..59e5b08cc0 100644 --- a/interface/src/avatar/FaceModel.cpp +++ b/interface/src/avatar/FaceModel.cpp @@ -51,10 +51,10 @@ void FaceModel::maybeUpdateNeckRotation(const JointState& parentState, const FBX glm::mat3 axes = glm::mat3_cast(glm::quat()); glm::mat3 inverse = glm::mat3(glm::inverse(parentState.getTransform() * glm::translate(state.getDefaultTranslationInParentFrame()) * joint.preTransform * glm::mat4_cast(joint.preRotation))); - state._rotationInParentFrame = glm::angleAxis(- RADIANS_PER_DEGREE * _owningHead->getFinalRoll(), glm::normalize(inverse * axes[2])) + state.setRotationInParentFrame(glm::angleAxis(- RADIANS_PER_DEGREE * _owningHead->getFinalRoll(), glm::normalize(inverse * axes[2])) * glm::angleAxis(RADIANS_PER_DEGREE * _owningHead->getFinalYaw(), glm::normalize(inverse * axes[1])) * glm::angleAxis(- RADIANS_PER_DEGREE * _owningHead->getFinalPitch(), glm::normalize(inverse * axes[0])) - * joint.rotation; + * joint.rotation); } void FaceModel::maybeUpdateEyeRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) { @@ -68,8 +68,8 @@ void FaceModel::maybeUpdateEyeRotation(const JointState& parentState, const FBXJ _owningHead->getSaccade() - _translation, 1.0f)); glm::quat between = rotationBetween(front, lookAt); const float MAX_ANGLE = 30.0f * RADIANS_PER_DEGREE; - state._rotationInParentFrame = glm::angleAxis(glm::clamp(glm::angle(between), -MAX_ANGLE, MAX_ANGLE), glm::axis(between)) * - joint.rotation; + state.setRotationInParentFrame(glm::angleAxis(glm::clamp(glm::angle(between), -MAX_ANGLE, MAX_ANGLE), glm::axis(between)) * + joint.rotation); } void FaceModel::updateJointState(int index) { diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 3b09a1a2ba..2f35b96181 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -219,10 +219,9 @@ void SkeletonModel::applyPalmData(int jointIndex, PalmData& palm) { JointState& parentState = _jointStates[parentJointIndex]; parentState.setRotationFromBindFrame(palmRotation, PALM_PRIORITY); // lock hand to forearm by slamming its rotation (in parent-frame) to identity - _jointStates[jointIndex]._rotationInParentFrame = glm::quat(); + _jointStates[jointIndex].setRotationInParentFrame(glm::quat()); } else { - setJointPosition(jointIndex, palmPosition, palmRotation, - true, -1, false, glm::vec3(0.0f, -1.0f, 0.0f), PALM_PRIORITY); + inverseKinematics(jointIndex, palmPosition, palmRotation, PALM_PRIORITY); } } @@ -258,9 +257,9 @@ void SkeletonModel::maybeUpdateLeanRotation(const JointState& parentState, const glm::mat3 axes = glm::mat3_cast(glm::quat()); glm::mat3 inverse = glm::mat3(glm::inverse(parentState.getTransform() * glm::translate(state.getDefaultTranslationInParentFrame()) * joint.preTransform * glm::mat4_cast(joint.preRotation * joint.rotation))); - state._rotationInParentFrame = glm::angleAxis(- RADIANS_PER_DEGREE * _owningAvatar->getHead()->getFinalLeanSideways(), + state.setRotationInParentFrame(glm::angleAxis(- RADIANS_PER_DEGREE * _owningAvatar->getHead()->getFinalLeanSideways(), glm::normalize(inverse * axes[2])) * glm::angleAxis(- RADIANS_PER_DEGREE * _owningAvatar->getHead()->getFinalLeanForward(), - glm::normalize(inverse * axes[0])) * joint.rotation; + glm::normalize(inverse * axes[0])) * joint.rotation); } void SkeletonModel::maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) { @@ -537,6 +536,11 @@ void SkeletonModel::buildRagdollConstraints() { } } +void SkeletonModel::updateVisibleJointStates() { + Model::updateVisibleJointStates(); + // TODO: implement this to move visible joints to agree with joint shape positions +} + // virtual void SkeletonModel::stepRagdollForward(float deltaTime) { const float RAGDOLL_FOLLOWS_JOINTS_TIMESCALE = 0.03f; @@ -644,7 +648,6 @@ void SkeletonModel::computeBoundingShape(const FBXGeometry& geometry) { _ragdollPoints[i]._lastPosition = _ragdollPoints[i]._position; continue; } - assert(parentIndex != -1); glm::quat modifiedRotation = joint.preRotation * joint.rotation * joint.postRotation; transforms[i] = transforms[parentIndex] * glm::translate(joint.translation) @@ -700,7 +703,7 @@ void SkeletonModel::computeBoundingShape(const FBXGeometry& geometry) { _boundingRadius = 0.5f * glm::length(diagonal); } -void SkeletonModel::resetShapePositions() { +void SkeletonModel::resetShapePositionsToDefaultPose() { // DEBUG method. // Moves shapes to the joint default locations for debug visibility into // how the bounding shape is computed. diff --git a/interface/src/avatar/SkeletonModel.h b/interface/src/avatar/SkeletonModel.h index b91c112b6a..76d0d45efa 100644 --- a/interface/src/avatar/SkeletonModel.h +++ b/interface/src/avatar/SkeletonModel.h @@ -93,6 +93,8 @@ public: /// Retrieve the positions of up to two eye meshes. /// \return whether or not both eye meshes were found bool getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const; + + virtual void updateVisibleJointStates(); // virtual overrride from Ragdoll virtual void stepRagdollForward(float deltaTime); @@ -104,7 +106,7 @@ public: float getBoundingShapeRadius() const { return _boundingShape.getRadius(); } const CapsuleShape& getBoundingShape() const { return _boundingShape; } - void resetShapePositions(); // DEBUG method + void resetShapePositionsToDefaultPose(); // DEBUG method void renderRagdoll(); protected: diff --git a/interface/src/devices/OculusManager.cpp b/interface/src/devices/OculusManager.cpp index d9549438b5..dbe444bf9b 100644 --- a/interface/src/devices/OculusManager.cpp +++ b/interface/src/devices/OculusManager.cpp @@ -17,6 +17,7 @@ #include #include +#include #include "Application.h" @@ -61,6 +62,9 @@ void OculusManager::connect() { _ovrHmd = ovrHmd_Create(0); if (_ovrHmd) { + if (!_isConnected) { + UserActivityLogger::getInstance().connectedDevice("hmd", "oculus"); + } _isConnected = true; ovrHmd_GetDesc(_ovrHmd, &_ovrHmdDesc); diff --git a/interface/src/devices/SixenseManager.cpp b/interface/src/devices/SixenseManager.cpp index 07536d0af8..1b7baf2ee1 100644 --- a/interface/src/devices/SixenseManager.cpp +++ b/interface/src/devices/SixenseManager.cpp @@ -13,6 +13,7 @@ #include "Application.h" #include "SixenseManager.h" +#include "UserActivityLogger.h" #ifdef HAVE_SIXENSE const int CALIBRATION_STATE_IDLE = 0; @@ -39,6 +40,7 @@ SixenseManager::SixenseManager() { sixenseInit(); #endif + _hydrasConnected = false; _triggerPressed[0] = false; _bumperPressed[0] = false; _oldX[0] = -1; @@ -70,7 +72,11 @@ void SixenseManager::setFilter(bool filter) { void SixenseManager::update(float deltaTime) { #ifdef HAVE_SIXENSE if (sixenseGetNumActiveControllers() == 0) { + _hydrasConnected = false; return; + } else if (!_hydrasConnected) { + _hydrasConnected = true; + UserActivityLogger::getInstance().connectedDevice("spatial_controller", "hydra"); } MyAvatar* avatar = Application::getInstance()->getAvatar(); Hand* hand = avatar->getHand(); diff --git a/interface/src/devices/SixenseManager.h b/interface/src/devices/SixenseManager.h index 8803c2c006..8ca27ef77c 100644 --- a/interface/src/devices/SixenseManager.h +++ b/interface/src/devices/SixenseManager.h @@ -71,6 +71,7 @@ private: float _lastDistance; #endif + bool _hydrasConnected; quint64 _lastMovement; glm::vec3 _amountMoved; diff --git a/interface/src/models/ModelTreeRenderer.cpp b/interface/src/models/ModelTreeRenderer.cpp index 9c4b08fd99..78107db699 100644 --- a/interface/src/models/ModelTreeRenderer.cpp +++ b/interface/src/models/ModelTreeRenderer.cpp @@ -135,7 +135,7 @@ void ModelTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args) args->_elementsTouched++; // actually render it here... // we need to iterate the actual modelItems of the element - ModelTreeElement* modelTreeElement = (ModelTreeElement*)element; + ModelTreeElement* modelTreeElement = static_cast(element); QList& modelItems = modelTreeElement->getModels(); diff --git a/interface/src/renderer/JointState.cpp b/interface/src/renderer/JointState.cpp index e66a2f44e9..8e4aecdf51 100644 --- a/interface/src/renderer/JointState.cpp +++ b/interface/src/renderer/JointState.cpp @@ -11,14 +11,26 @@ #include -//#include #include #include "JointState.h" JointState::JointState() : _animationPriority(0.0f), - _fbxJoint(NULL) { + _fbxJoint(NULL), + _isConstrained(false) { +} + +void JointState::copyState(const JointState& state) { + _animationPriority = state._animationPriority; + _transform = state._transform; + _rotation = extractRotation(_transform); + _rotationInParentFrame = state._rotationInParentFrame; + + _visibleTransform = state._visibleTransform; + _visibleRotation = extractRotation(_visibleTransform); + _visibleRotationInParentFrame = state._visibleRotationInParentFrame; + // DO NOT copy _fbxJoint } void JointState::setFBXJoint(const FBXJoint* joint) { @@ -26,14 +38,10 @@ void JointState::setFBXJoint(const FBXJoint* joint) { _rotationInParentFrame = joint->rotation; // NOTE: JointState does not own the FBXJoint to which it points. _fbxJoint = joint; -} - -void JointState::copyState(const JointState& state) { - _rotationInParentFrame = state._rotationInParentFrame; - _transform = state._transform; - _rotation = extractRotation(_transform); - _animationPriority = state._animationPriority; - // DO NOT copy _fbxJoint + // precompute whether there are any constraints or not + float distanceMin = glm::distance(_fbxJoint->rotationMin, glm::vec3(-PI)); + float distanceMax = glm::distance(_fbxJoint->rotationMax, glm::vec3(PI)); + _isConstrained = distanceMin > EPSILON || distanceMax > EPSILON; } void JointState::computeTransform(const glm::mat4& parentTransform) { @@ -43,6 +51,13 @@ void JointState::computeTransform(const glm::mat4& parentTransform) { _rotation = extractRotation(_transform); } +void JointState::computeVisibleTransform(const glm::mat4& parentTransform) { + glm::quat modifiedRotation = _fbxJoint->preRotation * _visibleRotationInParentFrame * _fbxJoint->postRotation; + glm::mat4 modifiedTransform = _fbxJoint->preTransform * glm::mat4_cast(modifiedRotation) * _fbxJoint->postTransform; + _visibleTransform = parentTransform * glm::translate(_fbxJoint->translation) * modifiedTransform; + _visibleRotation = extractRotation(_visibleTransform); +} + glm::quat JointState::getRotationFromBindToModelFrame() const { return _rotation * _fbxJoint->inverseBindRotation; } @@ -50,16 +65,16 @@ glm::quat JointState::getRotationFromBindToModelFrame() const { void JointState::restoreRotation(float fraction, float priority) { assert(_fbxJoint != NULL); if (priority == _animationPriority || _animationPriority == 0.0f) { - _rotationInParentFrame = safeMix(_rotationInParentFrame, _fbxJoint->rotation, fraction); + setRotationInParentFrame(safeMix(_rotationInParentFrame, _fbxJoint->rotation, fraction)); _animationPriority = 0.0f; } } void JointState::setRotationFromBindFrame(const glm::quat& rotation, float priority) { + // rotation is from bind- to model-frame assert(_fbxJoint != NULL); if (priority >= _animationPriority) { - // rotation is from bind- to model-frame - _rotationInParentFrame = _rotationInParentFrame * glm::inverse(_rotation) * rotation * glm::inverse(_fbxJoint->inverseBindRotation); + setRotationInParentFrame(_rotationInParentFrame * glm::inverse(_rotation) * rotation * glm::inverse(_fbxJoint->inverseBindRotation)); _animationPriority = priority; } } @@ -68,6 +83,9 @@ void JointState::clearTransformTranslation() { _transform[3][0] = 0.0f; _transform[3][1] = 0.0f; _transform[3][2] = 0.0f; + _visibleTransform[3][0] = 0.0f; + _visibleTransform[3][1] = 0.0f; + _visibleTransform[3][2] = 0.0f; } void JointState::setRotation(const glm::quat& rotation, bool constrain, float priority) { @@ -75,27 +93,59 @@ void JointState::setRotation(const glm::quat& rotation, bool constrain, float pr } void JointState::applyRotationDelta(const glm::quat& delta, bool constrain, float priority) { - // NOTE: delta is in jointParent-frame + // NOTE: delta is in model-frame assert(_fbxJoint != NULL); if (priority < _animationPriority) { return; } _animationPriority = priority; - if (!constrain || (_fbxJoint->rotationMin == glm::vec3(-PI, -PI, -PI) && - _fbxJoint->rotationMax == glm::vec3(PI, PI, PI))) { + if (!constrain || !_isConstrained) { // no constraints _rotationInParentFrame = _rotationInParentFrame * glm::inverse(_rotation) * delta * _rotation; _rotation = delta * _rotation; return; } - glm::quat targetRotation = delta * _rotation; - glm::vec3 eulers = safeEulerAngles(_rotationInParentFrame * glm::inverse(_rotation) * targetRotation); - glm::quat newRotation = glm::quat(glm::clamp(eulers, _fbxJoint->rotationMin, _fbxJoint->rotationMax)); - _rotation = _rotation * glm::inverse(_rotationInParentFrame) * newRotation; - _rotationInParentFrame = newRotation; + glm::quat targetRotation = _rotationInParentFrame * glm::inverse(_rotation) * delta * _rotation; + setRotationInParentFrame(targetRotation); +} + +/// Applies delta rotation to joint but mixes a little bit of the default pose as well. +/// This helps keep an IK solution stable. +void JointState::mixRotationDelta(const glm::quat& delta, float mixFactor, float priority) { + // NOTE: delta is in model-frame + assert(_fbxJoint != NULL); + if (priority < _animationPriority) { + return; + } + _animationPriority = priority; + glm::quat targetRotation = _rotationInParentFrame * glm::inverse(_rotation) * delta * _rotation; + if (mixFactor > 0.0f && mixFactor <= 1.0f) { + targetRotation = safeMix(targetRotation, _fbxJoint->rotation, mixFactor); + } + setRotationInParentFrame(targetRotation); +} + + +glm::quat JointState::computeParentRotation() const { + // R = Rp * Rpre * r * Rpost + // Rp = R * (Rpre * r * Rpost)^ + return _rotation * glm::inverse(_fbxJoint->preRotation * _rotationInParentFrame * _fbxJoint->postRotation); +} + +void JointState::setRotationInParentFrame(const glm::quat& targetRotation) { + glm::quat parentRotation = computeParentRotation(); + _rotationInParentFrame = targetRotation; + // R' = Rp * Rpre * r' * Rpost + _rotation = parentRotation * _fbxJoint->preRotation * _rotationInParentFrame * _fbxJoint->postRotation; } const glm::vec3& JointState::getDefaultTranslationInParentFrame() const { assert(_fbxJoint != NULL); return _fbxJoint->translation; } + +void JointState::slaveVisibleTransform() { + _visibleTransform = _transform; + _visibleRotation = _rotation; + _visibleRotationInParentFrame = _rotationInParentFrame; +} diff --git a/interface/src/renderer/JointState.h b/interface/src/renderer/JointState.h index b1a584d4ec..8412cfd0cb 100644 --- a/interface/src/renderer/JointState.h +++ b/interface/src/renderer/JointState.h @@ -22,12 +22,19 @@ class JointState { public: JointState(); + void copyState(const JointState& state); + void setFBXJoint(const FBXJoint* joint); const FBXJoint& getFBXJoint() const { return *_fbxJoint; } - void copyState(const JointState& state); void computeTransform(const glm::mat4& parentTransform); + + void computeVisibleTransform(const glm::mat4& parentTransform); + const glm::mat4& getVisibleTransform() const { return _visibleTransform; } + glm::quat getVisibleRotation() const { return _visibleRotation; } + glm::vec3 getVisiblePosition() const { return extractTranslation(_visibleTransform); } + const glm::mat4& getTransform() const { return _transform; } glm::quat getRotation() const { return _rotation; } @@ -39,11 +46,19 @@ public: /// \param rotation rotation of joint in model-frame void setRotation(const glm::quat& rotation, bool constrain, float priority); - /// \param delta is in the jointParent-frame + /// \param delta is in the model-frame void applyRotationDelta(const glm::quat& delta, bool constrain = true, float priority = 1.0f); - const glm::vec3& getDefaultTranslationInParentFrame() const; + /// Applies delta rotation to joint but mixes a little bit of the default pose as well. + /// This helps keep an IK solution stable. + /// \param delta rotation change in model-frame + /// \param mixFactor fraction in range [0,1] of how much default pose to blend in (0 is none, 1 is all) + /// \param priority priority level of this animation blend + void mixRotationDelta(const glm::quat& delta, float mixFactor, float priority = 1.0f); + /// Blends a fraciton of default pose into joint rotation. + /// \param fraction fraction in range [0,1] of how much default pose to blend in (0 is none, 1 is all) + /// \param priority priority level of this animation blend void restoreRotation(float fraction, float priority); /// \param rotation is from bind- to model-frame @@ -51,16 +66,36 @@ public: /// NOTE: the JointState's model-frame transform/rotation are NOT updated! void setRotationFromBindFrame(const glm::quat& rotation, float priority); + void setRotationInParentFrame(const glm::quat& targetRotation); + const glm::quat& getRotationInParentFrame() const { return _rotationInParentFrame; } + + const glm::vec3& getDefaultTranslationInParentFrame() const; + + void clearTransformTranslation(); - glm::quat _rotationInParentFrame; // joint- to parentJoint-frame + void slaveVisibleTransform(); + float _animationPriority; // the priority of the animation affecting this joint private: + /// \return parent model-frame rotation + // (used to keep _rotation consistent when modifying _rotationInWorldFrame directly) + glm::quat computeParentRotation() const; + + /// debug helper function + void loadBindRotation(); + glm::mat4 _transform; // joint- to model-frame glm::quat _rotation; // joint- to model-frame + glm::quat _rotationInParentFrame; // joint- to parentJoint-frame + + glm::mat4 _visibleTransform; + glm::quat _visibleRotation; + glm::quat _visibleRotationInParentFrame; const FBXJoint* _fbxJoint; // JointState does NOT own its FBXJoint + bool _isConstrained; }; #endif // hifi_JointState_h diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 3b5cda4fd2..ffca2c8ea6 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -39,8 +39,8 @@ Model::Model(QObject* parent) : _scaledToFit(false), _snapModelToCenter(false), _snappedToCenter(false), + _showTrueJointTransforms(false), _rootIndex(-1), - //_enableCollisionShapes(false), _lodDistance(0.0f), _pupilDilation(0.0f), _url("http://invalid.com") { @@ -460,7 +460,7 @@ void Model::reset() { } const FBXGeometry& geometry = _geometry->getFBXGeometry(); for (int i = 0; i < _jointStates.size(); i++) { - _jointStates[i]._rotationInParentFrame = geometry.joints.at(i).rotation; + _jointStates[i].setRotationInParentFrame(geometry.joints.at(i).rotation); } } @@ -571,6 +571,9 @@ void Model::setJointStates(QVector states) { radius = distance; } } + for (int i = 0; i < _jointStates.size(); i++) { + _jointStates[i].slaveVisibleTransform(); + } _boundingRadius = radius; } @@ -686,7 +689,7 @@ bool Model::getJointState(int index, glm::quat& rotation) const { if (index == -1 || index >= _jointStates.size()) { return false; } - rotation = _jointStates.at(index)._rotationInParentFrame; + rotation = _jointStates.at(index).getRotationInParentFrame(); const glm::quat& defaultRotation = _geometry->getFBXGeometry().joints.at(index).rotation; return glm::abs(rotation.x - defaultRotation.x) >= EPSILON || glm::abs(rotation.y - defaultRotation.y) >= EPSILON || @@ -699,7 +702,7 @@ void Model::setJointState(int index, bool valid, const glm::quat& rotation, floa JointState& state = _jointStates[index]; if (priority >= state._animationPriority) { if (valid) { - state._rotationInParentFrame = rotation; + state.setRotationInParentFrame(rotation); state._animationPriority = priority; } else { state.restoreRotation(1.0f, priority); @@ -765,6 +768,23 @@ bool Model::getJointCombinedRotation(int jointIndex, glm::quat& rotation) const return true; } +bool Model::getVisibleJointPositionInWorldFrame(int jointIndex, glm::vec3& position) const { + if (jointIndex == -1 || jointIndex >= _jointStates.size()) { + return false; + } + // position is in world-frame + position = _translation + _rotation * _jointStates[jointIndex].getVisiblePosition(); + return true; +} + +bool Model::getVisibleJointRotationInWorldFrame(int jointIndex, glm::quat& rotation) const { + if (jointIndex == -1 || jointIndex >= _jointStates.size()) { + return false; + } + rotation = _rotation * _jointStates[jointIndex].getVisibleRotation(); + return true; +} + QStringList Model::getJointNames() const { if (QThread::currentThread() != thread()) { QStringList result; @@ -918,6 +938,8 @@ void Model::simulateInternal(float deltaTime) { for (int i = 0; i < _jointStates.size(); i++) { updateJointState(i); } + updateVisibleJointStates(); + _shapesAreDirty = ! _shapes.isEmpty(); // update the attachment transforms and simulate them @@ -928,8 +950,13 @@ void Model::simulateInternal(float deltaTime) { glm::vec3 jointTranslation = _translation; glm::quat jointRotation = _rotation; - getJointPositionInWorldFrame(attachment.jointIndex, jointTranslation); - getJointRotationInWorldFrame(attachment.jointIndex, jointRotation); + if (_showTrueJointTransforms) { + getJointPositionInWorldFrame(attachment.jointIndex, jointTranslation); + getJointRotationInWorldFrame(attachment.jointIndex, jointRotation); + } else { + getVisibleJointPositionInWorldFrame(attachment.jointIndex, jointTranslation); + getVisibleJointRotationInWorldFrame(attachment.jointIndex, jointRotation); + } model->setTranslation(jointTranslation + jointRotation * attachment.translation * _scale); model->setRotation(jointRotation * attachment.rotation); @@ -944,9 +971,16 @@ void Model::simulateInternal(float deltaTime) { for (int i = 0; i < _meshStates.size(); i++) { MeshState& state = _meshStates[i]; const FBXMesh& mesh = geometry.meshes.at(i); - for (int j = 0; j < mesh.clusters.size(); j++) { - const FBXCluster& cluster = mesh.clusters.at(j); - state.clusterMatrices[j] = modelToWorld * _jointStates[cluster.jointIndex].getTransform() * cluster.inverseBindMatrix; + if (_showTrueJointTransforms) { + for (int j = 0; j < mesh.clusters.size(); j++) { + const FBXCluster& cluster = mesh.clusters.at(j); + state.clusterMatrices[j] = modelToWorld * _jointStates[cluster.jointIndex].getTransform() * cluster.inverseBindMatrix; + } + } else { + for (int j = 0; j < mesh.clusters.size(); j++) { + const FBXCluster& cluster = mesh.clusters.at(j); + state.clusterMatrices[j] = modelToWorld * _jointStates[cluster.jointIndex].getVisibleTransform() * cluster.inverseBindMatrix; + } } } @@ -972,6 +1006,14 @@ void Model::updateJointState(int index) { } } +void Model::updateVisibleJointStates() { + if (!_showTrueJointTransforms) { + for (int i = 0; i < _jointStates.size(); i++) { + _jointStates[i].slaveVisibleTransform(); + } + } +} + bool Model::setJointPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation, bool useRotation, int lastFreeIndex, bool allIntermediatesFree, const glm::vec3& alignment, float priority) { if (jointIndex == -1 || _jointStates.isEmpty()) { @@ -1058,6 +1100,128 @@ bool Model::setJointPosition(int jointIndex, const glm::vec3& position, const gl return true; } +void Model::inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm::quat& targetRotation, float priority) { + // NOTE: targetRotation is from bind- to model-frame + + if (endIndex == -1 || _jointStates.isEmpty()) { + return; + } + + const FBXGeometry& geometry = _geometry->getFBXGeometry(); + const QVector& freeLineage = geometry.joints.at(endIndex).freeLineage; + if (freeLineage.isEmpty()) { + return; + } + int numFree = freeLineage.size(); + + // store and remember topmost parent transform + glm::mat4 topParentTransform; + { + int index = freeLineage.last(); + const JointState& state = _jointStates.at(index); + const FBXJoint& joint = state.getFBXJoint(); + int parentIndex = joint.parentIndex; + if (parentIndex == -1) { + const FBXGeometry& geometry = _geometry->getFBXGeometry(); + topParentTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset; + } else { + topParentTransform = _jointStates[parentIndex].getTransform(); + } + } + + // this is a cyclic coordinate descent algorithm: see + // http://www.ryanjuckett.com/programming/animation/21-cyclic-coordinate-descent-in-2d + + // keep track of the position of the end-effector + JointState& endState = _jointStates[endIndex]; + glm::vec3 endPosition = endState.getPosition(); + float distanceToGo = glm::distance(targetPosition, endPosition); + + const int MAX_ITERATION_COUNT = 2; + const float ACCEPTABLE_IK_ERROR = 0.005f; // 5mm + int numIterations = 0; + do { + ++numIterations; + // moving up, rotate each free joint to get endPosition closer to target + for (int j = 1; j < numFree; j++) { + int nextIndex = freeLineage.at(j); + JointState& nextState = _jointStates[nextIndex]; + FBXJoint nextJoint = nextState.getFBXJoint(); + if (! nextJoint.isFree) { + continue; + } + + glm::vec3 pivot = nextState.getPosition(); + glm::vec3 leverArm = endPosition - pivot; + float leverLength = glm::length(leverArm); + if (leverLength < EPSILON) { + continue; + } + glm::quat deltaRotation = rotationBetween(leverArm, targetPosition - pivot); + + /* DON'T REMOVE! This code provides the gravitational effect on the IK solution. + * It is commented out for the moment because we're blending the IK solution with + * the default pose which provides similar stability, but we might want to use + * gravity again later. + + // We want to mix the shortest rotation with one that will pull the system down with gravity. + // So we compute a simplified center of mass, where each joint has a mass of 1.0 and we don't + // bother averaging it because we only need direction. + if (j > 1) { + + glm::vec3 centerOfMass(0.0f); + for (int k = 0; k < j; ++k) { + int massIndex = freeLineage.at(k); + centerOfMass += _jointStates[massIndex].getPosition() - pivot; + } + // the gravitational effect is a rotation that tends to align the two cross products + const glm::vec3 worldAlignment = glm::vec3(0.0f, -1.f, 0.0f); + glm::quat gravityDelta = rotationBetween(glm::cross(centerOfMass, leverArm), + glm::cross(worldAlignment, leverArm)); + + float gravityAngle = glm::angle(gravityDelta); + const float MIN_GRAVITY_ANGLE = 0.1f; + float mixFactor = 0.5f; + if (gravityAngle < MIN_GRAVITY_ANGLE) { + // the final rotation is a mix of the two + mixFactor = 0.5f * gravityAngle / MIN_GRAVITY_ANGLE; + } + deltaRotation = safeMix(deltaRotation, gravityDelta, mixFactor); + } + */ + + // Apply the rotation, but use mixRotationDelta() which blends a bit of the default pose + // at in the process. This provides stability to the IK solution and removes the necessity + // for the gravity effect. + glm::quat oldNextRotation = nextState.getRotation(); + float mixFactor = 0.03f; + nextState.mixRotationDelta(deltaRotation, mixFactor, priority); + + // measure the result of the rotation which may have been modified by + // blending and constraints + glm::quat actualDelta = nextState.getRotation() * glm::inverse(oldNextRotation); + endPosition = pivot + actualDelta * leverArm; + } + + // recompute transforms from the top down + glm::mat4 parentTransform = topParentTransform; + for (int j = numFree - 1; j >= 0; --j) { + JointState& freeState = _jointStates[freeLineage.at(j)]; + freeState.computeTransform(parentTransform); + parentTransform = freeState.getTransform(); + } + + // measure our success + endPosition = endState.getPosition(); + distanceToGo = glm::distance(targetPosition, endPosition); + } while (numIterations < MAX_ITERATION_COUNT && distanceToGo < ACCEPTABLE_IK_ERROR); + + // set final rotation of the end joint + endState.setRotationFromBindFrame(targetRotation, priority); + + _shapesAreDirty = !_shapes.isEmpty(); +} + bool Model::restoreJointPosition(int jointIndex, float fraction, float priority) { if (jointIndex == -1 || _jointStates.isEmpty()) { return false; @@ -1605,7 +1769,7 @@ void AnimationHandle::applyFrame(float frameIndex) { if (mapping != -1) { JointState& state = _model->_jointStates[mapping]; if (_priority >= state._animationPriority) { - state._rotationInParentFrame = safeMix(floorFrame.rotations.at(i), ceilFrame.rotations.at(i), frameFraction); + state.setRotationInParentFrame(safeMix(floorFrame.rotations.at(i), ceilFrame.rotations.at(i), frameFraction)); state._animationPriority = _priority; } } diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 2045a0c9b5..835207b7eb 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -120,6 +120,9 @@ public: bool getJointRotationInWorldFrame(int jointIndex, glm::quat& rotation) const; bool getJointCombinedRotation(int jointIndex, glm::quat& rotation) const; + bool getVisibleJointPositionInWorldFrame(int jointIndex, glm::vec3& position) const; + bool getVisibleJointRotationInWorldFrame(int jointIndex, glm::quat& rotation) const; + /// \param jointIndex index of joint in model structure /// \param position[out] position of joint in model-frame /// \return true if joint exists @@ -152,6 +155,7 @@ protected: bool _snapModelToCenter; /// is the model's offset automatically adjusted to center around 0,0,0 in model space bool _snappedToCenter; /// are we currently snapped to center + bool _showTrueJointTransforms; int _rootIndex; QVector _jointStates; @@ -176,6 +180,8 @@ protected: /// Updates the state of the joint at the specified index. virtual void updateJointState(int index); + + virtual void updateVisibleJointStates(); /// \param jointIndex index of joint in model structure /// \param position position of joint in model-frame @@ -188,6 +194,8 @@ protected: bool setJointPosition(int jointIndex, const glm::vec3& position, 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), float priority = 1.0f); + + void inverseKinematics(int jointIndex, glm::vec3 position, const glm::quat& rotation, float priority); /// Restores the indexed joint to its default position. /// \param fraction the fraction of the default position to apply (i.e., 0.25f to slerp one fourth of the way to diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 77e8986297..82a8f1c183 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -41,8 +41,7 @@ ApplicationOverlay::ApplicationOverlay() : _textureFov(DEFAULT_OCULUS_UI_ANGULAR_SIZE * RADIANS_PER_DEGREE), _alpha(1.0f), _active(true), - _crosshairTexture(0) -{ + _crosshairTexture(0) { memset(_reticleActive, 0, sizeof(_reticleActive)); memset(_magActive, 0, sizeof(_reticleActive)); diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 55d3360884..9c89826cb9 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -12,8 +12,9 @@ #include "Application.h" #include "Menu.h" -#include "PreferencesDialog.h" #include "ModelsBrowser.h" +#include "PreferencesDialog.h" +#include "UserActivityLogger.h" const int SCROLL_PANEL_BOTTOM_MARGIN = 30; const int OK_BUTTON_RIGHT_MARGIN = 30; @@ -176,6 +177,7 @@ void PreferencesDialog::savePreferences() { QString displayNameStr(ui.displayNameEdit->text()); if (displayNameStr != _displayNameString) { myAvatar->setDisplayName(displayNameStr); + UserActivityLogger::getInstance().changedDisplayName(displayNameStr); shouldDispatchIdentityPacket = true; } @@ -183,6 +185,7 @@ void PreferencesDialog::savePreferences() { if (faceModelURL.toString() != _faceURLString) { // change the faceModelURL in the profile, it will also update this user's BlendFace myAvatar->setFaceModelURL(faceModelURL); + UserActivityLogger::getInstance().changedModel("head", faceModelURL.toString()); shouldDispatchIdentityPacket = true; } @@ -190,6 +193,7 @@ void PreferencesDialog::savePreferences() { if (skeletonModelURL.toString() != _skeletonURLString) { // change the skeletonModelURL in the profile, it will also update this user's Body myAvatar->setSkeletonModelURL(skeletonModelURL); + UserActivityLogger::getInstance().changedModel("skeleton", skeletonModelURL.toString()); shouldDispatchIdentityPacket = true; } diff --git a/interface/src/ui/overlays/BillboardOverlay.cpp b/interface/src/ui/overlays/BillboardOverlay.cpp new file mode 100644 index 0000000000..40de565155 --- /dev/null +++ b/interface/src/ui/overlays/BillboardOverlay.cpp @@ -0,0 +1,136 @@ +// +// BillboardOverlay.cpp +// +// +// Created by Clement on 7/1/14. +// Copyright 2014 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 "../../Application.h" + +#include "BillboardOverlay.h" + +BillboardOverlay::BillboardOverlay() +: _manager(NULL), + _scale(1.0f), + _isFacingAvatar(true) { +} + +void BillboardOverlay::render() { + if (_billboard.isEmpty()) { + return; + } + if (!_billboardTexture) { + QImage image = QImage::fromData(_billboard); + if (image.format() != QImage::Format_ARGB32) { + image = image.convertToFormat(QImage::Format_ARGB32); + } + _size = image.size(); + _billboardTexture.reset(new Texture()); + glBindTexture(GL_TEXTURE_2D, _billboardTexture->getID()); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _size.width(), _size.height(), 0, + GL_BGRA, GL_UNSIGNED_BYTE, image.constBits()); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + } else { + glBindTexture(GL_TEXTURE_2D, _billboardTexture->getID()); + } + + glEnable(GL_ALPHA_TEST); + glAlphaFunc(GL_GREATER, 0.5f); + + glEnable(GL_TEXTURE_2D); + glDisable(GL_LIGHTING); + + glPushMatrix(); { + glTranslatef(_position.x, _position.y, _position.z); + if (_isFacingAvatar) { + // rotate about vertical to face the camera + glm::quat rotation = Application::getInstance()->getCamera()->getRotation(); + rotation *= glm::angleAxis(glm::pi(), glm::vec3(0.0f, 1.0f, 0.0f)); + glm::vec3 axis = glm::axis(rotation); + glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z); + } else { + glm::vec3 axis = glm::axis(_rotation); + glRotatef(glm::degrees(glm::angle(_rotation)), axis.x, axis.y, axis.z); + } + glScalef(_scale, _scale, _scale); + + float maxSize = glm::max(_size.width(), _size.height()); + float x = _size.width() / (2.0f * maxSize); + float y = -_size.height() / (2.0f * maxSize); + + glColor3f(1.0f, 1.0f, 1.0f); + glBegin(GL_QUADS); { + glTexCoord2f(0.0f, 0.0f); + glVertex2f(-x, -y); + glTexCoord2f(1.0f, 0.0f); + glVertex2f(x, -y); + glTexCoord2f(1.0f, 1.0f); + glVertex2f(x, y); + glTexCoord2f(0.0f, 1.0f); + glVertex2f(-x, y); + } glEnd(); + + } glPopMatrix(); + + glDisable(GL_TEXTURE_2D); + glEnable(GL_LIGHTING); + glDisable(GL_ALPHA_TEST); + + glBindTexture(GL_TEXTURE_2D, 0); +} + +void BillboardOverlay::setProperties(const QScriptValue &properties) { + Base3DOverlay::setProperties(properties); + + QScriptValue urlValue = properties.property("url"); + if (urlValue.isValid()) { + _url = urlValue.toVariant().toString(); + + setBillboardURL(_url); + } + + QScriptValue scaleValue = properties.property("scale"); + if (scaleValue.isValid()) { + _scale = scaleValue.toVariant().toFloat(); + } + + QScriptValue rotationValue = properties.property("rotation"); + if (rotationValue.isValid()) { + QScriptValue x = rotationValue.property("x"); + QScriptValue y = rotationValue.property("y"); + QScriptValue z = rotationValue.property("z"); + QScriptValue w = rotationValue.property("w"); + if (x.isValid() && y.isValid() && z.isValid() && w.isValid()) { + _rotation.x = x.toVariant().toFloat(); + _rotation.y = y.toVariant().toFloat(); + _rotation.z = z.toVariant().toFloat(); + _rotation.w = w.toVariant().toFloat(); + } + } + + QScriptValue isFacingAvatarValue = properties.property("isFacingAvatar"); + if (isFacingAvatarValue.isValid()) { + _isFacingAvatar = isFacingAvatarValue.toVariant().toBool(); + } +} + +// TODO: handle setting image multiple times, how do we manage releasing the bound texture? +void BillboardOverlay::setBillboardURL(const QUrl url) { + // TODO: are we creating too many QNetworkAccessManager() when multiple calls to setImageURL are made? + _manager->deleteLater(); + _manager = new QNetworkAccessManager(); + connect(_manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*))); + _manager->get(QNetworkRequest(url)); +} + +void BillboardOverlay::replyFinished(QNetworkReply* reply) { + // replace our byte array with the downloaded data + _billboard = reply->readAll(); + _manager->deleteLater(); + _manager = NULL; +} diff --git a/interface/src/ui/overlays/BillboardOverlay.h b/interface/src/ui/overlays/BillboardOverlay.h new file mode 100644 index 0000000000..473e8a066f --- /dev/null +++ b/interface/src/ui/overlays/BillboardOverlay.h @@ -0,0 +1,46 @@ +// +// BillboardOverlay.h +// +// +// Created by Clement on 7/1/14. +// Copyright 2014 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_BillboardOverlay_h +#define hifi_BillboardOverlay_h + +#include +#include + +#include "Base3DOverlay.h" +#include "../../renderer/TextureCache.h" + +class BillboardOverlay : public Base3DOverlay { + Q_OBJECT +public: + BillboardOverlay(); + + virtual void render(); + virtual void setProperties(const QScriptValue& properties); + +private slots: + void replyFinished(QNetworkReply* reply); + +private: + void setBillboardURL(const QUrl url); + + QNetworkAccessManager* _manager; + QUrl _url; + QByteArray _billboard; + QSize _size; + QScopedPointer _billboardTexture; + + glm::quat _rotation; + float _scale; + bool _isFacingAvatar; +}; + +#endif // hifi_BillboardOverlay_h \ No newline at end of file diff --git a/interface/src/ui/overlays/ImageOverlay.cpp b/interface/src/ui/overlays/ImageOverlay.cpp index aa4766488a..79b1b23de5 100644 --- a/interface/src/ui/overlays/ImageOverlay.cpp +++ b/interface/src/ui/overlays/ImageOverlay.cpp @@ -19,7 +19,7 @@ #include "ImageOverlay.h" ImageOverlay::ImageOverlay() : - _manager(0), + _manager(NULL), _textureID(0), _renderImage(false), _textureBound(false), @@ -37,6 +37,7 @@ ImageOverlay::~ImageOverlay() { // TODO: handle setting image multiple times, how do we manage releasing the bound texture? void ImageOverlay::setImageURL(const QUrl& url) { // TODO: are we creating too many QNetworkAccessManager() when multiple calls to setImageURL are made? + _manager->deleteLater(); _manager = new QNetworkAccessManager(); connect(_manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*))); _manager->get(QNetworkRequest(url)); @@ -49,6 +50,7 @@ void ImageOverlay::replyFinished(QNetworkReply* reply) { _textureImage.loadFromData(rawData); _renderImage = true; _manager->deleteLater(); + _manager = NULL; } void ImageOverlay::render() { diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp new file mode 100644 index 0000000000..bc0cc720c2 --- /dev/null +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -0,0 +1,115 @@ +// +// ModelOverlay.cpp +// +// +// Created by Clement on 6/30/14. +// Copyright 2014 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 "../../Menu.h" + +#include "ModelOverlay.h" + +ModelOverlay::ModelOverlay() + : _model(), + _scale(1.0f), + _updateModel(false) { + _model.init(); +} + +void ModelOverlay::update(float deltatime) { + if (_updateModel) { + _updateModel = false; + + _model.setScaleToFit(true, _scale); + _model.setSnapModelToCenter(true); + _model.setRotation(_rotation); + _model.setTranslation(_position); + _model.setURL(_url); + _model.simulate(deltatime, true); + } else { + _model.simulate(deltatime); + } +} + +void ModelOverlay::render() { + if (_model.isActive()) { + + if (_model.isRenderable()) { + _model.render(_alpha); + } + bool displayModelBounds = Menu::getInstance()->isOptionChecked(MenuOption::DisplayModelBounds); + if (displayModelBounds) { + glm::vec3 unRotatedMinimum = _model.getUnscaledMeshExtents().minimum; + glm::vec3 unRotatedMaximum = _model.getUnscaledMeshExtents().maximum; + glm::vec3 unRotatedExtents = unRotatedMaximum - unRotatedMinimum; + + float width = unRotatedExtents.x; + float height = unRotatedExtents.y; + float depth = unRotatedExtents.z; + + Extents rotatedExtents = _model.getUnscaledMeshExtents(); + calculateRotatedExtents(rotatedExtents, _rotation); + + glm::vec3 rotatedSize = rotatedExtents.maximum - rotatedExtents.minimum; + + const glm::vec3& modelScale = _model.getScale(); + + glPushMatrix(); { + glTranslatef(_position.x, _position.y, _position.z); + + // draw the rotated bounding cube + glColor4f(0.0f, 0.0f, 1.0f, 1.0f); + glPushMatrix(); { + glScalef(rotatedSize.x * modelScale.x, rotatedSize.y * modelScale.y, rotatedSize.z * modelScale.z); + glutWireCube(1.0); + } glPopMatrix(); + + // draw the model relative bounding box + glm::vec3 axis = glm::axis(_rotation); + glRotatef(glm::degrees(glm::angle(_rotation)), axis.x, axis.y, axis.z); + glScalef(width * modelScale.x, height * modelScale.y, depth * modelScale.z); + glColor3f(0.0f, 1.0f, 0.0f); + glutWireCube(1.0); + + } glPopMatrix(); + } + } +} + +void ModelOverlay::setProperties(const QScriptValue &properties) { + Base3DOverlay::setProperties(properties); + + QScriptValue urlValue = properties.property("url"); + if (urlValue.isValid()) { + _url = urlValue.toVariant().toString(); + _updateModel = true; + } + + QScriptValue scaleValue = properties.property("scale"); + if (scaleValue.isValid()) { + _scale = scaleValue.toVariant().toFloat(); + _updateModel = true; + } + + QScriptValue rotationValue = properties.property("rotation"); + if (rotationValue.isValid()) { + QScriptValue x = rotationValue.property("x"); + QScriptValue y = rotationValue.property("y"); + QScriptValue z = rotationValue.property("z"); + QScriptValue w = rotationValue.property("w"); + if (x.isValid() && y.isValid() && z.isValid() && w.isValid()) { + _rotation.x = x.toVariant().toFloat(); + _rotation.y = y.toVariant().toFloat(); + _rotation.z = z.toVariant().toFloat(); + _rotation.w = w.toVariant().toFloat(); + } + _updateModel = true; + } + + if (properties.property("position").isValid()) { + _updateModel = true; + } +} \ No newline at end of file diff --git a/interface/src/ui/overlays/ModelOverlay.h b/interface/src/ui/overlays/ModelOverlay.h new file mode 100644 index 0000000000..e0f979676f --- /dev/null +++ b/interface/src/ui/overlays/ModelOverlay.h @@ -0,0 +1,38 @@ +// +// ModelOverlay.h +// +// +// Created by Clement on 6/30/14. +// Copyright 2014 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_ModelOverlay_h +#define hifi_ModelOverlay_h + +#include "Base3DOverlay.h" + +#include "../../renderer/Model.h" + +class ModelOverlay : public Base3DOverlay { + Q_OBJECT +public: + ModelOverlay(); + + virtual void update(float deltatime); + virtual void render(); + virtual void setProperties(const QScriptValue& properties); +private: + + Model _model; + + QUrl _url; + glm::quat _rotation; + float _scale; + + bool _updateModel; +}; + +#endif // hifi_ModelOverlay_h \ No newline at end of file diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 95f4f2b2fe..581947c074 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -10,13 +10,15 @@ #include +#include "BillboardOverlay.h" #include "Cube3DOverlay.h" #include "ImageOverlay.h" #include "Line3DOverlay.h" +#include "LocalVoxelsOverlay.h" +#include "ModelOverlay.h" #include "Overlays.h" #include "Sphere3DOverlay.h" #include "TextOverlay.h" -#include "LocalVoxelsOverlay.h" Overlays::Overlays() : _nextOverlayID(1) { } @@ -82,13 +84,13 @@ void Overlays::render3D() { return; } bool myAvatarComputed = false; - MyAvatar* avatar; + MyAvatar* avatar = NULL; glm::quat myAvatarRotation; - glm::vec3 myAvatarPosition; - float angle; - glm::vec3 axis; - float myAvatarScale; - + glm::vec3 myAvatarPosition(0.0f); + float angle = 0.0f; + glm::vec3 axis(0.0f, 1.0f, 0.0f); + float myAvatarScale = 1.0f; + foreach(Overlay* thisOverlay, _overlays3D) { glPushMatrix(); switch (thisOverlay->getAnchor()) { @@ -156,6 +158,18 @@ unsigned int Overlays::addOverlay(const QString& type, const QScriptValue& prope thisOverlay->setProperties(properties); created = true; is3D = true; + } else if (type == "model") { + thisOverlay = new ModelOverlay(); + thisOverlay->init(_parent); + thisOverlay->setProperties(properties); + created = true; + is3D = true; + } else if (type == "billboard") { + thisOverlay = new BillboardOverlay(); + thisOverlay->init(_parent); + thisOverlay->setProperties(properties); + created = true; + is3D = true; } if (created) { diff --git a/interface/ui/runningScriptsWidget.ui b/interface/ui/runningScriptsWidget.ui index 71efe6970a..b58c8436d0 100644 --- a/interface/ui/runningScriptsWidget.ui +++ b/interface/ui/runningScriptsWidget.ui @@ -137,7 +137,7 @@ background: transparent; Helvetica,Arial,sans-serif - 16 + -1 75 false true @@ -326,9 +326,6 @@ padding-top: 3px; Qt::LeftToRight - - margin: 0; - QFrame::NoFrame @@ -336,7 +333,7 @@ padding-top: 3px; 0 - Qt::ScrollBarAlwaysOn + Qt::ScrollBarAsNeeded Qt::ScrollBarAlwaysOff @@ -352,7 +349,7 @@ padding-top: 3px; 0 0 - 269 + 284 16 @@ -460,7 +457,6 @@ font: bold 16px; background: transparent; font-size: 14px; - runningScriptsList @@ -627,10 +623,10 @@ QListView::item { QFrame::Plain - Qt::ScrollBarAlwaysOff + Qt::ScrollBarAlwaysOn - Qt::ScrollBarAsNeeded + Qt::ScrollBarAlwaysOff diff --git a/libraries/audio/src/AudioRingBuffer.cpp b/libraries/audio/src/AudioRingBuffer.cpp index 6ae3b19541..7937a925a8 100644 --- a/libraries/audio/src/AudioRingBuffer.cpp +++ b/libraries/audio/src/AudioRingBuffer.cpp @@ -53,7 +53,7 @@ void AudioRingBuffer::reset() { _isStarved = true; } -void AudioRingBuffer::resizeForFrameSize(qint64 numFrameSamples) { +void AudioRingBuffer::resizeForFrameSize(int numFrameSamples) { delete[] _buffer; _sampleCapacity = numFrameSamples * RING_BUFFER_LENGTH_FRAMES; _buffer = new int16_t[_sampleCapacity]; @@ -70,14 +70,14 @@ int AudioRingBuffer::parseData(const QByteArray& packet) { return writeData(packet.data() + numBytesBeforeAudioData, packet.size() - numBytesBeforeAudioData); } -qint64 AudioRingBuffer::readSamples(int16_t* destination, qint64 maxSamples) { +int AudioRingBuffer::readSamples(int16_t* destination, int maxSamples) { return readData((char*) destination, maxSamples * sizeof(int16_t)); } -qint64 AudioRingBuffer::readData(char *data, qint64 maxSize) { +int AudioRingBuffer::readData(char *data, int maxSize) { // only copy up to the number of samples we have available - int numReadSamples = std::min((unsigned) (maxSize / sizeof(int16_t)), samplesAvailable()); + int numReadSamples = std::min((int) (maxSize / sizeof(int16_t)), samplesAvailable()); // If we're in random access mode, then we consider our number of available read samples slightly // differently. Namely, if anything has been written, we say we have as many samples as they ask for @@ -118,14 +118,14 @@ qint64 AudioRingBuffer::readData(char *data, qint64 maxSize) { return numReadSamples * sizeof(int16_t); } -qint64 AudioRingBuffer::writeSamples(const int16_t* source, qint64 maxSamples) { +int AudioRingBuffer::writeSamples(const int16_t* source, int maxSamples) { return writeData((const char*) source, maxSamples * sizeof(int16_t)); } -qint64 AudioRingBuffer::writeData(const char* data, qint64 maxSize) { +int AudioRingBuffer::writeData(const char* data, int maxSize) { // make sure we have enough bytes left for this to be the right amount of audio // otherwise we should not copy that data, and leave the buffer pointers where they are - int samplesToCopy = std::min((quint64)(maxSize / sizeof(int16_t)), (quint64)_sampleCapacity); + int samplesToCopy = std::min((int)(maxSize / sizeof(int16_t)), _sampleCapacity); int samplesRoomFor = _sampleCapacity - samplesAvailable(); if (samplesToCopy > samplesRoomFor) { @@ -167,7 +167,7 @@ void AudioRingBuffer::shiftReadPosition(unsigned int numSamples) { } } -unsigned int AudioRingBuffer::samplesAvailable() const { +int AudioRingBuffer::samplesAvailable() const { if (!_endOfLastWrite) { return 0; } @@ -208,7 +208,7 @@ int AudioRingBuffer::addSilentFrame(int numSilentSamples) { return numSilentSamples * sizeof(int16_t); } -bool AudioRingBuffer::isNotStarvedOrHasMinimumSamples(unsigned int numRequiredSamples) const { +bool AudioRingBuffer::isNotStarvedOrHasMinimumSamples(int numRequiredSamples) const { if (!_isStarved) { return true; } else { diff --git a/libraries/audio/src/AudioRingBuffer.h b/libraries/audio/src/AudioRingBuffer.h index 8d19f9c0bb..80a2abeba4 100644 --- a/libraries/audio/src/AudioRingBuffer.h +++ b/libraries/audio/src/AudioRingBuffer.h @@ -43,7 +43,7 @@ public: ~AudioRingBuffer(); void reset(); - void resizeForFrameSize(qint64 numFrameSamples); + void resizeForFrameSize(int numFrameSamples); int getSampleCapacity() const { return _sampleCapacity; } @@ -53,20 +53,20 @@ public: const int16_t* getNextOutput() const { return _nextOutput; } const int16_t* getBuffer() const { return _buffer; } - qint64 readSamples(int16_t* destination, qint64 maxSamples); - qint64 writeSamples(const int16_t* source, qint64 maxSamples); + int readSamples(int16_t* destination, int maxSamples); + int writeSamples(const int16_t* source, int maxSamples); - qint64 readData(char* data, qint64 maxSize); - qint64 writeData(const char* data, qint64 maxSize); + int readData(char* data, int maxSize); + int writeData(const char* data, int maxSize); int16_t& operator[](const int index); const int16_t& operator[] (const int index) const; void shiftReadPosition(unsigned int numSamples); - unsigned int samplesAvailable() const; + int samplesAvailable() const; - bool isNotStarvedOrHasMinimumSamples(unsigned int numRequiredSamples) const; + bool isNotStarvedOrHasMinimumSamples(int numRequiredSamples) const; bool isStarved() const { return _isStarved; } void setIsStarved(bool isStarved) { _isStarved = isStarved; } diff --git a/libraries/audio/src/PositionalAudioRingBuffer.cpp b/libraries/audio/src/PositionalAudioRingBuffer.cpp index 546ed97fe2..e11d73358c 100644 --- a/libraries/audio/src/PositionalAudioRingBuffer.cpp +++ b/libraries/audio/src/PositionalAudioRingBuffer.cpp @@ -212,7 +212,7 @@ bool PositionalAudioRingBuffer::shouldBeAddedToMix() { } return false; - } else if (samplesAvailable() < (unsigned int)samplesPerFrame) { + } else if (samplesAvailable() < samplesPerFrame) { // if the buffer doesn't have a full frame of samples to take for mixing, it is starved _isStarved = true; diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index 95689f8e82..f603d21240 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -11,6 +11,7 @@ #include "NodeList.h" #include "PacketHeaders.h" +#include "UserActivityLogger.h" #include "DomainHandler.h" @@ -83,6 +84,7 @@ void DomainHandler::setHostname(const QString& hostname) { qDebug("Looking up DS hostname %s.", _hostname.toLocal8Bit().constData()); QHostInfo::lookupHost(_hostname, this, SLOT(completedHostnameLookup(const QHostInfo&))); + UserActivityLogger::getInstance().changedDomain(_hostname); emit hostnameChanged(_hostname); } } diff --git a/libraries/networking/src/UserActivityLogger.cpp b/libraries/networking/src/UserActivityLogger.cpp new file mode 100644 index 0000000000..aa18cb43ee --- /dev/null +++ b/libraries/networking/src/UserActivityLogger.cpp @@ -0,0 +1,155 @@ +// +// UserActivityLogger.cpp +// +// +// Created by Clement on 5/21/14. +// Copyright 2014 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 "UserActivityLogger.h" + +#include +#include +#include +#include + +static const QString USER_ACTIVITY_URL = "/api/v1/user_activities"; + +UserActivityLogger& UserActivityLogger::getInstance() { + static UserActivityLogger sharedInstance; + return sharedInstance; +} + +UserActivityLogger::UserActivityLogger() { +} + +void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCallbackParameters params) { + AccountManager& accountManager = AccountManager::getInstance(); + QHttpMultiPart* multipart = new QHttpMultiPart(QHttpMultiPart::FormDataType); + + // Adding the action name + QHttpPart actionPart; + actionPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"action_name\""); + actionPart.setBody(QByteArray().append(action)); + multipart->append(actionPart); + + // If there are action details, add them to the multipart + if (!details.isEmpty()) { + QHttpPart detailsPart; + detailsPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data;" + " name=\"action_details\""); + detailsPart.setBody(QJsonDocument(details).toJson(QJsonDocument::Compact)); + multipart->append(detailsPart); + } + qDebug() << "Logging activity" << action; + + // if no callbacks specified, call our owns + if (params.isEmpty()) { + params.jsonCallbackReceiver = this; + params.jsonCallbackMethod = "requestFinished"; + params.errorCallbackReceiver = this; + params.errorCallbackMethod = "requestError"; + } + + accountManager.authenticatedRequest(USER_ACTIVITY_URL, + QNetworkAccessManager::PostOperation, + params, + NULL, + multipart); +} + +void UserActivityLogger::requestFinished(const QJsonObject& object) { + qDebug() << object; +} + +void UserActivityLogger::requestError(QNetworkReply::NetworkError error,const QString& string) { + qDebug() << error << ": " << string; +} + +void UserActivityLogger::launch(QString applicationVersion) { + const QString ACTION_NAME = "launch"; + QJsonObject actionDetails; + QString VERSION_KEY = "version"; + actionDetails.insert(VERSION_KEY, applicationVersion); + + logAction(ACTION_NAME, actionDetails); +} + +void UserActivityLogger::close(int delayTime) { + const QString ACTION_NAME = "close"; + + // In order to get the end of the session, we need to give the account manager enough time to send the packet. + QEventLoop loop; + // Here we connect the callbacks to stop the event loop + JSONCallbackParameters params; + params.jsonCallbackReceiver = &loop; + params.errorCallbackReceiver = &loop; + params.jsonCallbackMethod = "quit"; + params.errorCallbackMethod = "quit"; + // In case something goes wrong, we also setup a timer so that the delai is not greater than delayTime + QTimer timer; + connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit); + // Now we can log it + logAction(ACTION_NAME, QJsonObject(), params); + timer.start(delayTime); + loop.exec(); +} + +void UserActivityLogger::changedDisplayName(QString displayName) { + const QString ACTION_NAME = "changed_display_name"; + QJsonObject actionDetails; + const QString DISPLAY_NAME = "display_name"; + + actionDetails.insert(DISPLAY_NAME, displayName); + + logAction(ACTION_NAME, actionDetails); +} + +void UserActivityLogger::changedModel(QString typeOfModel, QString modelURL) { + const QString ACTION_NAME = "changed_model"; + QJsonObject actionDetails; + const QString TYPE_OF_MODEL = "type_of_model"; + const QString MODEL_URL = "model_url"; + + actionDetails.insert(TYPE_OF_MODEL, typeOfModel); + actionDetails.insert(MODEL_URL, modelURL); + + logAction(ACTION_NAME, actionDetails); +} + +void UserActivityLogger::changedDomain(QString domainURL) { + const QString ACTION_NAME = "changed_domain"; + QJsonObject actionDetails; + const QString DOMAIN_URL = "domain_url"; + + actionDetails.insert(DOMAIN_URL, domainURL); + + logAction(ACTION_NAME, actionDetails); +} + +void UserActivityLogger::connectedDevice(QString typeOfDevice, QString deviceName) { + const QString ACTION_NAME = "connected_device"; + QJsonObject actionDetails; + const QString TYPE_OF_DEVICE = "type_of_device"; + const QString DEVICE_NAME = "device_name"; + + actionDetails.insert(TYPE_OF_DEVICE, typeOfDevice); + actionDetails.insert(DEVICE_NAME, deviceName); + + logAction(ACTION_NAME, actionDetails); + +} + +void UserActivityLogger::loadedScript(QString scriptName) { + const QString ACTION_NAME = "loaded_script"; + QJsonObject actionDetails; + const QString SCRIPT_NAME = "script_name"; + + actionDetails.insert(SCRIPT_NAME, scriptName); + + logAction(ACTION_NAME, actionDetails); + +} diff --git a/libraries/networking/src/UserActivityLogger.h b/libraries/networking/src/UserActivityLogger.h new file mode 100644 index 0000000000..4823143234 --- /dev/null +++ b/libraries/networking/src/UserActivityLogger.h @@ -0,0 +1,47 @@ +// +// UserActivityLogger.h +// +// +// Created by Clement on 5/21/14. +// Copyright 2014 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_UserActivityLogger_h +#define hifi_UserActivityLogger_h + +#include "AccountManager.h" + +#include +#include +#include +#include + +class UserActivityLogger : public QObject { + Q_OBJECT + +public: + static UserActivityLogger& getInstance(); + +public slots: + void logAction(QString action, QJsonObject details = QJsonObject(), JSONCallbackParameters params = JSONCallbackParameters()); + + void launch(QString applicationVersion); + void close(int delayTime); + void changedDisplayName(QString displayName); + void changedModel(QString typeOfModel, QString modelURL); + void changedDomain(QString domainURL); + void connectedDevice(QString typeOfDevice, QString deviceName); + void loadedScript(QString scriptName); + +private slots: + void requestFinished(const QJsonObject& object); + void requestError(QNetworkReply::NetworkError error,const QString& string); + +private: + UserActivityLogger(); +}; + +#endif // hifi_UserActivityLogger_h \ No newline at end of file diff --git a/libraries/shared/src/PhysicsSimulation.cpp b/libraries/shared/src/PhysicsSimulation.cpp index 637a5e955c..e2f95a213c 100644 --- a/libraries/shared/src/PhysicsSimulation.cpp +++ b/libraries/shared/src/PhysicsSimulation.cpp @@ -24,9 +24,6 @@ int MAX_ENTITIES_PER_SIMULATION = 64; int MAX_COLLISIONS_PER_SIMULATION = 256; -const int NUM_SHAPE_BITS = 6; -const int SHAPE_INDEX_MASK = (1 << (NUM_SHAPE_BITS + 1)) - 1; - PhysicsSimulation::PhysicsSimulation() : _collisionList(MAX_COLLISIONS_PER_SIMULATION), _numIterations(0), _numCollisions(0), _constraintError(0.0f), _stepTime(0) { } diff --git a/tests/networking/src/SequenceNumberStatsTests.cpp b/tests/networking/src/SequenceNumberStatsTests.cpp index 89a14deb20..62651eaa6f 100644 --- a/tests/networking/src/SequenceNumberStatsTests.cpp +++ b/tests/networking/src/SequenceNumberStatsTests.cpp @@ -23,7 +23,7 @@ void SequenceNumberStatsTests::runAllTests() { pruneTest(); } -const int UINT16_RANGE = std::numeric_limits::max() + 1; +const quint32 UINT16_RANGE = std::numeric_limits::max() + 1; void SequenceNumberStatsTests::rolloverTest() { @@ -34,7 +34,7 @@ void SequenceNumberStatsTests::rolloverTest() { quint16 seq = 79; // start on some random number for (int R = 0; R < 2; R++) { - for (int i = 0; i < 3 * UINT16_RANGE; i++) { + for (quint32 i = 0; i < 3 * UINT16_RANGE; i++) { stats.sequenceNumberReceived(seq); seq = seq + (quint16)1; @@ -53,12 +53,12 @@ void SequenceNumberStatsTests::earlyLateTest() { SequenceNumberStats stats; quint16 seq = 65530; - int numSent = 0; + quint32 numSent = 0; - int numEarly = 0; - int numLate = 0; - int numLost = 0; - int numRecovered = 0; + quint32 numEarly = 0; + quint32 numLate = 0; + quint32 numLost = 0; + quint32 numRecovered = 0; for (int R = 0; R < 2; R++) { for (int T = 0; T < 10000; T++) { @@ -122,12 +122,12 @@ void SequenceNumberStatsTests::duplicateTest() { SequenceNumberStats stats; quint16 seq = 12345; - int numSent = 0; + quint32 numSent = 0; - int numDuplicate = 0; - int numEarly = 0; - int numLate = 0; - int numLost = 0; + quint32 numDuplicate = 0; + quint32 numEarly = 0; + quint32 numLate = 0; + quint32 numLost = 0; for (int R = 0; R < 2; R++) { for (int T = 0; T < 10000; T++) { @@ -210,10 +210,10 @@ void SequenceNumberStatsTests::pruneTest() { SequenceNumberStats stats; quint16 seq = 54321; - int numSent = 0; + quint32 numSent = 0; - int numEarly = 0; - int numLost = 0; + quint32 numEarly = 0; + quint32 numLost = 0; for (int R = 0; R < 2; R++) { for (int T = 0; T < 1000; T++) {