diff --git a/examples/editModels.js b/examples/editModels.js index 70a2e178ae..9a20a7db27 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -76,38 +76,42 @@ function controller(wichSide) { this.oldModelRadius; this.laser = Overlays.addOverlay("line3d", { - position: this.palmPosition, - end: this.tipPosition, + position: { x: 0, y: 0, z: 0 }, + end: { x: 0, y: 0, z: 0 }, color: LASER_COLOR, alpha: 1, visible: false, - lineWidth: LASER_WIDTH + lineWidth: LASER_WIDTH, + anchor: "MyAvatar" }); this.guideScale = 0.02; this.ball = Overlays.addOverlay("sphere", { - position: this.palmPosition, + position: { x: 0, y: 0, z: 0 }, size: this.guideScale, solid: true, color: { red: 0, green: 255, blue: 0 }, alpha: 1, visible: false, + anchor: "MyAvatar" }); this.leftRight = Overlays.addOverlay("line3d", { - position: this.palmPosition, - end: this.tipPosition, + position: { x: 0, y: 0, z: 0 }, + end: { x: 0, y: 0, z: 0 }, color: { red: 0, green: 0, blue: 255 }, alpha: 1, visible: false, - lineWidth: LASER_WIDTH + lineWidth: LASER_WIDTH, + anchor: "MyAvatar" }); this.topDown = Overlays.addOverlay("line3d", { - position: this.palmPosition, - end: this.tipPosition, + position: { x: 0, y: 0, z: 0 }, + end: { x: 0, y: 0, z: 0 }, color: { red: 0, green: 0, blue: 255 }, alpha: 1, visible: false, - lineWidth: LASER_WIDTH + lineWidth: LASER_WIDTH, + anchor: "MyAvatar" }); @@ -170,10 +174,11 @@ function controller(wichSide) { } this.moveLaser = function () { - var endPosition = Vec3.sum(this.palmPosition, Vec3.multiply(this.front, LASER_LENGTH_FACTOR)); + var startPosition = Vec3.subtract(this.palmPosition, MyAvatar.position); + var endPosition = Vec3.sum(startPosition, Vec3.multiply(this.front, LASER_LENGTH_FACTOR)); Overlays.editOverlay(this.laser, { - position: this.palmPosition, + position: startPosition, end: endPosition, visible: true }); @@ -219,11 +224,11 @@ function controller(wichSide) { position: newPosition, modelRotation: newRotation }); - print("Moving " + this.modelID.id); +// print("Moving " + this.modelID.id); // Vec3.print("Old Position: ", this.oldModelPosition); // Vec3.print("Sav Position: ", newPosition); - Quat.print("Old Rotation: ", this.oldModelRotation); - Quat.print("New Rotation: ", newRotation); +// Quat.print("Old Rotation: ", this.oldModelRotation); +// Quat.print("New Rotation: ", newRotation); this.oldModelRotation = newRotation; this.oldModelPosition = newPosition; @@ -301,7 +306,7 @@ var rightController = new controller(RIGHT); function moveModels() { if (leftController.grabbing && rightController.grabbing && rightController.modelID.id == leftController.modelID.id) { - print("Both controllers"); + //print("Both controllers"); var oldLeftPoint = Vec3.sum(leftController.oldPalmPosition, Vec3.multiply(leftController.oldFront, leftController.x)); var oldRightPoint = Vec3.sum(rightController.oldPalmPosition, Vec3.multiply(rightController.oldFront, rightController.x)); @@ -319,7 +324,7 @@ function moveModels() { var newPosition = Vec3.sum(middle, Vec3.multiply(Vec3.subtract(leftController.oldModelPosition, oldMiddle), ratio)); - Vec3.print("Ratio : " + ratio + " New position: ", newPosition); + //Vec3.print("Ratio : " + ratio + " New position: ", newPosition); var rotation = Quat.multiply(leftController.rotation, Quat.inverse(leftController.oldRotation)); rotation = Quat.multiply(rotation, leftController.oldModelRotation); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index ebcdcd878d..91e95bb4e3 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -237,6 +237,13 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : connect(&nodeList->getDomainHandler(), SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&))); connect(&nodeList->getDomainHandler(), SIGNAL(connectedToDomain(const QString&)), SLOT(connectedToDomain(const QString&))); + + // update our location every 5 seconds in the data-server, assuming that we are authenticated with one + const float DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5.0f * 1000.0f; + + QTimer* locationUpdateTimer = new QTimer(this); + connect(locationUpdateTimer, &QTimer::timeout, this, &Application::updateLocationInServer); + locationUpdateTimer->start(DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS); connect(nodeList, &NodeList::nodeAdded, this, &Application::nodeAdded); connect(nodeList, &NodeList::nodeKilled, this, &Application::nodeKilled); @@ -3113,6 +3120,34 @@ void Application::updateWindowTitle(){ _window->setWindowTitle(title); } +void Application::updateLocationInServer() { + + AccountManager& accountManager = AccountManager::getInstance(); + + if (accountManager.isLoggedIn()) { + + static QJsonObject lastLocationObject; + + // construct a QJsonObject given the user's current address information + QJsonObject updatedLocationObject; + + QJsonObject addressObject; + addressObject.insert("position", QString(createByteArray(_myAvatar->getPosition()))); + addressObject.insert("orientation", QString(createByteArray(glm::degrees(safeEulerAngles(_myAvatar->getOrientation()))))); + addressObject.insert("domain", NodeList::getInstance()->getDomainHandler().getHostname()); + + updatedLocationObject.insert("address", addressObject); + + if (updatedLocationObject != lastLocationObject) { + + accountManager.authenticatedRequest("/api/v1/users/address", QNetworkAccessManager::PutOperation, + JSONCallbackParameters(), QJsonDocument(updatedLocationObject).toJson()); + + lastLocationObject = updatedLocationObject; + } + } +} + void Application::domainChanged(const QString& domainHostname) { updateWindowTitle(); diff --git a/interface/src/Application.h b/interface/src/Application.h index 1b0c78e5fa..33ec9ca856 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -278,6 +278,7 @@ signals: public slots: void domainChanged(const QString& domainHostname); void updateWindowTitle(); + void updateLocationInServer(); void nodeAdded(SharedNodePointer node); void nodeKilled(SharedNodePointer node); void packetSent(quint64 length); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 2daf5b0240..7c8f1d75a9 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -351,6 +351,7 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::HandsCollideWithSelf, 0, false); addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::ShowIKConstraints, 0, false); addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::AlignForearmsWithWrists, 0, true); + addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::AlternateIK, 0, false); addDisabledActionAndSeparator(developerMenu, "Testing"); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 230584bf07..2a521bf59c 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -267,6 +267,7 @@ private: namespace MenuOption { const QString AboutApp = "About Interface"; const QString AlignForearmsWithWrists = "Align Forearms with Wrists"; + const QString AlternateIK = "Alternate IK"; const QString AmbientOcclusion = "Ambient Occlusion"; const QString Atmosphere = "Atmosphere"; const QString Attachments = "Attachments..."; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 1eac264ae4..7c051d8984 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -49,8 +49,6 @@ const float COLLISION_RADIUS_SCALE = 0.125f; const float MIN_KEYBOARD_CONTROL_SPEED = 2.0f; const float MAX_WALKING_SPEED = 3.0f * MIN_KEYBOARD_CONTROL_SPEED; -const float DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5.0f * 1000.0f; - // TODO: normalize avatar speed for standard avatar size, then scale all motion logic // to properly follow avatar size. float DEFAULT_MOTOR_TIMESCALE = 0.25f; @@ -83,11 +81,6 @@ MyAvatar::MyAvatar() : for (int i = 0; i < MAX_DRIVE_KEYS; i++) { _driveKeys[i] = 0.0f; } - - // update our location every 5 seconds in the data-server, assuming that we are authenticated with one - QTimer* locationUpdateTimer = new QTimer(this); - connect(locationUpdateTimer, &QTimer::timeout, this, &MyAvatar::updateLocationInDataServer); - locationUpdateTimer->start(DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS); } MyAvatar::~MyAvatar() { @@ -237,7 +230,7 @@ void MyAvatar::simulate(float deltaTime) { void MyAvatar::updateFromTrackers(float deltaTime) { glm::vec3 estimatedPosition, estimatedRotation; - if (Application::getInstance()->getPrioVR()->isActive()) { + if (Application::getInstance()->getPrioVR()->hasHeadRotation()) { estimatedRotation = glm::degrees(safeEulerAngles(Application::getInstance()->getPrioVR()->getHeadRotation())); estimatedRotation.x *= -1.0f; estimatedRotation.z *= -1.0f; @@ -1434,29 +1427,6 @@ void MyAvatar::resetSize() { qDebug("Reseted scale to %f", _targetScale); } -static QByteArray createByteArray(const glm::vec3& vector) { - return QByteArray::number(vector.x) + ',' + QByteArray::number(vector.y) + ',' + QByteArray::number(vector.z); -} - -void MyAvatar::updateLocationInDataServer() { - // TODO: don't re-send this when it hasn't change or doesn't change by some threshold - // This will required storing the last sent values and clearing them when the AccountManager rootURL changes - - AccountManager& accountManager = AccountManager::getInstance(); - - if (accountManager.isLoggedIn()) { - QString positionString(createByteArray(_position)); - QString orientationString(createByteArray(glm::degrees(safeEulerAngles(getOrientation())))); - - // construct the json to put the user's location - QString locationPutJson = QString() + "{\"address\":{\"position\":\"" - + positionString + "\", \"orientation\":\"" + orientationString + "\"}}"; - - accountManager.authenticatedRequest("/api/v1/users/address", QNetworkAccessManager::PutOperation, - JSONCallbackParameters(), locationPutJson.toUtf8()); - } -} - void MyAvatar::goToLocationFromResponse(const QJsonObject& jsonObject) { if (jsonObject["status"].toString() == "success") { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 2df74f23c2..2e75ac984d 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -108,7 +108,6 @@ public slots: void decreaseSize(); void resetSize(); - void updateLocationInDataServer(); void goToLocationFromResponse(const QJsonObject& jsonObject); // Set/Get update the thrust that will move the avatar around diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index e48ebfa63c..3786280a3c 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -164,7 +164,8 @@ void SkeletonModel::applyPalmData(int jointIndex, PalmData& palm) { // rotate palm to align with its normal (normal points out of hand's palm) glm::quat palmRotation; - if (Menu::getInstance()->isOptionChecked(MenuOption::AlignForearmsWithWrists)) { + if (!Menu::getInstance()->isOptionChecked(MenuOption::AlternateIK) && + Menu::getInstance()->isOptionChecked(MenuOption::AlignForearmsWithWrists)) { getJointRotation(parentJointIndex, palmRotation, true); } else { getJointRotation(jointIndex, palmRotation, true); @@ -176,7 +177,10 @@ void SkeletonModel::applyPalmData(int jointIndex, PalmData& palm) { palmRotation = rotationBetween(palmRotation * glm::vec3(-sign, 0.0f, 0.0f), direction) * palmRotation; // set hand position, rotation - if (Menu::getInstance()->isOptionChecked(MenuOption::AlignForearmsWithWrists)) { + if (Menu::getInstance()->isOptionChecked(MenuOption::AlternateIK)) { + setHandPosition(jointIndex, palm.getPosition(), palmRotation); + + } else if (Menu::getInstance()->isOptionChecked(MenuOption::AlignForearmsWithWrists)) { glm::vec3 forearmVector = palmRotation * glm::vec3(sign, 0.0f, 0.0f); setJointPosition(parentJointIndex, palm.getPosition() + forearmVector * geometry.joints.at(jointIndex).distanceToParent * extractUniformScale(_scale)); @@ -276,3 +280,68 @@ void SkeletonModel::renderJointConstraints(int jointIndex) { glLineWidth(1.0f); } +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; + getJointRotation(shoulderJointIndex, shoulderRotation, true); + applyRotationDelta(shoulderJointIndex, rotationBetween(shoulderRotation * forwardVector, elbowPosition - shoulderPosition), false); + + glm::quat elbowRotation; + getJointRotation(elbowJointIndex, elbowRotation, true); + applyRotationDelta(elbowJointIndex, rotationBetween(elbowRotation * forwardVector, wristPosition - elbowPosition), false); + + setJointRotation(jointIndex, rotation, true); +} + diff --git a/interface/src/avatar/SkeletonModel.h b/interface/src/avatar/SkeletonModel.h index 20384829ea..77e6ea33d4 100644 --- a/interface/src/avatar/SkeletonModel.h +++ b/interface/src/avatar/SkeletonModel.h @@ -51,6 +51,7 @@ protected: private: void renderJointConstraints(int jointIndex); + void setHandPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation); Avatar* _owningAvatar; }; diff --git a/interface/src/devices/PrioVR.cpp b/interface/src/devices/PrioVR.cpp index 064e2be4b5..28564f6f2e 100644 --- a/interface/src/devices/PrioVR.cpp +++ b/interface/src/devices/PrioVR.cpp @@ -48,6 +48,7 @@ PrioVR::PrioVR() { return; } _jointRotations.resize(LIST_LENGTH); + _lastJointRotations.resize(LIST_LENGTH); for (int i = 0; i < LIST_LENGTH; i++) { _humanIKJointIndices.append(jointsDiscovered[i] ? indexOfHumanIKJoint(JOINT_NAMES[i]) : -1); } @@ -62,8 +63,13 @@ PrioVR::~PrioVR() { #endif } -glm::quat PrioVR::getHeadRotation() const { - const int HEAD_ROTATION_INDEX = 0; +const int HEAD_ROTATION_INDEX = 0; + +bool PrioVR::hasHeadRotation() const { + return _humanIKJointIndices.size() > HEAD_ROTATION_INDEX && _humanIKJointIndices.at(HEAD_ROTATION_INDEX) != -1; +} + +glm::quat PrioVR::getHeadRotation() const { return _jointRotations.size() > HEAD_ROTATION_INDEX ? _jointRotations.at(HEAD_ROTATION_INDEX) : glm::quat(); } @@ -81,10 +87,14 @@ void PrioVR::update() { yei_getLastStreamDataAll(_skeletalDevice, (char*)_jointRotations.data(), _jointRotations.size() * sizeof(glm::quat), ×tamp); - // convert to our expected coordinate system + // convert to our expected coordinate system, average with last rotations to smooth for (int i = 0; i < _jointRotations.size(); i++) { _jointRotations[i].y *= -1.0f; _jointRotations[i].z *= -1.0f; + + glm::quat lastRotation = _lastJointRotations.at(i); + _lastJointRotations[i] = _jointRotations.at(i); + _jointRotations[i] = safeMix(lastRotation, _jointRotations.at(i), 0.5f); } #endif } diff --git a/interface/src/devices/PrioVR.h b/interface/src/devices/PrioVR.h index 9cd7bda5d4..8f01574356 100644 --- a/interface/src/devices/PrioVR.h +++ b/interface/src/devices/PrioVR.h @@ -35,6 +35,8 @@ public: bool isActive() const { return !_jointRotations.isEmpty(); } + bool hasHeadRotation() const; + glm::quat getHeadRotation() const; glm::quat getTorsoRotation() const; @@ -55,6 +57,7 @@ private: QVector _humanIKJointIndices; QVector _jointRotations; + QVector _lastJointRotations; QDateTime _calibrationCountdownStarted; }; diff --git a/interface/src/ui/overlays/Overlay.cpp b/interface/src/ui/overlays/Overlay.cpp index 3b38addb76..8ec7cbace1 100644 --- a/interface/src/ui/overlays/Overlay.cpp +++ b/interface/src/ui/overlays/Overlay.cpp @@ -23,7 +23,8 @@ Overlay::Overlay() : _parent(NULL), _alpha(DEFAULT_ALPHA), _color(DEFAULT_BACKGROUND_COLOR), - _visible(true) + _visible(true), + _anchor(NO_ANCHOR) { } @@ -51,8 +52,15 @@ void Overlay::setProperties(const QScriptValue& properties) { if (properties.property("alpha").isValid()) { setAlpha(properties.property("alpha").toVariant().toFloat()); } - + if (properties.property("visible").isValid()) { setVisible(properties.property("visible").toVariant().toBool()); } + + if (properties.property("anchor").isValid()) { + QString property = properties.property("anchor").toVariant().toString(); + if (property == "MyAvatar") { + setAnchor(MY_AVATAR); + } + } } diff --git a/interface/src/ui/overlays/Overlay.h b/interface/src/ui/overlays/Overlay.h index 6feb159e05..7667b3d3fd 100644 --- a/interface/src/ui/overlays/Overlay.h +++ b/interface/src/ui/overlays/Overlay.h @@ -28,6 +28,11 @@ class Overlay : public QObject { Q_OBJECT public: + enum Anchor { + NO_ANCHOR, + MY_AVATAR + }; + Overlay(); ~Overlay(); void init(QGLWidget* parent); @@ -38,11 +43,13 @@ public: bool getVisible() const { return _visible; } const xColor& getColor() const { return _color; } float getAlpha() const { return _alpha; } + Anchor getAnchor() const { return _anchor; } // setters void setVisible(bool visible) { _visible = visible; } void setColor(const xColor& color) { _color = color; } void setAlpha(float alpha) { _alpha = alpha; } + void setAnchor(Anchor anchor) { _anchor = anchor; } virtual void setProperties(const QScriptValue& properties); @@ -51,6 +58,7 @@ protected: float _alpha; xColor _color; bool _visible; // should the overlay be drawn at all + Anchor _anchor; }; diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 4eb4f030ac..78fe54b267 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -8,6 +8,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include #include "Cube3DOverlay.h" #include "ImageOverlay.h" @@ -57,8 +58,19 @@ void Overlays::render2D() { } void Overlays::render3D() { + glm::vec3 myAvatarPosition = Application::getInstance()->getAvatar()->getPosition(); + foreach(Overlay* thisOverlay, _overlays3D) { + glPushMatrix(); + switch (thisOverlay->getAnchor()) { + case Overlay::MY_AVATAR: + glTranslatef(myAvatarPosition.x, myAvatarPosition.y, myAvatarPosition.z); + break; + default: + break; + } thisOverlay->render(); + glPopMatrix(); } } diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index 5cfd8961ec..1136d49dd4 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -828,3 +828,7 @@ bool isSimilarPosition(const glm::vec3& positionA, const glm::vec3& positionB, f float positionDistance = glm::distance(positionA, positionB); return (positionDistance <= similarEnough); } + +QByteArray createByteArray(const glm::vec3& vector) { + return QByteArray::number(vector.x) + ',' + QByteArray::number(vector.y) + ',' + QByteArray::number(vector.z); +} diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index d111439b7e..dbbfb02365 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -187,4 +187,6 @@ bool isSimilarPosition(const glm::vec3& positionA, const glm::vec3& positionB, f /// \return bool is the float NaN bool isNaN(float value); +QByteArray createByteArray(const glm::vec3& vector); + #endif // hifi_SharedUtil_h