Delay loading models until we know that we need them (i.e., that we're not

beyond billboard distance).  Closes #2121.
This commit is contained in:
Andrzej Kapolka 2014-02-27 12:30:46 -08:00
parent 89cf2f9a7a
commit cd2bd32210
12 changed files with 85 additions and 60 deletions

View file

@ -78,7 +78,7 @@ Avatar::Avatar() :
_owningAvatarMixer(), _owningAvatarMixer(),
_collisionFlags(0), _collisionFlags(0),
_initialized(false), _initialized(false),
_billboardHysteresis(false) _shouldRenderBillboard(true)
{ {
// we may have been created in the network thread, but we live in the main thread // we may have been created in the network thread, but we live in the main thread
moveToThread(Application::getInstance()->thread()); moveToThread(Application::getInstance()->thread());
@ -91,11 +91,14 @@ Avatar::Avatar() :
Avatar::~Avatar() { Avatar::~Avatar() {
} }
const float BILLBOARD_LOD_DISTANCE = 40.0f;
void Avatar::init() { void Avatar::init() {
getHead()->init(); getHead()->init();
getHand()->init(); getHand()->init();
_skeletonModel.init(); _skeletonModel.init();
_initialized = true; _initialized = true;
_shouldRenderBillboard = (getLODDistance() >= BILLBOARD_LOD_DISTANCE);
} }
glm::vec3 Avatar::getChestPosition() const { glm::vec3 Avatar::getChestPosition() const {
@ -117,20 +120,30 @@ void Avatar::simulate(float deltaTime) {
setScale(_targetScale); 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 // copy velocity so we can use it later for acceleration
glm::vec3 oldVelocity = getVelocity(); glm::vec3 oldVelocity = getVelocity();
getHand()->simulate(deltaTime, false); getHand()->simulate(deltaTime, false);
_skeletonModel.setLODDistance(getLODDistance()); _skeletonModel.setLODDistance(getLODDistance());
_skeletonModel.simulate(deltaTime); _skeletonModel.simulate(deltaTime, _shouldRenderBillboard);
Head* head = getHead();
glm::vec3 headPosition; glm::vec3 headPosition;
if (!_skeletonModel.getHeadPosition(headPosition)) { if (!_skeletonModel.getHeadPosition(headPosition)) {
headPosition = _position; headPosition = _position;
} }
Head* head = getHead();
head->setPosition(headPosition); head->setPosition(headPosition);
head->setScale(_scale); head->setScale(_scale);
getHead()->simulate(deltaTime, false); head->simulate(deltaTime, false, _shouldRenderBillboard);
// use speed and angular velocity to determine walking vs. standing // use speed and angular velocity to determine walking vs. standing
if (_speed + fabs(_bodyYawDelta) > 0.2) { if (_speed + fabs(_bodyYawDelta) > 0.2) {
@ -291,22 +304,10 @@ glm::quat Avatar::computeRotationFromBodyToWorldUp(float proportion) const {
return glm::angleAxis(angle * proportion, axis); return glm::angleAxis(angle * proportion, axis);
} }
const float BILLBOARD_LOD_DISTANCE = 40.0f;
void Avatar::renderBody() { void Avatar::renderBody() {
if (!_billboard.isEmpty()) { if (_shouldRenderBillboard) {
const float BILLBOARD_HYSTERESIS_PROPORTION = 0.1f; renderBillboard();
if (_billboardHysteresis) { return;
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;
}
} }
_skeletonModel.render(1.0f); _skeletonModel.render(1.0f);
getHead()->render(1.0f); getHead()->render(1.0f);
@ -314,9 +315,14 @@ void Avatar::renderBody() {
} }
void Avatar::renderBillboard() { void Avatar::renderBillboard() {
if (_billboard.isEmpty()) {
return;
}
if (!_billboardTexture) { 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()); _billboardTexture.reset(new Texture());
glBindTexture(GL_TEXTURE_2D, _billboardTexture->getID()); glBindTexture(GL_TEXTURE_2D, _billboardTexture->getID());
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width(), image.height(), 1, 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; return collided;
} }
void Avatar::setFaceModelURL(const QUrl &faceModelURL) { void Avatar::setFaceModelURL(const QUrl& faceModelURL) {
AvatarData::setFaceModelURL(faceModelURL); AvatarData::setFaceModelURL(faceModelURL);
const QUrl DEFAULT_FACE_MODEL_URL = QUrl::fromLocalFile("resources/meshes/defaultAvatar_head.fst"); 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); AvatarData::setSkeletonModelURL(skeletonModelURL);
const QUrl DEFAULT_SKELETON_MODEL_URL = QUrl::fromLocalFile("resources/meshes/defaultAvatar_body.fst"); 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) { void Avatar::setDisplayName(const QString& displayName) {
@ -577,9 +583,6 @@ void Avatar::setBillboard(const QByteArray& billboard) {
// clear out any existing billboard texture // clear out any existing billboard texture
_billboardTexture.reset(); _billboardTexture.reset();
// reset the hysteresis value
_billboardHysteresis = (getLODDistance() >= BILLBOARD_LOD_DISTANCE);
} }
int Avatar::parseData(const QByteArray& packet) { int Avatar::parseData(const QByteArray& packet) {

View file

@ -177,7 +177,7 @@ private:
bool _initialized; bool _initialized;
QScopedPointer<Texture> _billboardTexture; QScopedPointer<Texture> _billboardTexture;
bool _billboardHysteresis; bool _shouldRenderBillboard;
void renderBody(); void renderBody();
void renderBillboard(); void renderBillboard();

View file

@ -18,9 +18,9 @@ FaceModel::FaceModel(Head* owningHead) :
{ {
} }
void FaceModel::simulate(float deltaTime) { void FaceModel::simulate(float deltaTime, bool delayLoad) {
if (!isActive()) { if (!isActive()) {
Model::simulate(deltaTime); Model::simulate(deltaTime, delayLoad);
return; return;
} }
Avatar* owningAvatar = static_cast<Avatar*>(_owningHead->_owningAvatar); Avatar* owningAvatar = static_cast<Avatar*>(_owningHead->_owningAvatar);
@ -41,7 +41,7 @@ void FaceModel::simulate(float deltaTime) {
setPupilDilation(_owningHead->getPupilDilation()); setPupilDilation(_owningHead->getPupilDilation());
setBlendshapeCoefficients(_owningHead->getBlendshapeCoefficients()); setBlendshapeCoefficients(_owningHead->getBlendshapeCoefficients());
Model::simulate(deltaTime); Model::simulate(deltaTime, delayLoad);
} }
bool FaceModel::render(float alpha) { bool FaceModel::render(float alpha) {

View file

@ -21,7 +21,7 @@ public:
FaceModel(Head* owningHead); FaceModel(Head* owningHead);
void simulate(float deltaTime); void simulate(float deltaTime, bool delayLoad = false);
bool render(float alpha); bool render(float alpha);
protected: protected:

View file

@ -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 // Update audio trailing average for rendering facial animations
Faceshift* faceshift = Application::getInstance()->getFaceshift(); Faceshift* faceshift = Application::getInstance()->getFaceshift();
@ -161,7 +161,7 @@ void Head::simulate(float deltaTime, bool isMine) {
if (!isMine) { if (!isMine) {
_faceModel.setLODDistance(static_cast<Avatar*>(_owningAvatar)->getLODDistance()); _faceModel.setLODDistance(static_cast<Avatar*>(_owningAvatar)->getLODDistance());
} }
_faceModel.simulate(deltaTime); _faceModel.simulate(deltaTime, delayLoad);
// the blend face may have custom eye meshes // the blend face may have custom eye meshes
if (!_faceModel.getEyePositions(_leftEyePosition, _rightEyePosition)) { if (!_faceModel.getEyePositions(_leftEyePosition, _rightEyePosition)) {

View file

@ -36,7 +36,7 @@ public:
void init(); void init();
void reset(); void reset();
void simulate(float deltaTime, bool isMine); void simulate(float deltaTime, bool isMine, bool delayLoad = false);
void render(float alpha); void render(float alpha);
void setScale(float scale); void setScale(float scale);
void setPosition(glm::vec3 position) { _position = position; } void setPosition(glm::vec3 position) { _position = position; }

View file

@ -18,9 +18,9 @@ SkeletonModel::SkeletonModel(Avatar* owningAvatar) :
_owningAvatar(owningAvatar) { _owningAvatar(owningAvatar) {
} }
void SkeletonModel::simulate(float deltaTime) { void SkeletonModel::simulate(float deltaTime, bool delayLoad) {
if (!isActive()) { if (!isActive()) {
Model::simulate(deltaTime); Model::simulate(deltaTime, delayLoad);
return; return;
} }
setTranslation(_owningAvatar->getPosition()); setTranslation(_owningAvatar->getPosition());
@ -28,7 +28,7 @@ void SkeletonModel::simulate(float deltaTime) {
const float MODEL_SCALE = 0.0006f; const float MODEL_SCALE = 0.0006f;
setScale(glm::vec3(1.0f, 1.0f, 1.0f) * _owningAvatar->getScale() * MODEL_SCALE); 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 // find the left and rightmost active Leap palms
int leftPalmIndex, rightPalmIndex; int leftPalmIndex, rightPalmIndex;

View file

@ -22,7 +22,7 @@ public:
SkeletonModel(Avatar* owningAvatar); SkeletonModel(Avatar* owningAvatar);
void simulate(float deltaTime); void simulate(float deltaTime, bool delayLoad = false);
bool render(float alpha); bool render(float alpha);
protected: protected:

View file

@ -286,14 +286,14 @@ void GeometryCache::renderGrid(int xDivisions, int yDivisions) {
buffer.release(); buffer.release();
} }
QSharedPointer<NetworkGeometry> GeometryCache::getGeometry(const QUrl& url, const QUrl& fallback) { QSharedPointer<NetworkGeometry> GeometryCache::getGeometry(const QUrl& url, const QUrl& fallback, bool delayLoad) {
if (!url.isValid() && fallback.isValid()) { if (!url.isValid() && fallback.isValid()) {
return getGeometry(fallback); return getGeometry(fallback, QUrl(), delayLoad);
} }
QSharedPointer<NetworkGeometry> geometry = _networkGeometry.value(url); QSharedPointer<NetworkGeometry> geometry = _networkGeometry.value(url);
if (geometry.isNull()) { if (geometry.isNull()) {
geometry = QSharedPointer<NetworkGeometry>(new NetworkGeometry(url, fallback.isValid() ? geometry = QSharedPointer<NetworkGeometry>(new NetworkGeometry(url, fallback.isValid() ?
getGeometry(fallback) : QSharedPointer<NetworkGeometry>())); getGeometry(fallback, QUrl(), true) : QSharedPointer<NetworkGeometry>(), delayLoad));
geometry->setLODParent(geometry); geometry->setLODParent(geometry);
_networkGeometry.insert(url, geometry); _networkGeometry.insert(url, geometry);
} }
@ -302,7 +302,7 @@ QSharedPointer<NetworkGeometry> GeometryCache::getGeometry(const QUrl& url, cons
const float NetworkGeometry::NO_HYSTERESIS = -1.0f; const float NetworkGeometry::NO_HYSTERESIS = -1.0f;
NetworkGeometry::NetworkGeometry(const QUrl& url, const QSharedPointer<NetworkGeometry>& fallback, NetworkGeometry::NetworkGeometry(const QUrl& url, const QSharedPointer<NetworkGeometry>& fallback, bool delayLoad,
const QVariantHash& mapping, const QUrl& textureBase) : const QVariantHash& mapping, const QUrl& textureBase) :
_request(url), _request(url),
_reply(NULL), _reply(NULL),
@ -318,8 +318,8 @@ NetworkGeometry::NetworkGeometry(const QUrl& url, const QSharedPointer<NetworkGe
} }
_request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache); _request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
// if we already have a mapping (because we're an LOD), hold off on loading until we're requested // start loading immediately unless instructed otherwise
if (mapping.isEmpty()) { if (!delayLoad) {
makeRequest(); makeRequest();
} }
} }
@ -330,9 +330,15 @@ NetworkGeometry::~NetworkGeometry() {
} }
} }
QSharedPointer<NetworkGeometry> NetworkGeometry::getLODOrFallback(float distance, float& hysteresis) const { void NetworkGeometry::ensureLoading() {
if (!_startedLoading) {
makeRequest();
}
}
QSharedPointer<NetworkGeometry> NetworkGeometry::getLODOrFallback(float distance, float& hysteresis, bool delayLoad) const {
if (_lodParent.data() != this) { if (_lodParent.data() != this) {
return _lodParent.data()->getLODOrFallback(distance, hysteresis); return _lodParent.data()->getLODOrFallback(distance, hysteresis, delayLoad);
} }
if (_failedToLoad && _fallback) { if (_failedToLoad && _fallback) {
return _fallback; return _fallback;
@ -357,8 +363,8 @@ QSharedPointer<NetworkGeometry> NetworkGeometry::getLODOrFallback(float distance
return lod; 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 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) { if (!delayLoad) {
lod->makeRequest(); lod->ensureLoading();
} }
float closestDistance = FLT_MAX; float closestDistance = FLT_MAX;
if (isLoaded()) { if (isLoaded()) {
@ -438,7 +444,7 @@ void NetworkGeometry::handleDownloadProgress(qint64 bytesReceived, qint64 bytesT
QVariantHash lods = _mapping.value("lod").toHash(); QVariantHash lods = _mapping.value("lod").toHash();
for (QVariantHash::const_iterator it = lods.begin(); it != lods.end(); it++) { for (QVariantHash::const_iterator it = lods.begin(); it != lods.end(); it++) {
QSharedPointer<NetworkGeometry> geometry(new NetworkGeometry(url.resolved(it.key()), QSharedPointer<NetworkGeometry> geometry(new NetworkGeometry(url.resolved(it.key()),
QSharedPointer<NetworkGeometry>(), _mapping, _textureBase)); QSharedPointer<NetworkGeometry>(), true, _mapping, _textureBase));
geometry->setLODParent(_lodParent); geometry->setLODParent(_lodParent);
_lods.insert(it.value().toFloat(), geometry); _lods.insert(it.value().toFloat(), geometry);
} }

View file

@ -41,7 +41,8 @@ public:
/// Loads geometry from the specified URL. /// Loads geometry from the specified URL.
/// \param fallback a fallback URL to load if the desired one is unavailable /// \param fallback a fallback URL to load if the desired one is unavailable
QSharedPointer<NetworkGeometry> 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<NetworkGeometry> getGeometry(const QUrl& url, const QUrl& fallback = QUrl(), bool delayLoad = false);
private: private:
@ -65,16 +66,19 @@ public:
/// A hysteresis value indicating that we have no state memory. /// A hysteresis value indicating that we have no state memory.
static const float NO_HYSTERESIS; static const float NO_HYSTERESIS;
NetworkGeometry(const QUrl& url, const QSharedPointer<NetworkGeometry>& fallback, NetworkGeometry(const QUrl& url, const QSharedPointer<NetworkGeometry>& fallback, bool delayLoad,
const QVariantHash& mapping = QVariantHash(), const QUrl& textureBase = QUrl()); const QVariantHash& mapping = QVariantHash(), const QUrl& textureBase = QUrl());
~NetworkGeometry(); ~NetworkGeometry();
/// Checks whether the geometry is fulled loaded. /// Checks whether the geometry is fulled loaded.
bool isLoaded() const { return !_geometry.joints.isEmpty(); } 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. /// Returns a pointer to the geometry appropriate for the specified distance.
/// \param hysteresis a hysteresis parameter that prevents rapid model switching /// \param hysteresis a hysteresis parameter that prevents rapid model switching
QSharedPointer<NetworkGeometry> getLODOrFallback(float distance, float& hysteresis) const; QSharedPointer<NetworkGeometry> getLODOrFallback(float distance, float& hysteresis, bool delayLoad = false) const;
const FBXGeometry& getFBXGeometry() const { return _geometry; } const FBXGeometry& getFBXGeometry() const { return _geometry; }
const QVector<NetworkMesh>& getMeshes() const { return _meshes; } const QVector<NetworkMesh>& getMeshes() const { return _meshes; }

View file

@ -115,11 +115,11 @@ void Model::reset() {
} }
} }
void Model::simulate(float deltaTime) { void Model::simulate(float deltaTime, bool delayLoad) {
// update our LOD // update our LOD
QVector<JointState> newJointStates; QVector<JointState> newJointStates;
if (_geometry) { if (_geometry) {
QSharedPointer<NetworkGeometry> geometry = _geometry->getLODOrFallback(_lodDistance, _lodHysteresis); QSharedPointer<NetworkGeometry> geometry = _geometry->getLODOrFallback(_lodDistance, _lodHysteresis, delayLoad);
if (_geometry != geometry) { if (_geometry != geometry) {
if (!_jointStates.isEmpty()) { if (!_jointStates.isEmpty()) {
// copy the existing joint states // copy the existing joint states
@ -138,6 +138,9 @@ void Model::simulate(float deltaTime) {
_dilatedTextures.clear(); _dilatedTextures.clear();
_geometry = geometry; _geometry = geometry;
} }
if (!delayLoad) {
_geometry->ensureLoading();
}
} }
if (!isActive()) { if (!isActive()) {
return; return;
@ -443,7 +446,7 @@ float Model::getRightArmLength() const {
return getLimbLength(getRightHandJointIndex()); 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 // don't recreate the geometry if it's the same URL
if (_url == url) { if (_url == url) {
return; return;
@ -456,7 +459,13 @@ void Model::setURL(const QUrl& url, const QUrl& fallback) {
_lodHysteresis = NetworkGeometry::NO_HYSTERESIS; _lodHysteresis = NetworkGeometry::NO_HYSTERESIS;
// we retain a reference to the base geometry so that its reference count doesn't fall to zero // 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 { glm::vec4 Model::computeAverageColor() const {

View file

@ -50,12 +50,15 @@ public:
void init(); void init();
void reset(); void reset();
void simulate(float deltaTime); void simulate(float deltaTime, bool delayLoad = false);
bool render(float alpha); 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; } const QUrl& getURL() const { return _url; }
/// Makes sure our configured model is loading.
void ensureLoading();
/// Sets the distance parameter used for LOD computations. /// Sets the distance parameter used for LOD computations.
void setLODDistance(float distance) { _lodDistance = distance; } void setLODDistance(float distance) { _lodDistance = distance; }