diff --git a/interface/resources/meshes/body.jpg b/interface/resources/meshes/body.jpg new file mode 100644 index 0000000000..376cf87d32 Binary files /dev/null and b/interface/resources/meshes/body.jpg differ diff --git a/interface/resources/meshes/defaultAvatar_body.fst b/interface/resources/meshes/defaultAvatar_body.fst new file mode 100644 index 0000000000..3e8fa3ef45 --- /dev/null +++ b/interface/resources/meshes/defaultAvatar_body.fst @@ -0,0 +1,18 @@ +scale=130 +joint = jointRoot = jointRoot +joint = jointLean = jointSpine +joint = jointNeck = jointNeck +joint = jointHead = jointHeadtop +joint = joint_L_shoulder = joint_L_shoulder +freeJoint = joint_L_arm +freeJoint = joint_L_elbow +joint = jointLeftHand = joint_L_hand +joint = joint_R_shoulder = joint_R_shoulder +freeJoint = joint_R_arm +freeJoint = joint_R_elbow +joint = jointRightHand = joint_R_hand + + + + + diff --git a/interface/resources/meshes/defaultAvatar_head.fst b/interface/resources/meshes/defaultAvatar_head.fst new file mode 100644 index 0000000000..1352652efc --- /dev/null +++ b/interface/resources/meshes/defaultAvatar_head.fst @@ -0,0 +1,45 @@ +# faceshift target mapping file +name= defaultAvatar_head +filename=../../../Avatars/Jelly/jellyrob_blue.fbx +texdir=../../../Avatars/Jelly +scale=80 +rx=0 +ry=0 +rz=0 +tx=0 +ty=0 +tz=0 +joint = jointNeck = jointNeck +bs = BrowsD_L = Leye1.BrowsD_L = 0.97 +bs = BrowsD_R = Reye1.BrowsD_R = 1 +bs = CheekSquint_L = Leye1.CheekSquint_L = 1 +bs = CheekSquint_R = Reye1.CheekSquint_R = 1 +bs = EyeBlink_L = Leye1.EyeBlink_L = 1 +bs = EyeBlink_R = Reye1.EyeBlink_R = 1 +bs = EyeDown_L = Leye1.EyeDown_L = 1 +bs = EyeDown_R = Reye1.EyeDown_R = 0.99 +bs = EyeIn_L = Leye1.EyeIn_L = 0.92 +bs = EyeIn_R = Reye1.EyeIn_R = 1 +bs = EyeOpen_L = Leye1.EyeOpen_L = 1 +bs = EyeOpen_R = Reye1.EyeOpen_R = 1 +bs = EyeOut_L = Leye1.EyeOut_L = 0.99 +bs = EyeOut_R = Reye1.EyeOut_R = 1 +bs = EyeUp_L = Leye1.EyeUp_L = 0.93 +bs = EyeUp_R = Reye1.EyeUp_R = 1 +bs = JawOpen = Mouth.JawOpen = 1 +bs = LipsFunnel = Mouth.LipsFunnel = 1 +bs = LipsLowerDown = Mouth.LipsLowerDown = 1 +bs = LipsPucker = Mouth.LipsPucker = 1 +bs = LipsStretch_L = Mouth.LipsStretch_L = 0.96 +bs = LipsStretch_R = Mouth.LipsStretch_R = 1 +bs = LipsUpperUp = Mouth.LipsUpperUp = 1 +bs = MouthDimple_L = Mouth.MouthDimple_L = 1 +bs = MouthDimple_R = Mouth.MouthDimple_R = 1 +bs = MouthFrown_L = Mouth.MouthFrown_L = 1 +bs = MouthFrown_R = Mouth.MouthFrown_R = 1 +bs = MouthLeft = Mouth.MouthLeft = 1 +bs = MouthRight = Mouth.MouthRight = 1 +bs = MouthSmile_L = Mouth.MouthSmile_L = 1 +bs = MouthSmile_R = Mouth.MouthSmile_R = 1 +bs = Puff = Mouth.Puff = 1 +bs = Sneer = Mouth.Sneer = 1 diff --git a/interface/resources/meshes/tail.jpg b/interface/resources/meshes/tail.jpg new file mode 100644 index 0000000000..1b5bb89750 Binary files /dev/null and b/interface/resources/meshes/tail.jpg differ diff --git a/interface/resources/meshes/visor.png b/interface/resources/meshes/visor.png new file mode 100644 index 0000000000..e4e6292b2c Binary files /dev/null and b/interface/resources/meshes/visor.png differ diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index c299c0c617..761ed59db9 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -345,12 +345,14 @@ bool Avatar::findSphereCollisionWithSkeleton(const glm::vec3& sphereCenter, floa void Avatar::setFaceModelURL(const QUrl &faceModelURL) { AvatarData::setFaceModelURL(faceModelURL); - _head.getFaceModel().setURL(_faceModelURL); + const QUrl DEFAULT_FACE_MODEL_URL = QUrl::fromLocalFile("resources/meshes/defaultAvatar_head.fbx"); + _head.getFaceModel().setURL(_faceModelURL, DEFAULT_FACE_MODEL_URL); } void Avatar::setSkeletonModelURL(const QUrl &skeletonModelURL) { AvatarData::setSkeletonModelURL(skeletonModelURL); - _skeletonModel.setURL(_skeletonModelURL); + const QUrl DEFAULT_SKELETON_MODEL_URL = QUrl::fromLocalFile("resources/meshes/defaultAvatar_body.fbx"); + _skeletonModel.setURL(_skeletonModelURL, DEFAULT_SKELETON_MODEL_URL); } int Avatar::parseData(const QByteArray& packet) { diff --git a/interface/src/avatar/FaceModel.cpp b/interface/src/avatar/FaceModel.cpp index b9803c17cd..b041f5bc2d 100644 --- a/interface/src/avatar/FaceModel.cpp +++ b/interface/src/avatar/FaceModel.cpp @@ -58,7 +58,7 @@ void FaceModel::maybeUpdateNeckRotation(const JointState& parentState, const FBX glm::mat3 axes = glm::mat3_cast(_rotation); glm::mat3 inverse = glm::mat3(glm::inverse(parentState.transform * glm::translate(state.translation) * joint.preTransform * glm::mat4_cast(joint.preRotation))); - state.rotation = glm::angleAxis(-_owningHead->getRoll(), glm::normalize(inverse * axes[2])) * + state.rotation = glm::angleAxis(-_owningHead->getTweakedRoll(), glm::normalize(inverse * axes[2])) * glm::angleAxis(_owningHead->getTweakedYaw(), glm::normalize(inverse * axes[1])) * glm::angleAxis(-_owningHead->getTweakedPitch(), glm::normalize(inverse * axes[0])) * joint.rotation; } diff --git a/interface/src/renderer/FBXReader.cpp b/interface/src/renderer/FBXReader.cpp index 237ba7196d..b4e7c4abc5 100644 --- a/interface/src/renderer/FBXReader.cpp +++ b/interface/src/renderer/FBXReader.cpp @@ -834,6 +834,12 @@ QString getTopModelID(const QMultiHash& parentMap, } } +QString getString(const QVariant& value) { + // if it's a list, return the first entry + QVariantList list = value.toList(); + return list.isEmpty() ? value.toString() : list.at(0).toString(); +} + FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) { QHash meshes; QVector blendshapes; @@ -847,14 +853,14 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) QHash bumpTextures; QVariantHash joints = mapping.value("joint").toHash(); - QString jointEyeLeftName = processID(joints.value("jointEyeLeft", "jointEyeLeft").toString()); - QString jointEyeRightName = processID(joints.value("jointEyeRight", "jointEyeRight").toString()); - QString jointNeckName = processID(joints.value("jointNeck", "jointNeck").toString()); - QString jointRootName = processID(joints.value("jointRoot", "jointRoot").toString()); - QString jointLeanName = processID(joints.value("jointLean", "jointLean").toString()); - QString jointHeadName = processID(joints.value("jointHead", "jointHead").toString()); - QString jointLeftHandName = processID(joints.value("jointLeftHand", "jointLeftHand").toString()); - QString jointRightHandName = processID(joints.value("jointRightHand", "jointRightHand").toString()); + QString jointEyeLeftName = processID(getString(joints.value("jointEyeLeft", "jointEyeLeft"))); + QString jointEyeRightName = processID(getString(joints.value("jointEyeRight", "jointEyeRight"))); + QString jointNeckName = processID(getString(joints.value("jointNeck", "jointNeck"))); + QString jointRootName = processID(getString(joints.value("jointRoot", "jointRoot"))); + QString jointLeanName = processID(getString(joints.value("jointLean", "jointLean"))); + QString jointHeadName = processID(getString(joints.value("jointHead", "jointHead"))); + QString jointLeftHandName = processID(getString(joints.value("jointLeftHand", "jointLeftHand"))); + QString jointRightHandName = processID(getString(joints.value("jointRightHand", "jointRightHand"))); QVariantList jointLeftFingerNames = joints.values("jointLeftFinger"); QVariantList jointRightFingerNames = joints.values("jointRightFinger"); QVariantList jointLeftFingertipNames = joints.values("jointLeftFingertip"); diff --git a/interface/src/renderer/GeometryCache.cpp b/interface/src/renderer/GeometryCache.cpp index 63a0c51f0b..3526fa5050 100644 --- a/interface/src/renderer/GeometryCache.cpp +++ b/interface/src/renderer/GeometryCache.cpp @@ -290,19 +290,24 @@ void GeometryCache::renderGrid(int xDivisions, int yDivisions) { buffer.release(); } -QSharedPointer GeometryCache::getGeometry(const QUrl& url) { +QSharedPointer GeometryCache::getGeometry(const QUrl& url, const QUrl& fallback) { + if (!url.isValid() && fallback.isValid()) { + return getGeometry(fallback); + } QSharedPointer geometry = _networkGeometry.value(url); if (geometry.isNull()) { - geometry = QSharedPointer(new NetworkGeometry(url)); + geometry = QSharedPointer(new NetworkGeometry(url, fallback.isValid() ? + getGeometry(fallback) : QSharedPointer())); _networkGeometry.insert(url, geometry); } return geometry; } -NetworkGeometry::NetworkGeometry(const QUrl& url) : +NetworkGeometry::NetworkGeometry(const QUrl& url, const QSharedPointer& fallback) : _modelRequest(url), _modelReply(NULL), _mappingReply(NULL), + _fallback(fallback), _attempts(0) { if (!url.isValid()) { @@ -369,18 +374,37 @@ void NetworkGeometry::makeModelRequest() { void NetworkGeometry::handleModelReplyError() { QDebug debug = qDebug() << _modelReply->errorString(); + QNetworkReply::NetworkError error = _modelReply->error(); _modelReply->disconnect(this); _modelReply->deleteLater(); _modelReply = NULL; - // retry with increasing delays - const int MAX_ATTEMPTS = 8; - const int BASE_DELAY_MS = 1000; - if (++_attempts < MAX_ATTEMPTS) { - QTimer::singleShot(BASE_DELAY_MS * (int)pow(2.0, _attempts), this, SLOT(makeModelRequest())); - debug << " -- retrying..."; - + // retry for certain types of failures + switch (error) { + case QNetworkReply::RemoteHostClosedError: + case QNetworkReply::TimeoutError: + case QNetworkReply::TemporaryNetworkFailureError: + case QNetworkReply::ProxyConnectionClosedError: + case QNetworkReply::ProxyTimeoutError: + case QNetworkReply::UnknownNetworkError: + case QNetworkReply::UnknownProxyError: + case QNetworkReply::UnknownContentError: + case QNetworkReply::ProtocolFailure: { + // retry with increasing delays + const int MAX_ATTEMPTS = 8; + const int BASE_DELAY_MS = 1000; + if (++_attempts < MAX_ATTEMPTS) { + QTimer::singleShot(BASE_DELAY_MS * (int)pow(2.0, _attempts), this, SLOT(makeModelRequest())); + debug << " -- retrying..."; + return; + } + // fall through to final failure + } + default: + maybeLoadFallback(); + break; } + } void NetworkGeometry::handleMappingReplyError() { @@ -415,6 +439,7 @@ void NetworkGeometry::maybeReadModelWithMapping() { } catch (const QString& error) { qDebug() << "Error reading " << url << ": " << error; + maybeLoadFallback(); return; } @@ -507,6 +532,24 @@ void NetworkGeometry::maybeReadModelWithMapping() { _meshes.append(networkMesh); } + + emit loaded(); +} + +void NetworkGeometry::loadFallback() { + _geometry = _fallback->_geometry; + _meshes = _fallback->_meshes; + emit loaded(); +} + +void NetworkGeometry::maybeLoadFallback() { + if (_fallback) { + if (_fallback->isLoaded()) { + loadFallback(); + } else { + connect(_fallback.data(), SIGNAL(loaded()), SLOT(loadFallback())); + } + } } bool NetworkMeshPart::isTranslucent() const { diff --git a/interface/src/renderer/GeometryCache.h b/interface/src/renderer/GeometryCache.h index e65aed31d4..618796e907 100644 --- a/interface/src/renderer/GeometryCache.h +++ b/interface/src/renderer/GeometryCache.h @@ -37,7 +37,8 @@ public: void renderGrid(int xDivisions, int yDivisions); /// Loads geometry from the specified URL. - QSharedPointer getGeometry(const QUrl& url); + /// \param fallback a fallback URL to load if the desired one is unavailable + QSharedPointer getGeometry(const QUrl& url, const QUrl& fallback = QUrl()); private: @@ -58,7 +59,7 @@ class NetworkGeometry : public QObject { public: - NetworkGeometry(const QUrl& url); + NetworkGeometry(const QUrl& url, const QSharedPointer& fallback); ~NetworkGeometry(); bool isLoaded() const { return !_geometry.joints.isEmpty(); } @@ -69,18 +70,26 @@ public: /// Returns the average color of all meshes in the geometry. glm::vec4 computeAverageColor() const; +signals: + + void loaded(); + private slots: void makeModelRequest(); void handleModelReplyError(); void handleMappingReplyError(); void maybeReadModelWithMapping(); + void loadFallback(); private: + void maybeLoadFallback(); + QNetworkRequest _modelRequest; QNetworkReply* _modelReply; QNetworkReply* _mappingReply; + QSharedPointer _fallback; int _attempts; FBXGeometry _geometry; diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index e1652b1237..b14ed1036d 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -390,7 +390,7 @@ float Model::getRightArmLength() const { return getLimbLength(getRightHandJointIndex()); } -void Model::setURL(const QUrl& url) { +void Model::setURL(const QUrl& url, const QUrl& fallback) { // don't recreate the geometry if it's the same URL if (_url == url) { return; @@ -401,7 +401,7 @@ void Model::setURL(const QUrl& url) { deleteGeometry(); _dilatedTextures.clear(); - _geometry = Application::getInstance()->getGeometryCache()->getGeometry(url); + _geometry = Application::getInstance()->getGeometryCache()->getGeometry(url, fallback); } glm::vec4 Model::computeAverageColor() const { diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index f99e46c5f4..423b2a4c81 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -61,7 +61,7 @@ public: void simulate(float deltaTime); bool render(float alpha); - Q_INVOKABLE void setURL(const QUrl& url); + Q_INVOKABLE void setURL(const QUrl& url, const QUrl& fallback = QUrl()); const QUrl& getURL() const { return _url; } /// Returns the extents of the model in its bind pose.