diff --git a/cmake/modules/Find3DConnexionClient.cmake b/cmake/modules/Find3DConnexionClient.cmake index 522c25478e..e927ad1954 100644 --- a/cmake/modules/Find3DConnexionClient.cmake +++ b/cmake/modules/Find3DConnexionClient.cmake @@ -18,20 +18,18 @@ include("${MACRO_DIR}/HifiLibrarySearchHints.cmake") hifi_library_search_hints("connexionclient") if (APPLE) - find_library(3DConnexionClient 3DConnexionClient) + find_library(3DCONNEXIONCLIENT_LIBRARIES NAMES 3DConnexionClient HINTS 3DCONNEXIONCLIENT_SEARCH_DIRS) if(EXISTS ${3DConnexionClient}) set(3DCONNEXIONCLIENT_FOUND true) set(3DCONNEXIONCLIENT_INCLUDE_DIRS ${3DConnexionClient}) set(3DCONNEXIONCLIENT_LIBRARY ${3DConnexionClient}) message(STATUS "Found 3DConnexion at " ${3DConnexionClient}) - mark_as_advanced(3DCONNEXIONCLIENT_INCLUDE_DIR CONNEXIONCLIENT_LIBRARY) + mark_as_advanced(3DCONNEXIONCLIENT_INCLUDE_DIR 3DCONNEXIONCLIENT_LIBRARY) else () message(STATUS "Could NOT find 3DConnexionClient") endif() -endif() - -if (WIN32) - find_path(3DCONNEXIONCLIENT_INCLUDE_DIRS I3dMouseParams.h PATH_SUFFIXES Inc HINTS ${CONNEXIONCLIENT_SEARCH_DIRS}) +elseif (WIN32) + find_path(3DCONNEXIONCLIENT_INCLUDE_DIRS I3dMouseParams.h PATH_SUFFIXES include HINTS ${3DCONNEXIONCLIENT_SEARCH_DIRS}) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(3DConnexionClient DEFAULT_MSG 3DCONNEXIONCLIENT_INCLUDE_DIRS) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index ff2f4cf683..696a87d2b8 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -389,6 +389,10 @@ { "value": "json", "label": "Entity server persists data as JSON" + }, + { + "value": "json.gz", + "label": "Entity server persists data as gzipped JSON" } ], "advanced": true diff --git a/examples/gridTest.js b/examples/gridTest.js new file mode 100644 index 0000000000..2baa4650e3 --- /dev/null +++ b/examples/gridTest.js @@ -0,0 +1,78 @@ +// +// Created by Philip Rosedale on July 28, 2015 +// Copyright 2015 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 +// +// Creates a rectangular grid of objects, starting at the origin and proceeding along the X/Z plane. +// Useful for testing the rendering, LOD, and octree storage aspects of the system. +// +// Note that when creating things quickly, the entity server will ignore data if we send updates too quickly. +// like Internet MTU, these rates are set by th domain operator, so in this script there is a RATE_PER_SECOND +// variable letting you set this speed. If entities are missing from the grid after a relog, this number +// being too high may be the reason. + +var SIZE = 10.0; +var SEPARATION = 20.0; +var ROWS_X = 30; +var ROWS_Z = 30; +var TYPE = "Sphere"; // Right now this can be "Box" or "Model" or "Sphere" +var MODEL_URL = "https://hifi-public.s3.amazonaws.com/models/props/LowPolyIsland/CypressTreeGroup.fbx"; +var MODEL_DIMENSION = { x: 33, y: 16, z: 49 }; +var RATE_PER_SECOND = 1000; // The entity server will drop data if we create things too fast. +var SCRIPT_INTERVAL = 100; +var LIFETIME = 600; // By default, these entities will live in the server for 10 minutes + +var addRandom = false; + +var x = 0; +var z = 0; +var totalCreated = 0; + +Script.setInterval(function () { + if (!Entities.serversExist() || !Entities.canRez()) { + return; + } + + var numToCreate = RATE_PER_SECOND * (SCRIPT_INTERVAL / 1000.0); + for (var i = 0; i < numToCreate; i++) { + var position = { x: SIZE + (x * SEPARATION), y: SIZE, z: SIZE + (z * SEPARATION) }; + if (TYPE == "Model") { + Entities.addEntity({ + type: TYPE, + name: "gridTest", + modelURL: MODEL_URL, + position: position, + dimensions: MODEL_DIMENSION, + ignoreCollisions: true, + collisionsWillMove: false, + lifetime: LIFETIME + }); + } else { + Entities.addEntity({ + type: TYPE, + name: "gridTest", + position: position, + dimensions: { x: SIZE, y: SIZE, z: SIZE }, + color: { red: x / ROWS_X * 255, green: 50, blue: z / ROWS_Z * 255 }, + ignoreCollisions: true, + collisionsWillMove: false, + lifetime: LIFETIME + }); + } + + totalCreated++; + + x++; + if (x == ROWS_X) { + x = 0; + z++; + print("Created: " + totalCreated); + } + if (z == ROWS_Z) { + Script.stop(); + } + } +}, SCRIPT_INTERVAL); + diff --git a/interface/external/connexionclient/Inc/I3dMouseParams.h b/interface/external/3dconnexionclient/include/I3dMouseParams.h similarity index 100% rename from interface/external/connexionclient/Inc/I3dMouseParams.h rename to interface/external/3dconnexionclient/include/I3dMouseParams.h diff --git a/interface/external/connexionclient/readme.txt b/interface/external/3dconnexionclient/readme.txt similarity index 65% rename from interface/external/connexionclient/readme.txt rename to interface/external/3dconnexionclient/readme.txt index cbfc5a277a..a8014b386d 100644 --- a/interface/external/connexionclient/readme.txt +++ b/interface/external/3dconnexionclient/readme.txt @@ -1,3 +1,3 @@ The Mac version does not require any files. The 3D Connexion driver should be installed from http://www.3dconnexion.eu/service/drivers.html -For Windows the provided header file is required: Inc/I3dMouseParams.h \ No newline at end of file +For Windows the provided header file is required: include/I3dMouseParams.h \ No newline at end of file diff --git a/interface/resources/meshes/defaultAvatar_full.fst b/interface/resources/meshes/defaultAvatar_full.fst new file mode 100644 index 0000000000..eba175e771 --- /dev/null +++ b/interface/resources/meshes/defaultAvatar_full.fst @@ -0,0 +1,64 @@ +name = defaultAvatar_full +type = body+head +scale = 1 +filename = defaultAvatar_full/defaultAvatar_full.fbx +texdir = defaultAvatar_full/textures +joint = jointNeck = Head +joint = jointLeftHand = LeftHand +joint = jointRoot = Hips +joint = jointHead = HeadTop_End +joint = jointRightHand = RightHand +joint = jointLean = Spine +freeJoint = LeftArm +freeJoint = LeftForeArm +freeJoint = RightArm +freeJoint = RightForeArm +jointIndex = LeftHand = 35 +jointIndex = Reye = 3 +jointIndex = Hips = 10 +jointIndex = LeftHandIndex1 = 36 +jointIndex = LeftHandIndex2 = 37 +jointIndex = LeftHandIndex3 = 38 +jointIndex = LeftHandIndex4 = 39 +jointIndex = LeftShoulder = 32 +jointIndex = RightLeg = 12 +jointIndex = Grp_blendshapes = 0 +jointIndex = Leye = 4 +jointIndex = headphone = 8 +jointIndex = RightForeArm = 26 +jointIndex = Spine = 21 +jointIndex = LeftFoot = 18 +jointIndex = RightToeBase = 14 +jointIndex = face = 1 +jointIndex = LeftToe_End = 20 +jointIndex = Spine1 = 22 +jointIndex = body = 9 +jointIndex = Spine2 = 23 +jointIndex = RightUpLeg = 11 +jointIndex = top1 = 7 +jointIndex = Neck = 40 +jointIndex = HeadTop_End = 42 +jointIndex = RightShoulder = 24 +jointIndex = RightArm = 25 +jointIndex = Head = 41 +jointIndex = LeftLeg = 17 +jointIndex = LeftForeArm = 34 +jointIndex = hair = 6 +jointIndex = RightHand = 27 +jointIndex = LeftToeBase = 19 +jointIndex = LeftUpLeg = 16 +jointIndex = mouth = 2 +jointIndex = RightFoot = 13 +jointIndex = LeftArm = 33 +jointIndex = shield = 5 +jointIndex = RightHandIndex1 = 28 +jointIndex = RightHandIndex2 = 29 +jointIndex = RightToe_End = 15 +jointIndex = RightHandIndex3 = 30 +jointIndex = RightHandIndex4 = 31 +ry = 0 +rz = 0 +tx = 0 +ty = 0 +tz = 0 +rx = 0 diff --git a/interface/resources/meshes/defaultAvatar_full/defaultAvatar_full.fbx b/interface/resources/meshes/defaultAvatar_full/defaultAvatar_full.fbx new file mode 100644 index 0000000000..71bc127c41 Binary files /dev/null and b/interface/resources/meshes/defaultAvatar_full/defaultAvatar_full.fbx differ diff --git a/interface/resources/meshes/defaultAvatar_full/textures/visor.png b/interface/resources/meshes/defaultAvatar_full/textures/visor.png new file mode 100644 index 0000000000..e4e6292b2c Binary files /dev/null and b/interface/resources/meshes/defaultAvatar_full/textures/visor.png differ diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 6a34922583..503acc2ce3 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -641,7 +641,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : applicationUpdater->checkForUpdate(); // the 3Dconnexion device wants to be initiliazed after a window is displayed. - ConnexionClient::init(); + ConnexionClient::getInstance().init(); auto& packetReceiver = nodeList->getPacketReceiver(); packetReceiver.registerListener(PacketType::DomainConnectionDenied, this, "handleDomainConnectionDeniedPacket"); @@ -754,7 +754,7 @@ Application::~Application() { Leapmotion::destroy(); RealSense::destroy(); - ConnexionClient::destroy(); + ConnexionClient::getInstance().destroy(); qInstallMessageHandler(NULL); // NOTE: Do this as late as possible so we continue to get our log messages } @@ -2020,6 +2020,7 @@ void Application::setActiveFaceTracker() { #ifdef HAVE_DDE bool isUsingDDE = Menu::getInstance()->isOptionChecked(MenuOption::UseCamera); Menu::getInstance()->getActionForOption(MenuOption::BinaryEyelidControl)->setVisible(isUsingDDE); + Menu::getInstance()->getActionForOption(MenuOption::CoupleEyelids)->setVisible(isUsingDDE); Menu::getInstance()->getActionForOption(MenuOption::UseAudioForMouth)->setVisible(isUsingDDE); Menu::getInstance()->getActionForOption(MenuOption::VelocityFilter)->setVisible(isUsingDDE); Menu::getInstance()->getActionForOption(MenuOption::CalibrateCamera)->setVisible(isUsingDDE); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 3cade466e2..6fcc46478e 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -421,6 +421,8 @@ Menu::Menu() { faceTrackingMenu->addSeparator(); QAction* binaryEyelidControl = addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::BinaryEyelidControl, 0, true); binaryEyelidControl->setVisible(true); // DDE face tracking is on by default + QAction* coupleEyelids = addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::CoupleEyelids, 0, true); + coupleEyelids->setVisible(true); // DDE face tracking is on by default QAction* useAudioForMouth = addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::UseAudioForMouth, 0, true); useAudioForMouth->setVisible(true); // DDE face tracking is on by default QAction* ddeFiltering = addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::VelocityFilter, 0, true); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 62a1ae3b0f..bd6c982d70 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -165,6 +165,7 @@ namespace MenuOption { const QString ControlWithSpeech = "Control With Speech"; const QString CopyAddress = "Copy Address to Clipboard"; const QString CopyPath = "Copy Path to Clipboard"; + const QString CoupleEyelids = "Couple Eyelids"; const QString DebugAmbientOcclusion = "Debug Ambient Occlusion"; const QString DecreaseAvatarSize = "Decrease Avatar Size"; const QString DeleteBookmark = "Delete Bookmark..."; diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index e29e5e4408..b25eaa4030 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -936,8 +936,14 @@ void Avatar::setFaceModelURL(const QUrl& faceModelURL) { void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { AvatarData::setSkeletonModelURL(skeletonModelURL); + const QUrl DEFAULT_FULL_MODEL_URL = QUrl::fromLocalFile(PathUtils::resourcesPath() + "meshes/defaultAvatar_full.fst"); const QUrl DEFAULT_SKELETON_MODEL_URL = QUrl::fromLocalFile(PathUtils::resourcesPath() + "meshes/defaultAvatar_body.fst"); - _skeletonModel.setURL(_skeletonModelURL, DEFAULT_SKELETON_MODEL_URL, true, !isMyAvatar()); + if (isMyAvatar()) { + _skeletonModel.setURL(_skeletonModelURL, + getUseFullAvatar() ? DEFAULT_FULL_MODEL_URL : DEFAULT_SKELETON_MODEL_URL, true, !isMyAvatar()); + } else { + _skeletonModel.setURL(_skeletonModelURL, DEFAULT_SKELETON_MODEL_URL, true, !isMyAvatar()); + } } void Avatar::setAttachmentData(const QVector& attachmentData) { diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index e2711bc34b..a51da387d0 100644 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -150,6 +150,7 @@ public: Q_INVOKABLE glm::vec3 getAngularVelocity() const { return _angularVelocity; } Q_INVOKABLE glm::vec3 getAngularAcceleration() const { return _angularAcceleration; } + virtual bool getUseFullAvatar() const { return false; } /// Scales a world space position vector relative to the avatar position and scale /// \param vector position to be scaled. Will store the result diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 2d7cf4ca5e..3a49b1cc82 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -623,6 +623,12 @@ float loadSetting(QSettings& settings, const char* name, float defaultValue) { return value; } +void MyAvatar::setEnableRigAnimations(bool isEnabled) { + Settings settings; + settings.setValue("enableRig", isEnabled); + _rig->setEnableRig(isEnabled); +} + void MyAvatar::loadData() { Settings settings; settings.beginGroup("Avatar"); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 129a05f93b..13223b66b9 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -72,6 +72,7 @@ public: Q_INVOKABLE AnimationDetails getAnimationDetailsByRole(const QString& role); Q_INVOKABLE AnimationDetails getAnimationDetails(const QString& url); void clearJointAnimationPriorities(); + Q_INVOKABLE void setEnableRigAnimations(bool isEnabled); // get/set avatar data void saveData(); @@ -115,7 +116,7 @@ public: Q_INVOKABLE void useHeadAndBodyURLs(const QUrl& headURL, const QUrl& bodyURL, const QString& headName = QString(), const QString& bodyName = QString()); - Q_INVOKABLE bool getUseFullAvatar() const { return _useFullAvatar; } + Q_INVOKABLE virtual bool getUseFullAvatar() const { return _useFullAvatar; } Q_INVOKABLE const QUrl& getFullAvatarURLFromPreferences() const { return _fullAvatarURLFromPreferences; } Q_INVOKABLE const QUrl& getHeadURLFromPreferences() const { return _headURLFromPreferences; } Q_INVOKABLE const QUrl& getBodyURLFromPreferences() const { return _skeletonURLFromPreferences; } diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index c516f24fef..d5cb0517a7 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -42,7 +42,23 @@ SkeletonModel::~SkeletonModel() { void SkeletonModel::initJointStates(QVector states) { const FBXGeometry& geometry = _geometry->getFBXGeometry(); glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset; - _boundingRadius = _rig->initJointStates(states, parentTransform); + + int rootJointIndex = geometry.rootJointIndex; + int leftHandJointIndex = geometry.leftHandJointIndex; + int leftElbowJointIndex = leftHandJointIndex >= 0 ? geometry.joints.at(leftHandJointIndex).parentIndex : -1; + int leftShoulderJointIndex = leftElbowJointIndex >= 0 ? geometry.joints.at(leftElbowJointIndex).parentIndex : -1; + int rightHandJointIndex = geometry.rightHandJointIndex; + int rightElbowJointIndex = rightHandJointIndex >= 0 ? geometry.joints.at(rightHandJointIndex).parentIndex : -1; + int rightShoulderJointIndex = rightElbowJointIndex >= 0 ? geometry.joints.at(rightElbowJointIndex).parentIndex : -1; + + _boundingRadius = _rig->initJointStates(states, parentTransform, + rootJointIndex, + leftHandJointIndex, + leftElbowJointIndex, + leftShoulderJointIndex, + rightHandJointIndex, + rightElbowJointIndex, + rightShoulderJointIndex); // Determine the default eye position for avatar scale = 1.0 int headJointIndex = _geometry->getFBXGeometry().headJointIndex; @@ -227,7 +243,7 @@ void SkeletonModel::applyPalmData(int jointIndex, PalmData& palm) { palmRotation = rotationBetween(palmRotation * glm::vec3(-sign, 0.0f, 0.0f), fingerDirection) * palmRotation; if (Menu::getInstance()->isOptionChecked(MenuOption::AlternateIK)) { - setHandPosition(jointIndex, palmPosition, palmRotation); + _rig->setHandPosition(jointIndex, palmPosition, palmRotation, extractUniformScale(_scale), PALM_PRIORITY); } else if (Menu::getInstance()->isOptionChecked(MenuOption::AlignForearmsWithWrists)) { float forearmLength = geometry.joints.at(jointIndex).distanceToParent * extractUniformScale(_scale); glm::vec3 forearm = palmRotation * glm::vec3(sign * forearmLength, 0.0f, 0.0f); @@ -333,69 +349,6 @@ void SkeletonModel::renderOrientationDirections(gpu::Batch& batch, int jointInde geometryCache->renderLine(batch, position, pFront, blue, jointLineIDs._front); } - - -void SkeletonModel::setHandPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation) { - // this algorithm is from sample code from sixense - const FBXGeometry& geometry = _geometry->getFBXGeometry(); - int elbowJointIndex = geometry.joints.at(jointIndex).parentIndex; - if (elbowJointIndex == -1) { - return; - } - int shoulderJointIndex = geometry.joints.at(elbowJointIndex).parentIndex; - glm::vec3 shoulderPosition; - if (!getJointPosition(shoulderJointIndex, shoulderPosition)) { - return; - } - // precomputed lengths - float scale = extractUniformScale(_scale); - float upperArmLength = geometry.joints.at(elbowJointIndex).distanceToParent * scale; - float lowerArmLength = geometry.joints.at(jointIndex).distanceToParent * scale; - - // first set wrist position - glm::vec3 wristPosition = position; - - glm::vec3 shoulderToWrist = wristPosition - shoulderPosition; - float distanceToWrist = glm::length(shoulderToWrist); - - // prevent gimbal lock - if (distanceToWrist > upperArmLength + lowerArmLength - EPSILON) { - distanceToWrist = upperArmLength + lowerArmLength - EPSILON; - shoulderToWrist = glm::normalize(shoulderToWrist) * distanceToWrist; - wristPosition = shoulderPosition + shoulderToWrist; - } - - // cosine of angle from upper arm to hand vector - float cosA = (upperArmLength * upperArmLength + distanceToWrist * distanceToWrist - lowerArmLength * lowerArmLength) / - (2 * upperArmLength * distanceToWrist); - float mid = upperArmLength * cosA; - float height = sqrt(upperArmLength * upperArmLength + mid * mid - 2 * upperArmLength * mid * cosA); - - // direction of the elbow - glm::vec3 handNormal = glm::cross(rotation * glm::vec3(0.0f, 1.0f, 0.0f), shoulderToWrist); // elbow rotating with wrist - glm::vec3 relaxedNormal = glm::cross(glm::vec3(0.0f, 1.0f, 0.0f), shoulderToWrist); // elbow pointing straight down - const float NORMAL_WEIGHT = 0.5f; - glm::vec3 finalNormal = glm::mix(relaxedNormal, handNormal, NORMAL_WEIGHT); - - bool rightHand = (jointIndex == geometry.rightHandJointIndex); - if (rightHand ? (finalNormal.y > 0.0f) : (finalNormal.y < 0.0f)) { - finalNormal.y = 0.0f; // dont allow elbows to point inward (y is vertical axis) - } - - glm::vec3 tangent = glm::normalize(glm::cross(shoulderToWrist, finalNormal)); - - // ik solution - glm::vec3 elbowPosition = shoulderPosition + glm::normalize(shoulderToWrist) * mid - tangent * height; - glm::vec3 forwardVector(rightHand ? -1.0f : 1.0f, 0.0f, 0.0f); - glm::quat shoulderRotation = rotationBetween(forwardVector, elbowPosition - shoulderPosition); - - _rig->setJointRotationInBindFrame(shoulderJointIndex, shoulderRotation, PALM_PRIORITY); - _rig->setJointRotationInBindFrame(elbowJointIndex, - rotationBetween(shoulderRotation * forwardVector, wristPosition - elbowPosition) * - shoulderRotation, PALM_PRIORITY); - _rig->setJointRotationInBindFrame(jointIndex, rotation, PALM_PRIORITY); -} - bool SkeletonModel::getLeftHandPosition(glm::vec3& position) const { return getJointPositionInWorldFrame(getLeftHandJointIndex(), position); } diff --git a/interface/src/avatar/SkeletonModel.h b/interface/src/avatar/SkeletonModel.h index ecc5c80118..a13d3c4e75 100644 --- a/interface/src/avatar/SkeletonModel.h +++ b/interface/src/avatar/SkeletonModel.h @@ -131,11 +131,6 @@ private: QHash _jointOrientationLines; int _triangleFanID; - /// \param jointIndex index of joint in model - /// \param position position of joint in model-frame - /// \param rotation rotation of joint in model-frame - void setHandPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation); - bool getEyeModelPositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const; Avatar* _owningAvatar; diff --git a/interface/src/devices/3DConnexionClient.cpp b/interface/src/devices/3DConnexionClient.cpp index 2e0a30715c..7459f7da2a 100755 --- a/interface/src/devices/3DConnexionClient.cpp +++ b/interface/src/devices/3DConnexionClient.cpp @@ -158,19 +158,16 @@ ConnexionClient& ConnexionClient::getInstance() { #ifdef HAVE_3DCONNEXIONCLIENT -#ifdef _WIN32 - -static ConnexionClient* gMouseInput = 0; +#ifdef Q_OS_WIN void ConnexionClient::toggleConnexion(bool shouldEnable) { ConnexionData& connexiondata = ConnexionData::getInstance(); if (shouldEnable && connexiondata.getDeviceID() == 0) { - ConnexionClient::init(); + init(); } if (!shouldEnable && connexiondata.getDeviceID() != 0) { - ConnexionClient::destroy(); + destroy(); } - } void ConnexionClient::init() { @@ -179,14 +176,12 @@ void ConnexionClient::init() { InitializeRawInput(GetActiveWindow()); - gMouseInput = &this; - - QAbstractEventDispatcher::instance()->installNativeEventFilter(&cclient); + QAbstractEventDispatcher::instance()->installNativeEventFilter(this); } } void ConnexionClient::destroy() { - QAbstractEventDispatcher::instance()->removeNativeEventFilter(&this); + QAbstractEventDispatcher::instance()->removeNativeEventFilter(this); ConnexionData& connexiondata = ConnexionData::getInstance(); int deviceid = connexiondata.getDeviceID(); connexiondata.setDeviceID(0); @@ -294,17 +289,17 @@ unsigned short HidToVirtualKey(unsigned long pid, unsigned short hidKeyCode) { bool ConnexionClient::RawInputEventFilter(void* msg, long* result) { ConnexionData& connexiondata = ConnexionData::getInstance(); - if (ConnexionClient::Is3dmouseAttached() && connexiondata.getDeviceID() == 0) { + if (Is3dmouseAttached() && connexiondata.getDeviceID() == 0) { connexiondata.registerToUserInputMapper(*Application::getUserInputMapper()); connexiondata.assignDefaultInputMapping(*Application::getUserInputMapper()); UserActivityLogger::getInstance().connectedDevice("controller", "3Dconnexion"); - } else if (!ConnexionClient::Is3dmouseAttached() && connexiondata.getDeviceID() != 0) { + } else if (!Is3dmouseAttached() && connexiondata.getDeviceID() != 0) { int deviceid = connexiondata.getDeviceID(); connexiondata.setDeviceID(0); Application::getUserInputMapper()->removeDevice(deviceid); } - if (!ConnexionClient::Is3dmouseAttached()) { + if (!Is3dmouseAttached()) { return false; } @@ -312,7 +307,7 @@ bool ConnexionClient::RawInputEventFilter(void* msg, long* result) { if (message->message == WM_INPUT) { HRAWINPUT hRawInput = reinterpret_cast(message->lParam); - gMouseInput->OnRawInput(RIM_INPUT, hRawInput); + OnRawInput(RIM_INPUT, hRawInput); if (result != 0) { result = 0; } @@ -321,14 +316,6 @@ bool ConnexionClient::RawInputEventFilter(void* msg, long* result) { return false; } -ConnexionClient::ConnexionClient() { - -} - -ConnexionClient::~ConnexionClient() { - QAbstractEventDispatcher::instance()->removeNativeEventFilter(&this); -} - // Access the mouse parameters structure I3dMouseParam& ConnexionClient::MouseParams() { return f3dMouseParams; @@ -808,10 +795,6 @@ MouseParameters::MouseParameters() : { } -MouseParameters::~MouseParameters() -{ -} - bool MouseParameters::IsPanZoom() const { return fIsPanZoom; } @@ -881,11 +864,11 @@ static void DeviceRemovedHandler(unsigned int connection); static void MessageHandler(unsigned int connection, unsigned int messageType, void *messageArgument); void ConnexionClient::toggleConnexion(bool shouldEnable) { - if (shouldEnable && !ConnexionClient::Is3dmouseAttached()) { - ConnexionClient::init(); + if (shouldEnable && !Is3dmouseAttached()) { + init(); } - if (!shouldEnable && ConnexionClient::Is3dmouseAttached()) { - ConnexionClient::destroy(); + if (!shouldEnable && Is3dmouseAttached()) { + destroy(); } } @@ -908,7 +891,7 @@ void ConnexionClient::init() { // use default switches ConnexionClientControl(fConnexionClientID, kConnexionCtlSetSwitches, kConnexionSwitchesDisabled, NULL); - if (ConnexionClient::Is3dmouseAttached() && connexiondata.getDeviceID() == 0) { + if (Is3dmouseAttached() && connexiondata.getDeviceID() == 0) { connexiondata.registerToUserInputMapper(*Application::getUserInputMapper()); connexiondata.assignDefaultInputMapping(*Application::getUserInputMapper()); UserActivityLogger::getInstance().connectedDevice("controller", "3Dconnexion"); diff --git a/interface/src/devices/3DConnexionClient.h b/interface/src/devices/3DConnexionClient.h index 454c89f797..f0e9f10785 100755 --- a/interface/src/devices/3DConnexionClient.h +++ b/interface/src/devices/3DConnexionClient.h @@ -23,9 +23,9 @@ class ConnexionClient : public QObject { Q_OBJECT public: static ConnexionClient& getInstance(); - static void init() {}; - static void destroy() {}; - static bool Is3dmouseAttached() { return false; }; + void init() {}; + void destroy() {}; + bool Is3dmouseAttached() { return false; }; public slots: void toggleConnexion(bool shouldEnable) {}; }; @@ -33,7 +33,7 @@ public slots: #ifdef HAVE_3DCONNEXIONCLIENT // the windows connexion rawinput -#ifdef _WIN32 +#ifdef Q_OS_WIN #include "I3dMouseParams.h" #include @@ -45,7 +45,6 @@ public slots: class MouseParameters : public I3dMouseParam { public: MouseParameters(); - ~MouseParameters(); // I3dmouseSensor interface bool IsPanZoom() const; @@ -86,13 +85,12 @@ private: class ConnexionClient : public QObject, public QAbstractNativeEventFilter { Q_OBJECT public: - ConnexionClient(); - ~ConnexionClient(); + ConnexionClient() {}; static ConnexionClient& getInstance(); - static void init(); - static void destroy(); - static bool Is3dmouseAttached(); + void init(); + void destroy(); + bool Is3dmouseAttached(); ConnexionClient* client; @@ -106,7 +104,7 @@ public: virtual bool nativeEventFilter(const QByteArray& eventType, void* message, long* result) Q_DECL_OVERRIDE { MSG* msg = static_cast< MSG * >(message); - return ConnexionClient::RawInputEventFilter(message, result); + return RawInputEventFilter(message, result); } public slots: @@ -120,7 +118,7 @@ signals: private: bool InitializeRawInput(HWND hwndTarget); - static bool RawInputEventFilter(void* msg, long* result); + bool RawInputEventFilter(void* msg, long* result); void OnRawInput(UINT nInputCode, HRAWINPUT hRawInput); UINT GetRawInputBuffer(PRAWINPUT pData, PUINT pcbSize, UINT cbSizeHeader); @@ -165,9 +163,9 @@ class ConnexionClient : public QObject { Q_OBJECT public: static ConnexionClient& getInstance(); - static void init(); - static void destroy(); - static bool Is3dmouseAttached(); + void init(); + void destroy(); + bool Is3dmouseAttached(); public slots: void toggleConnexion(bool shouldEnable); }; diff --git a/interface/src/devices/DdeFaceTracker.cpp b/interface/src/devices/DdeFaceTracker.cpp index c9170bd413..396539c3cf 100644 --- a/interface/src/devices/DdeFaceTracker.cpp +++ b/interface/src/devices/DdeFaceTracker.cpp @@ -564,6 +564,13 @@ void DdeFaceTracker::decodePacket(const QByteArray& buffer) { eyeCoefficients[1] = _filteredEyeBlinks[1]; } + // Couple eyelid values if configured - use the most "open" value for both + if (Menu::getInstance()->isOptionChecked(MenuOption::CoupleEyelids)) { + float eyeCoefficient = std::min(eyeCoefficients[0], eyeCoefficients[1]); + eyeCoefficients[0] = eyeCoefficient; + eyeCoefficients[1] = eyeCoefficient; + } + // Use EyeBlink values to control both EyeBlink and EyeOpen if (eyeCoefficients[0] > 0) { _coefficients[_leftBlinkIndex] = eyeCoefficients[0]; diff --git a/libraries/animation/src/AnimationHandle.cpp b/libraries/animation/src/AnimationHandle.cpp index 605fb25f1c..ebf0e29b97 100644 --- a/libraries/animation/src/AnimationHandle.cpp +++ b/libraries/animation/src/AnimationHandle.cpp @@ -49,7 +49,7 @@ void AnimationHandle::setMaskedJoints(const QStringList& maskedJoints) { _jointMappings.clear(); } -void AnimationHandle::setRunning(bool running) { +void AnimationHandle::setRunning(bool running, bool doRestoreJoints) { if (running && isRunning()) { // if we're already running, this is the same as a restart setFrameIndex(getFirstFrame()); @@ -62,7 +62,9 @@ void AnimationHandle::setRunning(bool running) { } } else { _rig->removeRunningAnimation(getAnimationHandlePointer()); - restoreJoints(); + if (doRestoreJoints) { + restoreJoints(); + } replaceMatchingPriorities(0.0f); } emit runningChanged(isRunning()); @@ -71,7 +73,9 @@ void AnimationHandle::setRunning(bool running) { AnimationHandle::AnimationHandle(RigPointer rig) : QObject(rig.get()), _rig(rig), - _priority(1.0f) + _priority(1.0f), + _fade(0.0f), + _fadePerSecond(0.0f) { } diff --git a/libraries/animation/src/AnimationHandle.h b/libraries/animation/src/AnimationHandle.h index 42e564944e..65f7423aa5 100644 --- a/libraries/animation/src/AnimationHandle.h +++ b/libraries/animation/src/AnimationHandle.h @@ -64,6 +64,10 @@ public: void setPriority(float priority); float getPriority() const { return _priority; } void setMix(float mix) { _mix = mix; } + void setFade(float fade) { _fade = fade; } + float getFade() const { return _fade; } + void setFadePerSecond(float fadePerSecond) { _fadePerSecond = fadePerSecond; } + float getFadePerSecond() const { return _fadePerSecond; } void setMaskedJoints(const QStringList& maskedJoints); const QStringList& getMaskedJoints() const { return _maskedJoints; } @@ -87,7 +91,7 @@ public: void setLastFrame(float lastFrame) { _animationLoop.setLastFrame(lastFrame); } float getLastFrame() const { return _animationLoop.getLastFrame(); } - void setRunning(bool running); + void setRunning(bool running, bool restoreJoints = true); bool isRunning() const { return _animationLoop.isRunning(); } void setFrameIndex(float frameIndex) { _animationLoop.setFrameIndex(frameIndex); } @@ -111,7 +115,7 @@ signals: public slots: void start() { setRunning(true); } - void stop() { setRunning(false); } + void stop() { setRunning(false); _fadePerSecond = _fade = 0.0f; } private: @@ -120,7 +124,9 @@ private: QString _role; QUrl _url; float _priority; - float _mix; + float _mix; // How much of this animation to blend against what is already there. 1.0 sets to just this animation. + float _fade; // How far are we into full strength. 0.0 uses none of this animation, 1.0 (the max) is as much as possible. + float _fadePerSecond; // How fast should _fade change? +1.0 means _fade is increasing to 1.0 in 1 second. Negative is fading out. QStringList _maskedJoints; QVector _jointMappings; diff --git a/libraries/animation/src/AvatarRig.cpp b/libraries/animation/src/AvatarRig.cpp index 919ea43e7d..97fbb82944 100644 --- a/libraries/animation/src/AvatarRig.cpp +++ b/libraries/animation/src/AvatarRig.cpp @@ -20,15 +20,81 @@ void AvatarRig::updateJointState(int index, glm::mat4 parentTransform) { const FBXJoint& joint = state.getFBXJoint(); // compute model transforms - int parentIndex = joint.parentIndex; - if (parentIndex == -1) { + if (index == _rootJointIndex) { + // we always zero-out the translation part of an avatar's root join-transform. state.computeTransform(parentTransform); clearJointTransformTranslation(index); } else { // guard against out-of-bounds access to _jointStates - if (joint.parentIndex >= 0 && joint.parentIndex < _jointStates.size()) { + int parentIndex = joint.parentIndex; + if (parentIndex >= 0 && parentIndex < _jointStates.size()) { const JointState& parentState = _jointStates.at(parentIndex); state.computeTransform(parentState.getTransform(), parentState.getTransformChanged()); } } } + +void AvatarRig::setHandPosition(int jointIndex, + const glm::vec3& position, const glm::quat& rotation, + float scale, float priority) { + bool rightHand = (jointIndex == _rightHandJointIndex); + + int elbowJointIndex = rightHand ? _rightElbowJointIndex : _leftElbowJointIndex; + int shoulderJointIndex = rightHand ? _rightShoulderJointIndex : _leftShoulderJointIndex; + + // this algorithm is from sample code from sixense + if (elbowJointIndex == -1 || shoulderJointIndex == -1) { + return; + } + + glm::vec3 shoulderPosition; + if (!getJointPosition(shoulderJointIndex, shoulderPosition)) { + return; + } + + // precomputed lengths + float upperArmLength = _jointStates[elbowJointIndex].getFBXJoint().distanceToParent * scale; + float lowerArmLength = _jointStates[jointIndex].getFBXJoint().distanceToParent * scale; + + // first set wrist position + glm::vec3 wristPosition = position; + + glm::vec3 shoulderToWrist = wristPosition - shoulderPosition; + float distanceToWrist = glm::length(shoulderToWrist); + + // prevent gimbal lock + if (distanceToWrist > upperArmLength + lowerArmLength - EPSILON) { + distanceToWrist = upperArmLength + lowerArmLength - EPSILON; + shoulderToWrist = glm::normalize(shoulderToWrist) * distanceToWrist; + wristPosition = shoulderPosition + shoulderToWrist; + } + + // cosine of angle from upper arm to hand vector + float cosA = (upperArmLength * upperArmLength + distanceToWrist * distanceToWrist - lowerArmLength * lowerArmLength) / + (2 * upperArmLength * distanceToWrist); + float mid = upperArmLength * cosA; + float height = sqrt(upperArmLength * upperArmLength + mid * mid - 2 * upperArmLength * mid * cosA); + + // direction of the elbow + glm::vec3 handNormal = glm::cross(rotation * glm::vec3(0.0f, 1.0f, 0.0f), shoulderToWrist); // elbow rotating with wrist + glm::vec3 relaxedNormal = glm::cross(glm::vec3(0.0f, 1.0f, 0.0f), shoulderToWrist); // elbow pointing straight down + const float NORMAL_WEIGHT = 0.5f; + glm::vec3 finalNormal = glm::mix(relaxedNormal, handNormal, NORMAL_WEIGHT); + + if (rightHand ? (finalNormal.y > 0.0f) : (finalNormal.y < 0.0f)) { + finalNormal.y = 0.0f; // dont allow elbows to point inward (y is vertical axis) + } + + glm::vec3 tangent = glm::normalize(glm::cross(shoulderToWrist, finalNormal)); + + // ik solution + glm::vec3 elbowPosition = shoulderPosition + glm::normalize(shoulderToWrist) * mid - tangent * height; + glm::vec3 forwardVector(rightHand ? -1.0f : 1.0f, 0.0f, 0.0f); + glm::quat shoulderRotation = rotationBetween(forwardVector, elbowPosition - shoulderPosition); + + setJointRotationInBindFrame(shoulderJointIndex, shoulderRotation, priority); + setJointRotationInBindFrame(elbowJointIndex, + rotationBetween(shoulderRotation * forwardVector, wristPosition - elbowPosition) * + shoulderRotation, priority); + setJointRotationInBindFrame(jointIndex, rotation, priority); +} diff --git a/libraries/animation/src/AvatarRig.h b/libraries/animation/src/AvatarRig.h index 4a111a535b..5e2153e226 100644 --- a/libraries/animation/src/AvatarRig.h +++ b/libraries/animation/src/AvatarRig.h @@ -22,6 +22,8 @@ class AvatarRig : public Rig { public: ~AvatarRig() {} virtual void updateJointState(int index, glm::mat4 parentTransform); + virtual void setHandPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation, + float scale, float priority); }; #endif // hifi_AvatarRig_h diff --git a/libraries/animation/src/EntityRig.h b/libraries/animation/src/EntityRig.h index e8e15a5a28..9b519a7bfe 100644 --- a/libraries/animation/src/EntityRig.h +++ b/libraries/animation/src/EntityRig.h @@ -22,6 +22,8 @@ class EntityRig : public Rig { public: ~EntityRig() {} virtual void updateJointState(int index, glm::mat4 parentTransform); + virtual void setHandPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation, + float scale, float priority) {} }; #endif // hifi_EntityRig_h diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 74a29dd761..ec34e90b5c 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -80,8 +80,8 @@ void Rig::startAnimation(const QString& url, float fps, float priority, handle->start(); } -void Rig::addAnimationByRole(const QString& role, const QString& url, float fps, float priority, - bool loop, bool hold, float firstFrame, float lastFrame, const QStringList& maskedJoints, bool startAutomatically) { +AnimationHandlePointer Rig::addAnimationByRole(const QString& role, const QString& url, float fps, float priority, + bool loop, bool hold, float firstFrame, float lastFrame, const QStringList& maskedJoints, bool startAutomatically) { // check for a configured animation for the role //qCDebug(animation) << "addAnimationByRole" << role << url << fps << priority << loop << hold << firstFrame << lastFrame << maskedJoints << startAutomatically; foreach (const AnimationHandlePointer& candidate, _animationHandles) { @@ -89,7 +89,7 @@ void Rig::addAnimationByRole(const QString& role, const QString& url, float fps, if (startAutomatically) { candidate->start(); } - return; + return candidate; } } AnimationHandlePointer handle = createAnimationHandle(); @@ -131,16 +131,18 @@ void Rig::addAnimationByRole(const QString& role, const QString& url, float fps, if (startAutomatically) { handle->start(); } + return handle; } void Rig::startAnimationByRole(const QString& role, const QString& url, float fps, float priority, bool loop, bool hold, float firstFrame, float lastFrame, const QStringList& maskedJoints) { - addAnimationByRole(role, url, fps, priority, loop, hold, firstFrame, lastFrame, maskedJoints, true); + AnimationHandlePointer handle = addAnimationByRole(role, url, fps, priority, loop, hold, firstFrame, lastFrame, maskedJoints, true); + handle->setFadePerSecond(1.0f); // For now. Could be individualized later. } void Rig::stopAnimationByRole(const QString& role) { foreach (const AnimationHandlePointer& handle, getRunningAnimations()) { if (handle->getRole() == role) { - handle->stop(); + handle->setFadePerSecond(-1.0f); // For now. Could be individualized later. } } } @@ -166,7 +168,7 @@ bool Rig::isRunningAnimation(AnimationHandlePointer animationHandle) { } bool Rig::isRunningRole(const QString& role) { //obviously, there are more efficient ways to do this for (auto animation : _runningAnimations) { - if (animation->getRole() == role) { + if ((animation->getRole() == role) && (animation->getFadePerSecond() >= 0.0f)) { // Don't count those being faded out return true; } } @@ -180,8 +182,24 @@ void Rig::deleteAnimations() { _animationHandles.clear(); } -float Rig::initJointStates(QVector states, glm::mat4 parentTransform) { +float Rig::initJointStates(QVector states, glm::mat4 parentTransform, + int rootJointIndex, + int leftHandJointIndex, + int leftElbowJointIndex, + int leftShoulderJointIndex, + int rightHandJointIndex, + int rightElbowJointIndex, + int rightShoulderJointIndex) { _jointStates = states; + + _rootJointIndex = rootJointIndex; + _leftHandJointIndex = leftHandJointIndex; + _leftElbowJointIndex = leftElbowJointIndex; + _leftShoulderJointIndex = leftShoulderJointIndex; + _rightHandJointIndex = rightHandJointIndex; + _rightElbowJointIndex = rightElbowJointIndex; + _rightShoulderJointIndex = rightShoulderJointIndex; + initJointTransforms(parentTransform); int numStates = _jointStates.size(); @@ -428,13 +446,48 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos } void Rig::updateAnimations(float deltaTime, glm::mat4 parentTransform) { - int nAnimationsSoFar = 0; + + // First normalize the fades so that they sum to 1.0. + // update the fade data in each animation (not normalized as they are an independent propert of animation) foreach (const AnimationHandlePointer& handle, _runningAnimations) { - handle->setMix(1.0f / ++nAnimationsSoFar); - handle->setPriority(1.0); + float fadePerSecond = handle->getFadePerSecond(); + float fade = handle->getFade(); + if (fadePerSecond != 0.0f) { + fade += fadePerSecond * deltaTime; + if ((0.0f >= fade) || (fade >= 1.0f)) { + fade = glm::clamp(fade, 0.0f, 1.0f); + handle->setFadePerSecond(0.0f); + } + handle->setFade(fade); + if (fade <= 0.0f) { // stop any finished animations now + handle->setRunning(false, false); // but do not restore joints as it causes a flicker + } + } + } + // sum the remaining fade data + float fadeTotal = 0.0f; + foreach (const AnimationHandlePointer& handle, _runningAnimations) { + fadeTotal += handle->getFade(); + } + float fadeSumSoFar = 0.0f; + foreach (const AnimationHandlePointer& handle, _runningAnimations) { + handle->setPriority(1.0f); + float normalizedFade = handle->getFade() / fadeTotal; + // simulate() will blend each animation result into the result so far, based on the pairwise mix at at each step. + // i.e., slerp the 'mix' distance from the result so far towards this iteration's animation result. + // The formula here for mix is based on the idea that, at each step: + // fadeSum is to normalizedFade, as (1 - mix) is to mix + // i.e., fadeSumSoFar/normalizedFade = (1 - mix)/mix + // Then we solve for mix. + // Sanity check: For the first animation, fadeSum = 0, and the mix will always be 1. + // Sanity check: For equal blending, the formula is equivalent to mix = 1 / nAnimationsSoFar++ + float mix = 1.0f / ((fadeSumSoFar / normalizedFade) + 1.0f); + assert((0.0f <= mix) && (mix <= 1.0f)); + fadeSumSoFar += normalizedFade; + handle->setMix(mix); handle->simulate(deltaTime); } - + for (int i = 0; i < _jointStates.size(); i++) { updateJointState(i, parentTransform); } diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index a79b99f4dc..113d097f96 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -42,7 +42,6 @@ class AnimationHandle; typedef std::shared_ptr AnimationHandlePointer; -// typedef QWeakPointer WeakAnimationHandlePointer; class Rig; typedef std::shared_ptr RigPointer; @@ -87,11 +86,18 @@ public: float priority = 1.0f, bool loop = false, bool hold = false, float firstFrame = 0.0f, float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList()); void stopAnimationByRole(const QString& role); - void addAnimationByRole(const QString& role, const QString& url = QString(), float fps = 30.0f, - float priority = 1.0f, bool loop = false, bool hold = false, float firstFrame = 0.0f, - float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList(), bool startAutomatically = false); + AnimationHandlePointer addAnimationByRole(const QString& role, const QString& url = QString(), float fps = 30.0f, + float priority = 1.0f, bool loop = false, bool hold = false, float firstFrame = 0.0f, + float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList(), bool startAutomatically = false); - float initJointStates(QVector states, glm::mat4 parentTransform); + float initJointStates(QVector states, glm::mat4 parentTransform, + int rootJointIndex, + int leftHandJointIndex, + int leftElbowJointIndex, + int leftShoulderJointIndex, + int rightHandJointIndex, + int rightElbowJointIndex, + int rightShoulderJointIndex); bool jointStatesEmpty() { return _jointStates.isEmpty(); }; int getJointStateCount() const { return _jointStates.size(); } int indexOfJoint(const QString& jointName) ; @@ -150,6 +156,9 @@ public: void updateFromHeadParameters(const HeadParameters& params); + virtual void setHandPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation, + float scale, float priority) = 0; + protected: void updateLeanJoint(int index, float leanSideways, float leanForward, float torsoTwist); @@ -157,6 +166,15 @@ public: void updateEyeJoint(int index, const glm::quat& worldHeadOrientation, const glm::vec3& lookAt, const glm::vec3& saccade); QVector _jointStates; + int _rootJointIndex = -1; + + int _leftHandJointIndex = -1; + int _leftElbowJointIndex = -1; + int _leftShoulderJointIndex = -1; + + int _rightHandJointIndex = -1; + int _rightElbowJointIndex = -1; + int _rightShoulderJointIndex = -1; QList _animationHandles; QList _runningAnimations; @@ -164,6 +182,6 @@ public: bool _enableRig; glm::vec3 _lastFront; glm::vec3 _lastPosition; - }; +}; #endif /* defined(__hifi__Rig__) */ diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index ebe4ac6014..4e7228e573 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -71,7 +71,8 @@ CONSTRUCT_PROPERTY(exponent, 0.0f), CONSTRUCT_PROPERTY(cutoff, ENTITY_ITEM_DEFAULT_CUTOFF), CONSTRUCT_PROPERTY(locked, ENTITY_ITEM_DEFAULT_LOCKED), CONSTRUCT_PROPERTY(textures, ""), -CONSTRUCT_PROPERTY(animationSettings, ""), +CONSTRUCT_PROPERTY(animationSettings, "{\"firstFrame\":0,\"fps\":30,\"frameIndex\":0,\"hold\":false," + "\"lastFrame\":100000,\"loop\":false,\"running\":false,\"startAutomatically\":false}"), CONSTRUCT_PROPERTY(userData, ENTITY_ITEM_DEFAULT_USER_DATA), CONSTRUCT_PROPERTY(simulationOwner, SimulationOwner()), CONSTRUCT_PROPERTY(text, TextEntityItem::DEFAULT_TEXT), diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index 48f5f99906..999cb23407 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #include "CoverageMap.h" #include "OctreeConstants.h" @@ -48,7 +49,7 @@ #include "OctreeLogging.h" -QVector PERSIST_EXTENSIONS = {"svo", "json"}; +QVector PERSIST_EXTENSIONS = {"svo", "json", "json.gz"}; float boundaryDistanceForRenderLevel(unsigned int renderLevel, float voxelSizeScale) { return voxelSizeScale / powf(2, renderLevel); @@ -1801,29 +1802,52 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* element, } bool Octree::readFromFile(const char* fileName) { - bool fileOk = false; - QString qFileName = findMostRecentFileExtension(fileName, PERSIST_EXTENSIONS); - QFile file(qFileName); - fileOk = file.open(QIODevice::ReadOnly); - if(fileOk) { - QDataStream fileInputStream(&file); - QFileInfo fileInfo(qFileName); - unsigned long fileLength = fileInfo.size(); - - emit importSize(1.0f, 1.0f, 1.0f); - emit importProgress(0); - - qCDebug(octree) << "Loading file" << qFileName << "..."; - - fileOk = readFromStream(fileLength, fileInputStream); - - emit importProgress(100); - file.close(); + if (qFileName.endsWith(".json.gz")) { + return readJSONFromGzippedFile(qFileName); } - return fileOk; + QFile file(qFileName); + + if (!file.open(QIODevice::ReadOnly)) { + qCritical() << "unable to open for reading: " << fileName; + return false; + } + + QDataStream fileInputStream(&file); + QFileInfo fileInfo(qFileName); + unsigned long fileLength = fileInfo.size(); + + emit importSize(1.0f, 1.0f, 1.0f); + emit importProgress(0); + + qCDebug(octree) << "Loading file" << qFileName << "..."; + + bool success = readFromStream(fileLength, fileInputStream); + + emit importProgress(100); + file.close(); + + return success; +} + +bool Octree::readJSONFromGzippedFile(QString qFileName) { + QFile file(qFileName); + if (!file.open(QIODevice::ReadOnly)) { + qCritical() << "Cannot open gzipped json file for reading: " << qFileName; + return false; + } + QByteArray compressedJsonData = file.readAll(); + QByteArray jsonData; + + if (!gunzip(compressedJsonData, jsonData)) { + qCritical() << "json File not in gzip format: " << qFileName; + return false; + } + + QDataStream jsonStream(jsonData); + return readJSONFromStream(-1, jsonStream); } bool Octree::readFromURL(const QString& urlString) { @@ -1859,18 +1883,17 @@ bool Octree::readFromURL(const QString& urlString) { bool Octree::readFromStream(unsigned long streamLength, QDataStream& inputStream) { - - // decide if this is SVO or JSON + // decide if this is binary SVO or JSON-formatted SVO QIODevice *device = inputStream.device(); char firstChar; device->getChar(&firstChar); device->ungetChar(firstChar); if (firstChar == (char) PacketType::EntityData) { - qCDebug(octree) << "Reading from SVO Stream length:" << streamLength; + qCDebug(octree) << "Reading from binary SVO Stream length:" << streamLength; return readSVOFromStream(streamLength, inputStream); } else { - qCDebug(octree) << "Reading from JSON Stream length:" << streamLength; + qCDebug(octree) << "Reading from JSON SVO Stream length:" << streamLength; return readJSONFromStream(streamLength, inputStream); } } @@ -2005,12 +2028,28 @@ bool Octree::readSVOFromStream(unsigned long streamLength, QDataStream& inputStr return fileOk; } -bool Octree::readJSONFromStream(unsigned long streamLength, QDataStream& inputStream) { - char* rawData = new char[streamLength + 1]; // allocate enough room to null terminate - inputStream.readRawData(rawData, streamLength); - rawData[streamLength] = 0; // make sure we null terminate this string +const int READ_JSON_BUFFER_SIZE = 2048; - QJsonDocument asDocument = QJsonDocument::fromJson(rawData); +bool Octree::readJSONFromStream(unsigned long streamLength, QDataStream& inputStream) { + // if the data is gzipped we may not have a useful bytesAvailable() result, so just keep reading until + // we get an eof. Leave streamLength parameter for consistency. + + QByteArray jsonBuffer; + char* rawData = new char[READ_JSON_BUFFER_SIZE]; + while (true) { + int got = inputStream.readRawData(rawData, READ_JSON_BUFFER_SIZE - 1); + if (got < 0) { + qCritical() << "error while reading from json stream"; + delete[] rawData; + return false; + } + if (got == 0) { + break; + } + jsonBuffer += QByteArray(rawData, got); + } + + QJsonDocument asDocument = QJsonDocument::fromJson(jsonBuffer); QVariant asVariant = asDocument.toVariant(); QVariantMap asMap = asVariant.toMap(); readFromMap(asMap); @@ -2028,13 +2067,14 @@ void Octree::writeToFile(const char* fileName, OctreeElement* element, QString p writeToSVOFile(fileName, element); } else if (persistAsFileType == "json") { writeToJSONFile(cFileName, element); + } else if (persistAsFileType == "json.gz") { + writeToJSONFile(cFileName, element, true); } else { qCDebug(octree) << "unable to write octree to file of type" << persistAsFileType; } } -void Octree::writeToJSONFile(const char* fileName, OctreeElement* element) { - QFile persistFile(fileName); +void Octree::writeToJSONFile(const char* fileName, OctreeElement* element, bool doGzip) { QVariantMap entityDescription; qCDebug(octree, "Saving JSON SVO to file %s...", fileName); @@ -2053,10 +2093,27 @@ void Octree::writeToJSONFile(const char* fileName, OctreeElement* element) { // store the entity data bool entityDescriptionSuccess = writeToMap(entityDescription, top, true); + if (!entityDescriptionSuccess) { + qCritical("Failed to convert Entities to QVariantMap while saving to json."); + return; + } // convert the QVariantMap to JSON - if (entityDescriptionSuccess && persistFile.open(QIODevice::WriteOnly)) { - persistFile.write(QJsonDocument::fromVariant(entityDescription).toJson()); + QByteArray jsonData = QJsonDocument::fromVariant(entityDescription).toJson(); + QByteArray jsonDataForFile; + + if (doGzip) { + if (!gzip(jsonData, jsonDataForFile, -1)) { + qCritical("unable to gzip data while saving to json."); + return; + } + } else { + jsonDataForFile = jsonData; + } + + QFile persistFile(fileName); + if (persistFile.open(QIODevice::WriteOnly)) { + persistFile.write(jsonDataForFile); } else { qCritical("Could not write to JSON description of entities."); } diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index e00434be80..244359e394 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -330,7 +330,7 @@ public: // Octree exporters void writeToFile(const char* filename, OctreeElement* element = NULL, QString persistAsFileType = "svo"); - void writeToJSONFile(const char* filename, OctreeElement* element = NULL); + void writeToJSONFile(const char* filename, OctreeElement* element = NULL, bool doGzip = false); void writeToSVOFile(const char* filename, OctreeElement* element = NULL); virtual bool writeToMap(QVariantMap& entityDescription, OctreeElement* element, bool skipDefaultValues) = 0; @@ -340,6 +340,7 @@ public: bool readFromStream(unsigned long streamLength, QDataStream& inputStream); bool readSVOFromStream(unsigned long streamLength, QDataStream& inputStream); bool readJSONFromStream(unsigned long streamLength, QDataStream& inputStream); + bool readJSONFromGzippedFile(QString qFileName); virtual bool readFromMap(QVariantMap& entityDescription) = 0; unsigned long getOctreeElementsCount(); diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 9e06b8833e..cf0fb67d8c 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -468,7 +468,23 @@ bool Model::updateGeometry() { void Model::initJointStates(QVector states) { const FBXGeometry& geometry = _geometry->getFBXGeometry(); glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset; - _boundingRadius = _rig->initJointStates(states, parentTransform); + + int rootJointIndex = geometry.rootJointIndex; + int leftHandJointIndex = geometry.leftHandJointIndex; + int leftElbowJointIndex = leftHandJointIndex >= 0 ? geometry.joints.at(leftHandJointIndex).parentIndex : -1; + int leftShoulderJointIndex = leftElbowJointIndex >= 0 ? geometry.joints.at(leftElbowJointIndex).parentIndex : -1; + int rightHandJointIndex = geometry.rightHandJointIndex; + int rightElbowJointIndex = rightHandJointIndex >= 0 ? geometry.joints.at(rightHandJointIndex).parentIndex : -1; + int rightShoulderJointIndex = rightElbowJointIndex >= 0 ? geometry.joints.at(rightElbowJointIndex).parentIndex : -1; + + _boundingRadius = _rig->initJointStates(states, parentTransform, + rootJointIndex, + leftHandJointIndex, + leftElbowJointIndex, + leftShoulderJointIndex, + rightHandJointIndex, + rightElbowJointIndex, + rightShoulderJointIndex); } bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance, diff --git a/libraries/shared/CMakeLists.txt b/libraries/shared/CMakeLists.txt index 8deda7f4b1..00a80619bc 100644 --- a/libraries/shared/CMakeLists.txt +++ b/libraries/shared/CMakeLists.txt @@ -4,7 +4,17 @@ set(TARGET_NAME shared) # TODO: there isn't really a good reason to have Script linked here - let's get what is requiring it out (RegisteredMetaTypes.cpp) setup_hifi_library(Gui Network Script Widgets) +find_package(ZLIB REQUIRED) +target_link_libraries(${TARGET_NAME} ${ZLIB_LIBRARIES}) + add_dependency_external_projects(glm) find_package(GLM REQUIRED) -target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS}) +target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIRS}) +if (WIN32) + # Birarda will fix this when he finds it. + get_filename_component(ZLIB_LIB_DIR "${ZLIB_LIBRARIES}" DIRECTORY) + get_filename_component(ZLIB_DIR "${ZLIB_LIB_DIR}" DIRECTORY) + set(ZLIB_BIN_DIR "${ZLIB_DIR}/bin") + add_paths_to_fixup_libs(${ZLIB_BIN_DIR}) +endif () diff --git a/libraries/shared/src/Gzip.cpp b/libraries/shared/src/Gzip.cpp new file mode 100644 index 0000000000..a77f459fc9 --- /dev/null +++ b/libraries/shared/src/Gzip.cpp @@ -0,0 +1,153 @@ +// +// Gzip.cpp +// libraries/shared/src +// +// Created by Seth Alves on 2015-08-03. +// Copyright 2015 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 +#include "Gzip.h" + +const int GZIP_WINDOWS_BIT = 31; +const int GZIP_CHUNK_SIZE = 4096; +const int DEFAULT_MEM_LEVEL = 8; + +bool gunzip(QByteArray source, QByteArray &destination) { + destination.clear(); + if (source.length() == 0) { + return true; + } + + z_stream strm; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = 0; + strm.next_in = Z_NULL; + + int status = inflateInit2(&strm, GZIP_WINDOWS_BIT); + + if (status != Z_OK) { + return false; + } + + char *sourceData = source.data(); + int sourceDataLength = source.length(); + + for (;;) { + int chunkSize = qMin(GZIP_CHUNK_SIZE, sourceDataLength); + if (chunkSize <= 0) { + break; + } + + strm.next_in = (unsigned char*)sourceData; + strm.avail_in = chunkSize; + sourceData += chunkSize; + sourceDataLength -= chunkSize; + + for (;;) { + char out[GZIP_CHUNK_SIZE]; + + strm.next_out = (unsigned char*)out; + strm.avail_out = GZIP_CHUNK_SIZE; + + status = inflate(&strm, Z_NO_FLUSH); + + switch (status) { + case Z_NEED_DICT: + status = Z_DATA_ERROR; + case Z_DATA_ERROR: + case Z_MEM_ERROR: + case Z_STREAM_ERROR: + inflateEnd(&strm); + return false; + } + + int available = (GZIP_CHUNK_SIZE - strm.avail_out); + if (available > 0) { + destination.append((char*)out, available); + } + + if (strm.avail_out != 0) { + break; + } + } + + if (status == Z_STREAM_END) { + break; + } + } + + inflateEnd(&strm); + return status == Z_STREAM_END; +} + +bool gzip(QByteArray source, QByteArray &destination, int compressionLevel) { + destination.clear(); + if (source.length() == 0) { + return true; + } + + int flushOrFinish = 0; + z_stream strm; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.next_in = Z_NULL; + strm.avail_in = 0; + + int status = deflateInit2(&strm, + qMax(Z_DEFAULT_COMPRESSION, qMin(9, compressionLevel)), + Z_DEFLATED, + GZIP_WINDOWS_BIT, + DEFAULT_MEM_LEVEL, + Z_DEFAULT_STRATEGY); + if (status != Z_OK) { + return false; + } + char *sourceData = source.data(); + int sourceDataLength = source.length(); + + for (;;) { + int chunkSize = qMin(GZIP_CHUNK_SIZE, sourceDataLength); + strm.next_in = (unsigned char*)sourceData; + strm.avail_in = chunkSize; + sourceData += chunkSize; + sourceDataLength -= chunkSize; + + if (sourceDataLength <= 0) { + flushOrFinish = Z_FINISH; + } else { + flushOrFinish = Z_NO_FLUSH; + } + + for (;;) { + char out[GZIP_CHUNK_SIZE]; + strm.next_out = (unsigned char*)out; + strm.avail_out = GZIP_CHUNK_SIZE; + status = deflate(&strm, flushOrFinish); + if (status == Z_STREAM_ERROR) { + deflateEnd(&strm); + return false; + } + int available = (GZIP_CHUNK_SIZE - strm.avail_out); + if (available > 0) { + destination.append((char*)out, available); + } + if (strm.avail_out != 0) { + break; + } + } + + if (flushOrFinish == Z_FINISH) { + break; + } + } + + deflateEnd(&strm); + return status == Z_STREAM_END; +} diff --git a/libraries/shared/src/Gzip.h b/libraries/shared/src/Gzip.h new file mode 100644 index 0000000000..e3ed6bbbea --- /dev/null +++ b/libraries/shared/src/Gzip.h @@ -0,0 +1,27 @@ +// +// Gzip.h +// libraries/shared/src +// +// Created by Seth Alves on 2015-08-03. +// Copyright 2015 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 GZIP_H +#define GZIP_H + +#include + +// The compression level must be Z_DEFAULT_COMPRESSION (-1), or between 0 and +// 9: 1 gives best speed, 9 gives best compression, 0 gives no +// compression at all (the input data is simply copied a block at a +// time). Z_DEFAULT_COMPRESSION requests a default compromise between +// speed and compression (currently equivalent to level 6). + +bool gzip(QByteArray source, QByteArray &destination, int compressionLevel = -1); // -1 is Z_DEFAULT_COMPRESSION + +bool gunzip(QByteArray source, QByteArray &destination); + +#endif