From 21a149348b843596e34d5de532b663fc7230975e Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Fri, 28 Feb 2014 13:57:25 -0800 Subject: [PATCH] When switching models, retain old one until new one is loaded. Closes #2146. --- interface/src/avatar/Avatar.cpp | 4 +-- interface/src/renderer/GeometryCache.cpp | 21 +++++++++++-- interface/src/renderer/GeometryCache.h | 4 +-- interface/src/renderer/Model.cpp | 39 +++++++++++++----------- interface/src/renderer/Model.h | 11 +++++-- interface/src/renderer/TextureCache.cpp | 6 ++-- interface/src/renderer/TextureCache.h | 3 -- libraries/metavoxels/src/ScriptCache.cpp | 1 + libraries/metavoxels/src/ScriptCache.h | 2 -- libraries/shared/src/ResourceCache.cpp | 23 ++++++++++++-- libraries/shared/src/ResourceCache.h | 7 +++++ 11 files changed, 83 insertions(+), 38 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 85671c9e30..d537b86854 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -567,13 +567,13 @@ bool Avatar::findParticleCollisions(const glm::vec3& particleCenter, float parti void Avatar::setFaceModelURL(const QUrl& faceModelURL) { AvatarData::setFaceModelURL(faceModelURL); const QUrl DEFAULT_FACE_MODEL_URL = QUrl::fromLocalFile("resources/meshes/defaultAvatar_head.fst"); - getHead()->getFaceModel().setURL(_faceModelURL, DEFAULT_FACE_MODEL_URL, !isMyAvatar()); + getHead()->getFaceModel().setURL(_faceModelURL, DEFAULT_FACE_MODEL_URL, true, !isMyAvatar()); } void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { AvatarData::setSkeletonModelURL(skeletonModelURL); const QUrl DEFAULT_SKELETON_MODEL_URL = QUrl::fromLocalFile("resources/meshes/defaultAvatar_body.fst"); - _skeletonModel.setURL(_skeletonModelURL, DEFAULT_SKELETON_MODEL_URL, !isMyAvatar()); + _skeletonModel.setURL(_skeletonModelURL, DEFAULT_SKELETON_MODEL_URL, true, !isMyAvatar()); } void Avatar::setDisplayName(const QString& displayName) { diff --git a/interface/src/renderer/GeometryCache.cpp b/interface/src/renderer/GeometryCache.cpp index e9719e7a84..cf59f9578a 100644 --- a/interface/src/renderer/GeometryCache.cpp +++ b/interface/src/renderer/GeometryCache.cpp @@ -307,6 +307,21 @@ NetworkGeometry::NetworkGeometry(const QUrl& url, const QSharedPointerisLoaded()) || + (part.normalTexture && !part.normalTexture->isLoaded())) { + return false; + } + } + } + return true; +} + QSharedPointer NetworkGeometry::getLODOrFallback(float distance, float& hysteresis, bool delayLoad) const { if (_lodParent.data() != this) { return _lodParent.data()->getLODOrFallback(distance, hysteresis, delayLoad); @@ -438,7 +453,7 @@ void NetworkGeometry::downloadFinished(QNetworkReply* reply) { QString filename = _mapping.value("filename").toString(); if (filename.isNull()) { qDebug() << "Mapping file " << url << " has no filename."; - _failedToLoad = true; + finishedLoading(false); } else { QString texdir = _mapping.value("texdir").toString(); @@ -471,7 +486,7 @@ void NetworkGeometry::downloadFinished(QNetworkReply* reply) { } catch (const QString& error) { qDebug() << "Error reading " << url << ": " << error; - _failedToLoad = true; + finishedLoading(false); return; } @@ -567,6 +582,8 @@ void NetworkGeometry::downloadFinished(QNetworkReply* reply) { _meshes.append(networkMesh); } + + finishedLoading(true); } bool NetworkMeshPart::isTranslucent() const { diff --git a/interface/src/renderer/GeometryCache.h b/interface/src/renderer/GeometryCache.h index e5139088d3..2e6500b86a 100644 --- a/interface/src/renderer/GeometryCache.h +++ b/interface/src/renderer/GeometryCache.h @@ -69,8 +69,8 @@ public: NetworkGeometry(const QUrl& url, const QSharedPointer& fallback, bool delayLoad, const QVariantHash& mapping = QVariantHash(), const QUrl& textureBase = QUrl()); - /// Checks whether the geometry is fulled loaded. - bool isLoaded() const { return !_geometry.joints.isEmpty(); } + /// Checks whether the geometry and its textures are loaded. + bool isLoadedWithTextures() const; /// Returns a pointer to the geometry appropriate for the specified distance. /// \param hysteresis a hysteresis parameter that prevents rapid model switching diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 599abf29aa..d87879ac27 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -57,21 +57,6 @@ QVector Model::createJointStates(const FBXGeometry& geometry) return jointStates; } -bool Model::isLoadedWithTextures() const { - if (!isActive()) { - return false; - } - foreach (const NetworkMesh& mesh, _geometry->getMeshes()) { - foreach (const NetworkMeshPart& part, mesh.parts) { - if ((part.diffuseTexture && !part.diffuseTexture->isLoaded()) || - (part.normalTexture && !part.normalTexture->isLoaded())) { - return false; - } - } - } - return true; -} - void Model::init() { if (!_program.isLinked()) { switchToResourcesParentIfRequired(); @@ -119,7 +104,18 @@ void Model::simulate(float deltaTime, bool delayLoad) { // update our LOD QVector newJointStates; if (_geometry) { - QSharedPointer geometry = _geometry->getLODOrFallback(_lodDistance, _lodHysteresis, delayLoad); + QSharedPointer geometry = _geometry; + if (_nextGeometry) { + if (!delayLoad) { + _nextGeometry->setLoadPriority(this, -_lodDistance); + _nextGeometry->ensureLoading(); + } + if (_nextGeometry->isLoaded()) { + geometry = _nextGeometry; + _nextGeometry.clear(); + } + } + geometry = geometry->getLODOrFallback(_lodDistance, _lodHysteresis, delayLoad); if (_geometry != geometry) { if (!_jointStates.isEmpty()) { // copy the existing joint states @@ -447,20 +443,27 @@ float Model::getRightArmLength() const { return getLimbLength(getRightHandJointIndex()); } -void Model::setURL(const QUrl& url, const QUrl& fallback, bool delayLoad) { +void Model::setURL(const QUrl& url, const QUrl& fallback, bool retainCurrent, bool delayLoad) { // don't recreate the geometry if it's the same URL if (_url == url) { return; } _url = url; + // if so instructed, keep the current geometry until the new one is loaded + _nextGeometry = Application::getInstance()->getGeometryCache()->getGeometry(url, fallback, delayLoad); + if (retainCurrent && isActive() && !_nextGeometry->isLoaded()) { + return; + } + // delete our local geometry and custom textures deleteGeometry(); _dilatedTextures.clear(); _lodHysteresis = NetworkGeometry::NO_HYSTERESIS; // we retain a reference to the base geometry so that its reference count doesn't fall to zero - _baseGeometry = _geometry = Application::getInstance()->getGeometryCache()->getGeometry(url, fallback, delayLoad); + _baseGeometry = _geometry = _nextGeometry; + _nextGeometry.reset(); } glm::vec4 Model::computeAverageColor() const { diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index e200960b98..25bd1f08f5 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -48,14 +48,20 @@ public: bool isRenderable() const { return !_meshStates.isEmpty(); } - bool isLoadedWithTextures() const; + bool isLoadedWithTextures() const { return _geometry && _geometry->isLoadedWithTextures(); } void init(); void reset(); void simulate(float deltaTime, bool delayLoad = false); bool render(float alpha); - Q_INVOKABLE void setURL(const QUrl& url, const QUrl& fallback = QUrl(), bool delayLoad = false); + /// Sets the URL of the model to render. + /// \param fallback the URL of a fallback model to render if the requested model fails to load + /// \param retainCurrent if true, keep rendering the current model until the new one is loaded + /// \param delayLoad if true, don't load the model immediately; wait until actually requested + Q_INVOKABLE void setURL(const QUrl& url, const QUrl& fallback = QUrl(), + bool retainCurrent = false, bool delayLoad = false); + const QUrl& getURL() const { return _url; } /// Sets the distance parameter used for LOD computations. @@ -235,6 +241,7 @@ private: void renderMeshes(float alpha, bool translucent); QSharedPointer _baseGeometry; ///< reference required to prevent collection of base + QSharedPointer _nextGeometry; float _lodDistance; float _lodHysteresis; diff --git a/interface/src/renderer/TextureCache.cpp b/interface/src/renderer/TextureCache.cpp index 0a6a3fe6f8..2b14ff8c18 100644 --- a/interface/src/renderer/TextureCache.cpp +++ b/interface/src/renderer/TextureCache.cpp @@ -254,8 +254,7 @@ Texture::~Texture() { NetworkTexture::NetworkTexture(const QUrl& url, bool normalMap) : Resource(url), _averageColor(1.0f, 1.0f, 1.0f, 1.0f), - _translucent(false), - _loaded(false) { + _translucent(false) { if (!url.isValid()) { _loaded = true; @@ -269,8 +268,6 @@ NetworkTexture::NetworkTexture(const QUrl& url, bool normalMap) : } void NetworkTexture::downloadFinished(QNetworkReply* reply) { - _loaded = true; - QImage image = QImage::fromData(reply->readAll()); if (image.format() != QImage::Format_ARGB32) { image = image.convertToFormat(QImage::Format_ARGB32); @@ -298,6 +295,7 @@ void NetworkTexture::downloadFinished(QNetworkReply* reply) { _averageColor = accumulated / (imageArea * EIGHT_BIT_MAXIMUM); _translucent = (translucentPixels >= imageArea / 2); + finishedLoading(true); imageLoaded(image); glBindTexture(GL_TEXTURE_2D, getID()); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width(), image.height(), 1, diff --git a/interface/src/renderer/TextureCache.h b/interface/src/renderer/TextureCache.h index 2c8f4d7cf9..33b2834a9d 100644 --- a/interface/src/renderer/TextureCache.h +++ b/interface/src/renderer/TextureCache.h @@ -117,8 +117,6 @@ public: NetworkTexture(const QUrl& url, bool normalMap); - bool isLoaded() const { return _loaded; } - /// Returns the average color over the entire texture. const glm::vec4& getAverageColor() const { return _averageColor; } @@ -135,7 +133,6 @@ private: glm::vec4 _averageColor; bool _translucent; - bool _loaded; }; /// Caches derived, dilated textures. diff --git a/libraries/metavoxels/src/ScriptCache.cpp b/libraries/metavoxels/src/ScriptCache.cpp index da29186541..fd58f5b6f3 100644 --- a/libraries/metavoxels/src/ScriptCache.cpp +++ b/libraries/metavoxels/src/ScriptCache.cpp @@ -61,6 +61,7 @@ NetworkProgram::NetworkProgram(ScriptCache* cache, const QUrl& url) : void NetworkProgram::downloadFinished(QNetworkReply* reply) { _program = QScriptProgram(QTextStream(reply).readAll(), reply->url().toString()); + finishedLoading(true); emit loaded(); } diff --git a/libraries/metavoxels/src/ScriptCache.h b/libraries/metavoxels/src/ScriptCache.h index 072020d882..ac53e602f8 100644 --- a/libraries/metavoxels/src/ScriptCache.h +++ b/libraries/metavoxels/src/ScriptCache.h @@ -72,8 +72,6 @@ public: ScriptCache* getCache() const { return _cache; } - bool isLoaded() const { return !_program.isNull(); } - const QScriptProgram& getProgram() const { return _program; } signals: diff --git a/libraries/shared/src/ResourceCache.cpp b/libraries/shared/src/ResourceCache.cpp index 24350794bc..86aca599ae 100644 --- a/libraries/shared/src/ResourceCache.cpp +++ b/libraries/shared/src/ResourceCache.cpp @@ -77,6 +77,7 @@ Resource::Resource(const QUrl& url, bool delayLoad) : _request(url), _startedLoading(false), _failedToLoad(false), + _loaded(false), _attempts(0), _reply(NULL) { @@ -106,10 +107,15 @@ void Resource::ensureLoading() { } void Resource::setLoadPriority(const QPointer& owner, float priority) { - _loadPriorities.insert(owner, priority); + if (!(_failedToLoad || _loaded)) { + _loadPriorities.insert(owner, priority); + } } void Resource::setLoadPriorities(const QHash, float>& priorities) { + if (_failedToLoad || _loaded) { + return; + } for (QHash, float>::const_iterator it = priorities.constBegin(); it != priorities.constEnd(); it++) { _loadPriorities.insert(it.key(), it.value()); @@ -117,7 +123,9 @@ void Resource::setLoadPriorities(const QHash, float>& prioriti } void Resource::clearLoadPriority(const QPointer& owner) { - _loadPriorities.remove(owner); + if (!(_failedToLoad || _loaded)) { + _loadPriorities.remove(owner); + } } float Resource::getLoadPriority() { @@ -138,6 +146,15 @@ void Resource::attemptRequest() { ResourceCache::attemptRequest(this); } +void Resource::finishedLoading(bool success) { + if (success) { + _loaded = true; + } else { + _failedToLoad = true; + } + _loadPriorities.clear(); +} + void Resource::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) { if (!_reply->isFinished()) { return; @@ -182,7 +199,7 @@ void Resource::handleReplyError() { // fall through to final failure } default: - _failedToLoad = true; + finishedLoading(false); break; } } diff --git a/libraries/shared/src/ResourceCache.h b/libraries/shared/src/ResourceCache.h index ac8ef68a05..1c3f94ab7d 100644 --- a/libraries/shared/src/ResourceCache.h +++ b/libraries/shared/src/ResourceCache.h @@ -88,6 +88,9 @@ public: /// Returns the highest load priority across all owners. float getLoadPriority(); + /// Checks whether the resource has loaded. + bool isLoaded() const { return _loaded; } + protected slots: void attemptRequest(); @@ -96,9 +99,13 @@ protected: virtual void downloadFinished(QNetworkReply* reply) = 0; + /// Should be called by subclasses when all the loading that will be done has been done. + void finishedLoading(bool success); + QNetworkRequest _request; bool _startedLoading; bool _failedToLoad; + bool _loaded; QHash, float> _loadPriorities; private slots: