mirror of
https://github.com/overte-org/overte.git
synced 2025-08-10 02:31:13 +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
|
// ModelCache.cpp
|
||||||
// interface/src/renderer
|
// libraries/model-networking
|
||||||
//
|
//
|
||||||
// Created by Andrzej Kapolka on 6/21/13.
|
// Created by Zach Pomerantz on 3/15/16.
|
||||||
// Copyright 2013 High Fidelity, Inc.
|
// Copyright 2016 High Fidelity, Inc.
|
||||||
//
|
//
|
||||||
// Distributed under the Apache License, Version 2.0.
|
// Distributed under the Apache License, Version 2.0.
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "ModelCache.h"
|
#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 <QThreadPool>
|
||||||
|
|
||||||
#include <FSTReader.h>
|
|
||||||
#include <NumericalConstants.h>
|
|
||||||
|
|
||||||
#include "TextureCache.h"
|
|
||||||
#include "ModelNetworkingLogging.h"
|
#include "ModelNetworkingLogging.h"
|
||||||
|
|
||||||
#include "model/TextureMap.h"
|
class GeometryReader;
|
||||||
|
|
||||||
//#define WANT_DEBUG
|
class GeometryExtra {
|
||||||
|
public:
|
||||||
|
const QVariantHash& mapping;
|
||||||
|
const QUrl& textureBaseUrl;
|
||||||
|
};
|
||||||
|
|
||||||
ModelCache::ModelCache()
|
class GeometryMappingResource : public GeometryResource {
|
||||||
{
|
Q_OBJECT
|
||||||
const qint64 GEOMETRY_DEFAULT_UNUSED_MAX_SIZE = DEFAULT_UNUSED_MAX_SIZE;
|
public:
|
||||||
setUnusedResourceCacheSize(GEOMETRY_DEFAULT_UNUSED_MAX_SIZE);
|
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,
|
class GeometryReader : public QRunnable {
|
||||||
bool delayLoad, const void* extra) {
|
public:
|
||||||
// NetworkGeometry is no longer a subclass of Resource, but requires this method because, it is pure virtual.
|
GeometryReader(QWeakPointer<Resource>& resource, const QUrl& url, const QVariantHash& mapping,
|
||||||
assert(false);
|
const QByteArray& data) :
|
||||||
return QSharedPointer<Resource>();
|
_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) :
|
private:
|
||||||
_url(url),
|
QWeakPointer<Resource> _resource;
|
||||||
_data(data),
|
QUrl _url;
|
||||||
_mapping(mapping) {
|
QVariantHash _mapping;
|
||||||
}
|
QByteArray _data;
|
||||||
|
};
|
||||||
|
|
||||||
void GeometryReader::run() {
|
void GeometryReader::run() {
|
||||||
auto originalPriority = QThread::currentThread()->priority();
|
auto originalPriority = QThread::currentThread()->priority();
|
||||||
|
@ -55,458 +103,353 @@ void GeometryReader::run() {
|
||||||
originalPriority = QThread::NormalPriority;
|
originalPriority = QThread::NormalPriority;
|
||||||
}
|
}
|
||||||
QThread::currentThread()->setPriority(QThread::LowPriority);
|
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 {
|
try {
|
||||||
if (_data.isEmpty()) {
|
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) {
|
QString urlname = _url.path().toLower();
|
||||||
// Let's read the binaries from the network
|
if (!urlname.isEmpty() && !_url.path().isEmpty() &&
|
||||||
FBXGeometry* fbxgeo = nullptr;
|
(_url.path().toLower().endsWith(".fbx") || _url.path().toLower().endsWith(".obj"))) {
|
||||||
|
FBXGeometry* fbxGeometry = nullptr;
|
||||||
|
|
||||||
if (_url.path().toLower().endsWith(".fbx")) {
|
if (_url.path().toLower().endsWith(".fbx")) {
|
||||||
const bool grabLightmaps = true;
|
fbxGeometry = readFBX(_data, _mapping, _url.path());
|
||||||
const float lightmapLevel = 1.0f;
|
if (fbxGeometry->meshes.size() == 0 && fbxGeometry->joints.size() == 0) {
|
||||||
fbxgeo = readFBX(_data, _mapping, _url.path(), grabLightmaps, lightmapLevel);
|
|
||||||
if (fbxgeo->meshes.size() == 0 && fbxgeo->joints.size() == 0) {
|
|
||||||
// empty fbx geometry, indicates error
|
|
||||||
throw QString("empty geometry, possibly due to an unsupported FBX version");
|
throw QString("empty geometry, possibly due to an unsupported FBX version");
|
||||||
}
|
}
|
||||||
} else if (_url.path().toLower().endsWith(".obj")) {
|
} else if (_url.path().toLower().endsWith(".obj")) {
|
||||||
fbxgeo = OBJReader().readOBJ(_data, _mapping, _url);
|
fbxGeometry = OBJReader().readOBJ(_data, _mapping, _url);
|
||||||
} else {
|
} else {
|
||||||
QString errorStr("unsupported format");
|
throw QString("unsupported format");
|
||||||
throw errorStr;
|
|
||||||
}
|
}
|
||||||
emit onSuccess(fbxgeo);
|
|
||||||
|
QMetaObject::invokeMethod(resource.data(), "setGeometryDefinition",
|
||||||
|
Q_ARG(void*, fbxGeometry));
|
||||||
} else {
|
} else {
|
||||||
throw QString("url is invalid");
|
throw QString("url is invalid");
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (const QString& error) {
|
} catch (const QString& error) {
|
||||||
qCDebug(modelnetworking) << "Error reading " << _url << ": " << 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);
|
QThread::currentThread()->setPriority(originalPriority);
|
||||||
}
|
}
|
||||||
|
|
||||||
NetworkGeometry::NetworkGeometry(const QUrl& url, bool delayLoad, const QVariantHash& mapping, const QUrl& textureBaseUrl) :
|
class GeometryDefinitionResource : public GeometryResource {
|
||||||
_url(url),
|
Q_OBJECT
|
||||||
_mapping(mapping),
|
public:
|
||||||
_textureBaseUrl(textureBaseUrl.isValid() ? textureBaseUrl : url) {
|
GeometryDefinitionResource(const QUrl& url, const QVariantHash& mapping, const QUrl& textureBaseUrl) :
|
||||||
|
GeometryResource(url), _mapping(mapping), _textureBaseUrl(textureBaseUrl) {}
|
||||||
|
|
||||||
if (delayLoad) {
|
virtual void downloadFinished(const QByteArray& data) override;
|
||||||
_state = DelayState;
|
|
||||||
} else {
|
protected:
|
||||||
attemptRequestInternal();
|
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() {
|
void GeometryDefinitionResource::setGeometryDefinition(void* fbxGeometry) {
|
||||||
if (_resource) {
|
// Assume ownership of the geometry pointer
|
||||||
_resource->deleteLater();
|
_geometry.reset(static_cast<FBXGeometry*>(fbxGeometry));
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkGeometry::attemptRequest() {
|
// Copy materials
|
||||||
if (_state == DelayState) {
|
QHash<QString, size_t> materialIDAtlas;
|
||||||
attemptRequestInternal();
|
for (const FBXMaterial& material : _geometry->materials) {
|
||||||
}
|
materialIDAtlas[material.materialID] = _materials.size();
|
||||||
}
|
_materials.push_back(std::make_shared<NetworkMaterial>(material, _textureBaseUrl));
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_isLoadedWithTextures) {
|
std::shared_ptr<NetworkMeshes> meshes = std::make_shared<NetworkMeshes>();
|
||||||
_hasTransparentTextures = false;
|
std::shared_ptr<NetworkShapes> shapes = std::make_shared<NetworkShapes>();
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
int meshID = 0;
|
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;
|
int partID = 0;
|
||||||
foreach (const FBXMeshPart& part, mesh.parts) {
|
for (const FBXMeshPart& part : mesh.parts) {
|
||||||
NetworkShape* networkShape = new NetworkShape();
|
// Construct local shapes
|
||||||
networkShape->_meshID = meshID;
|
shapes->emplace_back(meshID, partID, (int)materialIDAtlas[part.materialID]);
|
||||||
networkShape->_partID = partID;
|
|
||||||
networkShape->_materialID = (int)fbxMatIDToMatID[part.materialID];
|
|
||||||
_shapes.emplace_back(networkShape);
|
|
||||||
partID++;
|
partID++;
|
||||||
}
|
}
|
||||||
meshID++;
|
meshID++;
|
||||||
}
|
}
|
||||||
|
_meshes = meshes;
|
||||||
|
_shapes = shapes;
|
||||||
|
|
||||||
_state = SuccessState;
|
finishedLoading(true);
|
||||||
emit onSuccess(*this, *_geometry.get());
|
|
||||||
|
|
||||||
delete _resource;
|
|
||||||
_resource = nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkGeometry::modelParseError(int error, QString str) {
|
ModelCache::ModelCache() {
|
||||||
_state = ErrorState;
|
const qint64 GEOMETRY_DEFAULT_UNUSED_MAX_SIZE = DEFAULT_UNUSED_MAX_SIZE;
|
||||||
emit onFailure(*this, (NetworkGeometry::Error)error);
|
setUnusedResourceCacheSize(GEOMETRY_DEFAULT_UNUSED_MAX_SIZE);
|
||||||
|
|
||||||
delete _resource;
|
|
||||||
_resource = nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const NetworkMaterial* NetworkGeometry::getShapeMaterial(int shapeID) {
|
QSharedPointer<Resource> ModelCache::createResource(const QUrl& url, const QSharedPointer<Resource>& fallback,
|
||||||
if ((shapeID >= 0) && (shapeID < (int)_shapes.size())) {
|
bool delayLoad, const void* extra) {
|
||||||
int materialID = _shapes[shapeID]->_materialID;
|
const GeometryExtra* geometryExtra = static_cast<const GeometryExtra*>(extra);
|
||||||
if ((materialID >= 0) && ((unsigned int)materialID < _materials.size())) {
|
|
||||||
return _materials[materialID].get();
|
Resource* resource = nullptr;
|
||||||
} else {
|
if (url.path().toLower().endsWith(".fst")) {
|
||||||
return 0;
|
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 {
|
} else {
|
||||||
return 0;
|
qCWarning(modelnetworking) << "Ignoring setTextures(); geometry not ready";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkGeometry::textureLoaded(const QWeakPointer<NetworkTexture>& networkTexture) {
|
bool Geometry::areTexturesLoaded() const {
|
||||||
numTextureLoaded++;
|
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
|
// ModelCache.h
|
||||||
// libraries/model-networking/src/model-networking
|
// libraries/model-networking
|
||||||
//
|
//
|
||||||
// Created by Sam Gateau on 9/21/15.
|
// Created by Zach Pomerantz on 3/15/16.
|
||||||
// Copyright 2013 High Fidelity, Inc.
|
// Copyright 2016 High Fidelity, Inc.
|
||||||
//
|
//
|
||||||
// Distributed under the Apache License, Version 2.0.
|
// Distributed under the Apache License, Version 2.0.
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
@ -12,200 +12,156 @@
|
||||||
#ifndef hifi_ModelCache_h
|
#ifndef hifi_ModelCache_h
|
||||||
#define hifi_ModelCache_h
|
#define hifi_ModelCache_h
|
||||||
|
|
||||||
#include <QMap>
|
|
||||||
#include <QRunnable>
|
|
||||||
|
|
||||||
#include <DependencyManager.h>
|
#include <DependencyManager.h>
|
||||||
#include <ResourceCache.h>
|
#include <ResourceCache.h>
|
||||||
|
|
||||||
#include "FBXReader.h"
|
|
||||||
#include "OBJReader.h"
|
|
||||||
|
|
||||||
#include <gpu/Batch.h>
|
|
||||||
#include <gpu/Stream.h>
|
|
||||||
|
|
||||||
|
|
||||||
#include <model/Material.h>
|
#include <model/Material.h>
|
||||||
#include <model/Asset.h>
|
#include <model/Asset.h>
|
||||||
|
|
||||||
class NetworkGeometry;
|
#include "FBXReader.h"
|
||||||
class NetworkMesh;
|
#include "TextureCache.h"
|
||||||
|
|
||||||
|
// Alias instead of derive to avoid copying
|
||||||
|
using NetworkMesh = model::Mesh;
|
||||||
|
|
||||||
class NetworkTexture;
|
class NetworkTexture;
|
||||||
class NetworkMaterial;
|
class NetworkMaterial;
|
||||||
class NetworkShape;
|
class NetworkShape;
|
||||||
|
class NetworkGeometry;
|
||||||
|
|
||||||
/// Stores cached geometry.
|
class GeometryMappingResource;
|
||||||
|
|
||||||
|
/// Stores cached model geometries.
|
||||||
class ModelCache : public ResourceCache, public Dependency {
|
class ModelCache : public ResourceCache, public Dependency {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
SINGLETON_DEPENDENCY
|
SINGLETON_DEPENDENCY
|
||||||
|
|
||||||
public:
|
public:
|
||||||
virtual QSharedPointer<Resource> createResource(const QUrl& url, const QSharedPointer<Resource>& fallback,
|
/// Loads a model geometry from the specified URL.
|
||||||
bool delayLoad, const void* extra);
|
std::shared_ptr<NetworkGeometry> getGeometry(const QUrl& url,
|
||||||
|
const QVariantHash& mapping = QVariantHash(), const QUrl& textureBaseUrl = QUrl());
|
||||||
|
|
||||||
/// Loads geometry from the specified URL.
|
protected:
|
||||||
/// \param fallback a fallback URL to load if the desired one is unavailable
|
friend class GeometryMappingResource;
|
||||||
/// \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);
|
virtual QSharedPointer<Resource> createResource(const QUrl& url,
|
||||||
|
const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ModelCache();
|
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 {
|
class NetworkGeometry : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// mapping is only used if url is a .fbx or .obj file, it is essentially the content of an fst file.
|
using Pointer = std::shared_ptr<NetworkGeometry>;
|
||||||
// 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();
|
|
||||||
|
|
||||||
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)
|
/// Returns the geometry, if it is loaded (must be checked!)
|
||||||
bool isLoaded() const;
|
const Geometry::Pointer& getGeometry() { return _instance; }
|
||||||
|
|
||||||
// true when the requested geometry and its textures are loaded.
|
private slots:
|
||||||
bool isLoadedWithTextures() const;
|
void resourceFinished();
|
||||||
|
void resourceRefreshed();
|
||||||
|
|
||||||
// true if the albedo texture has a non-masking alpha channel.
|
private:
|
||||||
// This can only be known after isLoadedWithTextures().
|
GeometryResource::Pointer _resource;
|
||||||
bool hasTransparentTextures() const { return _hasTransparentTextures; }
|
Geometry::Pointer _instance { nullptr };
|
||||||
|
};
|
||||||
|
|
||||||
// WARNING: only valid when isLoaded returns true.
|
class NetworkMaterial : public model::Material {
|
||||||
const FBXGeometry& getFBXGeometry() const { return *_geometry; }
|
public:
|
||||||
const std::vector<std::unique_ptr<NetworkMesh>>& getMeshes() const { return _meshes; }
|
using MapChannel = model::Material::MapChannel;
|
||||||
// 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);
|
|
||||||
|
|
||||||
|
NetworkMaterial(const FBXMaterial& material, const QUrl& textureBaseUrl);
|
||||||
|
NetworkMaterial(const NetworkMaterial& material, const QVariantMap& textureMap);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void attemptRequestInternal();
|
friend class Geometry;
|
||||||
void requestMapping(const QUrl& url);
|
|
||||||
void requestModel(const QUrl& url);
|
|
||||||
|
|
||||||
enum State { DelayState,
|
class Texture {
|
||||||
RequestMappingState,
|
public:
|
||||||
RequestModelState,
|
QString name;
|
||||||
ParsingModelState,
|
QSharedPointer<NetworkTexture> texture;
|
||||||
SuccessState,
|
};
|
||||||
ErrorState };
|
using Textures = std::vector<Texture>;
|
||||||
State _state;
|
|
||||||
|
|
||||||
QUrl _url;
|
Textures _textures;
|
||||||
QUrl _mappingUrl;
|
|
||||||
QUrl _modelUrl;
|
|
||||||
QVariantHash _mapping;
|
|
||||||
QUrl _textureBaseUrl;
|
|
||||||
int numTextureLoaded = 0;
|
|
||||||
|
|
||||||
Resource* _resource = nullptr;
|
static const QString NO_TEXTURE;
|
||||||
std::unique_ptr<FBXGeometry> _geometry; // This should go away evenutally once we can put everything we need in the model::AssetPointer
|
const QString& getTextureName(MapChannel channel);
|
||||||
std::vector<std::unique_ptr<NetworkMesh>> _meshes;
|
|
||||||
std::vector<std::unique_ptr<NetworkMaterial>> _materials;
|
|
||||||
std::vector<std::unique_ptr<NetworkShape>> _shapes;
|
|
||||||
|
|
||||||
|
|
||||||
// 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:
|
private:
|
||||||
QUrl _url;
|
// Helpers for the ctors
|
||||||
QByteArray _data;
|
QUrl getTextureUrl(const QUrl& baseUrl, const FBXTexture& fbxTexture);
|
||||||
QVariantHash _mapping;
|
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 {
|
class NetworkShape {
|
||||||
public:
|
public:
|
||||||
int _meshID{ -1 };
|
NetworkShape(int mesh, int part, int material) : meshID { mesh }, partID { part }, materialID { material } {}
|
||||||
int _partID{ -1 };
|
int meshID { -1 };
|
||||||
int _materialID{ -1 };
|
int partID { -1 };
|
||||||
|
int materialID { -1 };
|
||||||
};
|
};
|
||||||
|
|
||||||
class NetworkMaterial {
|
#endif // hifi_ModelCache_h
|
||||||
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
|
|
||||||
|
|
Loading…
Reference in a new issue