diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 73032bf368..27788eb354 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -78,7 +78,7 @@ Avatar::Avatar() : _owningAvatarMixer(), _collisionFlags(0), _initialized(false), - _billboardHysteresis(false) + _shouldRenderBillboard(true) { // we may have been created in the network thread, but we live in the main thread moveToThread(Application::getInstance()->thread()); @@ -91,11 +91,14 @@ Avatar::Avatar() : Avatar::~Avatar() { } +const float BILLBOARD_LOD_DISTANCE = 40.0f; + void Avatar::init() { getHead()->init(); getHand()->init(); _skeletonModel.init(); _initialized = true; + _shouldRenderBillboard = (getLODDistance() >= BILLBOARD_LOD_DISTANCE); } glm::vec3 Avatar::getChestPosition() const { @@ -117,20 +120,30 @@ void Avatar::simulate(float deltaTime) { setScale(_targetScale); } + // update the billboard render flag + const float BILLBOARD_HYSTERESIS_PROPORTION = 0.1f; + if (_shouldRenderBillboard) { + if (getLODDistance() < BILLBOARD_LOD_DISTANCE * (1.0f - BILLBOARD_HYSTERESIS_PROPORTION)) { + _shouldRenderBillboard = false; + } + } else if (getLODDistance() > BILLBOARD_LOD_DISTANCE * (1.0f + BILLBOARD_HYSTERESIS_PROPORTION)) { + _shouldRenderBillboard = true; + } + // copy velocity so we can use it later for acceleration glm::vec3 oldVelocity = getVelocity(); getHand()->simulate(deltaTime, false); _skeletonModel.setLODDistance(getLODDistance()); - _skeletonModel.simulate(deltaTime); - Head* head = getHead(); + _skeletonModel.simulate(deltaTime, _shouldRenderBillboard); glm::vec3 headPosition; if (!_skeletonModel.getHeadPosition(headPosition)) { headPosition = _position; } + Head* head = getHead(); head->setPosition(headPosition); head->setScale(_scale); - getHead()->simulate(deltaTime, false); + head->simulate(deltaTime, false, _shouldRenderBillboard); // use speed and angular velocity to determine walking vs. standing if (_speed + fabs(_bodyYawDelta) > 0.2) { @@ -291,22 +304,10 @@ glm::quat Avatar::computeRotationFromBodyToWorldUp(float proportion) const { return glm::angleAxis(angle * proportion, axis); } -const float BILLBOARD_LOD_DISTANCE = 40.0f; - void Avatar::renderBody() { - if (!_billboard.isEmpty()) { - const float BILLBOARD_HYSTERESIS_PROPORTION = 0.1f; - if (_billboardHysteresis) { - if (getLODDistance() < BILLBOARD_LOD_DISTANCE * (1.0f - BILLBOARD_HYSTERESIS_PROPORTION)) { - _billboardHysteresis = false; - } - } else if (getLODDistance() > BILLBOARD_LOD_DISTANCE * (1.0f + BILLBOARD_HYSTERESIS_PROPORTION)) { - _billboardHysteresis = true; - } - if (_billboardHysteresis) { - renderBillboard(); - return; - } + if (_shouldRenderBillboard) { + renderBillboard(); + return; } _skeletonModel.render(1.0f); getHead()->render(1.0f); @@ -314,9 +315,14 @@ void Avatar::renderBody() { } void Avatar::renderBillboard() { + if (_billboard.isEmpty()) { + return; + } if (!_billboardTexture) { - QImage image = QImage::fromData(_billboard).convertToFormat(QImage::Format_ARGB32); - + QImage image = QImage::fromData(_billboard); + if (image.format() != QImage::Format_ARGB32) { + image = image.convertToFormat(QImage::Format_ARGB32); + } _billboardTexture.reset(new Texture()); glBindTexture(GL_TEXTURE_2D, _billboardTexture->getID()); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width(), image.height(), 1, @@ -555,16 +561,16 @@ bool Avatar::findParticleCollisions(const glm::vec3& particleCenter, float parti return collided; } -void Avatar::setFaceModelURL(const QUrl &faceModelURL) { +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); + getHead()->getFaceModel().setURL(_faceModelURL, DEFAULT_FACE_MODEL_URL, !isMyAvatar()); } -void Avatar::setSkeletonModelURL(const QUrl &skeletonModelURL) { +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); + _skeletonModel.setURL(_skeletonModelURL, DEFAULT_SKELETON_MODEL_URL, !isMyAvatar()); } void Avatar::setDisplayName(const QString& displayName) { @@ -577,9 +583,6 @@ void Avatar::setBillboard(const QByteArray& billboard) { // clear out any existing billboard texture _billboardTexture.reset(); - - // reset the hysteresis value - _billboardHysteresis = (getLODDistance() >= BILLBOARD_LOD_DISTANCE); } int Avatar::parseData(const QByteArray& packet) { diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 9ca6173089..8de5da8d50 100755 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -177,7 +177,7 @@ private: bool _initialized; QScopedPointer _billboardTexture; - bool _billboardHysteresis; + bool _shouldRenderBillboard; void renderBody(); void renderBillboard(); diff --git a/interface/src/avatar/FaceModel.cpp b/interface/src/avatar/FaceModel.cpp index 2a1593b776..04f1388015 100644 --- a/interface/src/avatar/FaceModel.cpp +++ b/interface/src/avatar/FaceModel.cpp @@ -18,9 +18,9 @@ FaceModel::FaceModel(Head* owningHead) : { } -void FaceModel::simulate(float deltaTime) { +void FaceModel::simulate(float deltaTime, bool delayLoad) { if (!isActive()) { - Model::simulate(deltaTime); + Model::simulate(deltaTime, delayLoad); return; } Avatar* owningAvatar = static_cast(_owningHead->_owningAvatar); @@ -41,7 +41,7 @@ void FaceModel::simulate(float deltaTime) { setPupilDilation(_owningHead->getPupilDilation()); setBlendshapeCoefficients(_owningHead->getBlendshapeCoefficients()); - Model::simulate(deltaTime); + Model::simulate(deltaTime, delayLoad); } bool FaceModel::render(float alpha) { diff --git a/interface/src/avatar/FaceModel.h b/interface/src/avatar/FaceModel.h index d0f0f6baef..597075dc42 100644 --- a/interface/src/avatar/FaceModel.h +++ b/interface/src/avatar/FaceModel.h @@ -21,7 +21,7 @@ public: FaceModel(Head* owningHead); - void simulate(float deltaTime); + void simulate(float deltaTime, bool delayLoad = false); bool render(float alpha); protected: diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index e27c625bae..91cf3ab07f 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -58,7 +58,7 @@ void Head::reset() { -void Head::simulate(float deltaTime, bool isMine) { +void Head::simulate(float deltaTime, bool isMine, bool delayLoad) { // Update audio trailing average for rendering facial animations Faceshift* faceshift = Application::getInstance()->getFaceshift(); @@ -161,7 +161,7 @@ void Head::simulate(float deltaTime, bool isMine) { if (!isMine) { _faceModel.setLODDistance(static_cast(_owningAvatar)->getLODDistance()); } - _faceModel.simulate(deltaTime); + _faceModel.simulate(deltaTime, delayLoad); // the blend face may have custom eye meshes if (!_faceModel.getEyePositions(_leftEyePosition, _rightEyePosition)) { diff --git a/interface/src/avatar/Head.h b/interface/src/avatar/Head.h index 39a2f4eeb6..d51df8d168 100644 --- a/interface/src/avatar/Head.h +++ b/interface/src/avatar/Head.h @@ -36,7 +36,7 @@ public: void init(); void reset(); - void simulate(float deltaTime, bool isMine); + void simulate(float deltaTime, bool isMine, bool delayLoad = false); void render(float alpha); void setScale(float scale); void setPosition(glm::vec3 position) { _position = position; } diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 0396b80e58..edb11a631e 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -18,9 +18,9 @@ SkeletonModel::SkeletonModel(Avatar* owningAvatar) : _owningAvatar(owningAvatar) { } -void SkeletonModel::simulate(float deltaTime) { +void SkeletonModel::simulate(float deltaTime, bool delayLoad) { if (!isActive()) { - Model::simulate(deltaTime); + Model::simulate(deltaTime, delayLoad); return; } setTranslation(_owningAvatar->getPosition()); @@ -28,7 +28,7 @@ void SkeletonModel::simulate(float deltaTime) { const float MODEL_SCALE = 0.0006f; setScale(glm::vec3(1.0f, 1.0f, 1.0f) * _owningAvatar->getScale() * MODEL_SCALE); - Model::simulate(deltaTime); + Model::simulate(deltaTime, delayLoad); // find the left and rightmost active Leap palms int leftPalmIndex, rightPalmIndex; diff --git a/interface/src/avatar/SkeletonModel.h b/interface/src/avatar/SkeletonModel.h index e5ca5c7623..6f80d77edc 100644 --- a/interface/src/avatar/SkeletonModel.h +++ b/interface/src/avatar/SkeletonModel.h @@ -22,7 +22,7 @@ public: SkeletonModel(Avatar* owningAvatar); - void simulate(float deltaTime); + void simulate(float deltaTime, bool delayLoad = false); bool render(float alpha); protected: diff --git a/interface/src/renderer/GeometryCache.cpp b/interface/src/renderer/GeometryCache.cpp index 78cc657018..13563be200 100644 --- a/interface/src/renderer/GeometryCache.cpp +++ b/interface/src/renderer/GeometryCache.cpp @@ -286,14 +286,14 @@ void GeometryCache::renderGrid(int xDivisions, int yDivisions) { buffer.release(); } -QSharedPointer GeometryCache::getGeometry(const QUrl& url, const QUrl& fallback) { +QSharedPointer GeometryCache::getGeometry(const QUrl& url, const QUrl& fallback, bool delayLoad) { if (!url.isValid() && fallback.isValid()) { - return getGeometry(fallback); + return getGeometry(fallback, QUrl(), delayLoad); } QSharedPointer geometry = _networkGeometry.value(url); if (geometry.isNull()) { geometry = QSharedPointer(new NetworkGeometry(url, fallback.isValid() ? - getGeometry(fallback) : QSharedPointer())); + getGeometry(fallback, QUrl(), true) : QSharedPointer(), delayLoad)); geometry->setLODParent(geometry); _networkGeometry.insert(url, geometry); } @@ -302,7 +302,7 @@ QSharedPointer GeometryCache::getGeometry(const QUrl& url, cons const float NetworkGeometry::NO_HYSTERESIS = -1.0f; -NetworkGeometry::NetworkGeometry(const QUrl& url, const QSharedPointer& fallback, +NetworkGeometry::NetworkGeometry(const QUrl& url, const QSharedPointer& fallback, bool delayLoad, const QVariantHash& mapping, const QUrl& textureBase) : _request(url), _reply(NULL), @@ -318,8 +318,8 @@ NetworkGeometry::NetworkGeometry(const QUrl& url, const QSharedPointer NetworkGeometry::getLODOrFallback(float distance, float& hysteresis) const { +void NetworkGeometry::ensureLoading() { + if (!_startedLoading) { + makeRequest(); + } +} + +QSharedPointer NetworkGeometry::getLODOrFallback(float distance, float& hysteresis, bool delayLoad) const { if (_lodParent.data() != this) { - return _lodParent.data()->getLODOrFallback(distance, hysteresis); + return _lodParent.data()->getLODOrFallback(distance, hysteresis, delayLoad); } if (_failedToLoad && _fallback) { return _fallback; @@ -357,8 +363,8 @@ QSharedPointer NetworkGeometry::getLODOrFallback(float distance return lod; } // if the ideal LOD isn't loaded, we need to make sure it's started to load, and possibly return the closest loaded one - if (!lod->_startedLoading) { - lod->makeRequest(); + if (!delayLoad) { + lod->ensureLoading(); } float closestDistance = FLT_MAX; if (isLoaded()) { @@ -438,7 +444,7 @@ void NetworkGeometry::handleDownloadProgress(qint64 bytesReceived, qint64 bytesT QVariantHash lods = _mapping.value("lod").toHash(); for (QVariantHash::const_iterator it = lods.begin(); it != lods.end(); it++) { QSharedPointer geometry(new NetworkGeometry(url.resolved(it.key()), - QSharedPointer(), _mapping, _textureBase)); + QSharedPointer(), true, _mapping, _textureBase)); geometry->setLODParent(_lodParent); _lods.insert(it.value().toFloat(), geometry); } diff --git a/interface/src/renderer/GeometryCache.h b/interface/src/renderer/GeometryCache.h index ef53fe9c3e..a1d159be31 100644 --- a/interface/src/renderer/GeometryCache.h +++ b/interface/src/renderer/GeometryCache.h @@ -41,7 +41,8 @@ public: /// Loads geometry from the specified URL. /// \param fallback a fallback URL to load if the desired one is unavailable - QSharedPointer getGeometry(const QUrl& url, const QUrl& fallback = QUrl()); + /// \param delayLoad if true, don't load the geometry immediately; wait until load is first requested + QSharedPointer getGeometry(const QUrl& url, const QUrl& fallback = QUrl(), bool delayLoad = false); private: @@ -65,16 +66,19 @@ public: /// A hysteresis value indicating that we have no state memory. static const float NO_HYSTERESIS; - NetworkGeometry(const QUrl& url, const QSharedPointer& fallback, + NetworkGeometry(const QUrl& url, const QSharedPointer& fallback, bool delayLoad, const QVariantHash& mapping = QVariantHash(), const QUrl& textureBase = QUrl()); ~NetworkGeometry(); /// Checks whether the geometry is fulled loaded. bool isLoaded() const { return !_geometry.joints.isEmpty(); } + /// Makes sure that the geometry has started loading. + void ensureLoading(); + /// Returns a pointer to the geometry appropriate for the specified distance. /// \param hysteresis a hysteresis parameter that prevents rapid model switching - QSharedPointer getLODOrFallback(float distance, float& hysteresis) const; + QSharedPointer getLODOrFallback(float distance, float& hysteresis, bool delayLoad = false) const; const FBXGeometry& getFBXGeometry() const { return _geometry; } const QVector& getMeshes() const { return _meshes; } diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 7fc16855e7..25c96c60c3 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -115,11 +115,11 @@ void Model::reset() { } } -void Model::simulate(float deltaTime) { +void Model::simulate(float deltaTime, bool delayLoad) { // update our LOD QVector newJointStates; if (_geometry) { - QSharedPointer geometry = _geometry->getLODOrFallback(_lodDistance, _lodHysteresis); + QSharedPointer geometry = _geometry->getLODOrFallback(_lodDistance, _lodHysteresis, delayLoad); if (_geometry != geometry) { if (!_jointStates.isEmpty()) { // copy the existing joint states @@ -138,6 +138,9 @@ void Model::simulate(float deltaTime) { _dilatedTextures.clear(); _geometry = geometry; } + if (!delayLoad) { + _geometry->ensureLoading(); + } } if (!isActive()) { return; @@ -443,7 +446,7 @@ float Model::getRightArmLength() const { return getLimbLength(getRightHandJointIndex()); } -void Model::setURL(const QUrl& url, const QUrl& fallback) { +void Model::setURL(const QUrl& url, const QUrl& fallback, bool delayLoad) { // don't recreate the geometry if it's the same URL if (_url == url) { return; @@ -456,7 +459,13 @@ void Model::setURL(const QUrl& url, const QUrl& fallback) { _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); + _baseGeometry = _geometry = Application::getInstance()->getGeometryCache()->getGeometry(url, fallback, delayLoad); +} + +void Model::ensureLoading() { + if (_geometry) { + _geometry->ensureLoading(); + } } glm::vec4 Model::computeAverageColor() const { diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index fd44361a1b..fa89840a82 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -50,12 +50,15 @@ public: void init(); void reset(); - void simulate(float deltaTime); + void simulate(float deltaTime, bool delayLoad = false); bool render(float alpha); - Q_INVOKABLE void setURL(const QUrl& url, const QUrl& fallback = QUrl()); + Q_INVOKABLE void setURL(const QUrl& url, const QUrl& fallback = QUrl(), bool delayLoad = false); const QUrl& getURL() const { return _url; } + /// Makes sure our configured model is loading. + void ensureLoading(); + /// Sets the distance parameter used for LOD computations. void setLODDistance(float distance) { _lodDistance = distance; }