Fetch model geometries through cache

This commit is contained in:
Zach Pomerantz 2016-03-23 14:01:22 -07:00
parent fc8b34f8c7
commit 7f05e4453b
2 changed files with 493 additions and 594 deletions

View file

@ -1,53 +1,101 @@
//
// ModelCache.cpp
// interface/src/renderer
// libraries/model-networking
//
// Created by Andrzej Kapolka on 6/21/13.
// Copyright 2013 High Fidelity, Inc.
// Created by Zach Pomerantz on 3/15/16.
// Copyright 2016 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 "ModelCache.h"
#include <FSTReader.h>
#include "FBXReader.h"
#include "OBJReader.h"
#include <cmath>
#include <gpu/Batch.h>
#include <gpu/Stream.h>
#include <QNetworkReply>
#include <QThreadPool>
#include <FSTReader.h>
#include <NumericalConstants.h>
#include "TextureCache.h"
#include "ModelNetworkingLogging.h"
#include "model/TextureMap.h"
class GeometryReader;
//#define WANT_DEBUG
class GeometryExtra {
public:
const QVariantHash& mapping;
const QUrl& textureBaseUrl;
};
ModelCache::ModelCache()
{
const qint64 GEOMETRY_DEFAULT_UNUSED_MAX_SIZE = DEFAULT_UNUSED_MAX_SIZE;
setUnusedResourceCacheSize(GEOMETRY_DEFAULT_UNUSED_MAX_SIZE);
class GeometryMappingResource : public GeometryResource {
Q_OBJECT
public:
GeometryMappingResource(const QUrl& url) : GeometryResource(url) {};
virtual void downloadFinished(const QByteArray& data) override;
private slots:
void onGeometryMappingLoaded(bool success);
private:
GeometryResource::Pointer _geometryResource;
};
void GeometryMappingResource::downloadFinished(const QByteArray& data) {
auto mapping = FSTReader::readMapping(data);
QString filename = mapping.value("filename").toString();
if (filename.isNull()) {
qCDebug(modelnetworking) << "Mapping file" << _url << "has no \"filename\" field";
finishedLoading(false);
} else {
QUrl url = _url.resolved(filename);
QUrl textureBaseUrl;
QString texdir = mapping.value("texdir").toString();
if (!texdir.isNull()) {
if (!texdir.endsWith('/')) {
texdir += '/';
}
textureBaseUrl = _url.resolved(texdir);
}
auto modelCache = DependencyManager::get<ModelCache>();
GeometryExtra extra{ mapping, textureBaseUrl };
// Get the raw GeometryResource, not the wrapped NetworkGeometry
_geometryResource = modelCache->getResource(url, QUrl(), true, &extra).staticCast<GeometryResource>();
connect(_geometryResource.data(), &Resource::finished, this, &GeometryMappingResource::onGeometryMappingLoaded);
}
}
ModelCache::~ModelCache() {
void GeometryMappingResource::onGeometryMappingLoaded(bool success) {
if (success) {
_geometry = _geometryResource->_geometry;
_shapes = _geometryResource->_shapes;
_meshes = _geometryResource->_meshes;
_materials = _geometryResource->_materials;
}
finishedLoading(success);
}
QSharedPointer<Resource> ModelCache::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>();
}
class GeometryReader : public QRunnable {
public:
GeometryReader(QWeakPointer<Resource>& resource, const QUrl& url, const QVariantHash& mapping,
const QByteArray& data) :
_resource(resource), _url(url), _mapping(mapping), _data(data) {}
virtual ~GeometryReader() = default;
virtual void run() override;
GeometryReader::GeometryReader(const QUrl& url, const QByteArray& data, const QVariantHash& mapping) :
_url(url),
_data(data),
_mapping(mapping) {
}
private:
QWeakPointer<Resource> _resource;
QUrl _url;
QVariantHash _mapping;
QByteArray _data;
};
void GeometryReader::run() {
auto originalPriority = QThread::currentThread()->priority();
@ -55,458 +103,353 @@ void GeometryReader::run() {
originalPriority = QThread::NormalPriority;
}
QThread::currentThread()->setPriority(QThread::LowPriority);
// Ensure the resource is still being requested
auto resource = _resource.toStrongRef();
if (!resource) {
qCWarning(modelnetworking) << "Abandoning load of" << _url << "; could not get strong ref";
return;
}
try {
if (_data.isEmpty()) {
throw QString("Reply is NULL ?!");
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;
QString urlname = _url.path().toLower();
if (!urlname.isEmpty() && !_url.path().isEmpty() &&
(_url.path().toLower().endsWith(".fbx") || _url.path().toLower().endsWith(".obj"))) {
FBXGeometry* fbxGeometry = nullptr;
if (_url.path().toLower().endsWith(".fbx")) {
const bool grabLightmaps = true;
const float lightmapLevel = 1.0f;
fbxgeo = readFBX(_data, _mapping, _url.path(), grabLightmaps, lightmapLevel);
if (fbxgeo->meshes.size() == 0 && fbxgeo->joints.size() == 0) {
// empty fbx geometry, indicates error
fbxGeometry = readFBX(_data, _mapping, _url.path());
if (fbxGeometry->meshes.size() == 0 && fbxGeometry->joints.size() == 0) {
throw QString("empty geometry, possibly due to an unsupported FBX version");
}
} else if (_url.path().toLower().endsWith(".obj")) {
fbxgeo = OBJReader().readOBJ(_data, _mapping, _url);
fbxGeometry = OBJReader().readOBJ(_data, _mapping, _url);
} else {
QString errorStr("unsupported format");
throw errorStr;
throw QString("unsupported format");
}
emit onSuccess(fbxgeo);
QMetaObject::invokeMethod(resource.data(), "setGeometryDefinition",
Q_ARG(void*, fbxGeometry));
} else {
throw QString("url is invalid");
}
} catch (const QString& error) {
qCDebug(modelnetworking) << "Error reading " << _url << ": " << error;
emit onError(NetworkGeometry::ModelParseError, error);
QMetaObject::invokeMethod(resource.data(), "finishedLoading", Q_ARG(bool, false));
}
QThread::currentThread()->setPriority(originalPriority);
}
NetworkGeometry::NetworkGeometry(const QUrl& url, bool delayLoad, const QVariantHash& mapping, const QUrl& textureBaseUrl) :
_url(url),
_mapping(mapping),
_textureBaseUrl(textureBaseUrl.isValid() ? textureBaseUrl : url) {
class GeometryDefinitionResource : public GeometryResource {
Q_OBJECT
public:
GeometryDefinitionResource(const QUrl& url, const QVariantHash& mapping, const QUrl& textureBaseUrl) :
GeometryResource(url), _mapping(mapping), _textureBaseUrl(textureBaseUrl) {}
if (delayLoad) {
_state = DelayState;
} else {
attemptRequestInternal();
}
virtual void downloadFinished(const QByteArray& data) override;
protected:
Q_INVOKABLE void setGeometryDefinition(void* fbxGeometry);
private:
QVariantHash _mapping;
QUrl _textureBaseUrl;
};
void GeometryDefinitionResource::downloadFinished(const QByteArray& data) {
QThreadPool::globalInstance()->start(new GeometryReader(_self, _url, _mapping, data));
}
NetworkGeometry::~NetworkGeometry() {
if (_resource) {
_resource->deleteLater();
}
}
void GeometryDefinitionResource::setGeometryDefinition(void* fbxGeometry) {
// Assume ownership of the geometry pointer
_geometry.reset(static_cast<FBXGeometry*>(fbxGeometry));
void NetworkGeometry::attemptRequest() {
if (_state == DelayState) {
attemptRequestInternal();
}
}
void NetworkGeometry::attemptRequestInternal() {
if (_url.path().toLower().endsWith(".fst")) {
_mappingUrl = _url;
requestMapping(_url);
} else {
_modelUrl = _url;
requestModel(_url);
}
}
bool NetworkGeometry::isLoaded() const {
return _state == SuccessState;
}
bool NetworkGeometry::isLoadedWithTextures() const {
if (!isLoaded()) {
return false;
// Copy materials
QHash<QString, size_t> materialIDAtlas;
for (const FBXMaterial& material : _geometry->materials) {
materialIDAtlas[material.materialID] = _materials.size();
_materials.push_back(std::make_shared<NetworkMaterial>(material, _textureBaseUrl));
}
if (!_isLoadedWithTextures) {
_hasTransparentTextures = false;
for (auto&& material : _materials) {
if ((material->albedoTexture && !material->albedoTexture->isLoaded()) ||
(material->normalTexture && !material->normalTexture->isLoaded()) ||
(material->roughnessTexture && !material->roughnessTexture->isLoaded()) ||
(material->metallicTexture && !material->metallicTexture->isLoaded()) ||
(material->occlusionTexture && !material->occlusionTexture->isLoaded()) ||
(material->emissiveTexture && !material->emissiveTexture->isLoaded()) ||
(material->lightmapTexture && !material->lightmapTexture->isLoaded())) {
return false;
}
if (material->albedoTexture && material->albedoTexture->getGPUTexture()) {
// Reassign the texture to make sure that itsalbedo alpha channel material key is detected correctly
material->_material->setTextureMap(model::MaterialKey::ALBEDO_MAP, material->_material->getTextureMap(model::MaterialKey::ALBEDO_MAP));
const auto& usage = material->albedoTexture->getGPUTexture()->getUsage();
bool isTransparentTexture = usage.isAlpha() && !usage.isAlphaMask();
_hasTransparentTextures |= isTransparentTexture;
}
}
_isLoadedWithTextures = true;
}
return true;
}
void NetworkGeometry::setTextureWithNameToURL(const QString& name, const QUrl& url) {
if (_meshes.size() > 0) {
auto textureCache = DependencyManager::get<TextureCache>();
for (auto&& material : _materials) {
auto networkMaterial = material->_material;
auto oldTextureMaps = networkMaterial->getTextureMaps();
if (material->albedoTextureName == name) {
material->albedoTexture = textureCache->getTexture(url, DEFAULT_TEXTURE);
auto albedoMap = model::TextureMapPointer(new model::TextureMap());
albedoMap->setTextureSource(material->albedoTexture->_textureSource);
albedoMap->setTextureTransform(oldTextureMaps[model::MaterialKey::ALBEDO_MAP]->getTextureTransform());
// when reassigning the albedo texture we also check for the alpha channel used as opacity
albedoMap->setUseAlphaChannel(true);
networkMaterial->setTextureMap(model::MaterialKey::ALBEDO_MAP, albedoMap);
} else if (material->normalTextureName == name) {
material->normalTexture = textureCache->getTexture(url);
auto normalMap = model::TextureMapPointer(new model::TextureMap());
normalMap->setTextureSource(material->normalTexture->_textureSource);
networkMaterial->setTextureMap(model::MaterialKey::NORMAL_MAP, normalMap);
} else if (material->roughnessTextureName == name) {
// FIXME: If passing a gloss map instead of a roughmap how to say that ? looking for gloss in the name ?
material->roughnessTexture = textureCache->getTexture(url, ROUGHNESS_TEXTURE);
auto roughnessMap = model::TextureMapPointer(new model::TextureMap());
roughnessMap->setTextureSource(material->roughnessTexture->_textureSource);
networkMaterial->setTextureMap(model::MaterialKey::ROUGHNESS_MAP, roughnessMap);
} else if (material->metallicTextureName == name) {
// FIXME: If passing a specular map instead of a metallic how to say that ? looking for wtf in the name ?
material->metallicTexture = textureCache->getTexture(url, METALLIC_TEXTURE);
auto glossMap = model::TextureMapPointer(new model::TextureMap());
glossMap->setTextureSource(material->metallicTexture->_textureSource);
networkMaterial->setTextureMap(model::MaterialKey::METALLIC_MAP, glossMap);
} else if (material->emissiveTextureName == name) {
material->emissiveTexture = textureCache->getTexture(url, EMISSIVE_TEXTURE);
auto emissiveMap = model::TextureMapPointer(new model::TextureMap());
emissiveMap->setTextureSource(material->emissiveTexture->_textureSource);
networkMaterial->setTextureMap(model::MaterialKey::EMISSIVE_MAP, emissiveMap);
} else if (material->lightmapTextureName == name) {
material->lightmapTexture = textureCache->getTexture(url, LIGHTMAP_TEXTURE);
auto lightmapMap = model::TextureMapPointer(new model::TextureMap());
lightmapMap->setTextureSource(material->lightmapTexture->_textureSource);
lightmapMap->setTextureTransform(
oldTextureMaps[model::MaterialKey::LIGHTMAP_MAP]->getTextureTransform());
glm::vec2 oldOffsetScale =
oldTextureMaps[model::MaterialKey::LIGHTMAP_MAP]->getLightmapOffsetScale();
lightmapMap->setLightmapOffsetScale(oldOffsetScale.x, oldOffsetScale.y);
networkMaterial->setTextureMap(model::MaterialKey::LIGHTMAP_MAP, lightmapMap);
}
}
} else {
qCWarning(modelnetworking) << "Ignoring setTextureWithNameToURL() geometry not ready." << name << url;
}
_isLoadedWithTextures = false;
}
QStringList NetworkGeometry::getTextureNames() const {
QStringList result;
for (auto&& material : _materials) {
if (!material->emissiveTextureName.isEmpty() && material->emissiveTexture) {
QString textureURL = material->emissiveTexture->getURL().toString();
result << material->emissiveTextureName + ":\"" + textureURL + "\"";
}
if (!material->albedoTextureName.isEmpty() && material->albedoTexture) {
QString textureURL = material->albedoTexture->getURL().toString();
result << material->albedoTextureName + ":\"" + textureURL + "\"";
}
if (!material->normalTextureName.isEmpty() && material->normalTexture) {
QString textureURL = material->normalTexture->getURL().toString();
result << material->normalTextureName + ":\"" + textureURL + "\"";
}
if (!material->roughnessTextureName.isEmpty() && material->roughnessTexture) {
QString textureURL = material->roughnessTexture->getURL().toString();
result << material->roughnessTextureName + ":\"" + textureURL + "\"";
}
if (!material->metallicTextureName.isEmpty() && material->metallicTexture) {
QString textureURL = material->metallicTexture->getURL().toString();
result << material->metallicTextureName + ":\"" + textureURL + "\"";
}
if (!material->occlusionTextureName.isEmpty() && material->occlusionTexture) {
QString textureURL = material->occlusionTexture->getURL().toString();
result << material->occlusionTextureName + ":\"" + textureURL + "\"";
}
if (!material->lightmapTextureName.isEmpty() && material->lightmapTexture) {
QString textureURL = material->lightmapTexture->getURL().toString();
result << material->lightmapTextureName + ":\"" + textureURL + "\"";
}
}
return result;
}
void NetworkGeometry::requestMapping(const QUrl& url) {
_state = RequestMappingState;
if (_resource) {
_resource->deleteLater();
}
_resource = new Resource(url, false);
connect(_resource, &Resource::loaded, this, &NetworkGeometry::mappingRequestDone);
connect(_resource, &Resource::failed, this, &NetworkGeometry::mappingRequestError);
}
void NetworkGeometry::requestModel(const QUrl& url) {
_state = RequestModelState;
if (_resource) {
_resource->deleteLater();
}
_modelUrl = url;
_resource = new Resource(url, false);
connect(_resource, &Resource::loaded, this, &NetworkGeometry::modelRequestDone);
connect(_resource, &Resource::failed, this, &NetworkGeometry::modelRequestError);
}
void NetworkGeometry::mappingRequestDone(const QByteArray& data) {
assert(_state == RequestMappingState);
// parse the mapping file
_mapping = FSTReader::readMapping(data);
QUrl replyUrl = _mappingUrl;
QString modelUrlStr = _mapping.value("filename").toString();
if (modelUrlStr.isNull()) {
qCDebug(modelnetworking) << "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 += '/';
}
_textureBaseUrl = replyUrl.resolved(texdir);
}
_modelUrl = replyUrl.resolved(modelUrlStr);
requestModel(_modelUrl);
}
}
void NetworkGeometry::mappingRequestError(QNetworkReply::NetworkError error) {
assert(_state == RequestMappingState);
_state = ErrorState;
emit onFailure(*this, MappingRequestError);
}
void NetworkGeometry::modelRequestDone(const QByteArray& data) {
assert(_state == RequestModelState);
_state = ParsingModelState;
// asynchronously parse the model file.
GeometryReader* geometryReader = new GeometryReader(_modelUrl, data, _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) {
NetworkMesh* networkMesh = new NetworkMesh();
networkMesh->_mesh = mesh._mesh;
return networkMesh;
}
static model::TextureMapPointer setupNetworkTextureMap(NetworkGeometry* geometry, const QUrl& textureBaseUrl,
const FBXTexture& texture, TextureType type,
NetworkTexturePointer& networkTexture, QString& networkTextureName) {
auto textureCache = DependencyManager::get<TextureCache>();
// If content is inline, cache it under the fbx file, not its base url
const auto baseUrl = texture.content.isEmpty() ? textureBaseUrl : QUrl(textureBaseUrl.url() + "/");
const auto filename = baseUrl.resolved(QUrl(texture.filename));
networkTexture = textureCache->getTexture(filename, type, texture.content);
QObject::connect(networkTexture.data(), &NetworkTexture::networkTextureCreated, geometry, &NetworkGeometry::textureLoaded);
networkTextureName = texture.name;
auto map = std::make_shared<model::TextureMap>();
map->setTextureSource(networkTexture->_textureSource);
return map;
}
static NetworkMaterial* buildNetworkMaterial(NetworkGeometry* geometry, const FBXMaterial& material, const QUrl& textureBaseUrl) {
NetworkMaterial* networkMaterial = new NetworkMaterial();
networkMaterial->_material = material._material;
if (!material.albedoTexture.filename.isEmpty()) {
auto albedoMap = setupNetworkTextureMap(geometry, textureBaseUrl, material.albedoTexture, DEFAULT_TEXTURE,
networkMaterial->albedoTexture, networkMaterial->albedoTextureName);
albedoMap->setTextureTransform(material.albedoTexture.transform);
if (!material.opacityTexture.filename.isEmpty()) {
if (material.albedoTexture.filename == material.opacityTexture.filename) {
// Best case scenario, just indicating that the albedo map contains transparency
albedoMap->setUseAlphaChannel(true);
} else {
// Opacity Map is different from the Abledo map, not supported
}
}
material._material->setTextureMap(model::MaterialKey::ALBEDO_MAP, albedoMap);
}
if (!material.normalTexture.filename.isEmpty()) {
auto normalMap = setupNetworkTextureMap(geometry, textureBaseUrl, material.normalTexture,
(material.normalTexture.isBumpmap ? BUMP_TEXTURE : NORMAL_TEXTURE),
networkMaterial->normalTexture, networkMaterial->normalTextureName);
networkMaterial->_material->setTextureMap(model::MaterialKey::NORMAL_MAP, normalMap);
}
// Roughness first or gloss maybe
if (!material.roughnessTexture.filename.isEmpty()) {
auto roughnessMap = setupNetworkTextureMap(geometry, textureBaseUrl, material.roughnessTexture, ROUGHNESS_TEXTURE,
networkMaterial->roughnessTexture, networkMaterial->roughnessTextureName);
material._material->setTextureMap(model::MaterialKey::ROUGHNESS_MAP, roughnessMap);
} else if (!material.glossTexture.filename.isEmpty()) {
auto roughnessMap = setupNetworkTextureMap(geometry, textureBaseUrl, material.glossTexture, GLOSS_TEXTURE,
networkMaterial->roughnessTexture, networkMaterial->roughnessTextureName);
material._material->setTextureMap(model::MaterialKey::ROUGHNESS_MAP, roughnessMap);
}
// Metallic first or specular maybe
if (!material.metallicTexture.filename.isEmpty()) {
auto metallicMap = setupNetworkTextureMap(geometry, textureBaseUrl, material.metallicTexture, METALLIC_TEXTURE,
networkMaterial->metallicTexture, networkMaterial->metallicTextureName);
material._material->setTextureMap(model::MaterialKey::METALLIC_MAP, metallicMap);
} else if (!material.specularTexture.filename.isEmpty()) {
auto metallicMap = setupNetworkTextureMap(geometry, textureBaseUrl, material.specularTexture, SPECULAR_TEXTURE,
networkMaterial->metallicTexture, networkMaterial->metallicTextureName);
material._material->setTextureMap(model::MaterialKey::METALLIC_MAP, metallicMap);
}
if (!material.occlusionTexture.filename.isEmpty()) {
auto occlusionMap = setupNetworkTextureMap(geometry, textureBaseUrl, material.occlusionTexture, OCCLUSION_TEXTURE,
networkMaterial->occlusionTexture, networkMaterial->occlusionTextureName);
material._material->setTextureMap(model::MaterialKey::OCCLUSION_MAP, occlusionMap);
}
if (!material.emissiveTexture.filename.isEmpty()) {
auto emissiveMap = setupNetworkTextureMap(geometry, textureBaseUrl, material.emissiveTexture, EMISSIVE_TEXTURE,
networkMaterial->emissiveTexture, networkMaterial->emissiveTextureName);
material._material->setTextureMap(model::MaterialKey::EMISSIVE_MAP, emissiveMap);
}
if (!material.lightmapTexture.filename.isEmpty()) {
auto lightmapMap = setupNetworkTextureMap(geometry, textureBaseUrl, material.lightmapTexture, LIGHTMAP_TEXTURE,
networkMaterial->lightmapTexture, networkMaterial->lightmapTextureName);
lightmapMap->setTextureTransform(material.lightmapTexture.transform);
lightmapMap->setLightmapOffsetScale(material.lightmapParams.x, material.lightmapParams.y);
material._material->setTextureMap(model::MaterialKey::LIGHTMAP_MAP, lightmapMap);
}
return networkMaterial;
}
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));
}
QHash<QString, size_t> fbxMatIDToMatID;
foreach(const FBXMaterial& material, _geometry->materials) {
fbxMatIDToMatID[material.materialID] = _materials.size();
_materials.emplace_back(buildNetworkMaterial(this, material, _textureBaseUrl));
}
std::shared_ptr<NetworkMeshes> meshes = std::make_shared<NetworkMeshes>();
std::shared_ptr<NetworkShapes> shapes = std::make_shared<NetworkShapes>();
int meshID = 0;
foreach(const FBXMesh& mesh, _geometry->meshes) {
for (const FBXMesh& mesh : _geometry->meshes) {
// Copy mesh pointers
meshes->emplace_back(mesh._mesh);
int partID = 0;
foreach (const FBXMeshPart& part, mesh.parts) {
NetworkShape* networkShape = new NetworkShape();
networkShape->_meshID = meshID;
networkShape->_partID = partID;
networkShape->_materialID = (int)fbxMatIDToMatID[part.materialID];
_shapes.emplace_back(networkShape);
for (const FBXMeshPart& part : mesh.parts) {
// Construct local shapes
shapes->emplace_back(meshID, partID, (int)materialIDAtlas[part.materialID]);
partID++;
}
meshID++;
}
_meshes = meshes;
_shapes = shapes;
_state = SuccessState;
emit onSuccess(*this, *_geometry.get());
delete _resource;
_resource = nullptr;
finishedLoading(true);
}
void NetworkGeometry::modelParseError(int error, QString str) {
_state = ErrorState;
emit onFailure(*this, (NetworkGeometry::Error)error);
delete _resource;
_resource = nullptr;
ModelCache::ModelCache() {
const qint64 GEOMETRY_DEFAULT_UNUSED_MAX_SIZE = DEFAULT_UNUSED_MAX_SIZE;
setUnusedResourceCacheSize(GEOMETRY_DEFAULT_UNUSED_MAX_SIZE);
}
const NetworkMaterial* NetworkGeometry::getShapeMaterial(int shapeID) {
if ((shapeID >= 0) && (shapeID < (int)_shapes.size())) {
int materialID = _shapes[shapeID]->_materialID;
if ((materialID >= 0) && ((unsigned int)materialID < _materials.size())) {
return _materials[materialID].get();
} else {
return 0;
QSharedPointer<Resource> ModelCache::createResource(const QUrl& url, const QSharedPointer<Resource>& fallback,
bool delayLoad, const void* extra) {
const GeometryExtra* geometryExtra = static_cast<const GeometryExtra*>(extra);
Resource* resource = nullptr;
if (url.path().toLower().endsWith(".fst")) {
resource = new GeometryMappingResource(url);
} else {
resource = new GeometryDefinitionResource(url, geometryExtra->mapping, geometryExtra->textureBaseUrl);
}
return QSharedPointer<Resource>(resource, &Resource::allReferencesCleared);
}
std::shared_ptr<NetworkGeometry> ModelCache::getGeometry(const QUrl& url, const QVariantHash& mapping, const QUrl& textureBaseUrl) {
GeometryExtra geometryExtra = { mapping, textureBaseUrl };
GeometryResource::Pointer resource = getResource(url, QUrl(), true, &geometryExtra).staticCast<GeometryResource>();
return std::make_shared<NetworkGeometry>(resource);
}
const QVariantMap Geometry::getTextures() const {
QVariantMap textures;
for (const auto& material : _materials) {
for (const auto& texture : material->_textures) {
if (texture.texture) {
textures[texture.name] = texture.texture->getURL();
}
}
}
return textures;
}
void Geometry::setTextures(const QVariantMap& textureMap) {
if (_meshes->size() > 0) {
for (auto& material : _materials) {
// Check if any material textures actually changed
if (std::any_of(material->_textures.cbegin(), material->_textures.cend(),
[&textureMap](const NetworkMaterial::Textures::value_type& it) { return it.texture && textureMap.contains(it.name); })) {
material = std::make_shared<NetworkMaterial>(*material, textureMap);
_areTexturesLoaded = false;
}
}
} else {
return 0;
qCWarning(modelnetworking) << "Ignoring setTextures(); geometry not ready";
}
}
void NetworkGeometry::textureLoaded(const QWeakPointer<NetworkTexture>& networkTexture) {
numTextureLoaded++;
bool Geometry::areTexturesLoaded() const {
if (!_areTexturesLoaded) {
_hasTransparentTextures = false;
for (auto& material : _materials) {
// Check if material textures are loaded
if (std::any_of(material->_textures.cbegin(), material->_textures.cend(),
[](const NetworkMaterial::Textures::value_type& it) { return it.texture && !it.texture->isLoaded(); })) {
return false;
}
// If material textures are loaded, check the material translucency
const auto albedoTexture = material->_textures[NetworkMaterial::MapChannel::ALBEDO_MAP];
if (albedoTexture.texture && albedoTexture.texture->getGPUTexture()) {
material->resetOpacityMap();
_hasTransparentTextures |= material->getKey().isTranslucent();
}
}
_areTexturesLoaded = true;
}
return true;
}
const std::shared_ptr<const NetworkMaterial> Geometry::getShapeMaterial(int shapeID) const {
if ((shapeID >= 0) && (shapeID < (int)_shapes->size())) {
int materialID = _shapes->at(shapeID).materialID;
if ((materialID >= 0) && (materialID < (int)_materials.size())) {
return _materials[materialID];
}
}
return nullptr;
}
NetworkGeometry::NetworkGeometry(const GeometryResource::Pointer& networkGeometry) : _resource(networkGeometry) {
connect(_resource.data(), &Resource::finished, this, &NetworkGeometry::resourceFinished);
connect(_resource.data(), &Resource::onRefresh, this, &NetworkGeometry::resourceRefreshed);
if (_resource->isLoaded()) {
resourceFinished();
}
}
void NetworkGeometry::resourceFinished() {
_instance = std::make_shared<Geometry>(*_resource);
}
void NetworkGeometry::resourceRefreshed() {
_instance.reset();
}
const QString NetworkMaterial::NO_TEXTURE = QString();
const QString& NetworkMaterial::getTextureName(MapChannel channel) {
if (_textures[channel].texture) {
return _textures[channel].name;
}
return NO_TEXTURE;
}
QUrl NetworkMaterial::getTextureUrl(const QUrl& url, const FBXTexture& texture) {
// If content is inline, cache it under the fbx file, not its url
const auto baseUrl = texture.content.isEmpty() ? url: QUrl(url.url() + "/");
return baseUrl.resolved(QUrl(texture.filename));
}
model::TextureMapPointer NetworkMaterial::fetchTextureMap(const QUrl& baseUrl, const FBXTexture& fbxTexture,
TextureType type, MapChannel channel) {
const auto url = getTextureUrl(baseUrl, fbxTexture);
const auto texture = DependencyManager::get<TextureCache>()->getTexture(url, type, fbxTexture.content);
_textures[channel] = Texture { fbxTexture.name, texture };
auto map = std::make_shared<model::TextureMap>();
map->setTextureSource(texture->_textureSource);
return map;
}
model::TextureMapPointer NetworkMaterial::fetchTextureMap(const QUrl& url, TextureType type, MapChannel channel) {
const auto texture = DependencyManager::get<TextureCache>()->getTexture(url, type);
_textures[channel].texture = texture;
auto map = std::make_shared<model::TextureMap>();
map->setTextureSource(texture->_textureSource);
return map;
}
NetworkMaterial::NetworkMaterial(const FBXMaterial& material, const QUrl& textureBaseUrl) {
_textures = Textures(MapChannel::NUM_MAP_CHANNELS);
if (!material.albedoTexture.filename.isEmpty()) {
auto map = fetchTextureMap(textureBaseUrl, material.albedoTexture, DEFAULT_TEXTURE, MapChannel::ALBEDO_MAP);
map->setTextureTransform(material.albedoTexture.transform);
if (!material.opacityTexture.filename.isEmpty()) {
if (material.albedoTexture.filename == material.opacityTexture.filename) {
// Best case scenario, just indicating that the albedo map contains transparency
// TODO: Different albedo/opacity maps are not currently supported
map->setUseAlphaChannel(true);
}
}
setTextureMap(MapChannel::ALBEDO_MAP, map);
}
if (!material.normalTexture.filename.isEmpty()) {
auto type = (material.normalTexture.isBumpmap ? BUMP_TEXTURE : NORMAL_TEXTURE);
auto map = fetchTextureMap(textureBaseUrl, material.normalTexture, type, MapChannel::NORMAL_MAP);
setTextureMap(MapChannel::NORMAL_MAP, map);
}
if (!material.roughnessTexture.filename.isEmpty()) {
auto map = fetchTextureMap(textureBaseUrl, material.roughnessTexture, ROUGHNESS_TEXTURE, MapChannel::ROUGHNESS_MAP);
setTextureMap(MapChannel::ROUGHNESS_MAP, map);
} else if (!material.glossTexture.filename.isEmpty()) {
auto map = fetchTextureMap(textureBaseUrl, material.glossTexture, GLOSS_TEXTURE, MapChannel::ROUGHNESS_MAP);
setTextureMap(MapChannel::ROUGHNESS_MAP, map);
}
if (!material.metallicTexture.filename.isEmpty()) {
auto map = fetchTextureMap(textureBaseUrl, material.metallicTexture, METALLIC_TEXTURE, MapChannel::METALLIC_MAP);
setTextureMap(MapChannel::METALLIC_MAP, map);
} else if (!material.specularTexture.filename.isEmpty()) {
auto map = fetchTextureMap(textureBaseUrl, material.specularTexture, SPECULAR_TEXTURE, MapChannel::METALLIC_MAP);
setTextureMap(MapChannel::METALLIC_MAP, map);
}
if (!material.occlusionTexture.filename.isEmpty()) {
auto map = fetchTextureMap(textureBaseUrl, material.occlusionTexture, OCCLUSION_TEXTURE, MapChannel::OCCLUSION_MAP);
setTextureMap(MapChannel::OCCLUSION_MAP, map);
}
if (!material.emissiveTexture.filename.isEmpty()) {
auto map = fetchTextureMap(textureBaseUrl, material.emissiveTexture, EMISSIVE_TEXTURE, MapChannel::EMISSIVE_MAP);
setTextureMap(MapChannel::EMISSIVE_MAP, map);
}
if (!material.lightmapTexture.filename.isEmpty()) {
auto map = fetchTextureMap(textureBaseUrl, material.lightmapTexture, LIGHTMAP_TEXTURE, MapChannel::LIGHTMAP_MAP);
map->setTextureTransform(material.lightmapTexture.transform);
map->setLightmapOffsetScale(material.lightmapParams.x, material.lightmapParams.y);
setTextureMap(MapChannel::LIGHTMAP_MAP, map);
}
}
NetworkMaterial::NetworkMaterial(const NetworkMaterial& material, const QVariantMap& textureMap) : NetworkMaterial(material) {
_textures = material._textures;
const auto& albedoName = getTextureName(MapChannel::ALBEDO_MAP);
const auto& normalName = getTextureName(MapChannel::NORMAL_MAP);
const auto& roughnessName = getTextureName(MapChannel::ROUGHNESS_MAP);
const auto& metallicName = getTextureName(MapChannel::METALLIC_MAP);
const auto& occlusionName = getTextureName(MapChannel::OCCLUSION_MAP);
const auto& emissiveName = getTextureName(MapChannel::EMISSIVE_MAP);
const auto& lightmapName = getTextureName(MapChannel::LIGHTMAP_MAP);
if (!albedoName.isEmpty() && textureMap.contains(albedoName)) {
auto map = fetchTextureMap(textureMap[albedoName].toUrl(), DEFAULT_TEXTURE, MapChannel::ALBEDO_MAP);
map->setTextureTransform(getTextureMap(MapChannel::ALBEDO_MAP)->getTextureTransform());
// when reassigning the albedo texture we also check for the alpha channel used as opacity
map->setUseAlphaChannel(true);
setTextureMap(MapChannel::ALBEDO_MAP, map);
}
if (!normalName.isEmpty() && textureMap.contains(normalName)) {
auto map = fetchTextureMap(textureMap[normalName].toUrl(), DEFAULT_TEXTURE, MapChannel::NORMAL_MAP);
setTextureMap(MapChannel::NORMAL_MAP, map);
}
if (!roughnessName.isEmpty() && textureMap.contains(roughnessName)) {
// FIXME: If passing a gloss map instead of a roughmap how do we know?
auto map = fetchTextureMap(textureMap[roughnessName].toUrl(), ROUGHNESS_TEXTURE, MapChannel::ROUGHNESS_MAP);
setTextureMap(MapChannel::ROUGHNESS_MAP, map);
}
if (!metallicName.isEmpty() && textureMap.contains(metallicName)) {
// FIXME: If passing a specular map instead of a metallic how do we know?
auto map = fetchTextureMap(textureMap[metallicName].toUrl(), METALLIC_TEXTURE, MapChannel::METALLIC_MAP);
setTextureMap(MapChannel::METALLIC_MAP, map);
}
if (!occlusionName.isEmpty() && textureMap.contains(occlusionName)) {
auto map = fetchTextureMap(textureMap[occlusionName].toUrl(), OCCLUSION_TEXTURE, MapChannel::OCCLUSION_MAP);
setTextureMap(MapChannel::OCCLUSION_MAP, map);
}
if (!emissiveName.isEmpty() && textureMap.contains(emissiveName)) {
auto map = fetchTextureMap(textureMap[emissiveName].toUrl(), EMISSIVE_TEXTURE, MapChannel::EMISSIVE_MAP);
setTextureMap(MapChannel::EMISSIVE_MAP, map);
}
if (!lightmapName.isEmpty() && textureMap.contains(lightmapName)) {
auto map = fetchTextureMap(textureMap[lightmapName].toUrl(), LIGHTMAP_TEXTURE, MapChannel::LIGHTMAP_MAP);
auto oldMap = getTextureMap(MapChannel::LIGHTMAP_MAP);
map->setTextureTransform(oldMap->getTextureTransform());
glm::vec2 offsetScale = oldMap->getLightmapOffsetScale();
map->setLightmapOffsetScale(offsetScale.x, offsetScale.y);
setTextureMap(MapChannel::LIGHTMAP_MAP, map);
}
}
#include "ModelCache.moc"

View file

@ -1,9 +1,9 @@
//
// ModelCache.h
// libraries/model-networking/src/model-networking
// libraries/model-networking
//
// Created by Sam Gateau on 9/21/15.
// Copyright 2013 High Fidelity, Inc.
// Created by Zach Pomerantz on 3/15/16.
// Copyright 2016 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
@ -12,200 +12,156 @@
#ifndef hifi_ModelCache_h
#define hifi_ModelCache_h
#include <QMap>
#include <QRunnable>
#include <DependencyManager.h>
#include <ResourceCache.h>
#include "FBXReader.h"
#include "OBJReader.h"
#include <gpu/Batch.h>
#include <gpu/Stream.h>
#include <model/Material.h>
#include <model/Asset.h>
class NetworkGeometry;
class NetworkMesh;
#include "FBXReader.h"
#include "TextureCache.h"
// Alias instead of derive to avoid copying
using NetworkMesh = model::Mesh;
class NetworkTexture;
class NetworkMaterial;
class NetworkShape;
class NetworkGeometry;
/// Stores cached geometry.
class GeometryMappingResource;
/// Stores cached model geometries.
class ModelCache : public ResourceCache, public Dependency {
Q_OBJECT
SINGLETON_DEPENDENCY
public:
virtual QSharedPointer<Resource> createResource(const QUrl& url, const QSharedPointer<Resource>& fallback,
bool delayLoad, const void* extra);
/// Loads a model geometry from the specified URL.
std::shared_ptr<NetworkGeometry> getGeometry(const QUrl& url,
const QVariantHash& mapping = QVariantHash(), const QUrl& textureBaseUrl = QUrl());
/// Loads geometry from the specified URL.
/// \param fallback a fallback URL to load if the desired one is unavailable
/// \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);
protected:
friend class GeometryMappingResource;
virtual QSharedPointer<Resource> createResource(const QUrl& url,
const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra);
private:
ModelCache();
virtual ~ModelCache();
virtual ~ModelCache() = default;
};
QHash<QUrl, QWeakPointer<NetworkGeometry> > _networkGeometry;
class Geometry {
public:
using Pointer = std::shared_ptr<Geometry>;
// Immutable over lifetime
using NetworkMeshes = std::vector<std::shared_ptr<const NetworkMesh>>;
using NetworkShapes = std::vector<NetworkShape>;
// Mutable, but must retain structure of vector
using NetworkMaterials = std::vector<std::shared_ptr<const NetworkMaterial>>;
const FBXGeometry& getGeometry() const { return *_geometry; }
const NetworkMeshes& getMeshes() const { return *_meshes; }
const std::shared_ptr<const NetworkMaterial> getShapeMaterial(int shapeID) const;
const QVariantMap getTextures() const;
void setTextures(const QVariantMap& textureMap);
virtual bool areTexturesLoaded() const;
// Returns true if any albedo texture has a non-masking alpha channel.
// This can only be known after areTexturesLoaded().
bool hasTransparentTextures() const { return _hasTransparentTextures; }
protected:
friend class GeometryMappingResource;
// Shared across all geometries, constant throughout lifetime
std::shared_ptr<const FBXGeometry> _geometry;
std::shared_ptr<const NetworkMeshes> _meshes;
std::shared_ptr<const NetworkShapes> _shapes;
// Copied to each geometry, mutable throughout lifetime via setTextures
NetworkMaterials _materials;
private:
mutable bool _areTexturesLoaded { false };
mutable bool _hasTransparentTextures { false };
};
/// A geometry loaded from the network.
class GeometryResource : public Resource, public Geometry {
public:
using Pointer = QSharedPointer<GeometryResource>;
GeometryResource(const QUrl& url) : Resource(url) {}
virtual bool areTexturesLoaded() const { return isLoaded() && Geometry::areTexturesLoaded(); }
protected:
virtual bool isCacheable() const override { return _loaded; }
};
class NetworkGeometry : public QObject {
Q_OBJECT
public:
// 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();
using Pointer = std::shared_ptr<NetworkGeometry>;
const QUrl& getURL() const { return _url; }
NetworkGeometry() = delete;
NetworkGeometry(const GeometryResource::Pointer& networkGeometry);
void attemptRequest();
const QUrl& getURL() { return _resource->getURL(); }
// true when the geometry is loaded (but maybe not it's associated textures)
bool isLoaded() const;
/// Returns the geometry, if it is loaded (must be checked!)
const Geometry::Pointer& getGeometry() { return _instance; }
// true when the requested geometry and its textures are loaded.
bool isLoadedWithTextures() const;
private slots:
void resourceFinished();
void resourceRefreshed();
// true if the albedo texture has a non-masking alpha channel.
// This can only be known after isLoadedWithTextures().
bool hasTransparentTextures() const { return _hasTransparentTextures; }
private:
GeometryResource::Pointer _resource;
Geometry::Pointer _instance { nullptr };
};
// 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 model::AssetPointer getAsset() const { return _asset; }
// model::MeshPointer getShapeMesh(int shapeID);
// int getShapePart(int shapeID);
// This would be the final verison
// model::MaterialPointer getShapeMaterial(int shapeID);
const NetworkMaterial* getShapeMaterial(int shapeID);
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);
public slots:
void textureLoaded(const QWeakPointer<NetworkTexture>& networkTexture);
protected slots:
void mappingRequestDone(const QByteArray& data);
void mappingRequestError(QNetworkReply::NetworkError error);
void modelRequestDone(const QByteArray& data);
void modelRequestError(QNetworkReply::NetworkError error);
void modelParseSuccess(FBXGeometry* geometry);
void modelParseError(int error, QString str);
class NetworkMaterial : public model::Material {
public:
using MapChannel = model::Material::MapChannel;
NetworkMaterial(const FBXMaterial& material, const QUrl& textureBaseUrl);
NetworkMaterial(const NetworkMaterial& material, const QVariantMap& textureMap);
protected:
void attemptRequestInternal();
void requestMapping(const QUrl& url);
void requestModel(const QUrl& url);
friend class Geometry;
enum State { DelayState,
RequestMappingState,
RequestModelState,
ParsingModelState,
SuccessState,
ErrorState };
State _state;
class Texture {
public:
QString name;
QSharedPointer<NetworkTexture> texture;
};
using Textures = std::vector<Texture>;
QUrl _url;
QUrl _mappingUrl;
QUrl _modelUrl;
QVariantHash _mapping;
QUrl _textureBaseUrl;
int numTextureLoaded = 0;
Textures _textures;
Resource* _resource = nullptr;
std::unique_ptr<FBXGeometry> _geometry; // This should go away evenutally once we can put everything we need in the model::AssetPointer
std::vector<std::unique_ptr<NetworkMesh>> _meshes;
std::vector<std::unique_ptr<NetworkMaterial>> _materials;
std::vector<std::unique_ptr<NetworkShape>> _shapes;
static const QString NO_TEXTURE;
const QString& getTextureName(MapChannel channel);
// The model asset created from this NetworkGeometry
// model::AssetPointer _asset;
// cache for isLoadedWithTextures()
mutable bool _isLoadedWithTextures = false;
mutable bool _hasTransparentTextures = false;
};
/// Reads geometry in a worker thread.
class GeometryReader : public QObject, public QRunnable {
Q_OBJECT
public:
GeometryReader(const QUrl& url, const QByteArray& data, const QVariantHash& mapping);
virtual void run();
signals:
void onSuccess(FBXGeometry* geometry);
void onError(int error, QString str);
private:
QUrl _url;
QByteArray _data;
QVariantHash _mapping;
// Helpers for the ctors
QUrl getTextureUrl(const QUrl& baseUrl, const FBXTexture& fbxTexture);
model::TextureMapPointer fetchTextureMap(const QUrl& baseUrl, const FBXTexture& fbxTexture,
TextureType type, MapChannel channel);
model::TextureMapPointer fetchTextureMap(const QUrl& url, TextureType type, MapChannel channel);
};
class NetworkShape {
public:
int _meshID{ -1 };
int _partID{ -1 };
int _materialID{ -1 };
NetworkShape(int mesh, int part, int material) : meshID { mesh }, partID { part }, materialID { material } {}
int meshID { -1 };
int partID { -1 };
int materialID { -1 };
};
class NetworkMaterial {
public:
model::MaterialPointer _material;
QString emissiveTextureName;
QSharedPointer<NetworkTexture> emissiveTexture;
QString albedoTextureName;
QSharedPointer<NetworkTexture> albedoTexture;
QString normalTextureName;
QSharedPointer<NetworkTexture> normalTexture;
QString roughnessTextureName;
QSharedPointer<NetworkTexture> roughnessTexture;
QString metallicTextureName;
QSharedPointer<NetworkTexture> metallicTexture;
QString occlusionTextureName;
QSharedPointer<NetworkTexture> occlusionTexture;
QString lightmapTextureName;
QSharedPointer<NetworkTexture> lightmapTexture;
};
/// The state associated with a single mesh.
class NetworkMesh {
public:
model::MeshPointer _mesh;
};
#endif // hifi_GeometryCache_h
#endif // hifi_ModelCache_h