Merge pull request #5622 from hyperlogic/ajt/network-geometry-refactor

ResourceCache, NetworkGeometry and Model refactoring and optimizations.
This commit is contained in:
Brad Davis 2015-08-20 23:57:16 -07:00
commit c3991dbb8c
20 changed files with 682 additions and 833 deletions

View file

@ -106,16 +106,17 @@ bool ModelPackager::loadModel() {
}
qCDebug(interfaceapp) << "Reading FBX file : " << _fbxInfo.filePath();
QByteArray fbxContents = fbx.readAll();
_geometry = readFBX(fbxContents, QVariantHash(), _fbxInfo.filePath());
_geometry.reset(readFBX(fbxContents, QVariantHash(), _fbxInfo.filePath()));
// make sure we have some basic mappings
populateBasicMapping(_mapping, _fbxInfo.filePath(), _geometry);
populateBasicMapping(_mapping, _fbxInfo.filePath(), *_geometry);
return true;
}
bool ModelPackager::editProperties() {
// open the dialog to configure the rest
ModelPropertiesDialog properties(_modelType, _mapping, _modelFile.path(), _geometry);
ModelPropertiesDialog properties(_modelType, _mapping, _modelFile.path(), *_geometry);
if (properties.exec() == QDialog::Rejected) {
return false;
}
@ -339,7 +340,7 @@ void ModelPackager::populateBasicMapping(QVariantHash& mapping, QString filename
void ModelPackager::listTextures() {
_textures.clear();
foreach (FBXMesh mesh, _geometry.meshes) {
foreach (FBXMesh mesh, _geometry->meshes) {
foreach (FBXMeshPart part, mesh.parts) {
if (!part.diffuseTexture.filename.isEmpty() && part.diffuseTexture.content.isEmpty() &&
!_textures.contains(part.diffuseTexture.filename)) {

View file

@ -39,11 +39,11 @@ private:
QString _texDir;
QVariantHash _mapping;
FBXGeometry _geometry;
std::unique_ptr<FBXGeometry> _geometry;
QStringList _textures;
};
#endif // hifi_ModelPackager_h
#endif // hifi_ModelPackager_h

View file

@ -196,7 +196,6 @@ void Avatar::simulate(float deltaTime) {
PerformanceTimer perfTimer("hand");
getHand()->simulate(deltaTime, false);
}
_skeletonModel.setLODDistance(getLODDistance());
if (!_shouldRenderBillboard && inViewFrustum) {
{
@ -562,24 +561,22 @@ glm::quat Avatar::computeRotationFromBodyToWorldUp(float proportion) const {
}
void Avatar::fixupModelsInScene() {
if (!(_skeletonModel.isRenderable() && getHead()->getFaceModel().isRenderable())) {
return;
}
// check to see if when we added our models to the scene they were ready, if they were not ready, then
// fix them up in the scene
render::ScenePointer scene = Application::getInstance()->getMain3DScene();
render::PendingChanges pendingChanges;
if (_skeletonModel.needsFixupInScene()) {
if (_skeletonModel.isRenderable() && _skeletonModel.needsFixupInScene()) {
_skeletonModel.removeFromScene(scene, pendingChanges);
_skeletonModel.addToScene(scene, pendingChanges);
}
if (getHead()->getFaceModel().needsFixupInScene()) {
getHead()->getFaceModel().removeFromScene(scene, pendingChanges);
getHead()->getFaceModel().addToScene(scene, pendingChanges);
Model& faceModel = getHead()->getFaceModel();
if (faceModel.isRenderable() && faceModel.needsFixupInScene()) {
faceModel.removeFromScene(scene, pendingChanges);
faceModel.addToScene(scene, pendingChanges);
}
for (auto attachmentModel : _attachmentModels) {
if (attachmentModel->needsFixupInScene()) {
if (attachmentModel->isRenderable() && attachmentModel->needsFixupInScene()) {
attachmentModel->removeFromScene(scene, pendingChanges);
attachmentModel->addToScene(scene, pendingChanges);
}
@ -621,11 +618,8 @@ void Avatar::simulateAttachments(float deltaTime) {
int jointIndex = getJointIndex(attachment.jointName);
glm::vec3 jointPosition;
glm::quat jointRotation;
if (!isMyAvatar()) {
model->setLODDistance(getLODDistance());
}
if (_skeletonModel.getJointPositionInWorldFrame(jointIndex, jointPosition) &&
_skeletonModel.getJointCombinedRotation(jointIndex, jointRotation)) {
_skeletonModel.getJointCombinedRotation(jointIndex, jointRotation)) {
model->setTranslation(jointPosition + jointRotation * attachment.translation * _scale);
model->setRotation(jointRotation * attachment.rotation);
model->setScaleToFit(true, _scale * attachment.scale, true); // hack to force rescale
@ -978,12 +972,12 @@ void Avatar::scaleVectorRelativeToPosition(glm::vec3 &positionToScale) const {
void Avatar::setFaceModelURL(const QUrl& faceModelURL) {
AvatarData::setFaceModelURL(faceModelURL);
getHead()->getFaceModel().setURL(_faceModelURL, AvatarData::defaultFullAvatarModelUrl(), true, !isMyAvatar());
getHead()->getFaceModel().setURL(_faceModelURL);
}
void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
AvatarData::setSkeletonModelURL(skeletonModelURL);
_skeletonModel.setURL(_skeletonModelURL, AvatarData::defaultFullAvatarModelUrl(), true, !isMyAvatar());
_skeletonModel.setURL(_skeletonModelURL);
}
void Avatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) {

View file

@ -233,9 +233,6 @@ void Head::simulate(float deltaTime, bool isMine, bool billboard) {
_saccade = glm::vec3();
}
if (!isMine) {
_faceModel.setLODDistance(static_cast<Avatar*>(_owningAvatar)->getLODDistance());
}
_leftEyePosition = _rightEyePosition = getPosition();
if (!billboard) {
_faceModel.simulate(deltaTime);

View file

@ -13,6 +13,7 @@
#include <QThreadPool>
#include "AnimationCache.h"
#include "AnimationLogging.h"
static int animationPointerMetaTypeId = qRegisterMetaType<AnimationPointer>();
@ -62,11 +63,15 @@ void AnimationReader::run() {
QSharedPointer<Resource> animation = _animation.toStrongRef();
if (!animation.isNull()) {
QMetaObject::invokeMethod(animation.data(), "setGeometry",
Q_ARG(const FBXGeometry&, readFBX(_reply->readAll(), QVariantHash(), _reply->property("url").toString())));
Q_ARG(FBXGeometry*, readFBX(_reply->readAll(), QVariantHash(), _reply->property("url").toString())));
}
_reply->deleteLater();
}
bool Animation::isLoaded() const {
return _loaded && _geometry;
}
QStringList Animation::getJointNames() const {
if (QThread::currentThread() != thread()) {
QStringList result;
@ -75,7 +80,7 @@ QStringList Animation::getJointNames() const {
return result;
}
QStringList names;
foreach (const FBXJoint& joint, _geometry.joints) {
foreach (const FBXJoint& joint, _geometry->joints) {
names.append(joint.name);
}
return names;
@ -88,15 +93,15 @@ QVector<FBXAnimationFrame> Animation::getFrames() const {
Q_RETURN_ARG(QVector<FBXAnimationFrame>, result));
return result;
}
return _geometry.animationFrames;
return _geometry->animationFrames;
}
const QVector<FBXAnimationFrame>& Animation::getFramesReference() const {
return _geometry.animationFrames;
return _geometry->animationFrames;
}
void Animation::setGeometry(const FBXGeometry& geometry) {
_geometry = geometry;
void Animation::setGeometry(FBXGeometry* geometry) {
_geometry.reset(geometry);
finishedLoading(true);
}

View file

@ -52,7 +52,10 @@ public:
Animation(const QUrl& url);
const FBXGeometry& getGeometry() const { return _geometry; }
const FBXGeometry& getGeometry() const { return *_geometry; }
virtual bool isLoaded() const override;
Q_INVOKABLE QStringList getJointNames() const;
@ -62,13 +65,13 @@ public:
protected:
Q_INVOKABLE void setGeometry(const FBXGeometry& geometry);
Q_INVOKABLE void setGeometry(FBXGeometry* geometry);
virtual void downloadFinished(QNetworkReply* reply);
private:
FBXGeometry _geometry;
std::unique_ptr<FBXGeometry> _geometry;
};

View file

@ -38,7 +38,7 @@ void RenderableZoneEntityItem::changeProperties(Lambda setNewProperties) {
_model = getModel();
_needsInitialSimulation = true;
_model->setURL(getCompoundShapeURL(), QUrl(), true, true);
_model->setURL(getCompoundShapeURL());
}
if (oldPosition != getPosition() ||
oldRotation != getRotation() ||
@ -85,7 +85,7 @@ void RenderableZoneEntityItem::initialSimulation() {
void RenderableZoneEntityItem::updateGeometry() {
if (_model && !_model->isActive() && hasCompoundShapeURL()) {
// Since we have a delayload, we need to update the geometry if it has been downloaded
_model->setURL(getCompoundShapeURL(), QUrl(), true);
_model->setURL(getCompoundShapeURL());
}
if (_model && _model->isActive() && _needsInitialSimulation) {
initialSimulation();

View file

@ -1373,12 +1373,12 @@ FBXLight extractLight(const FBXNode& object) {
#if USE_MODEL_MESH
void buildModelMesh(ExtractedMesh& extracted) {
void buildModelMesh(ExtractedMesh& extracted, const QString& url) {
static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex("buildModelMesh failed -- .*");
if (extracted.mesh.vertices.size() == 0) {
extracted.mesh._mesh = model::Mesh();
qCDebug(modelformat) << "buildModelMesh failed -- no vertices";
qCDebug(modelformat) << "buildModelMesh failed -- no vertices, url = " << url;
return;
}
FBXMesh& fbxMesh = extracted.mesh;
@ -1465,7 +1465,7 @@ void buildModelMesh(ExtractedMesh& extracted) {
if (! totalIndices) {
extracted.mesh._mesh = model::Mesh();
qCDebug(modelformat) << "buildModelMesh failed -- no indices";
qCDebug(modelformat) << "buildModelMesh failed -- no indices, url = " << url;
return;
}
@ -1505,7 +1505,7 @@ void buildModelMesh(ExtractedMesh& extracted) {
mesh.setPartBuffer(pbv);
} else {
extracted.mesh._mesh = model::Mesh();
qCDebug(modelformat) << "buildModelMesh failed -- no parts";
qCDebug(modelformat) << "buildModelMesh failed -- no parts, url = " << url;
return;
}
@ -1530,7 +1530,7 @@ QByteArray fileOnUrl(const QByteArray& filenameString, const QString& url) {
return filename;
}
FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) {
FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) {
QHash<QString, ExtractedMesh> meshes;
QHash<QString, QString> modelIDsToNames;
QHash<QString, int> meshIDsToMeshIndices;
@ -1615,7 +1615,9 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping,
#if defined(DEBUG_FBXREADER)
int unknown = 0;
#endif
FBXGeometry geometry;
FBXGeometry* geometryPtr = new FBXGeometry;
FBXGeometry& geometry = *geometryPtr;
float unitScaleFactor = 1.0f;
glm::vec3 ambientColor;
QString hifiGlobalNodeID;
@ -2680,7 +2682,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping,
extracted.mesh.isEye = (maxJointIndex == geometry.leftEyeJointIndex || maxJointIndex == geometry.rightEyeJointIndex);
# if USE_MODEL_MESH
buildModelMesh(extracted);
buildModelMesh(extracted, url);
# endif
if (extracted.mesh.isEye) {
@ -2761,15 +2763,15 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping,
}
}
return geometry;
return geometryPtr;
}
FBXGeometry readFBX(const QByteArray& model, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) {
FBXGeometry* readFBX(const QByteArray& model, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) {
QBuffer buffer(const_cast<QByteArray*>(&model));
buffer.open(QIODevice::ReadOnly);
return readFBX(&buffer, mapping, url, loadLightmaps, lightmapLevel);
}
FBXGeometry readFBX(QIODevice* device, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) {
FBXGeometry* readFBX(QIODevice* device, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) {
return extractFBXGeometry(parseFBX(device), mapping, url, loadLightmaps, lightmapLevel);
}

View file

@ -272,10 +272,10 @@ Q_DECLARE_METATYPE(FBXGeometry)
/// Reads FBX geometry from the supplied model and mapping data.
/// \exception QString if an error occurs in parsing
FBXGeometry readFBX(const QByteArray& model, const QVariantHash& mapping, const QString& url = "", bool loadLightmaps = true, float lightmapLevel = 1.0f);
FBXGeometry* readFBX(const QByteArray& model, const QVariantHash& mapping, const QString& url = "", bool loadLightmaps = true, float lightmapLevel = 1.0f);
/// Reads FBX geometry from the supplied model and mapping data.
/// \exception QString if an error occurs in parsing
FBXGeometry readFBX(QIODevice* device, const QVariantHash& mapping, const QString& url = "", bool loadLightmaps = true, float lightmapLevel = 1.0f);
FBXGeometry* readFBX(QIODevice* device, const QVariantHash& mapping, const QString& url = "", bool loadLightmaps = true, float lightmapLevel = 1.0f);
#endif // hifi_FBXReader_h

View file

@ -399,15 +399,16 @@ done:
}
FBXGeometry OBJReader::readOBJ(const QByteArray& model, const QVariantHash& mapping) {
FBXGeometry* OBJReader::readOBJ(const QByteArray& model, const QVariantHash& mapping) {
QBuffer buffer(const_cast<QByteArray*>(&model));
buffer.open(QIODevice::ReadOnly);
return readOBJ(&buffer, mapping, nullptr);
}
FBXGeometry OBJReader::readOBJ(QIODevice* device, const QVariantHash& mapping, QUrl* url) {
FBXGeometry geometry;
FBXGeometry* OBJReader::readOBJ(QIODevice* device, const QVariantHash& mapping, QUrl* url) {
FBXGeometry* geometryPtr = new FBXGeometry();
FBXGeometry& geometry = *geometryPtr;
OBJTokenizer tokenizer(device);
float scaleGuess = 1.0f;
@ -545,7 +546,7 @@ FBXGeometry OBJReader::readOBJ(QIODevice* device, const QVariantHash& mapping, Q
qCDebug(modelformat) << "OBJ reader fail: " << e.what();
}
return geometry;
return geometryPtr;
}

View file

@ -71,8 +71,8 @@ public:
QHash<QString, OBJMaterial> materials;
QNetworkReply* request(QUrl& url, bool isTest);
FBXGeometry readOBJ(const QByteArray& model, const QVariantHash& mapping);
FBXGeometry readOBJ(QIODevice* device, const QVariantHash& mapping, QUrl* url);
FBXGeometry* readOBJ(const QByteArray& model, const QVariantHash& mapping);
FBXGeometry* readOBJ(QIODevice* device, const QVariantHash& mapping, QUrl* url);
private:
QUrl* _url = nullptr;

View file

@ -320,7 +320,6 @@ void Resource::attemptRequest() {
void Resource::finishedLoading(bool success) {
if (success) {
_loaded = true;
emit loaded();
} else {
_failedToLoad = true;
}
@ -333,91 +332,26 @@ void Resource::reinsert() {
static const int REPLY_TIMEOUT_MS = 5000;
void Resource::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
if (!_reply->isFinished()) {
_bytesReceived = bytesReceived;
_bytesTotal = bytesTotal;
_replyTimer->start(REPLY_TIMEOUT_MS);
return;
}
_reply->disconnect(this);
_replyTimer->disconnect(this);
QNetworkReply* reply = _reply;
_reply = nullptr;
_replyTimer->deleteLater();
_replyTimer = nullptr;
ResourceCache::requestCompleted(this);
downloadFinished(reply);
_bytesReceived = bytesReceived;
_bytesTotal = bytesTotal;
_replyTimer->start(REPLY_TIMEOUT_MS);
}
void Resource::handleReplyError() {
handleReplyError(_reply->error(), qDebug() << _reply->errorString());
handleReplyErrorInternal(_reply->error());
}
void Resource::handleReplyTimeout() {
handleReplyError(QNetworkReply::TimeoutError, qDebug() << "Timed out loading" << _reply->url() <<
"received" << _bytesReceived << "total" << _bytesTotal);
}
void Resource::maybeRefresh() {
if (Q_LIKELY(NetworkAccessManager::getInstance().cache())) {
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
QVariant variant = reply->header(QNetworkRequest::LastModifiedHeader);
QNetworkCacheMetaData metaData = NetworkAccessManager::getInstance().cache()->metaData(_url);
if (variant.isValid() && variant.canConvert<QDateTime>() && metaData.isValid()) {
QDateTime lastModified = variant.value<QDateTime>();
QDateTime lastModifiedOld = metaData.lastModified();
if (lastModified.isValid() && lastModifiedOld.isValid() &&
lastModifiedOld >= lastModified) { // With >=, cache won't thrash in eventually-consistent cdn.
qCDebug(networking) << "Using cached version of" << _url.fileName();
// We don't need to update, return
return;
}
} else if (!variant.isValid() || !variant.canConvert<QDateTime>() ||
!variant.value<QDateTime>().isValid() || variant.value<QDateTime>().isNull()) {
qCDebug(networking) << "Cannot determine when" << _url.fileName() << "was modified last, cached version might be outdated";
return;
}
qCDebug(networking) << "Loaded" << _url.fileName() << "from the disk cache but the network version is newer, refreshing.";
refresh();
}
handleReplyErrorInternal(QNetworkReply::TimeoutError);
}
void Resource::makeRequest() {
_reply = NetworkAccessManager::getInstance().get(_request);
connect(_reply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(handleDownloadProgress(qint64,qint64)));
connect(_reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleReplyError()));
connect(_reply, SIGNAL(finished()), SLOT(handleReplyFinished()));
if (_reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool()) {
// If the file as been updated since it was cached, refresh it
QNetworkRequest request(_request);
request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork);
request.setAttribute(QNetworkRequest::CacheSaveControlAttribute, false);
QNetworkReply* reply = NetworkAccessManager::getInstance().head(request);
connect(reply, &QNetworkReply::finished, this, &Resource::maybeRefresh);
} else {
if (Q_LIKELY(NetworkAccessManager::getInstance().cache())) {
QNetworkCacheMetaData metaData = NetworkAccessManager::getInstance().cache()->metaData(_url);
bool needUpdate = false;
if (metaData.expirationDate().isNull() || metaData.expirationDate() <= QDateTime::currentDateTime()) {
// If the expiration date is NULL or in the past,
// put one far enough away that it won't be an issue.
metaData.setExpirationDate(QDateTime::currentDateTime().addYears(100));
needUpdate = true;
}
if (metaData.lastModified().isNull()) {
// If the lastModified date is NULL, set it to now.
metaData.setLastModified(QDateTime::currentDateTime());
needUpdate = true;
}
if (needUpdate) {
NetworkAccessManager::getInstance().cache()->updateMetaData(metaData);
}
}
}
_replyTimer = new QTimer(this);
connect(_replyTimer, SIGNAL(timeout()), SLOT(handleReplyTimeout()));
_replyTimer->setSingleShot(true);
@ -425,7 +359,8 @@ void Resource::makeRequest() {
_bytesReceived = _bytesTotal = 0;
}
void Resource::handleReplyError(QNetworkReply::NetworkError error, QDebug debug) {
void Resource::handleReplyErrorInternal(QNetworkReply::NetworkError error) {
_reply->disconnect(this);
_replyTimer->disconnect(this);
_reply->deleteLater();
@ -433,7 +368,7 @@ void Resource::handleReplyError(QNetworkReply::NetworkError error, QDebug debug)
_replyTimer->deleteLater();
_replyTimer = nullptr;
ResourceCache::requestCompleted(this);
// retry for certain types of failures
switch (error) {
case QNetworkReply::RemoteHostClosedError:
@ -444,26 +379,46 @@ void Resource::handleReplyError(QNetworkReply::NetworkError error, QDebug debug)
case QNetworkReply::UnknownNetworkError:
case QNetworkReply::UnknownProxyError:
case QNetworkReply::UnknownContentError:
case QNetworkReply::ProtocolFailure: {
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(attemptRequest()));
debug << "-- retrying...";
qCWarning(networking) << "error downloading url =" << _url.toDisplayString() << ", error =" << error << ", retrying (" << _attempts << "/" << MAX_ATTEMPTS << ")";
return;
}
// fall through to final failure
}
}
default:
qCCritical(networking) << "error downloading, url =" << _url.toDisplayString() << ", error =" << error;
emit failed(error);
finishedLoading(false);
break;
}
}
void Resource::handleReplyFinished() {
qCDebug(networking) << "Got finished without download progress/error?" << _url;
handleDownloadProgress(0, 0);
bool fromCache = _reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool();
qCDebug(networking) << "success downloading url =" << _url.toDisplayString() << (fromCache ? "from cache" : "");
_reply->disconnect(this);
_replyTimer->disconnect(this);
QNetworkReply* reply = _reply;
_reply = nullptr;
_replyTimer->deleteLater();
_replyTimer = nullptr;
ResourceCache::requestCompleted(this);
finishedLoading(true);
emit loaded(*reply);
downloadFinished(reply);
}
void Resource::downloadFinished(QNetworkReply* reply) {
;
}
uint qHash(const QPointer<QObject>& value, uint seed) {

View file

@ -150,7 +150,7 @@ public:
float getLoadPriority();
/// Checks whether the resource has loaded.
bool isLoaded() const { return _loaded; }
virtual bool isLoaded() const { return _loaded; }
/// For loading resources, returns the number of bytes received.
qint64 getBytesReceived() const { return _bytesReceived; }
@ -174,21 +174,22 @@ public:
signals:
/// Fired when the resource has been loaded.
void loaded();
void loaded(QNetworkReply& request);
/// Fired when resource failed to load.
void failed(QNetworkReply::NetworkError error);
/// Fired when resource is refreshed.
void onRefresh();
protected slots:
void attemptRequest();
/// Refreshes the resource if the last modified date on the network
/// is greater than the last modified date in the cache.
void maybeRefresh();
protected:
virtual void init();
/// Called when the download has finished. The recipient should delete the reply when done with it.
virtual void downloadFinished(QNetworkReply* reply) = 0;
virtual void downloadFinished(QNetworkReply* reply);
/// Should be called by subclasses when all the loading that will be done has been done.
Q_INVOKABLE void finishedLoading(bool success);
@ -216,7 +217,7 @@ private:
void makeRequest();
void handleReplyError(QNetworkReply::NetworkError error, QDebug debug);
void handleReplyErrorInternal(QNetworkReply::NetworkError error);
friend class ResourceCache;

View file

@ -14,7 +14,6 @@
#include <cmath>
#include <QNetworkReply>
#include <QRunnable>
#include <QThreadPool>
#include <FSTReader.h>
@ -50,6 +49,13 @@ GeometryCache::~GeometryCache() {
#endif //def WANT_DEBUG
}
QSharedPointer<Resource> GeometryCache::createResource(const QUrl& url, const QSharedPointer<Resource>& fallback,
bool delayLoad, const void* extra) {
// NetworkGeometry is no longer a subclass of Resource, but requires this method because, it is pure virtual.
assert(false);
return QSharedPointer<Resource>();
}
const int NUM_VERTICES_PER_TRIANGLE = 3;
const int NUM_TRIANGLES_PER_QUAD = 2;
const int NUM_VERTICES_PER_TRIANGULATED_QUAD = NUM_VERTICES_PER_TRIANGLE * NUM_TRIANGLES_PER_QUAD;
@ -1643,19 +1649,6 @@ void GeometryCache::renderLine(gpu::Batch& batch, const glm::vec2& p1, const glm
batch.draw(gpu::LINES, 2, 0);
}
QSharedPointer<NetworkGeometry> GeometryCache::getGeometry(const QUrl& url, const QUrl& fallback, bool delayLoad) {
return getResource(url, fallback, delayLoad, NULL).staticCast<NetworkGeometry>();
}
QSharedPointer<Resource> GeometryCache::createResource(const QUrl& url, const QSharedPointer<Resource>& fallback,
bool delayLoad, const void* extra) {
QSharedPointer<NetworkGeometry> geometry(new NetworkGeometry(url, fallback.staticCast<NetworkGeometry>(), delayLoad),
&Resource::allReferencesCleared);
geometry->setLODParent(geometry);
return geometry.staticCast<Resource>();
}
void GeometryCache::useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend) {
if (!_standardDrawPipeline) {
auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(standardTransformPNTC_vert)));
@ -1685,33 +1678,82 @@ void GeometryCache::useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend) {
}
}
const float NetworkGeometry::NO_HYSTERESIS = -1.0f;
GeometryReader::GeometryReader(const QUrl& url, QNetworkReply* reply, const QVariantHash& mapping) :
_url(url),
_reply(reply),
_mapping(mapping) {
}
NetworkGeometry::NetworkGeometry(const QUrl& url, const QSharedPointer<NetworkGeometry>& fallback, bool delayLoad,
const QVariantHash& mapping, const QUrl& textureBase) :
Resource(url, delayLoad),
_mapping(mapping),
_textureBase(textureBase.isValid() ? textureBase : url),
_fallback(fallback)
{
if (url.isEmpty()) {
// make the minimal amount of dummy geometry to satisfy Model
FBXJoint joint = { false, QVector<int>(), -1, 0.0f, 0.0f, glm::vec3(), glm::mat4(), glm::quat(), glm::quat(),
glm::quat(), glm::mat4(), glm::mat4(), glm::vec3(), glm::vec3(), glm::quat(), glm::quat(),
glm::mat4(), QString(""), false};
_geometry.joints.append(joint);
_geometry.leftEyeJointIndex = -1;
_geometry.rightEyeJointIndex = -1;
_geometry.neckJointIndex = -1;
_geometry.rootJointIndex = -1;
_geometry.leanJointIndex = -1;
_geometry.headJointIndex = -1;
_geometry.leftHandJointIndex = -1;
_geometry.rightHandJointIndex = -1;
void GeometryReader::run() {
try {
if (!_reply) {
throw QString("Reply is NULL ?!");
}
QString urlname = _url.path().toLower();
bool urlValid = true;
urlValid &= !urlname.isEmpty();
urlValid &= !_url.path().isEmpty();
urlValid &= _url.path().toLower().endsWith(".fbx") || _url.path().toLower().endsWith(".obj");
if (urlValid) {
// Let's read the binaries from the network
FBXGeometry* fbxgeo = nullptr;
if (_url.path().toLower().endsWith(".fbx")) {
const bool grabLightmaps = true;
const float lightmapLevel = 1.0f;
fbxgeo = readFBX(_reply, _mapping, _url.path(), grabLightmaps, lightmapLevel);
} else if (_url.path().toLower().endsWith(".obj")) {
fbxgeo = OBJReader().readOBJ(_reply, _mapping, &_url);
} else {
QString errorStr("usupported format");
emit onError(NetworkGeometry::ModelParseError, errorStr);
}
emit onSuccess(fbxgeo);
} else {
throw QString("url is invalid");
}
} catch (const QString& error) {
qCDebug(renderutils) << "Error reading " << _url << ": " << error;
emit onError(NetworkGeometry::ModelParseError, error);
}
connect(this, &Resource::loaded, this, &NetworkGeometry::replaceTexturesWithPendingChanges);
_reply->deleteLater();
}
NetworkGeometry::NetworkGeometry(const QUrl& url, bool delayLoad, const QVariantHash& mapping, const QUrl& textureBaseUrl) :
_url(url),
_mapping(mapping),
_textureBaseUrl(textureBaseUrl) {
if (delayLoad) {
_state = DelayState;
} else {
attemptRequestInternal();
}
}
NetworkGeometry::~NetworkGeometry() {
if (_resource) {
_resource->deleteLater();
}
}
void NetworkGeometry::attemptRequest() {
if (_state == DelayState) {
attemptRequestInternal();
}
}
void NetworkGeometry::attemptRequestInternal() {
if (_url.path().toLower().endsWith(".fst")) {
requestMapping(_url);
} else {
requestModel(_url);
}
}
bool NetworkGeometry::isLoaded() const {
return _state == SuccessState;
}
bool NetworkGeometry::isLoadedWithTextures() const {
@ -1719,12 +1761,12 @@ bool NetworkGeometry::isLoadedWithTextures() const {
return false;
}
if (!_isLoadedWithTextures) {
foreach (const NetworkMesh& mesh, _meshes) {
foreach (const NetworkMeshPart& part, mesh.parts) {
if ((part.diffuseTexture && !part.diffuseTexture->isLoaded()) ||
(part.normalTexture && !part.normalTexture->isLoaded()) ||
(part.specularTexture && !part.specularTexture->isLoaded()) ||
(part.emissiveTexture && !part.emissiveTexture->isLoaded())) {
for (auto&& mesh : _meshes) {
for (auto && part : mesh->_parts) {
if ((part->diffuseTexture && !part->diffuseTexture->isLoaded()) ||
(part->normalTexture && !part->normalTexture->isLoaded()) ||
(part->specularTexture && !part->specularTexture->isLoaded()) ||
(part->emissiveTexture && !part->emissiveTexture->isLoaded())) {
return false;
}
}
@ -1734,183 +1776,38 @@ bool NetworkGeometry::isLoadedWithTextures() const {
return true;
}
QSharedPointer<NetworkGeometry> NetworkGeometry::getLODOrFallback(float distance, float& hysteresis, bool delayLoad) const {
if (_lodParent.data() != this) {
return _lodParent.data()->getLODOrFallback(distance, hysteresis, delayLoad);
}
if (_failedToLoad && _fallback) {
return _fallback;
}
QSharedPointer<NetworkGeometry> lod = _lodParent;
float lodDistance = 0.0f;
QMap<float, QSharedPointer<NetworkGeometry> >::const_iterator it = _lods.upperBound(distance);
if (it != _lods.constBegin()) {
it = it - 1;
lod = it.value();
lodDistance = it.key();
}
if (hysteresis != NO_HYSTERESIS && hysteresis != lodDistance) {
// if we previously selected a different distance, make sure we've moved far enough to justify switching
const float HYSTERESIS_PROPORTION = 0.1f;
if (glm::abs(distance - qMax(hysteresis, lodDistance)) / fabsf(hysteresis - lodDistance) < HYSTERESIS_PROPORTION) {
lod = _lodParent;
lodDistance = 0.0f;
it = _lods.upperBound(hysteresis);
if (it != _lods.constBegin()) {
it = it - 1;
lod = it.value();
lodDistance = it.key();
}
}
}
if (lod && lod->isLoaded()) {
hysteresis = lodDistance;
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 (!delayLoad) {
lod->ensureLoading();
}
float closestDistance = FLT_MAX;
if (isLoaded()) {
lod = _lodParent;
closestDistance = distance;
}
for (it = _lods.constBegin(); it != _lods.constEnd(); it++) {
float distanceToLOD = glm::abs(distance - it.key());
if (it.value()->isLoaded() && distanceToLOD < closestDistance) {
lod = it.value();
closestDistance = distanceToLOD;
}
}
hysteresis = NO_HYSTERESIS;
return lod;
}
uint qHash(const QWeakPointer<Animation>& animation, uint seed = 0) {
return qHash(animation.data(), seed);
}
QVector<int> NetworkGeometry::getJointMappings(const AnimationPointer& animation) {
QVector<int> mappings = _jointMappings.value(animation);
if (mappings.isEmpty() && isLoaded() && animation && animation->isLoaded()) {
const FBXGeometry& animationGeometry = animation->getGeometry();
for (int i = 0; i < animationGeometry.joints.size(); i++) {
mappings.append(_geometry.jointIndices.value(animationGeometry.joints.at(i).name) - 1);
}
_jointMappings.insert(animation, mappings);
}
return mappings;
}
void NetworkGeometry::setLoadPriority(const QPointer<QObject>& owner, float priority) {
Resource::setLoadPriority(owner, priority);
for (int i = 0; i < _meshes.size(); i++) {
NetworkMesh& mesh = _meshes[i];
for (int j = 0; j < mesh.parts.size(); j++) {
NetworkMeshPart& part = mesh.parts[j];
if (part.diffuseTexture) {
part.diffuseTexture->setLoadPriority(owner, priority);
}
if (part.normalTexture) {
part.normalTexture->setLoadPriority(owner, priority);
}
if (part.specularTexture) {
part.specularTexture->setLoadPriority(owner, priority);
}
if (part.emissiveTexture) {
part.emissiveTexture->setLoadPriority(owner, priority);
}
}
}
}
void NetworkGeometry::setLoadPriorities(const QHash<QPointer<QObject>, float>& priorities) {
Resource::setLoadPriorities(priorities);
for (int i = 0; i < _meshes.size(); i++) {
NetworkMesh& mesh = _meshes[i];
for (int j = 0; j < mesh.parts.size(); j++) {
NetworkMeshPart& part = mesh.parts[j];
if (part.diffuseTexture) {
part.diffuseTexture->setLoadPriorities(priorities);
}
if (part.normalTexture) {
part.normalTexture->setLoadPriorities(priorities);
}
if (part.specularTexture) {
part.specularTexture->setLoadPriorities(priorities);
}
if (part.emissiveTexture) {
part.emissiveTexture->setLoadPriorities(priorities);
}
}
}
}
void NetworkGeometry::clearLoadPriority(const QPointer<QObject>& owner) {
Resource::clearLoadPriority(owner);
for (int i = 0; i < _meshes.size(); i++) {
NetworkMesh& mesh = _meshes[i];
for (int j = 0; j < mesh.parts.size(); j++) {
NetworkMeshPart& part = mesh.parts[j];
if (part.diffuseTexture) {
part.diffuseTexture->clearLoadPriority(owner);
}
if (part.normalTexture) {
part.normalTexture->clearLoadPriority(owner);
}
if (part.specularTexture) {
part.specularTexture->clearLoadPriority(owner);
}
if (part.emissiveTexture) {
part.emissiveTexture->clearLoadPriority(owner);
}
}
}
}
void NetworkGeometry::setTextureWithNameToURL(const QString& name, const QUrl& url) {
if (_meshes.size() > 0) {
auto textureCache = DependencyManager::get<TextureCache>();
for (int i = 0; i < _meshes.size(); i++) {
NetworkMesh& mesh = _meshes[i];
for (int j = 0; j < mesh.parts.size(); j++) {
NetworkMeshPart& part = mesh.parts[j];
for (size_t i = 0; i < _meshes.size(); i++) {
NetworkMesh& mesh = *(_meshes[i].get());
for (size_t j = 0; j < mesh._parts.size(); j++) {
NetworkMeshPart& part = *(mesh._parts[j].get());
QSharedPointer<NetworkTexture> matchingTexture = QSharedPointer<NetworkTexture>();
if (part.diffuseTextureName == name) {
part.diffuseTexture = textureCache->getTexture(url, DEFAULT_TEXTURE, _geometry.meshes[i].isEye);
part.diffuseTexture->setLoadPriorities(_loadPriorities);
part.diffuseTexture = textureCache->getTexture(url, DEFAULT_TEXTURE, _geometry->meshes[i].isEye);
} else if (part.normalTextureName == name) {
part.normalTexture = textureCache->getTexture(url);
part.normalTexture->setLoadPriorities(_loadPriorities);
} else if (part.specularTextureName == name) {
part.specularTexture = textureCache->getTexture(url);
part.specularTexture->setLoadPriorities(_loadPriorities);
} else if (part.emissiveTextureName == name) {
part.emissiveTexture = textureCache->getTexture(url);
part.emissiveTexture->setLoadPriorities(_loadPriorities);
}
}
}
} else {
qCDebug(renderutils) << "Adding a name url pair to pending" << name << url;
// we don't have meshes downloaded yet, so hold this texture as pending
_pendingTextureChanges.insert(name, url);
qCWarning(renderutils) << "Ignoring setTextureWirthNameToURL() geometry not ready." << name << url;
}
_isLoadedWithTextures = false;
}
QStringList NetworkGeometry::getTextureNames() const {
QStringList result;
for (int i = 0; i < _meshes.size(); i++) {
const NetworkMesh& mesh = _meshes[i];
for (int j = 0; j < mesh.parts.size(); j++) {
const NetworkMeshPart& part = mesh.parts[j];
for (size_t i = 0; i < _meshes.size(); i++) {
const NetworkMesh& mesh = *(_meshes[i].get());
for (size_t j = 0; j < mesh._parts.size(); j++) {
const NetworkMeshPart& part = *(mesh._parts[j].get());
if (!part.diffuseTextureName.isEmpty() && part.diffuseTexture) {
QString textureURL = part.diffuseTexture->getURL().toString();
result << part.diffuseTextureName + ":" + textureURL;
@ -1935,320 +1832,259 @@ QStringList NetworkGeometry::getTextureNames() const {
return result;
}
void NetworkGeometry::replaceTexturesWithPendingChanges() {
QHash<QString, QUrl>::Iterator it = _pendingTextureChanges.begin();
while (it != _pendingTextureChanges.end()) {
setTextureWithNameToURL(it.key(), it.value());
it = _pendingTextureChanges.erase(it);
void NetworkGeometry::requestMapping(const QUrl& url) {
_state = RequestMappingState;
if (_resource) {
_resource->deleteLater();
}
_resource = new Resource(url, false);
connect(_resource, SIGNAL(loaded(QNetworkReply&)), SLOT(mappingRequestDone(QNetworkReply&)));
connect(_resource, SIGNAL(failed(QNetworkReply::NetworkError)), SLOT(mappingRequestError(QNetworkReply::NetworkError)));
}
/// Reads geometry in a worker thread.
class GeometryReader : public QRunnable {
public:
GeometryReader(const QWeakPointer<Resource>& geometry, const QUrl& url,
QNetworkReply* reply, const QVariantHash& mapping);
virtual void run();
private:
QWeakPointer<Resource> _geometry;
QUrl _url;
QNetworkReply* _reply;
QVariantHash _mapping;
};
GeometryReader::GeometryReader(const QWeakPointer<Resource>& geometry, const QUrl& url,
QNetworkReply* reply, const QVariantHash& mapping) :
_geometry(geometry),
_url(url),
_reply(reply),
_mapping(mapping) {
void NetworkGeometry::requestModel(const QUrl& url) {
_state = RequestModelState;
if (_resource) {
_resource->deleteLater();
}
_resource = new Resource(url, false);
connect(_resource, SIGNAL(loaded(QNetworkReply&)), SLOT(modelRequestDone(QNetworkReply&)));
connect(_resource, SIGNAL(failed(QNetworkReply::NetworkError)), SLOT(modelRequestError(QNetworkReply::NetworkError)));
}
void GeometryReader::run() {
QSharedPointer<Resource> geometry = _geometry.toStrongRef();
if (geometry.isNull()) {
_reply->deleteLater();
return;
}
try {
if (!_reply) {
throw QString("Reply is NULL ?!");
}
QString urlname = _url.path().toLower();
bool urlValid = true;
urlValid &= !urlname.isEmpty();
urlValid &= !_url.path().isEmpty();
urlValid &= _url.path().toLower().endsWith(".fbx")
|| _url.path().toLower().endsWith(".obj")
|| _url.path().toLower().endsWith(".svo");
void NetworkGeometry::mappingRequestDone(QNetworkReply& reply) {
assert(_state == RequestMappingState);
if (urlValid) {
// Let's read the binaries from the network
FBXGeometry fbxgeo;
if (_url.path().toLower().endsWith(".fbx")) {
bool grabLightmaps = true;
float lightmapLevel = 1.0f;
// HACK: For monday 12/01/2014 we need to kill lighmaps loading in starchamber...
if (_url.path().toLower().endsWith("loungev4_11-18.fbx")) {
grabLightmaps = false;
} else if (_url.path().toLower().endsWith("apt8_reboot.fbx")) {
lightmapLevel = 4.0f;
} else if (_url.path().toLower().endsWith("palaceoforinthilian4.fbx")) {
lightmapLevel = 3.5f;
}
fbxgeo = readFBX(_reply, _mapping, _url.path(), grabLightmaps, lightmapLevel);
} else if (_url.path().toLower().endsWith(".obj")) {
fbxgeo = OBJReader().readOBJ(_reply, _mapping, &_url);
// parse the mapping file
_mapping = FSTReader::readMapping(reply.readAll());
QUrl replyUrl = reply.url();
QString modelUrlStr = _mapping.value("filename").toString();
if (modelUrlStr.isNull()) {
qCDebug(renderutils) << "Mapping file " << _url << "has no \"filename\" entry";
emit onFailure(*this, MissingFilenameInMapping);
} else {
// read _textureBase from mapping file, if present
QString texdir = _mapping.value("texdir").toString();
if (!texdir.isNull()) {
if (!texdir.endsWith('/')) {
texdir += '/';
}
QMetaObject::invokeMethod(geometry.data(), "setGeometry", Q_ARG(const FBXGeometry&, fbxgeo));
} else {
throw QString("url is invalid");
_textureBaseUrl = replyUrl.resolved(texdir);
}
} catch (const QString& error) {
qCDebug(renderutils) << "Error reading " << _url << ": " << error;
QMetaObject::invokeMethod(geometry.data(), "finishedLoading", Q_ARG(bool, false));
}
_reply->deleteLater();
}
void NetworkGeometry::init() {
_mapping = QVariantHash();
_geometry = FBXGeometry();
_meshes.clear();
_lods.clear();
_pendingTextureChanges.clear();
_request.setUrl(_url);
Resource::init();
}
void NetworkGeometry::downloadFinished(QNetworkReply* reply) {
QUrl url = reply->url();
if (url.path().toLower().endsWith(".fst")) {
// it's a mapping file; parse it and get the mesh filename
_mapping = FSTReader::readMapping(reply->readAll());
reply->deleteLater();
QString filename = _mapping.value("filename").toString();
if (filename.isNull()) {
qCDebug(renderutils) << "Mapping file " << url << " has no filename.";
finishedLoading(false);
} else {
QString texdir = _mapping.value("texdir").toString();
if (!texdir.isNull()) {
if (!texdir.endsWith('/')) {
texdir += '/';
}
_textureBase = url.resolved(texdir);
}
QVariantHash lods = _mapping.value("lod").toHash();
for (QVariantHash::const_iterator it = lods.begin(); it != lods.end(); it++) {
auto geometry = QSharedPointer<NetworkGeometry>::create(url.resolved(it.key()),
QSharedPointer<NetworkGeometry>(), true, _mapping, _textureBase);
geometry->setSelf(geometry.staticCast<Resource>());
geometry->setLODParent(_lodParent);
_lods.insert(it.value().toFloat(), geometry);
}
_request.setUrl(url.resolved(filename));
// make the request immediately only if we have no LODs to switch between
_startedLoading = false;
if (_lods.isEmpty()) {
attemptRequest();
}
}
return;
}
// send the reader off to the thread pool
QThreadPool::globalInstance()->start(new GeometryReader(_self, url, reply, _mapping));
}
void NetworkGeometry::reinsert() {
Resource::reinsert();
_lodParent = qWeakPointerCast<NetworkGeometry, Resource>(_self);
foreach (const QSharedPointer<NetworkGeometry>& lod, _lods) {
lod->setLODParent(_lodParent);
QUrl modelUrl = replyUrl.resolved(modelUrlStr);
requestModel(modelUrl);
}
}
void NetworkGeometry::setGeometry(const FBXGeometry& geometry) {
_geometry = geometry;
void NetworkGeometry::mappingRequestError(QNetworkReply::NetworkError error) {
assert(_state == RequestMappingState);
_state = ErrorState;
emit onFailure(*this, MappingRequestError);
}
void NetworkGeometry::modelRequestDone(QNetworkReply& reply) {
assert(_state == RequestModelState);
_state = ParsingModelState;
// asynchronously parse the model file.
GeometryReader* geometryReader = new GeometryReader(reply.url(), &reply, _mapping);
connect(geometryReader, SIGNAL(onSuccess(FBXGeometry*)), SLOT(modelParseSuccess(FBXGeometry*)));
connect(geometryReader, SIGNAL(onError(int, QString)), SLOT(modelParseError(int, QString)));
QThreadPool::globalInstance()->start(geometryReader);
}
void NetworkGeometry::modelRequestError(QNetworkReply::NetworkError error) {
assert(_state == RequestModelState);
_state = ErrorState;
emit onFailure(*this, ModelRequestError);
}
static NetworkMesh* buildNetworkMesh(const FBXMesh& mesh, const QUrl& textureBaseUrl) {
auto textureCache = DependencyManager::get<TextureCache>();
foreach (const FBXMesh& mesh, _geometry.meshes) {
NetworkMesh networkMesh;
int totalIndices = 0;
bool checkForTexcoordLightmap = false;
foreach (const FBXMeshPart& part, mesh.parts) {
NetworkMeshPart networkPart;
if (!part.diffuseTexture.filename.isEmpty()) {
networkPart.diffuseTexture = textureCache->getTexture(
_textureBase.resolved(QUrl(part.diffuseTexture.filename)), DEFAULT_TEXTURE,
mesh.isEye, part.diffuseTexture.content);
networkPart.diffuseTextureName = part.diffuseTexture.name;
networkPart.diffuseTexture->setLoadPriorities(_loadPriorities);
}
if (!part.normalTexture.filename.isEmpty()) {
networkPart.normalTexture = textureCache->getTexture(
_textureBase.resolved(QUrl(part.normalTexture.filename)), NORMAL_TEXTURE,
false, part.normalTexture.content);
networkPart.normalTextureName = part.normalTexture.name;
networkPart.normalTexture->setLoadPriorities(_loadPriorities);
}
if (!part.specularTexture.filename.isEmpty()) {
networkPart.specularTexture = textureCache->getTexture(
_textureBase.resolved(QUrl(part.specularTexture.filename)), SPECULAR_TEXTURE,
false, part.specularTexture.content);
networkPart.specularTextureName = part.specularTexture.name;
networkPart.specularTexture->setLoadPriorities(_loadPriorities);
}
if (!part.emissiveTexture.filename.isEmpty()) {
networkPart.emissiveTexture = textureCache->getTexture(
_textureBase.resolved(QUrl(part.emissiveTexture.filename)), EMISSIVE_TEXTURE,
false, part.emissiveTexture.content);
networkPart.emissiveTextureName = part.emissiveTexture.name;
networkPart.emissiveTexture->setLoadPriorities(_loadPriorities);
checkForTexcoordLightmap = true;
}
networkMesh.parts.append(networkPart);
totalIndices += (part.quadIndices.size() + part.triangleIndices.size());
NetworkMesh* networkMesh = new NetworkMesh();
int totalIndices = 0;
bool checkForTexcoordLightmap = false;
// process network parts
foreach (const FBXMeshPart& part, mesh.parts) {
NetworkMeshPart* networkPart = new NetworkMeshPart();
if (!part.diffuseTexture.filename.isEmpty()) {
networkPart->diffuseTexture = textureCache->getTexture(textureBaseUrl.resolved(QUrl(part.diffuseTexture.filename)), DEFAULT_TEXTURE,
mesh.isEye, part.diffuseTexture.content);
networkPart->diffuseTextureName = part.diffuseTexture.name;
}
{
networkMesh._indexBuffer = std::make_shared<gpu::Buffer>();
networkMesh._indexBuffer->resize(totalIndices * sizeof(int));
int offset = 0;
foreach(const FBXMeshPart& part, mesh.parts) {
networkMesh._indexBuffer->setSubData(offset, part.quadIndices.size() * sizeof(int),
(gpu::Byte*) part.quadIndices.constData());
offset += part.quadIndices.size() * sizeof(int);
networkMesh._indexBuffer->setSubData(offset, part.triangleIndices.size() * sizeof(int),
(gpu::Byte*) part.triangleIndices.constData());
offset += part.triangleIndices.size() * sizeof(int);
}
if (!part.normalTexture.filename.isEmpty()) {
networkPart->normalTexture = textureCache->getTexture(textureBaseUrl.resolved(QUrl(part.normalTexture.filename)), NORMAL_TEXTURE,
false, part.normalTexture.content);
networkPart->normalTextureName = part.normalTexture.name;
}
{
networkMesh._vertexBuffer = std::make_shared<gpu::Buffer>();
// if we don't need to do any blending, the positions/normals can be static
if (mesh.blendshapes.isEmpty()) {
int normalsOffset = mesh.vertices.size() * sizeof(glm::vec3);
int tangentsOffset = normalsOffset + mesh.normals.size() * sizeof(glm::vec3);
int colorsOffset = tangentsOffset + mesh.tangents.size() * sizeof(glm::vec3);
int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
int texCoords1Offset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2);
int clusterIndicesOffset = texCoords1Offset + mesh.texCoords1.size() * sizeof(glm::vec2);
int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4);
networkMesh._vertexBuffer->resize(clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4));
networkMesh._vertexBuffer->setSubData(0, mesh.vertices.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.vertices.constData());
networkMesh._vertexBuffer->setSubData(normalsOffset, mesh.normals.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.normals.constData());
networkMesh._vertexBuffer->setSubData(tangentsOffset,
mesh.tangents.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.tangents.constData());
networkMesh._vertexBuffer->setSubData(colorsOffset, mesh.colors.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.colors.constData());
networkMesh._vertexBuffer->setSubData(texCoordsOffset,
mesh.texCoords.size() * sizeof(glm::vec2), (gpu::Byte*) mesh.texCoords.constData());
networkMesh._vertexBuffer->setSubData(texCoords1Offset,
mesh.texCoords1.size() * sizeof(glm::vec2), (gpu::Byte*) mesh.texCoords1.constData());
networkMesh._vertexBuffer->setSubData(clusterIndicesOffset,
mesh.clusterIndices.size() * sizeof(glm::vec4), (gpu::Byte*) mesh.clusterIndices.constData());
networkMesh._vertexBuffer->setSubData(clusterWeightsOffset,
mesh.clusterWeights.size() * sizeof(glm::vec4), (gpu::Byte*) mesh.clusterWeights.constData());
// otherwise, at least the cluster indices/weights can be static
networkMesh._vertexStream = std::make_shared<gpu::BufferStream>();
networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, 0, sizeof(glm::vec3));
if (mesh.normals.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, normalsOffset, sizeof(glm::vec3));
if (mesh.tangents.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, tangentsOffset, sizeof(glm::vec3));
if (mesh.colors.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, colorsOffset, sizeof(glm::vec3));
if (mesh.texCoords.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, texCoordsOffset, sizeof(glm::vec2));
if (mesh.texCoords1.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, texCoords1Offset, sizeof(glm::vec2));
if (mesh.clusterIndices.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, clusterIndicesOffset, sizeof(glm::vec4));
if (mesh.clusterWeights.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, clusterWeightsOffset, sizeof(glm::vec4));
int channelNum = 0;
networkMesh._vertexFormat = std::make_shared<gpu::Stream::Format>();
networkMesh._vertexFormat->setAttribute(gpu::Stream::POSITION, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0);
if (mesh.normals.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::NORMAL, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ));
if (mesh.tangents.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::TANGENT, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ));
if (mesh.colors.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::COLOR, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RGB));
if (mesh.texCoords.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::TEXCOORD, channelNum++, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV));
if (mesh.texCoords1.size()) {
networkMesh._vertexFormat->setAttribute(gpu::Stream::TEXCOORD1, channelNum++, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV));
} else if (checkForTexcoordLightmap && mesh.texCoords.size()) {
// need lightmap texcoord UV but doesn't have uv#1 so just reuse the same channel
networkMesh._vertexFormat->setAttribute(gpu::Stream::TEXCOORD1, channelNum - 1, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV));
}
if (mesh.clusterIndices.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_INDEX, channelNum++, gpu::Element(gpu::VEC4, gpu::NFLOAT, gpu::XYZW));
if (mesh.clusterWeights.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT, channelNum++, gpu::Element(gpu::VEC4, gpu::NFLOAT, gpu::XYZW));
}
else {
int colorsOffset = mesh.tangents.size() * sizeof(glm::vec3);
int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
int clusterIndicesOffset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2);
int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4);
networkMesh._vertexBuffer->resize(clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4));
networkMesh._vertexBuffer->setSubData(0, mesh.tangents.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.tangents.constData());
networkMesh._vertexBuffer->setSubData(colorsOffset, mesh.colors.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.colors.constData());
networkMesh._vertexBuffer->setSubData(texCoordsOffset,
mesh.texCoords.size() * sizeof(glm::vec2), (gpu::Byte*) mesh.texCoords.constData());
networkMesh._vertexBuffer->setSubData(clusterIndicesOffset,
mesh.clusterIndices.size() * sizeof(glm::vec4), (gpu::Byte*) mesh.clusterIndices.constData());
networkMesh._vertexBuffer->setSubData(clusterWeightsOffset,
mesh.clusterWeights.size() * sizeof(glm::vec4), (gpu::Byte*) mesh.clusterWeights.constData());
networkMesh._vertexStream = std::make_shared<gpu::BufferStream>();
if (mesh.tangents.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, 0, sizeof(glm::vec3));
if (mesh.colors.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, colorsOffset, sizeof(glm::vec3));
if (mesh.texCoords.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, texCoordsOffset, sizeof(glm::vec2));
if (mesh.clusterIndices.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, clusterIndicesOffset, sizeof(glm::vec4));
if (mesh.clusterWeights.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, clusterWeightsOffset, sizeof(glm::vec4));
int channelNum = 0;
networkMesh._vertexFormat = std::make_shared<gpu::Stream::Format>();
networkMesh._vertexFormat->setAttribute(gpu::Stream::POSITION, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ));
if (mesh.normals.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::NORMAL, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ));
if (mesh.tangents.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::TANGENT, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ));
if (mesh.colors.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::COLOR, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RGB));
if (mesh.texCoords.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::TEXCOORD, channelNum++, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV));
if (mesh.clusterIndices.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_INDEX, channelNum++, gpu::Element(gpu::VEC4, gpu::NFLOAT, gpu::XYZW));
if (mesh.clusterWeights.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT, channelNum++, gpu::Element(gpu::VEC4, gpu::NFLOAT, gpu::XYZW));
}
if (!part.specularTexture.filename.isEmpty()) {
networkPart->specularTexture = textureCache->getTexture(textureBaseUrl.resolved(QUrl(part.specularTexture.filename)), SPECULAR_TEXTURE,
false, part.specularTexture.content);
networkPart->specularTextureName = part.specularTexture.name;
}
_meshes.append(networkMesh);
if (!part.emissiveTexture.filename.isEmpty()) {
networkPart->emissiveTexture = textureCache->getTexture(textureBaseUrl.resolved(QUrl(part.emissiveTexture.filename)), EMISSIVE_TEXTURE,
false, part.emissiveTexture.content);
networkPart->emissiveTextureName = part.emissiveTexture.name;
checkForTexcoordLightmap = true;
}
networkMesh->_parts.emplace_back(networkPart);
totalIndices += (part.quadIndices.size() + part.triangleIndices.size());
}
finishedLoading(true);
// initialize index buffer
{
networkMesh->_indexBuffer = std::make_shared<gpu::Buffer>();
networkMesh->_indexBuffer->resize(totalIndices * sizeof(int));
int offset = 0;
foreach(const FBXMeshPart& part, mesh.parts) {
networkMesh->_indexBuffer->setSubData(offset, part.quadIndices.size() * sizeof(int),
(gpu::Byte*) part.quadIndices.constData());
offset += part.quadIndices.size() * sizeof(int);
networkMesh->_indexBuffer->setSubData(offset, part.triangleIndices.size() * sizeof(int),
(gpu::Byte*) part.triangleIndices.constData());
offset += part.triangleIndices.size() * sizeof(int);
}
}
// initialize vertex buffer
{
networkMesh->_vertexBuffer = std::make_shared<gpu::Buffer>();
// if we don't need to do any blending, the positions/normals can be static
if (mesh.blendshapes.isEmpty()) {
int normalsOffset = mesh.vertices.size() * sizeof(glm::vec3);
int tangentsOffset = normalsOffset + mesh.normals.size() * sizeof(glm::vec3);
int colorsOffset = tangentsOffset + mesh.tangents.size() * sizeof(glm::vec3);
int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
int texCoords1Offset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2);
int clusterIndicesOffset = texCoords1Offset + mesh.texCoords1.size() * sizeof(glm::vec2);
int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4);
networkMesh->_vertexBuffer->resize(clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4));
networkMesh->_vertexBuffer->setSubData(0, mesh.vertices.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.vertices.constData());
networkMesh->_vertexBuffer->setSubData(normalsOffset, mesh.normals.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.normals.constData());
networkMesh->_vertexBuffer->setSubData(tangentsOffset,
mesh.tangents.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.tangents.constData());
networkMesh->_vertexBuffer->setSubData(colorsOffset, mesh.colors.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.colors.constData());
networkMesh->_vertexBuffer->setSubData(texCoordsOffset,
mesh.texCoords.size() * sizeof(glm::vec2), (gpu::Byte*) mesh.texCoords.constData());
networkMesh->_vertexBuffer->setSubData(texCoords1Offset,
mesh.texCoords1.size() * sizeof(glm::vec2), (gpu::Byte*) mesh.texCoords1.constData());
networkMesh->_vertexBuffer->setSubData(clusterIndicesOffset,
mesh.clusterIndices.size() * sizeof(glm::vec4), (gpu::Byte*) mesh.clusterIndices.constData());
networkMesh->_vertexBuffer->setSubData(clusterWeightsOffset,
mesh.clusterWeights.size() * sizeof(glm::vec4), (gpu::Byte*) mesh.clusterWeights.constData());
// otherwise, at least the cluster indices/weights can be static
networkMesh->_vertexStream = std::make_shared<gpu::BufferStream>();
networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, 0, sizeof(glm::vec3));
if (mesh.normals.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, normalsOffset, sizeof(glm::vec3));
if (mesh.tangents.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, tangentsOffset, sizeof(glm::vec3));
if (mesh.colors.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, colorsOffset, sizeof(glm::vec3));
if (mesh.texCoords.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, texCoordsOffset, sizeof(glm::vec2));
if (mesh.texCoords1.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, texCoords1Offset, sizeof(glm::vec2));
if (mesh.clusterIndices.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, clusterIndicesOffset, sizeof(glm::vec4));
if (mesh.clusterWeights.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, clusterWeightsOffset, sizeof(glm::vec4));
int channelNum = 0;
networkMesh->_vertexFormat = std::make_shared<gpu::Stream::Format>();
networkMesh->_vertexFormat->setAttribute(gpu::Stream::POSITION, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0);
if (mesh.normals.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::NORMAL, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ));
if (mesh.tangents.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::TANGENT, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ));
if (mesh.colors.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::COLOR, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RGB));
if (mesh.texCoords.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::TEXCOORD, channelNum++, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV));
if (mesh.texCoords1.size()) {
networkMesh->_vertexFormat->setAttribute(gpu::Stream::TEXCOORD1, channelNum++, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV));
} else if (checkForTexcoordLightmap && mesh.texCoords.size()) {
// need lightmap texcoord UV but doesn't have uv#1 so just reuse the same channel
networkMesh->_vertexFormat->setAttribute(gpu::Stream::TEXCOORD1, channelNum - 1, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV));
}
if (mesh.clusterIndices.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_INDEX, channelNum++, gpu::Element(gpu::VEC4, gpu::NFLOAT, gpu::XYZW));
if (mesh.clusterWeights.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT, channelNum++, gpu::Element(gpu::VEC4, gpu::NFLOAT, gpu::XYZW));
}
else {
int colorsOffset = mesh.tangents.size() * sizeof(glm::vec3);
int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
int clusterIndicesOffset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2);
int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4);
networkMesh->_vertexBuffer->resize(clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4));
networkMesh->_vertexBuffer->setSubData(0, mesh.tangents.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.tangents.constData());
networkMesh->_vertexBuffer->setSubData(colorsOffset, mesh.colors.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.colors.constData());
networkMesh->_vertexBuffer->setSubData(texCoordsOffset,
mesh.texCoords.size() * sizeof(glm::vec2), (gpu::Byte*) mesh.texCoords.constData());
networkMesh->_vertexBuffer->setSubData(clusterIndicesOffset,
mesh.clusterIndices.size() * sizeof(glm::vec4), (gpu::Byte*) mesh.clusterIndices.constData());
networkMesh->_vertexBuffer->setSubData(clusterWeightsOffset,
mesh.clusterWeights.size() * sizeof(glm::vec4), (gpu::Byte*) mesh.clusterWeights.constData());
networkMesh->_vertexStream = std::make_shared<gpu::BufferStream>();
if (mesh.tangents.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, 0, sizeof(glm::vec3));
if (mesh.colors.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, colorsOffset, sizeof(glm::vec3));
if (mesh.texCoords.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, texCoordsOffset, sizeof(glm::vec2));
if (mesh.clusterIndices.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, clusterIndicesOffset, sizeof(glm::vec4));
if (mesh.clusterWeights.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, clusterWeightsOffset, sizeof(glm::vec4));
int channelNum = 0;
networkMesh->_vertexFormat = std::make_shared<gpu::Stream::Format>();
networkMesh->_vertexFormat->setAttribute(gpu::Stream::POSITION, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ));
if (mesh.normals.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::NORMAL, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ));
if (mesh.tangents.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::TANGENT, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ));
if (mesh.colors.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::COLOR, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RGB));
if (mesh.texCoords.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::TEXCOORD, channelNum++, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV));
if (mesh.clusterIndices.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_INDEX, channelNum++, gpu::Element(gpu::VEC4, gpu::NFLOAT, gpu::XYZW));
if (mesh.clusterWeights.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT, channelNum++, gpu::Element(gpu::VEC4, gpu::NFLOAT, gpu::XYZW));
}
}
return networkMesh;
}
void NetworkGeometry::modelParseSuccess(FBXGeometry* geometry) {
// assume owner ship of geometry pointer
_geometry.reset(geometry);
foreach(const FBXMesh& mesh, _geometry->meshes) {
_meshes.emplace_back(buildNetworkMesh(mesh, _textureBaseUrl));
}
_state = SuccessState;
emit onSuccess(*this, *_geometry.get());
delete _resource;
_resource = nullptr;
}
void NetworkGeometry::modelParseError(int error, QString str) {
_state = ErrorState;
emit onFailure(*this, (NetworkGeometry::Error)error);
delete _resource;
_resource = nullptr;
}
bool NetworkMeshPart::isTranslucent() const {
return diffuseTexture && diffuseTexture->isTranslucent();
}
bool NetworkMesh::isPartTranslucent(const FBXMesh& fbxMesh, int partIndex) const {
assert(partIndex >= 0);
assert(partIndex < parts.size());
return (parts.at(partIndex).isTranslucent() || fbxMesh.parts.at(partIndex).opacity != 1.0f);
assert((size_t)partIndex < _parts.size());
return (_parts.at(partIndex)->isTranslucent() || fbxMesh.parts.at(partIndex).opacity != 1.0f);
}
int NetworkMesh::getTranslucentPartCount(const FBXMesh& fbxMesh) const {
int count = 0;
for (int i = 0; i < parts.size(); i++) {
for (size_t i = 0; i < _parts.size(); i++) {
if (isPartTranslucent(fbxMesh, i)) {
count++;
}

View file

@ -13,6 +13,7 @@
#define hifi_GeometryCache_h
#include <QMap>
#include <QRunnable>
#include <DependencyManager.h>
#include <ResourceCache.h>
@ -129,6 +130,9 @@ public:
int allocateID() { return _nextID++; }
static const int UNKNOWN_ID;
virtual QSharedPointer<Resource> createResource(const QUrl& url, const QSharedPointer<Resource>& fallback,
bool delayLoad, const void* extra);
void renderSphere(gpu::Batch& batch, float radius, int slices, int stacks, const glm::vec3& color, bool solid = true, int id = UNKNOWN_ID)
{ renderSphere(batch, radius, slices, stacks, glm::vec4(color, 1.0f), solid, id); }
@ -208,11 +212,6 @@ public:
/// Set a batch to the simple pipeline, returning the previous pipeline
void useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend = false);
protected:
virtual QSharedPointer<Resource> createResource(const QUrl& url,
const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra);
private:
GeometryCache();
virtual ~GeometryCache();
@ -305,70 +304,104 @@ private:
QHash<QUrl, QWeakPointer<NetworkGeometry> > _networkGeometry;
};
/// Geometry loaded from the network.
class NetworkGeometry : public Resource {
class NetworkGeometry : public QObject {
Q_OBJECT
public:
/// A hysteresis value indicating that we have no state memory.
static const float NO_HYSTERESIS;
NetworkGeometry(const QUrl& url, const QSharedPointer<NetworkGeometry>& fallback, bool delayLoad,
const QVariantHash& mapping = QVariantHash(), const QUrl& textureBase = QUrl());
// mapping is only used if url is a .fbx or .obj file, it is essentially the content of an fst file.
// if delayLoad is true, the url will not be immediately downloaded.
// use the attemptRequest method to initiate the download.
NetworkGeometry(const QUrl& url, bool delayLoad, const QVariantHash& mapping, const QUrl& textureBaseUrl = QUrl());
~NetworkGeometry();
/// Checks whether the geometry and its textures are loaded.
const QUrl& getURL() const { return _url; }
void attemptRequest();
// true when the geometry is loaded (but maybe not it's associated textures)
bool isLoaded() const;
// true when the requested 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
QSharedPointer<NetworkGeometry> getLODOrFallback(float distance, float& hysteresis, bool delayLoad = false) const;
// WARNING: only valid when isLoaded returns true.
const FBXGeometry& getFBXGeometry() const { return *_geometry; }
const std::vector<std::unique_ptr<NetworkMesh>>& getMeshes() const { return _meshes; }
const FBXGeometry& getFBXGeometry() const { return _geometry; }
const QVector<NetworkMesh>& getMeshes() const { return _meshes; }
QVector<int> getJointMappings(const AnimationPointer& animation);
virtual void setLoadPriority(const QPointer<QObject>& owner, float priority);
virtual void setLoadPriorities(const QHash<QPointer<QObject>, float>& priorities);
virtual void clearLoadPriority(const QPointer<QObject>& owner);
void setTextureWithNameToURL(const QString& name, const QUrl& url);
QStringList getTextureNames() const;
enum Error {
MissingFilenameInMapping = 0,
MappingRequestError,
ModelRequestError,
ModelParseError
};
signals:
// Fired when everything has downloaded and parsed successfully.
void onSuccess(NetworkGeometry& networkGeometry, FBXGeometry& fbxGeometry);
// Fired when something went wrong.
void onFailure(NetworkGeometry& networkGeometry, Error error);
protected slots:
void mappingRequestDone(QNetworkReply& reply);
void mappingRequestError(QNetworkReply::NetworkError error);
void modelRequestDone(QNetworkReply& reply);
void modelRequestError(QNetworkReply::NetworkError error);
void modelParseSuccess(FBXGeometry* geometry);
void modelParseError(int error, QString str);
protected:
void attemptRequestInternal();
void requestMapping(const QUrl& url);
void requestModel(const QUrl& url);
virtual void init();
virtual void downloadFinished(QNetworkReply* reply);
virtual void reinsert();
Q_INVOKABLE void setGeometry(const FBXGeometry& geometry);
private slots:
void replaceTexturesWithPendingChanges();
private:
friend class GeometryCache;
void setLODParent(const QWeakPointer<NetworkGeometry>& lodParent) { _lodParent = lodParent; }
enum State { DelayState,
RequestMappingState,
RequestModelState,
ParsingModelState,
SuccessState,
ErrorState };
State _state;
QUrl _url;
QVariantHash _mapping;
QUrl _textureBase;
QSharedPointer<NetworkGeometry> _fallback;
QMap<float, QSharedPointer<NetworkGeometry> > _lods;
FBXGeometry _geometry;
QVector<NetworkMesh> _meshes;
QWeakPointer<NetworkGeometry> _lodParent;
QHash<QWeakPointer<Animation>, QVector<int> > _jointMappings;
QHash<QString, QUrl> _pendingTextureChanges;
QUrl _textureBaseUrl;
Resource* _resource = nullptr;
std::unique_ptr<FBXGeometry> _geometry;
std::vector<std::unique_ptr<NetworkMesh>> _meshes;
// cache for isLoadedWithTextures()
mutable bool _isLoadedWithTextures = false;
};
/// Reads geometry in a worker thread.
class GeometryReader : public QObject, public QRunnable {
Q_OBJECT
public:
GeometryReader(const QUrl& url, QNetworkReply* reply, const QVariantHash& mapping);
virtual void run();
signals:
void onSuccess(FBXGeometry* geometry);
void onError(int error, QString str);
private:
QWeakPointer<Resource> _geometry;
QUrl _url;
QNetworkReply* _reply;
QVariantHash _mapping;
};
/// The state associated with a single mesh part.
class NetworkMeshPart {
public:
@ -394,9 +427,9 @@ public:
gpu::BufferStreamPointer _vertexStream;
gpu::Stream::FormatPointer _vertexFormat;
QVector<NetworkMeshPart> parts;
std::vector<std::unique_ptr<NetworkMeshPart>> _parts;
int getTranslucentPartCount(const FBXMesh& fbxMesh) const;
bool isPartTranslucent(const FBXMesh& fbxMesh, int partIndex) const;
};

View file

@ -48,6 +48,8 @@
#include "model_lightmap_specular_map_frag.h"
#include "model_translucent_frag.h"
#include "RenderUtilsLogging.h"
using namespace std;
static int modelPointerTypeId = qRegisterMetaType<QPointer<Model> >();
@ -66,7 +68,6 @@ Model::Model(RigPointer rig, QObject* parent) :
_snappedToRegistrationPoint(false),
_showTrueJointTransforms(true),
_cauterizeBones(false),
_lodDistance(0.0f),
_pupilDilation(0.0f),
_url(HTTP_INVALID_COM),
_urlAsString(HTTP_INVALID_COM),
@ -234,7 +235,7 @@ QVector<JointState> Model::createJointStates(const FBXGeometry& geometry) {
};
void Model::initJointTransforms() {
if (!_geometry) {
if (!_geometry || !_geometry->isLoaded()) {
return;
}
const FBXGeometry& geometry = _geometry->getFBXGeometry();
@ -368,8 +369,10 @@ void Model::init() {
}
void Model::reset() {
const FBXGeometry& geometry = _geometry->getFBXGeometry();
_rig->reset(geometry.joints);
if (_geometry && _geometry->isLoaded()) {
const FBXGeometry& geometry = _geometry->getFBXGeometry();
_rig->reset(geometry.joints);
}
_meshGroupsKnown = false;
_readyWhenAdded = false; // in case any of our users are using scenes
invalidCalculatedMeshBoxes(); // if we have to reload, we need to assume our mesh boxes are all invalid
@ -378,68 +381,23 @@ void Model::reset() {
bool Model::updateGeometry() {
PROFILE_RANGE(__FUNCTION__);
bool needFullUpdate = false;
bool needToRebuild = false;
if (_nextGeometry) {
_nextGeometry = _nextGeometry->getLODOrFallback(_lodDistance, _nextLODHysteresis);
_nextGeometry->setLoadPriority(this, -_lodDistance);
_nextGeometry->ensureLoading();
if (_nextGeometry->isLoaded()) {
applyNextGeometry();
needToRebuild = true;
}
}
if (!_geometry) {
if (!_geometry || !_geometry->isLoaded()) {
// geometry is not ready
return false;
}
QSharedPointer<NetworkGeometry> geometry = _geometry->getLODOrFallback(_lodDistance, _lodHysteresis);
if (_geometry != geometry) {
_needsReload = false;
// NOTE: it is theoretically impossible to reach here after passing through the applyNextGeometry() call above.
// Which means we don't need to worry about calling deleteGeometry() below immediately after creating new geometry.
const FBXGeometry& newGeometry = geometry->getFBXGeometry();
QVector<JointState> newJointStates = createJointStates(newGeometry);
if (! _rig->jointStatesEmpty()) {
// copy the existing joint states
const FBXGeometry& oldGeometry = _geometry->getFBXGeometry();
for (QHash<QString, int>::const_iterator it = oldGeometry.jointIndices.constBegin();
it != oldGeometry.jointIndices.constEnd(); it++) {
int oldIndex = it.value() - 1;
int newIndex = newGeometry.getJointIndex(it.key());
if (newIndex != -1) {
newJointStates[newIndex].copyState(_rig->getJointState(oldIndex));
}
}
}
deleteGeometry();
_dilatedTextures.clear();
if (!geometry) {
std::cout << "WARNING: no geometry in Model::updateGeometry\n";
}
setGeometry(geometry);
_meshGroupsKnown = false;
_readyWhenAdded = false; // in case any of our users are using scenes
invalidCalculatedMeshBoxes(); // if we have to reload, we need to assume our mesh boxes are all invalid
initJointStates(newJointStates);
needToRebuild = true;
} else if (_rig->jointStatesEmpty()) {
QSharedPointer<NetworkGeometry> geometry = _geometry;
if (_rig->jointStatesEmpty()) {
const FBXGeometry& fbxGeometry = geometry->getFBXGeometry();
if (fbxGeometry.joints.size() > 0) {
initJointStates(createJointStates(fbxGeometry));
needToRebuild = true;
}
} else if (!geometry->isLoaded()) {
deleteGeometry();
_dilatedTextures.clear();
}
_geometry->setLoadPriority(this, -_lodDistance);
_geometry->ensureLoading();
if (needToRebuild) {
const FBXGeometry& fbxGeometry = geometry->getFBXGeometry();
@ -454,7 +412,7 @@ bool Model::updateGeometry() {
buffer->resize((mesh.vertices.size() + mesh.normals.size()) * sizeof(glm::vec3));
buffer->setSubData(0, mesh.vertices.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.vertices.constData());
buffer->setSubData(mesh.vertices.size() * sizeof(glm::vec3),
mesh.normals.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.normals.constData());
mesh.normals.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.normals.constData());
}
_blendedVertexBuffers.push_back(buffer);
}
@ -1069,53 +1027,36 @@ int Model::getLastFreeJointIndex(int jointIndex) const {
return (isActive() && jointIndex != -1) ? _geometry->getFBXGeometry().joints.at(jointIndex).freeLineage.last() : -1;
}
void Model::setURL(const QUrl& url, const QUrl& fallback, bool retainCurrent, bool delayLoad) {
void Model::setURL(const QUrl& url) {
// don't recreate the geometry if it's the same URL
if (_url == url && _geometry && _geometry->getURL() == url) {
return;
}
_readyWhenAdded = false; // reset out render items.
_needsReload = true;
invalidCalculatedMeshBoxes();
_url = url;
_urlAsString = _url.toString();
{
render::PendingChanges pendingChanges;
render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene();
removeFromScene(scene, pendingChanges);
scene->enqueuePendingChanges(pendingChanges);
}
_needsReload = true;
_meshGroupsKnown = false;
invalidCalculatedMeshBoxes();
deleteGeometry();
_geometry.reset(new NetworkGeometry(url, false, QVariantHash()));
onInvalidate();
// if so instructed, keep the current geometry until the new one is loaded
_nextGeometry = DependencyManager::get<GeometryCache>()->getGeometry(url, fallback, delayLoad);
_nextLODHysteresis = NetworkGeometry::NO_HYSTERESIS;
if (!retainCurrent || !isActive() || (_nextGeometry && _nextGeometry->isLoaded())) {
applyNextGeometry();
}
}
void Model::geometryRefreshed() {
QObject* sender = QObject::sender();
if (sender == _geometry) {
_readyWhenAdded = false; // reset out render items.
_needsReload = true;
invalidCalculatedMeshBoxes();
onInvalidate();
// if so instructed, keep the current geometry until the new one is loaded
_nextGeometry = DependencyManager::get<GeometryCache>()->getGeometry(_url);
_nextLODHysteresis = NetworkGeometry::NO_HYSTERESIS;
applyNextGeometry();
} else {
sender->disconnect(this, SLOT(geometryRefreshed()));
}
}
const QSharedPointer<NetworkGeometry> Model::getCollisionGeometry(bool delayLoad)
{
if (_collisionGeometry.isNull() && !_collisionUrl.isEmpty()) {
_collisionGeometry = DependencyManager::get<GeometryCache>()->getGeometry(_collisionUrl, QUrl(), delayLoad);
_collisionGeometry.reset(new NetworkGeometry(_collisionUrl, delayLoad, QVariantHash()));
}
if (_collisionGeometry && _collisionGeometry->isLoaded()) {
@ -1130,7 +1071,7 @@ void Model::setCollisionModelURL(const QUrl& url) {
return;
}
_collisionUrl = url;
_collisionGeometry = DependencyManager::get<GeometryCache>()->getGeometry(url, QUrl(), true);
_collisionGeometry.reset(new NetworkGeometry(url, false, QVariantHash()));
}
bool Model::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position) const {
@ -1461,46 +1402,23 @@ void Model::setGeometry(const QSharedPointer<NetworkGeometry>& newGeometry) {
if (_geometry == newGeometry) {
return;
}
if (_geometry) {
_geometry->disconnect(_geometry.data(), &Resource::onRefresh, this, &Model::geometryRefreshed);
}
_geometry = newGeometry;
QObject::connect(_geometry.data(), &Resource::onRefresh, this, &Model::geometryRefreshed);
}
void Model::applyNextGeometry() {
// delete our local geometry and custom textures
deleteGeometry();
_dilatedTextures.clear();
_lodHysteresis = _nextLODHysteresis;
// we retain a reference to the base geometry so that its reference count doesn't fall to zero
setGeometry(_nextGeometry);
_meshGroupsKnown = false;
_readyWhenAdded = false; // in case any of our users are using scenes
_needsReload = false; // we are loaded now!
invalidCalculatedMeshBoxes();
_nextGeometry.reset();
}
void Model::deleteGeometry() {
_blendedVertexBuffers.clear();
_rig->clearJointStates();
_meshStates.clear();
_rig->deleteAnimations();
if (_geometry) {
_geometry->clearLoadPriority(this);
}
_blendedBlendshapeCoefficients.clear();
}
AABox Model::getPartBounds(int meshIndex, int partIndex) {
if (!_geometry || !_geometry->isLoaded()) {
return AABox();
}
if (meshIndex < _meshStates.size()) {
const MeshState& state = _meshStates.at(meshIndex);
bool isSkinned = state.clusterMatrices.size() > 1;
@ -1531,7 +1449,7 @@ AABox Model::getPartBounds(int meshIndex, int partIndex) {
}
void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool translucent) {
// PROFILE_RANGE(__FUNCTION__);
// PROFILE_RANGE(__FUNCTION__);
PerformanceTimer perfTimer("Model::renderPart");
if (!_readyWhenAdded) {
return; // bail asap
@ -1557,14 +1475,14 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool tran
auto alphaThreshold = args->_alphaThreshold; //translucent ? TRANSPARENT_ALPHA_THRESHOLD : OPAQUE_ALPHA_THRESHOLD; // FIX ME
const FBXGeometry& geometry = _geometry->getFBXGeometry();
const QVector<NetworkMesh>& networkMeshes = _geometry->getMeshes();
const std::vector<std::unique_ptr<NetworkMesh>>& networkMeshes = _geometry->getMeshes();
// guard against partially loaded meshes
if (meshIndex >= networkMeshes.size() || meshIndex >= geometry.meshes.size() || meshIndex >= _meshStates.size() ) {
if (meshIndex >= (int)networkMeshes.size() || meshIndex >= (int)geometry.meshes.size() || meshIndex >= (int)_meshStates.size() ) {
return;
}
const NetworkMesh& networkMesh = networkMeshes.at(meshIndex);
const NetworkMesh& networkMesh = *(networkMeshes.at(meshIndex).get());
const FBXMesh& mesh = geometry.meshes.at(meshIndex);
const MeshState& state = _meshStates.at(meshIndex);
@ -1614,8 +1532,7 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool tran
// if our index is ever out of range for either meshes or networkMeshes, then skip it, and set our _meshGroupsKnown
// to false to rebuild out mesh groups.
if (meshIndex < 0 || meshIndex >= networkMeshes.size() || meshIndex > geometry.meshes.size()) {
if (meshIndex < 0 || meshIndex >= (int)networkMeshes.size() || meshIndex > geometry.meshes.size()) {
_meshGroupsKnown = false; // regenerate these lists next time around.
_readyWhenAdded = false; // in case any of our users are using scenes
invalidCalculatedMeshBoxes(); // if we have to reload, we need to assume our mesh boxes are all invalid
@ -1669,11 +1586,11 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool tran
}
// guard against partially loaded meshes
if (partIndex >= networkMesh.parts.size() || partIndex >= mesh.parts.size()) {
if (partIndex >= (int)networkMesh._parts.size() || partIndex >= mesh.parts.size()) {
return;
}
const NetworkMeshPart& networkPart = networkMesh.parts.at(partIndex);
const NetworkMeshPart& networkPart = *(networkMesh._parts.at(partIndex).get());
const FBXMeshPart& part = mesh.parts.at(partIndex);
model::MaterialPointer material = part._material;
@ -1790,10 +1707,10 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool tran
void Model::segregateMeshGroups() {
const FBXGeometry& geometry = _geometry->getFBXGeometry();
const QVector<NetworkMesh>& networkMeshes = _geometry->getMeshes();
const std::vector<std::unique_ptr<NetworkMesh>>& networkMeshes = _geometry->getMeshes();
// all of our mesh vectors must match in size
if (networkMeshes.size() != geometry.meshes.size() ||
if ((int)networkMeshes.size() != geometry.meshes.size() ||
geometry.meshes.size() != _meshStates.size()) {
qDebug() << "WARNING!!!! Mesh Sizes don't match! We will not segregate mesh groups yet.";
return;
@ -1803,12 +1720,12 @@ void Model::segregateMeshGroups() {
_opaqueRenderItems.clear();
// Run through all of the meshes, and place them into their segregated, but unsorted buckets
for (int i = 0; i < networkMeshes.size(); i++) {
const NetworkMesh& networkMesh = networkMeshes.at(i);
for (int i = 0; i < (int)networkMeshes.size(); i++) {
const NetworkMesh& networkMesh = *(networkMeshes.at(i).get());
const FBXMesh& mesh = geometry.meshes.at(i);
const MeshState& state = _meshStates.at(i);
bool translucentMesh = networkMesh.getTranslucentPartCount(mesh) == networkMesh.parts.size();
bool translucentMesh = networkMesh.getTranslucentPartCount(mesh) == (int)networkMesh._parts.size();
bool hasTangents = !mesh.tangents.isEmpty();
bool hasSpecular = mesh.hasSpecularTexture();
bool hasLightmap = mesh.hasEmissiveTexture();

View file

@ -68,11 +68,7 @@ public:
/// 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);
Q_INVOKABLE void setURL(const QUrl& url);
const QUrl& getURL() const { return _url; }
const QString& getURLAsString() const { return _urlAsString; }
@ -89,7 +85,7 @@ public:
render::Item::Status::Getters& statusGetters);
void removeFromScene(std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges);
void renderSetup(RenderArgs* args);
bool isRenderable() const { return !_meshStates.isEmpty() || (isActive() && _geometry->getMeshes().isEmpty()); }
bool isRenderable() const { return !_meshStates.isEmpty() || (isActive() && _geometry->getMeshes().empty()); }
bool isVisible() const { return _isVisible; }
@ -141,14 +137,11 @@ public:
const QUrl& getCollisionURL() const { return _collisionUrl; }
/// Returns a reference to the shared collision geometry.
const QSharedPointer<NetworkGeometry> getCollisionGeometry(bool delayLoad = true);
const QSharedPointer<NetworkGeometry> getCollisionGeometry(bool delayLoad = false);
void setOffset(const glm::vec3& offset);
const glm::vec3& getOffset() const { return _offset; }
/// Sets the distance parameter used for LOD computations.
void setLODDistance(float distance) { _lodDistance = distance; }
void setScaleToFit(bool scaleToFit, float largestDimension = 0.0f, bool forceRescale = false);
bool getScaleToFit() const { return _scaleToFit; } /// is scale to fit enabled
@ -309,20 +302,12 @@ protected:
// hook for derived classes to be notified when setUrl invalidates the current model.
virtual void onInvalidate() {};
void geometryRefreshed();
private:
void applyNextGeometry();
void deleteGeometry();
QVector<JointState> createJointStates(const FBXGeometry& geometry);
void initJointTransforms();
QSharedPointer<NetworkGeometry> _nextGeometry;
float _lodDistance;
float _lodHysteresis;
float _nextLODHysteresis;
QSharedPointer<NetworkGeometry> _collisionGeometry;
float _pupilDilation;

View file

@ -0,0 +1,95 @@
//
// ResoruceTests.cpp
//
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QNetworkDiskCache>
#include "ResourceCache.h"
#include "NetworkAccessManager.h"
#include "DependencyManager.h"
#include "ResourceTests.h"
QTEST_MAIN(ResourceTests)
void ResourceTests::initTestCase() {
auto resourceCacheSharedItems = DependencyManager::set<ResourceCacheSharedItems>();
const qint64 MAXIMUM_CACHE_SIZE = 1024 * 1024 * 1024; // 1GB
// set up the file cache
//QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
QString cachePath = "./resourceTestCache";
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkDiskCache* cache = new QNetworkDiskCache();
cache->setMaximumCacheSize(MAXIMUM_CACHE_SIZE);
cache->setCacheDirectory(cachePath);
cache->clear(); // clear the cache
networkAccessManager.setCache(cache);
}
static Resource* resource = nullptr;
static bool waitForSignal(QObject *sender, const char *signal, int timeout = 1000) {
QEventLoop loop;
QTimer timer;
timer.setInterval(timeout);
timer.setSingleShot(true);
loop.connect(sender, signal, SLOT(quit()));
loop.connect(&timer, SIGNAL(timeout()), SLOT(quit()));
timer.start();
loop.exec();
return timer.isActive();
}
void ResourceTests::downloadFirst() {
// download the Mery fst file
QUrl meryUrl = QUrl("http://hifi-public.s3.amazonaws.com/marketplace/contents/e21c0b95-e502-4d15-8c41-ea2fc40f1125/3585ddf674869a67d31d5964f7b52de1.fst");
resource = new Resource(meryUrl, false);
const int timeout = 1000;
QEventLoop loop;
QTimer timer;
timer.setInterval(timeout);
timer.setSingleShot(true);
loop.connect(resource, SIGNAL(loaded(QNetworkReply&)), SLOT(quit()));
loop.connect(resource, SIGNAL(failed(QNetworkReply::NetworkError)), SLOT(quit()));
loop.connect(&timer, SIGNAL(timeout()), SLOT(quit()));
timer.start();
loop.exec();
QVERIFY(resource->isLoaded());
}
void ResourceTests::downloadAgain() {
// download the Mery fst file
QUrl meryUrl = QUrl("http://hifi-public.s3.amazonaws.com/marketplace/contents/e21c0b95-e502-4d15-8c41-ea2fc40f1125/3585ddf674869a67d31d5964f7b52de1.fst");
resource = new Resource(meryUrl, false);
const int timeout = 1000;
QEventLoop loop;
QTimer timer;
timer.setInterval(timeout);
timer.setSingleShot(true);
loop.connect(resource, SIGNAL(loaded(QNetworkReply&)), SLOT(quit()));
loop.connect(resource, SIGNAL(failed(QNetworkReply::NetworkError)), SLOT(quit()));
loop.connect(&timer, SIGNAL(timeout()), SLOT(quit()));
timer.start();
loop.exec();
QVERIFY(resource->isLoaded());
}

View file

@ -0,0 +1,23 @@
//
// ResourceTests.h
//
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_ResourceTests_h
#define hifi_ResourceTests_h
#include <QtTest/QtTest>
class ResourceTests : public QObject {
Q_OBJECT
private slots:
void initTestCase();
void downloadFirst();
void downloadAgain();
};
#endif // hifi_ResourceTests_h

View file

@ -36,15 +36,16 @@ bool vhacd::VHACDUtil::loadFBX(const QString filename, FBXGeometry& result) {
std::cout << "Reading FBX.....\n";
QByteArray fbxContents = fbx.readAll();
FBXGeometry* geom;
if (filename.toLower().endsWith(".obj")) {
result = OBJReader().readOBJ(fbxContents, QVariantHash());
geom = OBJReader().readOBJ(fbxContents, QVariantHash());
} else if (filename.toLower().endsWith(".fbx")) {
result = readFBX(fbxContents, QVariantHash(), filename);
geom = readFBX(fbxContents, QVariantHash(), filename);
} else {
qDebug() << "unknown file extension";
return false;
}
result = *geom;
reSortFBXGeometryMeshes(result);