diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index cdaa825ad8..104f65d7d9 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -92,6 +92,9 @@ void Agent::run() { loop.exec(); + // let the AvatarData class use our QNetworkAcessManager + AvatarData::setNetworkAccessManager(networkManager); + QString scriptContents(reply->readAll()); qDebug() << "Downloaded script:" << scriptContents; diff --git a/examples/bot.js b/examples/bot.js index 53f723381c..c8280e063d 100644 --- a/examples/bot.js +++ b/examples/bot.js @@ -50,5 +50,6 @@ if (botNumber <= 20) { // there is no need to change the body model - we're using the default Avatar.faceModelURL = "https://s3-us-west-1.amazonaws.com/highfidelity-public/meshes/" + newFaceFilePrefix + ".fst"; Avatar.skeletonModelURL = "https://s3-us-west-1.amazonaws.com/highfidelity-public/meshes/" + newBodyFilePrefix + ".fst"; +Avatar.billboardURL = "https://dl.dropboxusercontent.com/u/1864924/bot-billboard.png"; Agent.isAvatar = true; \ No newline at end of file diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 035845eda4..03905a45c1 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -227,6 +227,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : connect(nodeList, SIGNAL(nodeAdded(SharedNodePointer)), &_voxels, SLOT(nodeAdded(SharedNodePointer))); connect(nodeList, SIGNAL(nodeKilled(SharedNodePointer)), &_voxels, SLOT(nodeKilled(SharedNodePointer))); connect(nodeList, &NodeList::uuidChanged, this, &Application::updateWindowTitle); + connect(nodeList, &NodeList::limitOfSilentDomainCheckInsReached, nodeList, &NodeList::reset); // connect to appropriate slots on AccountManager AccountManager& accountManager = AccountManager::getInstance(); diff --git a/interface/src/Util.cpp b/interface/src/Util.cpp index 73ec791714..183ef116e6 100644 --- a/interface/src/Util.cpp +++ b/interface/src/Util.cpp @@ -554,6 +554,78 @@ void renderCircle(glm::vec3 position, float radius, glm::vec3 surfaceNormal, int } +void renderBevelCornersRect(int x, int y, int width, int height, int bevelDistance) { + glBegin(GL_POLYGON); + + // left side + glVertex2f(x, y + bevelDistance); + glVertex2f(x, y + height - bevelDistance); + + // top side + glVertex2f(x + bevelDistance, y + height); + glVertex2f(x + width - bevelDistance, y + height); + + // right + glVertex2f(x + width, y + height - bevelDistance); + glVertex2f(x + width, y + bevelDistance); + + // bottom + glVertex2f(x + width - bevelDistance, y); + glVertex2f(x +bevelDistance, y); + + glEnd(); +} + +void renderRoundedCornersRect(int x, int y, int width, int height, int radius, int numPointsCorner) { +#define MAX_POINTS_CORNER 50 + // At least "2" is needed + if (numPointsCorner <= 1) { + return; + } + if (numPointsCorner > MAX_POINTS_CORNER) { + numPointsCorner = MAX_POINTS_CORNER; + } + + // Precompute sin and cos for [0, pi/2) for the number of points (numPointCorner) + double radiusTimesSin[MAX_POINTS_CORNER]; + double radiusTimesCos[MAX_POINTS_CORNER]; + int i = 0; + for (int i = 0; i < numPointsCorner; i++) { + double t = i * PIf / (2.0f * (numPointsCorner - 1)); + radiusTimesSin[i] = radius * sin(t); + radiusTimesCos[i] = radius * cos(t); + } + + glm::dvec2 cornerCenter; + glBegin(GL_POINTS); + + // Top left corner + cornerCenter = glm::vec2(x + radius, y + height - radius); + for (i = 0; i < numPointsCorner; i++) { + glVertex2d(cornerCenter.x - radiusTimesCos[i], cornerCenter.y + radiusTimesSin[i]); + } + + // Top rigth corner + cornerCenter = glm::vec2(x + width - radius, y + height - radius); + for (i = 0; i < numPointsCorner; i++) { + glVertex2d(cornerCenter.x + radiusTimesSin[i], cornerCenter.y + radiusTimesCos[i]); + } + + // Bottom right + cornerCenter = glm::vec2(x + width - radius, y + radius); + for (i = 0; i < numPointsCorner; i++) { + glVertex2d(cornerCenter.x + radiusTimesCos[i], cornerCenter.y - radiusTimesSin[i]); + } + + // Bottom left + cornerCenter = glm::vec2(x + radius, y + radius); + for (i = 0; i < numPointsCorner; i++) { + glVertex2d(cornerCenter.x - radiusTimesSin[i], cornerCenter.y - radiusTimesCos[i]); + } + glEnd(); +} + + void renderOrientationDirections(glm::vec3 position, const glm::quat& orientation, float size) { glm::vec3 pRight = position + orientation * IDENTITY_RIGHT * size; glm::vec3 pUp = position + orientation * IDENTITY_UP * size; diff --git a/interface/src/Util.h b/interface/src/Util.h index 0c762ccd79..5710be5f2b 100644 --- a/interface/src/Util.h +++ b/interface/src/Util.h @@ -73,6 +73,8 @@ void renderOrientationDirections( glm::vec3 position, const glm::quat& orientati void renderSphereOutline(glm::vec3 position, float radius, int numSides, glm::vec3 cameraPosition); void renderCircle(glm::vec3 position, float radius, glm::vec3 surfaceNormal, int numSides ); +void renderRoundedCornersRect(int x, int y, int width, int height, int radius, int numPointsCorner); +void renderBevelCornersRect(int x, int y, int width, int height, int bevelDistance); void runTimingTests(); diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index a946a19bd9..4940550180 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -207,18 +207,23 @@ void Avatar::render() { renderBody(); } - // render sphere when far away - const float MAX_ANGLE = 10.f; + // render voice intensity sphere for avatars that are farther away + const float MAX_SPHERE_ANGLE = 10.f; + const float MIN_SPHERE_ANGLE = 1.f; + const float MIN_SPHERE_SIZE = 0.01; + const float SPHERE_LOUDNESS_SCALING = 0.0005f; + const float SPHERE_COLOR[] = { 0.5f, 0.8f, 0.8f }; float height = getSkeletonHeight(); glm::vec3 delta = height * (getHead()->getCameraOrientation() * IDENTITY_UP) / 2.f; float angle = abs(angleBetween(toTarget + delta, toTarget - delta)); - - if (angle < MAX_ANGLE) { - glColor4f(0.5f, 0.8f, 0.8f, 1.f - angle / MAX_ANGLE); + float sphereRadius = getHead()->getAverageLoudness() * SPHERE_LOUDNESS_SCALING; + + if ((sphereRadius > MIN_SPHERE_SIZE) && (angle < MAX_SPHERE_ANGLE) && (angle > MIN_SPHERE_ANGLE)) { + glColor4f(SPHERE_COLOR[0], SPHERE_COLOR[1], SPHERE_COLOR[2], 1.f - angle / MAX_SPHERE_ANGLE); glPushMatrix(); glTranslatef(_position.x, _position.y, _position.z); - glScalef(height / 2.f, height / 2.f, height / 2.f); - glutSolidSphere(1.2f + getHead()->getAverageLoudness() * .0005f, 20, 20); + glScalef(height, height, height); + glutSolidSphere(sphereRadius, 15, 15); glPopMatrix(); } } @@ -424,16 +429,9 @@ void Avatar::renderDisplayName() { glPolygonOffset(1.0f, 1.0f); glColor4f(0.2f, 0.2f, 0.2f, _displayNameAlpha * DISPLAYNAME_BACKGROUND_ALPHA / DISPLAYNAME_ALPHA); - glBegin(GL_QUADS); - glVertex2f(left, bottom); - glVertex2f(right, bottom); - glVertex2f(right, top); - glVertex2f(left, top); - glEnd(); - - + renderBevelCornersRect(left, bottom, right - left, top - bottom, 3); + glColor4f(0.93f, 0.93f, 0.93f, _displayNameAlpha); - QByteArray ba = _displayName.toLocal8Bit(); const char* text = ba.data(); diff --git a/libraries/audio/src/Sound.cpp b/libraries/audio/src/Sound.cpp index a403439ec6..07eec031b5 100644 --- a/libraries/audio/src/Sound.cpp +++ b/libraries/audio/src/Sound.cpp @@ -44,13 +44,8 @@ void Sound::replyFinished(QNetworkReply* reply) { QByteArray headerContentType = reply->rawHeader("Content-Type"); - // RAW audio file encountered - if (headerContentType == "application/octet-stream") { - downSample(rawAudioByteArray); - } - // WAV audio file encountered - else if (headerContentType == "audio/x-wav" + if (headerContentType == "audio/x-wav" || headerContentType == "audio/wav" || headerContentType == "audio/wave") { @@ -59,7 +54,8 @@ void Sound::replyFinished(QNetworkReply* reply) { interpretAsWav(rawAudioByteArray, outputAudioByteArray); downSample(outputAudioByteArray); } else { - qDebug() << "Unknown audio file 'Content-Type'."; + // Process as RAW file + downSample(rawAudioByteArray); } } else { qDebug() << "Network reply without 'Content-Type'."; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 2cb719c446..482cd7564a 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -11,6 +11,9 @@ #include #include +#include +#include +#include #include #include @@ -24,6 +27,8 @@ using namespace std; static const float fingerVectorRadix = 4; // bits of precision when converting from float<->fixed +QNetworkAccessManager* AvatarData::networkAccessManager = NULL; + AvatarData::AvatarData() : NodeData(), _handPosition(0,0,0), @@ -38,7 +43,8 @@ AvatarData::AvatarData() : _handData(NULL), _displayNameBoundingRect(), _displayNameTargetAlpha(0.0f), - _displayNameAlpha(0.0f) + _displayNameAlpha(0.0f), + _billboard() { } @@ -338,6 +344,28 @@ void AvatarData::setBillboard(const QByteArray& billboard) { qDebug() << "Changing billboard for avatar."; } +void AvatarData::setBillboardFromURL(const QString &billboardURL) { + _billboardURL = billboardURL; + + if (AvatarData::networkAccessManager) { + qDebug() << "Changing billboard for avatar to PNG at" << qPrintable(billboardURL); + + QNetworkRequest billboardRequest; + billboardRequest.setUrl(QUrl(billboardURL)); + + QNetworkReply* networkReply = AvatarData::networkAccessManager->get(billboardRequest); + connect(networkReply, SIGNAL(finished()), this, SLOT(setBillboardFromNetworkReply())); + + } else { + qDebug() << "Billboard PNG download requested but no network access manager is available."; + } +} + +void AvatarData::setBillboardFromNetworkReply() { + QNetworkReply* networkReply = reinterpret_cast(sender()); + setBillboard(networkReply->readAll()); +} + void AvatarData::setClampedTargetScale(float targetScale) { targetScale = glm::clamp(targetScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE); @@ -361,8 +389,10 @@ void AvatarData::sendIdentityPacket() { } void AvatarData::sendBillboardPacket() { - QByteArray billboardPacket = byteArrayWithPopulatedHeader(PacketTypeAvatarBillboard); - billboardPacket.append(_billboard); - - NodeList::getInstance()->broadcastToNodes(billboardPacket, NodeSet() << NodeType::AvatarMixer); + if (!_billboard.isEmpty()) { + QByteArray billboardPacket = byteArrayWithPopulatedHeader(PacketTypeAvatarBillboard); + billboardPacket.append(_billboard); + + NodeList::getInstance()->broadcastToNodes(billboardPacket, NodeSet() << NodeType::AvatarMixer); + } } diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index c5cd72c184..0ce2910398 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -68,6 +68,8 @@ enum KeyState { const glm::vec3 vec3Zero(0.0f); +class QNetworkAccessManager; + class AvatarData : public NodeData { Q_OBJECT @@ -88,6 +90,7 @@ class AvatarData : public NodeData { Q_PROPERTY(QString faceModelURL READ getFaceModelURLFromScript WRITE setFaceModelURLFromScript) Q_PROPERTY(QString skeletonModelURL READ getSkeletonModelURLFromScript WRITE setSkeletonModelURLFromScript) + Q_PROPERTY(QString billboardURL READ getBillboardURL WRITE setBillboardFromURL) public: AvatarData(); ~AvatarData(); @@ -170,6 +173,9 @@ public: virtual void setBillboard(const QByteArray& billboard); const QByteArray& getBillboard() const { return _billboard; } + void setBillboardFromURL(const QString& billboardURL); + const QString& getBillboardURL() { return _billboardURL; } + QString getFaceModelURLFromScript() const { return _faceModelURL.toString(); } void setFaceModelURLFromScript(const QString& faceModelString) { setFaceModelURL(faceModelString); } @@ -177,11 +183,13 @@ public: void setSkeletonModelURLFromScript(const QString& skeletonModelString) { setSkeletonModelURL(QUrl(skeletonModelString)); } virtual float getBoundingRadius() const { return 1.f; } + + static void setNetworkAccessManager(QNetworkAccessManager* sharedAccessManager) { networkAccessManager = sharedAccessManager; } public slots: void sendIdentityPacket(); void sendBillboardPacket(); - + void setBillboardFromNetworkReply(); protected: glm::vec3 _position; glm::vec3 _handPosition; @@ -217,6 +225,9 @@ protected: float _displayNameAlpha; QByteArray _billboard; + QString _billboardURL; + + static QNetworkAccessManager* networkAccessManager; private: // privatize the copy constructor and assignment operator so they cannot be called diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 076f941222..c1b122b523 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -45,6 +45,7 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, bool wantMenuItems, co AbstractControllerScriptingInterface* controllerScriptingInterface) : _isAvatar(false), _avatarIdentityTimer(NULL), + _avatarBillboardTimer(NULL), _avatarData(NULL) { _scriptContents = scriptContents; @@ -79,14 +80,17 @@ void ScriptEngine::setIsAvatar(bool isAvatar) { _isAvatar = isAvatar; if (_isAvatar && !_avatarIdentityTimer) { - // set up the avatar identity timer + // set up the avatar timers _avatarIdentityTimer = new QTimer(this); + _avatarBillboardTimer = new QTimer(this); // connect our slot connect(_avatarIdentityTimer, &QTimer::timeout, this, &ScriptEngine::sendAvatarIdentityPacket); + connect(_avatarBillboardTimer, &QTimer::timeout, this, &ScriptEngine::sendAvatarBillboardPacket); - // start the timer + // start the timers _avatarIdentityTimer->start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS); + _avatarBillboardTimer->start(AVATAR_BILLBOARD_PACKET_SEND_INTERVAL_MSECS); } } @@ -196,6 +200,12 @@ void ScriptEngine::sendAvatarIdentityPacket() { } } +void ScriptEngine::sendAvatarBillboardPacket() { + if (_isAvatar && _avatarData) { + _avatarData->sendBillboardPacket(); + } +} + void ScriptEngine::run() { if (!_isInitialized) { init(); diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index cf5ad1a68b..72ac848bff 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -85,10 +85,12 @@ protected: QScriptEngine _engine; bool _isAvatar; QTimer* _avatarIdentityTimer; + QTimer* _avatarBillboardTimer; QHash _timerFunctionMap; private: void sendAvatarIdentityPacket(); + void sendAvatarBillboardPacket(); QObject* setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot); void stopTimer(QTimer* timer); diff --git a/libraries/shared/src/NodeList.cpp b/libraries/shared/src/NodeList.cpp index 98387ce420..d8c6cd09f1 100644 --- a/libraries/shared/src/NodeList.cpp +++ b/libraries/shared/src/NodeList.cpp @@ -549,6 +549,12 @@ void NodeList::sendDomainServerCheckIn() { sendSTUNRequest(); } + if (_numNoReplyDomainCheckIns >= MAX_SILENT_DOMAIN_SERVER_CHECK_INS) { + // we haven't heard back from DS in MAX_SILENT_DOMAIN_SERVER_CHECK_INS + // so emit our signal that indicates that + emit limitOfSilentDomainCheckInsReached(); + } + // increment the count of un-replied check-ins _numNoReplyDomainCheckIns++; } else if (AccountManager::getInstance().hasValidAccessToken()) { diff --git a/libraries/shared/src/NodeList.h b/libraries/shared/src/NodeList.h index a66bcf3535..590a2ce83f 100644 --- a/libraries/shared/src/NodeList.h +++ b/libraries/shared/src/NodeList.h @@ -132,6 +132,7 @@ signals: void uuidChanged(const QUuid& ownerUUID); void nodeAdded(SharedNodePointer); void nodeKilled(SharedNodePointer); + void limitOfSilentDomainCheckInsReached(); private slots: void domainServerAuthReply(const QJsonObject& jsonObject); private: diff --git a/libraries/shared/src/OAuthAccessToken.cpp b/libraries/shared/src/OAuthAccessToken.cpp index 522a3700cd..5dc6672290 100644 --- a/libraries/shared/src/OAuthAccessToken.cpp +++ b/libraries/shared/src/OAuthAccessToken.cpp @@ -22,7 +22,7 @@ OAuthAccessToken::OAuthAccessToken() : OAuthAccessToken::OAuthAccessToken(const QJsonObject& jsonObject) : token(jsonObject["access_token"].toString()), refreshToken(jsonObject["refresh_token"].toString()), - expiryTimestamp(QDateTime::currentMSecsSinceEpoch() + jsonObject["expires_in"].toDouble()), + expiryTimestamp(QDateTime::currentMSecsSinceEpoch() + (jsonObject["expires_in"].toDouble() * 1000)), tokenType(jsonObject["token_type"].toString()) {