mirror of
https://github.com/overte-org/overte.git
synced 2025-04-14 04:07:11 +02:00
Fetch model geometries through cache
This commit is contained in:
parent
fc8b34f8c7
commit
7f05e4453b
2 changed files with 493 additions and 594 deletions
|
@ -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"
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue