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(),
_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) {

View file

@ -177,7 +177,7 @@ private:
bool _initialized;
QScopedPointer<Texture> _billboardTexture;
bool _billboardHysteresis;
bool _shouldRenderBillboard;
void renderBody();
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()) {
Model::simulate(deltaTime);
Model::simulate(deltaTime, delayLoad);
return;
}
Avatar* owningAvatar = static_cast<Avatar*>(_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) {

View file

@ -21,7 +21,7 @@ public:
FaceModel(Head* owningHead);
void simulate(float deltaTime);
void simulate(float deltaTime, bool delayLoad = false);
bool render(float alpha);
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
Faceshift* faceshift = Application::getInstance()->getFaceshift();
@ -161,7 +161,7 @@ void Head::simulate(float deltaTime, bool isMine) {
if (!isMine) {
_faceModel.setLODDistance(static_cast<Avatar*>(_owningAvatar)->getLODDistance());
}
_faceModel.simulate(deltaTime);
_faceModel.simulate(deltaTime, delayLoad);
// the blend face may have custom eye meshes
if (!_faceModel.getEyePositions(_leftEyePosition, _rightEyePosition)) {

View file

@ -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; }

View file

@ -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;

View file

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

View file

@ -286,14 +286,14 @@ void GeometryCache::renderGrid(int xDivisions, int yDivisions) {
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()) {
return getGeometry(fallback);
return getGeometry(fallback, QUrl(), delayLoad);
}
QSharedPointer<NetworkGeometry> geometry = _networkGeometry.value(url);
if (geometry.isNull()) {
geometry = QSharedPointer<NetworkGeometry>(new NetworkGeometry(url, fallback.isValid() ?
getGeometry(fallback) : QSharedPointer<NetworkGeometry>()));
getGeometry(fallback, QUrl(), true) : QSharedPointer<NetworkGeometry>(), delayLoad));
geometry->setLODParent(geometry);
_networkGeometry.insert(url, geometry);
}
@ -302,7 +302,7 @@ QSharedPointer<NetworkGeometry> GeometryCache::getGeometry(const QUrl& url, cons
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) :
_request(url),
_reply(NULL),
@ -318,8 +318,8 @@ NetworkGeometry::NetworkGeometry(const QUrl& url, const QSharedPointer<NetworkGe
}
_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
if (mapping.isEmpty()) {
// start loading immediately unless instructed otherwise
if (!delayLoad) {
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) {
return _lodParent.data()->getLODOrFallback(distance, hysteresis);
return _lodParent.data()->getLODOrFallback(distance, hysteresis, delayLoad);
}
if (_failedToLoad && _fallback) {
return _fallback;
@ -357,8 +363,8 @@ QSharedPointer<NetworkGeometry> 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<NetworkGeometry> geometry(new NetworkGeometry(url.resolved(it.key()),
QSharedPointer<NetworkGeometry>(), _mapping, _textureBase));
QSharedPointer<NetworkGeometry>(), true, _mapping, _textureBase));
geometry->setLODParent(_lodParent);
_lods.insert(it.value().toFloat(), geometry);
}

View file

@ -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<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:
@ -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<NetworkGeometry>& fallback,
NetworkGeometry(const QUrl& url, const QSharedPointer<NetworkGeometry>& 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<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 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
QVector<JointState> newJointStates;
if (_geometry) {
QSharedPointer<NetworkGeometry> geometry = _geometry->getLODOrFallback(_lodDistance, _lodHysteresis);
QSharedPointer<NetworkGeometry> 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 {

View file

@ -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; }