mirror of
https://github.com/overte-org/overte.git
synced 2025-04-08 07:12:40 +02:00
Merge pull request #14479 from sabrina-shanman/hfm_mimetype
(case 20037) Add robust MIME type detection for loading models in ModelCache
This commit is contained in:
commit
a3c870ed90
22 changed files with 537 additions and 73 deletions
|
@ -100,6 +100,7 @@
|
|||
#include <MainWindow.h>
|
||||
#include <MappingRequest.h>
|
||||
#include <MessagesClient.h>
|
||||
#include <hfm/ModelFormatRegistry.h>
|
||||
#include <model-networking/ModelCacheScriptingInterface.h>
|
||||
#include <model-networking/TextureCacheScriptingInterface.h>
|
||||
#include <ModelEntityItem.h>
|
||||
|
@ -830,6 +831,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
|
|||
DependencyManager::set<NodeList>(NodeType::Agent, listenPort);
|
||||
DependencyManager::set<recording::ClipCache>();
|
||||
DependencyManager::set<GeometryCache>();
|
||||
DependencyManager::set<ModelFormatRegistry>(); // ModelFormatRegistry must be defined before ModelCache. See the ModelCache constructor.
|
||||
DependencyManager::set<ModelCache>();
|
||||
DependencyManager::set<ModelCacheScriptingInterface>();
|
||||
DependencyManager::set<ScriptCache>();
|
||||
|
@ -2690,6 +2692,7 @@ Application::~Application() {
|
|||
DependencyManager::destroy<TextureCache>();
|
||||
DependencyManager::destroy<ModelCacheScriptingInterface>();
|
||||
DependencyManager::destroy<ModelCache>();
|
||||
DependencyManager::destroy<ModelFormatRegistry>();
|
||||
DependencyManager::destroy<ScriptCache>();
|
||||
DependencyManager::destroy<SoundCacheScriptingInterface>();
|
||||
DependencyManager::destroy<SoundCache>();
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include <Profile.h>
|
||||
|
||||
#include "AnimationLogging.h"
|
||||
#include <FBXSerializer.h>
|
||||
|
||||
int animationPointerMetaTypeId = qRegisterMetaType<AnimationPointer>();
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
#include <QtScript/QScriptValue>
|
||||
|
||||
#include <DependencyManager.h>
|
||||
#include <FBXSerializer.h>
|
||||
#include <hfm/HFM.h>
|
||||
#include <ResourceCache.h>
|
||||
|
||||
class Animation;
|
||||
|
|
|
@ -1854,6 +1854,17 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
|
|||
return hfmModelPtr;
|
||||
}
|
||||
|
||||
MediaType FBXSerializer::getMediaType() const {
|
||||
MediaType mediaType("fbx");
|
||||
mediaType.extensions.push_back("fbx");
|
||||
mediaType.fileSignatures.emplace_back("Kaydara FBX Binary \x00", 0);
|
||||
return mediaType;
|
||||
}
|
||||
|
||||
std::unique_ptr<hfm::Serializer::Factory> FBXSerializer::getFactory() const {
|
||||
return std::make_unique<hfm::Serializer::SimpleFactory<FBXSerializer>>();
|
||||
}
|
||||
|
||||
HFMModel::Pointer FBXSerializer::read(const QByteArray& data, const QVariantHash& mapping, const QUrl& url) {
|
||||
QBuffer buffer(const_cast<QByteArray*>(&data));
|
||||
buffer.open(QIODevice::ReadOnly);
|
||||
|
|
|
@ -96,6 +96,9 @@ class ExtractedMesh;
|
|||
|
||||
class FBXSerializer : public HFMSerializer {
|
||||
public:
|
||||
MediaType getMediaType() const override;
|
||||
std::unique_ptr<hfm::Serializer::Factory> getFactory() const override;
|
||||
|
||||
HFMModel* _hfmModel;
|
||||
/// Reads HFMModel from the supplied model and mapping data.
|
||||
/// \exception QString if an error occurs in parsing
|
||||
|
|
|
@ -35,11 +35,6 @@
|
|||
|
||||
#include "FBXSerializer.h"
|
||||
|
||||
|
||||
GLTFSerializer::GLTFSerializer() {
|
||||
|
||||
}
|
||||
|
||||
bool GLTFSerializer::getStringVal(const QJsonObject& object, const QString& fieldname,
|
||||
QString& value, QMap<QString, bool>& defined) {
|
||||
bool _defined = (object.contains(fieldname) && object[fieldname].isString());
|
||||
|
@ -910,6 +905,17 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const QUrl& url) {
|
|||
return true;
|
||||
}
|
||||
|
||||
MediaType GLTFSerializer::getMediaType() const {
|
||||
MediaType mediaType("gltf");
|
||||
mediaType.extensions.push_back("gltf");
|
||||
mediaType.webMediaTypes.push_back("model/gltf+json");
|
||||
return mediaType;
|
||||
}
|
||||
|
||||
std::unique_ptr<hfm::Serializer::Factory> GLTFSerializer::getFactory() const {
|
||||
return std::make_unique<hfm::Serializer::SimpleFactory<GLTFSerializer>>();
|
||||
}
|
||||
|
||||
HFMModel::Pointer GLTFSerializer::read(const QByteArray& data, const QVariantHash& mapping, const QUrl& url) {
|
||||
|
||||
_url = url;
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
#include <QtNetwork/QNetworkReply>
|
||||
#include <hfm/ModelFormatLogging.h>
|
||||
#include <hfm/HFMSerializer.h>
|
||||
#include "FBXSerializer.h"
|
||||
|
||||
|
||||
struct GLTFAsset {
|
||||
|
@ -703,7 +702,9 @@ struct GLTFFile {
|
|||
class GLTFSerializer : public QObject, public HFMSerializer {
|
||||
Q_OBJECT
|
||||
public:
|
||||
GLTFSerializer();
|
||||
MediaType getMediaType() const override;
|
||||
std::unique_ptr<hfm::Serializer::Factory> getFactory() const override;
|
||||
|
||||
HFMModel::Pointer read(const QByteArray& data, const QVariantHash& mapping, const QUrl& url = QUrl()) override;
|
||||
private:
|
||||
GLTFFile _file;
|
||||
|
|
|
@ -651,6 +651,15 @@ done:
|
|||
return result;
|
||||
}
|
||||
|
||||
MediaType OBJSerializer::getMediaType() const {
|
||||
MediaType mediaType("obj");
|
||||
mediaType.extensions.push_back("obj");
|
||||
return mediaType;
|
||||
}
|
||||
|
||||
std::unique_ptr<hfm::Serializer::Factory> OBJSerializer::getFactory() const {
|
||||
return std::make_unique<hfm::Serializer::SimpleFactory<OBJSerializer>>();
|
||||
}
|
||||
|
||||
HFMModel::Pointer OBJSerializer::read(const QByteArray& data, const QVariantHash& mapping, const QUrl& url) {
|
||||
PROFILE_RANGE_EX(resource_parse, __FUNCTION__, 0xffff0000, nullptr);
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
#include <hfm/HFMSerializer.h>
|
||||
#include "FBXSerializer.h"
|
||||
|
||||
class OBJTokenizer {
|
||||
public:
|
||||
|
@ -92,6 +91,9 @@ public:
|
|||
class OBJSerializer: public QObject, public HFMSerializer { // QObject so we can make network requests.
|
||||
Q_OBJECT
|
||||
public:
|
||||
MediaType getMediaType() const override;
|
||||
std::unique_ptr<hfm::Serializer::Factory> getFactory() const override;
|
||||
|
||||
typedef QVector<OBJFace> FaceGroup;
|
||||
QVector<glm::vec3> vertices;
|
||||
QVector<glm::vec3> vertexColors;
|
||||
|
|
65
libraries/hfm/src/hfm/HFMFormatRegistry.cpp
Normal file
65
libraries/hfm/src/hfm/HFMFormatRegistry.cpp
Normal file
|
@ -0,0 +1,65 @@
|
|||
//
|
||||
// HFMFormatRegistry.cpp
|
||||
// libraries/hfm/src/hfm
|
||||
//
|
||||
// Created by Sabrina Shanman on 2018/11/29.
|
||||
// Copyright 2018 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 "HFMFormatRegistry.h"
|
||||
|
||||
namespace hfm {
|
||||
|
||||
FormatRegistry::MediaTypeID FormatRegistry::registerMediaType(const MediaType& mediaType, std::unique_ptr<Serializer::Factory> supportedFactory) {
|
||||
std::lock_guard<std::mutex> lock(_libraryLock);
|
||||
|
||||
MediaTypeID id = _mediaTypeLibrary.registerMediaType(mediaType);
|
||||
_supportedFormats.emplace_back(id, supportedFactory);
|
||||
return id;
|
||||
}
|
||||
|
||||
void FormatRegistry::unregisterMediaType(const MediaTypeID& mediaTypeID) {
|
||||
std::lock_guard<std::mutex> lock(_libraryLock);
|
||||
|
||||
for (auto it = _supportedFormats.begin(); it != _supportedFormats.end(); it++) {
|
||||
if ((*it).mediaTypeID == mediaTypeID) {
|
||||
_supportedFormats.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
_mediaTypeLibrary.unregisterMediaType(mediaTypeID);
|
||||
}
|
||||
|
||||
std::shared_ptr<Serializer> FormatRegistry::getSerializerForMediaTypeID(FormatRegistry::MediaTypeID mediaTypeID) const {
|
||||
// TODO: shared_lock in C++14
|
||||
std::lock_guard<std::mutex> lock(*const_cast<std::mutex*>(&_libraryLock));
|
||||
|
||||
for (auto it = _supportedFormats.begin(); it != _supportedFormats.end(); it++) {
|
||||
if ((*it).mediaTypeID == mediaTypeID) {
|
||||
return (*it).factory->get();
|
||||
}
|
||||
}
|
||||
return std::shared_ptr<Serializer>();
|
||||
}
|
||||
|
||||
std::shared_ptr<Serializer> FormatRegistry::getSerializerForMediaType(const hifi::ByteArray& data, const hifi::URL& url, const std::string& webMediaType) const {
|
||||
MediaTypeID id;
|
||||
{
|
||||
// TODO: shared_lock in C++14
|
||||
std::lock_guard<std::mutex> lock(*const_cast<std::mutex*>(&_libraryLock));
|
||||
|
||||
id = _mediaTypeLibrary.findMediaTypeForData(data);
|
||||
if (id == INVALID_MEDIA_TYPE_ID) {
|
||||
id = _mediaTypeLibrary.findMediaTypeForURL(url);
|
||||
if (id == INVALID_MEDIA_TYPE_ID) {
|
||||
id = _mediaTypeLibrary.findMediaTypeForWebID(webMediaType);
|
||||
}
|
||||
}
|
||||
}
|
||||
return getSerializerForMediaTypeID(id);
|
||||
}
|
||||
|
||||
};
|
50
libraries/hfm/src/hfm/HFMFormatRegistry.h
Normal file
50
libraries/hfm/src/hfm/HFMFormatRegistry.h
Normal file
|
@ -0,0 +1,50 @@
|
|||
//
|
||||
// HFMFormatRegistry.h
|
||||
// libraries/hfm/src/hfm
|
||||
//
|
||||
// Created by Sabrina Shanman on 2018/11/28.
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_HFMFormatRegistry_h
|
||||
#define hifi_HFMFormatRegistry_h
|
||||
|
||||
#include "HFMSerializer.h"
|
||||
#include <shared/MediaTypeLibrary.h>
|
||||
#include <shared/ReadWriteLockable.h>
|
||||
|
||||
namespace hfm {
|
||||
|
||||
class FormatRegistry {
|
||||
public:
|
||||
using MediaTypeID = MediaTypeLibrary::ID;
|
||||
static const MediaTypeID INVALID_MEDIA_TYPE_ID { MediaTypeLibrary::INVALID_ID };
|
||||
|
||||
MediaTypeID registerMediaType(const MediaType& mediaType, std::unique_ptr<Serializer::Factory> supportedFactory);
|
||||
void unregisterMediaType(const MediaTypeID& id);
|
||||
|
||||
std::shared_ptr<Serializer> getSerializerForMediaType(const hifi::ByteArray& data, const hifi::URL& url, const std::string& webMediaType) const;
|
||||
|
||||
protected:
|
||||
std::shared_ptr<Serializer> getSerializerForMediaTypeID(MediaTypeID id) const;
|
||||
|
||||
MediaTypeLibrary _mediaTypeLibrary;
|
||||
std::mutex _libraryLock;
|
||||
class SupportedFormat {
|
||||
public:
|
||||
SupportedFormat(const MediaTypeID& mediaTypeID, std::unique_ptr<Serializer::Factory>& factory) :
|
||||
mediaTypeID(mediaTypeID),
|
||||
factory(std::move(factory)) {
|
||||
}
|
||||
MediaTypeID mediaTypeID;
|
||||
std::unique_ptr<Serializer::Factory> factory;
|
||||
};
|
||||
std::vector<SupportedFormat> _supportedFormats;
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
#endif // hifi_HFMFormatRegistry_h
|
|
@ -15,10 +15,27 @@
|
|||
#include <shared/HifiTypes.h>
|
||||
|
||||
#include "HFM.h"
|
||||
#include <shared/MediaTypeLibrary.h>
|
||||
|
||||
namespace hfm {
|
||||
|
||||
class Serializer {
|
||||
public:
|
||||
class Factory {
|
||||
public:
|
||||
virtual std::shared_ptr<Serializer> get() = 0;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class SimpleFactory : public Factory {
|
||||
std::shared_ptr<Serializer> get() override {
|
||||
return std::make_shared<T>();
|
||||
}
|
||||
};
|
||||
|
||||
virtual MediaType getMediaType() const = 0;
|
||||
virtual std::unique_ptr<Factory> getFactory() const = 0;
|
||||
|
||||
virtual Model::Pointer read(const hifi::ByteArray& data, const hifi::VariantHash& mapping, const hifi::URL& url = hifi::URL()) = 0;
|
||||
};
|
||||
|
||||
|
|
20
libraries/hfm/src/hfm/ModelFormatRegistry.cpp
Normal file
20
libraries/hfm/src/hfm/ModelFormatRegistry.cpp
Normal file
|
@ -0,0 +1,20 @@
|
|||
//
|
||||
// ModelFormatRegistry.cpp
|
||||
// libraries/model-networking/src/model-networking
|
||||
//
|
||||
// Created by Sabrina Shanman on 2018/11/30.
|
||||
// Copyright 2018 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 "ModelFormatRegistry.h"
|
||||
|
||||
void ModelFormatRegistry::addFormat(const hfm::Serializer& serializer) {
|
||||
_hfmFormatRegistry.registerMediaType(serializer.getMediaType(), serializer.getFactory());
|
||||
}
|
||||
|
||||
std::shared_ptr<hfm::Serializer> ModelFormatRegistry::getSerializerForMediaType(const hifi::ByteArray& data, const hifi::URL& url, const std::string& webMediaType) const {
|
||||
return _hfmFormatRegistry.getSerializerForMediaType(data, url, webMediaType);
|
||||
}
|
28
libraries/hfm/src/hfm/ModelFormatRegistry.h
Normal file
28
libraries/hfm/src/hfm/ModelFormatRegistry.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
//
|
||||
// ModelFormatRegistry.h
|
||||
// libraries/hfm/src/hfm
|
||||
//
|
||||
// Created by Sabrina Shanman on 2018/11/30.
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_ModelFormatRegistry_h
|
||||
#define hifi_ModelFormatRegistry_h
|
||||
|
||||
#include "HFMFormatRegistry.h"
|
||||
|
||||
#include <DependencyManager.h>
|
||||
|
||||
class ModelFormatRegistry : public Dependency {
|
||||
public:
|
||||
void addFormat(const hfm::Serializer& serializer);
|
||||
std::shared_ptr<hfm::Serializer> getSerializerForMediaType(const hifi::ByteArray& data, const hifi::URL& url, const std::string& webMediaType) const;
|
||||
|
||||
protected:
|
||||
hfm::FormatRegistry _hfmFormatRegistry;
|
||||
};
|
||||
|
||||
#endif // hifi_ModelFormatRegistry_h
|
|
@ -12,9 +12,6 @@
|
|||
#include "ModelCache.h"
|
||||
#include <Finally.h>
|
||||
#include <FSTReader.h>
|
||||
#include "FBXSerializer.h"
|
||||
#include "OBJSerializer.h"
|
||||
#include "GLTFSerializer.h"
|
||||
|
||||
#include <gpu/Batch.h>
|
||||
#include <gpu/Stream.h>
|
||||
|
@ -26,6 +23,10 @@
|
|||
#include "ModelNetworkingLogging.h"
|
||||
#include <Trace.h>
|
||||
#include <StatTracker.h>
|
||||
#include <hfm/ModelFormatRegistry.h>
|
||||
#include <FBXSerializer.h>
|
||||
#include <OBJSerializer.h>
|
||||
#include <GLTFSerializer.h>
|
||||
|
||||
Q_LOGGING_CATEGORY(trace_resource_parse_geometry, "trace.resource.parse.geometry")
|
||||
|
||||
|
@ -144,9 +145,9 @@ void GeometryMappingResource::onGeometryMappingLoaded(bool success) {
|
|||
|
||||
class GeometryReader : public QRunnable {
|
||||
public:
|
||||
GeometryReader(QWeakPointer<Resource>& resource, const QUrl& url, const QVariantHash& mapping,
|
||||
const QByteArray& data, bool combineParts) :
|
||||
_resource(resource), _url(url), _mapping(mapping), _data(data), _combineParts(combineParts) {
|
||||
GeometryReader(const ModelLoader& modelLoader, QWeakPointer<Resource>& resource, const QUrl& url, const QVariantHash& mapping,
|
||||
const QByteArray& data, bool combineParts, const QString& webMediaType) :
|
||||
_modelLoader(modelLoader), _resource(resource), _url(url), _mapping(mapping), _data(data), _combineParts(combineParts), _webMediaType(webMediaType) {
|
||||
|
||||
DependencyManager::get<StatTracker>()->incrementStat("PendingProcessing");
|
||||
}
|
||||
|
@ -154,11 +155,13 @@ public:
|
|||
virtual void run() override;
|
||||
|
||||
private:
|
||||
ModelLoader _modelLoader;
|
||||
QWeakPointer<Resource> _resource;
|
||||
QUrl _url;
|
||||
QVariantHash _mapping;
|
||||
QByteArray _data;
|
||||
bool _combineParts;
|
||||
QString _webMediaType;
|
||||
};
|
||||
|
||||
void GeometryReader::run() {
|
||||
|
@ -183,62 +186,53 @@ void GeometryReader::run() {
|
|||
throw QString("reply is NULL");
|
||||
}
|
||||
|
||||
QString urlname = _url.path().toLower();
|
||||
if (!urlname.isEmpty() && !_url.path().isEmpty() &&
|
||||
// Ensure the resource has not been deleted
|
||||
auto resource = _resource.toStrongRef();
|
||||
if (!resource) {
|
||||
qCWarning(modelnetworking) << "Abandoning load of" << _url << "; could not get strong ref";
|
||||
return;
|
||||
}
|
||||
|
||||
(_url.path().toLower().endsWith(".fbx") ||
|
||||
_url.path().toLower().endsWith(".obj") ||
|
||||
_url.path().toLower().endsWith(".obj.gz") ||
|
||||
_url.path().toLower().endsWith(".gltf"))) {
|
||||
|
||||
HFMModel::Pointer hfmModel;
|
||||
|
||||
QVariantHash serializerMapping = _mapping;
|
||||
serializerMapping["combineParts"] = _combineParts;
|
||||
|
||||
if (_url.path().toLower().endsWith(".fbx")) {
|
||||
hfmModel = FBXSerializer().read(_data, serializerMapping, _url);
|
||||
if (hfmModel->meshes.size() == 0 && hfmModel->joints.size() == 0) {
|
||||
throw QString("empty geometry, possibly due to an unsupported FBX version");
|
||||
}
|
||||
} else if (_url.path().toLower().endsWith(".obj")) {
|
||||
hfmModel = OBJSerializer().read(_data, serializerMapping, _url);
|
||||
} else if (_url.path().toLower().endsWith(".obj.gz")) {
|
||||
QByteArray uncompressedData;
|
||||
if (gunzip(_data, uncompressedData)){
|
||||
hfmModel = OBJSerializer().read(uncompressedData, serializerMapping, _url);
|
||||
} else {
|
||||
throw QString("failed to decompress .obj.gz");
|
||||
}
|
||||
|
||||
} else if (_url.path().toLower().endsWith(".gltf")) {
|
||||
hfmModel = GLTFSerializer().read(_data, serializerMapping, _url);
|
||||
if (hfmModel->meshes.size() == 0 && hfmModel->joints.size() == 0) {
|
||||
throw QString("empty geometry, possibly due to an unsupported GLTF version");
|
||||
}
|
||||
} else {
|
||||
throw QString("unsupported format");
|
||||
}
|
||||
|
||||
// Add scripts to hfmModel
|
||||
if (!_mapping.value(SCRIPT_FIELD).isNull()) {
|
||||
QVariantList scripts = _mapping.values(SCRIPT_FIELD);
|
||||
for (auto &script : scripts) {
|
||||
hfmModel->scripts.push_back(script.toString());
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the resource has not been deleted
|
||||
auto resource = _resource.toStrongRef();
|
||||
if (!resource) {
|
||||
qCWarning(modelnetworking) << "Abandoning load of" << _url << "; could not get strong ref";
|
||||
} else {
|
||||
QMetaObject::invokeMethod(resource.data(), "setGeometryDefinition",
|
||||
Q_ARG(HFMModel::Pointer, hfmModel));
|
||||
}
|
||||
} else {
|
||||
if (_url.path().isEmpty()) {
|
||||
throw QString("url is invalid");
|
||||
}
|
||||
|
||||
HFMModel::Pointer hfmModel;
|
||||
QVariantHash serializerMapping = _mapping;
|
||||
serializerMapping["combineParts"] = _combineParts;
|
||||
|
||||
if (_url.path().toLower().endsWith(".gz")) {
|
||||
QByteArray uncompressedData;
|
||||
if (!gunzip(_data, uncompressedData)) {
|
||||
throw QString("failed to decompress .gz model");
|
||||
}
|
||||
// Strip the compression extension from the path, so the loader can infer the file type from what remains.
|
||||
// This is okay because we don't expect the serializer to be able to read the contents of a compressed model file.
|
||||
auto strippedUrl = _url;
|
||||
strippedUrl.setPath(_url.path().left(_url.path().size() - 3));
|
||||
hfmModel = _modelLoader.load(uncompressedData, serializerMapping, strippedUrl, "");
|
||||
} else {
|
||||
hfmModel = _modelLoader.load(_data, serializerMapping, _url, _webMediaType.toStdString());
|
||||
}
|
||||
|
||||
if (!hfmModel) {
|
||||
throw QString("unsupported format");
|
||||
}
|
||||
|
||||
if (hfmModel->meshes.empty() || hfmModel->joints.empty()) {
|
||||
throw QString("empty geometry, possibly due to an unsupported model version");
|
||||
}
|
||||
|
||||
// Add scripts to hfmModel
|
||||
if (!_mapping.value(SCRIPT_FIELD).isNull()) {
|
||||
QVariantList scripts = _mapping.values(SCRIPT_FIELD);
|
||||
for (auto &script : scripts) {
|
||||
hfmModel->scripts.push_back(script.toString());
|
||||
}
|
||||
}
|
||||
|
||||
QMetaObject::invokeMethod(resource.data(), "setGeometryDefinition",
|
||||
Q_ARG(HFMModel::Pointer, hfmModel));
|
||||
} catch (const std::exception&) {
|
||||
auto resource = _resource.toStrongRef();
|
||||
if (resource) {
|
||||
|
@ -258,8 +252,8 @@ void GeometryReader::run() {
|
|||
class GeometryDefinitionResource : public GeometryResource {
|
||||
Q_OBJECT
|
||||
public:
|
||||
GeometryDefinitionResource(const QUrl& url, const QVariantHash& mapping, const QUrl& textureBaseUrl, bool combineParts) :
|
||||
GeometryResource(url, resolveTextureBaseUrl(url, textureBaseUrl)), _mapping(mapping), _combineParts(combineParts) {}
|
||||
GeometryDefinitionResource(const ModelLoader& modelLoader, const QUrl& url, const QVariantHash& mapping, const QUrl& textureBaseUrl, bool combineParts) :
|
||||
GeometryResource(url, resolveTextureBaseUrl(url, textureBaseUrl)), _modelLoader(modelLoader), _mapping(mapping), _combineParts(combineParts) {}
|
||||
|
||||
QString getType() const override { return "GeometryDefinition"; }
|
||||
|
||||
|
@ -269,6 +263,7 @@ protected:
|
|||
Q_INVOKABLE void setGeometryDefinition(HFMModel::Pointer hfmModel);
|
||||
|
||||
private:
|
||||
ModelLoader _modelLoader;
|
||||
QVariantHash _mapping;
|
||||
bool _combineParts;
|
||||
};
|
||||
|
@ -278,7 +273,7 @@ void GeometryDefinitionResource::downloadFinished(const QByteArray& data) {
|
|||
_url = _effectiveBaseURL;
|
||||
_textureBaseUrl = _effectiveBaseURL;
|
||||
}
|
||||
QThreadPool::globalInstance()->start(new GeometryReader(_self, _effectiveBaseURL, _mapping, data, _combineParts));
|
||||
QThreadPool::globalInstance()->start(new GeometryReader(_modelLoader, _self, _effectiveBaseURL, _mapping, data, _combineParts, _request->getWebMediaType()));
|
||||
}
|
||||
|
||||
void GeometryDefinitionResource::setGeometryDefinition(HFMModel::Pointer hfmModel) {
|
||||
|
@ -316,6 +311,11 @@ ModelCache::ModelCache() {
|
|||
const qint64 GEOMETRY_DEFAULT_UNUSED_MAX_SIZE = DEFAULT_UNUSED_MAX_SIZE;
|
||||
setUnusedResourceCacheSize(GEOMETRY_DEFAULT_UNUSED_MAX_SIZE);
|
||||
setObjectName("ModelCache");
|
||||
|
||||
auto modelFormatRegistry = DependencyManager::get<ModelFormatRegistry>();
|
||||
modelFormatRegistry->addFormat(FBXSerializer());
|
||||
modelFormatRegistry->addFormat(OBJSerializer());
|
||||
modelFormatRegistry->addFormat(GLTFSerializer());
|
||||
}
|
||||
|
||||
QSharedPointer<Resource> ModelCache::createResource(const QUrl& url, const QSharedPointer<Resource>& fallback,
|
||||
|
@ -328,7 +328,7 @@ QSharedPointer<Resource> ModelCache::createResource(const QUrl& url, const QShar
|
|||
auto mapping = geometryExtra ? geometryExtra->mapping : QVariantHash();
|
||||
auto textureBaseUrl = geometryExtra ? geometryExtra->textureBaseUrl : QUrl();
|
||||
bool combineParts = geometryExtra ? geometryExtra->combineParts : true;
|
||||
resource = new GeometryDefinitionResource(url, mapping, textureBaseUrl, combineParts);
|
||||
resource = new GeometryDefinitionResource(_modelLoader, url, mapping, textureBaseUrl, combineParts);
|
||||
}
|
||||
|
||||
return QSharedPointer<Resource>(resource, &Resource::deleter);
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
|
||||
#include "FBXSerializer.h"
|
||||
#include "TextureCache.h"
|
||||
#include "ModelLoader.h"
|
||||
|
||||
// Alias instead of derive to avoid copying
|
||||
|
||||
|
@ -158,6 +159,7 @@ protected:
|
|||
private:
|
||||
ModelCache();
|
||||
virtual ~ModelCache() = default;
|
||||
ModelLoader _modelLoader;
|
||||
};
|
||||
|
||||
class NetworkMaterial : public graphics::Material {
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
//
|
||||
// ModelLoader.cpp
|
||||
// libraries/model-networking/src/model-networking
|
||||
//
|
||||
// Created by Sabrina Shanman on 2018/11/14.
|
||||
// Copyright 2018 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 "ModelLoader.h"
|
||||
|
||||
#include <DependencyManager.h>
|
||||
#include <hfm/ModelFormatRegistry.h>
|
||||
|
||||
|
||||
hfm::Model::Pointer ModelLoader::load(const hifi::ByteArray& data, const hifi::VariantHash& mapping, const hifi::URL& url, const std::string& webMediaType) const {
|
||||
auto serializer = DependencyManager::get<ModelFormatRegistry>()->getSerializerForMediaType(data, url, webMediaType);
|
||||
if (!serializer) {
|
||||
return hfm::Model::Pointer();
|
||||
}
|
||||
return serializer->read(data, mapping, url);
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
//
|
||||
// ModelLoader.h
|
||||
// libraries/model-networking/src/model-networking
|
||||
//
|
||||
// Created by Sabrina Shanman on 2018/11/13.
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_ModelLoader_h
|
||||
#define hifi_ModelLoader_h
|
||||
|
||||
#include <shared/HifiTypes.h>
|
||||
#include <hfm/HFM.h>
|
||||
|
||||
class ModelLoader {
|
||||
public:
|
||||
// Given the currently stored list of supported file formats, determine how to load a model from the given parameters.
|
||||
// If successful, return an owned reference to the newly loaded model.
|
||||
// If failed, return an empty reference.
|
||||
hfm::Model::Pointer load(const hifi::ByteArray& data, const hifi::VariantHash& mapping, const hifi::URL& url, const std::string& webMediaType) const;
|
||||
};
|
||||
|
||||
#endif // hifi_ModelLoader_h
|
|
@ -94,7 +94,7 @@ void HTTPResourceRequest::onRequestFinished() {
|
|||
// Content-Range: <unit> <range-start>-<range-end>/*
|
||||
// Content-Range: <unit> */<size>
|
||||
//
|
||||
auto parseContentRangeHeader = [](QString contentRangeHeader) -> std::pair<bool, uint64_t> {
|
||||
static auto parseContentRangeHeader = [](QString contentRangeHeader) -> std::pair<bool, uint64_t> {
|
||||
auto unitRangeParts = contentRangeHeader.split(' ');
|
||||
if (unitRangeParts.size() != 2) {
|
||||
return { false, 0 };
|
||||
|
@ -115,6 +115,15 @@ void HTTPResourceRequest::onRequestFinished() {
|
|||
}
|
||||
};
|
||||
|
||||
static auto parseMediaType = [](QString contentTypeHeader) -> std::pair<bool, QString> {
|
||||
auto contentTypeParts = contentTypeHeader.split(';');
|
||||
if (contentTypeParts.size() < 1) {
|
||||
return { false, "" };
|
||||
}
|
||||
|
||||
return { true, contentTypeParts[0] };
|
||||
};
|
||||
|
||||
switch(_reply->error()) {
|
||||
case QNetworkReply::NoError:
|
||||
_data = _reply->readAll();
|
||||
|
@ -141,6 +150,16 @@ void HTTPResourceRequest::onRequestFinished() {
|
|||
}
|
||||
}
|
||||
|
||||
{
|
||||
auto contentTypeHeader = _reply->rawHeader("Content-Type");
|
||||
bool success;
|
||||
QString mediaType;
|
||||
std::tie(success, mediaType) = parseMediaType(contentTypeHeader);
|
||||
if (success) {
|
||||
_webMediaType = mediaType;
|
||||
}
|
||||
}
|
||||
|
||||
recordBytesDownloadedInStats(STAT_HTTP_RESOURCE_TOTAL_BYTES, _data.size());
|
||||
|
||||
break;
|
||||
|
|
|
@ -84,6 +84,7 @@ public:
|
|||
bool loadedFromCache() const { return _loadedFromCache; }
|
||||
bool getRangeRequestSuccessful() const { return _rangeRequestSuccessful; }
|
||||
bool getTotalSizeOfResource() const { return _totalSizeOfResource; }
|
||||
QString getWebMediaType() const { return _webMediaType; }
|
||||
void setFailOnRedirect(bool failOnRedirect) { _failOnRedirect = failOnRedirect; }
|
||||
|
||||
void setCacheEnabled(bool value) { _cacheEnabled = value; }
|
||||
|
@ -111,6 +112,7 @@ protected:
|
|||
ByteRange _byteRange;
|
||||
bool _rangeRequestSuccessful { false };
|
||||
uint64_t _totalSizeOfResource { 0 };
|
||||
QString _webMediaType;
|
||||
int64_t _lastRecordedBytesDownloaded { 0 };
|
||||
bool _isObservable;
|
||||
qint64 _callerId;
|
||||
|
|
85
libraries/shared/src/shared/MediaTypeLibrary.cpp
Normal file
85
libraries/shared/src/shared/MediaTypeLibrary.cpp
Normal file
|
@ -0,0 +1,85 @@
|
|||
//
|
||||
// MediaTypeLibrary.cpp
|
||||
// libraries/shared/src/shared
|
||||
//
|
||||
// Created by Sabrina Shanman on 2018/11/29.
|
||||
// Copyright 2018 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 "MediaTypeLibrary.h"
|
||||
|
||||
MediaType MediaType::NONE = MediaType("");
|
||||
|
||||
MediaTypeLibrary::ID MediaTypeLibrary::registerMediaType(const MediaType& mediaType) {
|
||||
ID id = nextID++;
|
||||
_mediaTypes.emplace_back(id, mediaType);
|
||||
return id;
|
||||
}
|
||||
|
||||
void MediaTypeLibrary::unregisterMediaType(const MediaTypeLibrary::ID& id) {
|
||||
for (auto it = _mediaTypes.begin(); it != _mediaTypes.end(); it++) {
|
||||
if ((*it).id == id) {
|
||||
_mediaTypes.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MediaType MediaTypeLibrary::getMediaType(const MediaTypeLibrary::ID& id) const {
|
||||
for (auto& supportedFormat : _mediaTypes) {
|
||||
if (supportedFormat.id == id) {
|
||||
return supportedFormat.mediaType;
|
||||
}
|
||||
}
|
||||
return MediaType::NONE;
|
||||
}
|
||||
|
||||
MediaTypeLibrary::ID MediaTypeLibrary::findMediaTypeForData(const hifi::ByteArray& data) const {
|
||||
// Check file contents
|
||||
for (auto& mediaType : _mediaTypes) {
|
||||
for (auto& fileSignature : mediaType.mediaType.fileSignatures) {
|
||||
auto testBytes = data.mid(fileSignature.byteOffset, (int)fileSignature.bytes.size()).toStdString();
|
||||
if (testBytes == fileSignature.bytes) {
|
||||
return mediaType.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return INVALID_ID;
|
||||
}
|
||||
|
||||
MediaTypeLibrary::ID MediaTypeLibrary::findMediaTypeForURL(const hifi::URL& url) const {
|
||||
// Check file extension
|
||||
std::string urlString = url.path().toStdString();
|
||||
std::size_t extensionSeparator = urlString.rfind('.');
|
||||
if (extensionSeparator != std::string::npos) {
|
||||
std::string detectedExtension = urlString.substr(extensionSeparator + 1);
|
||||
for (auto& supportedFormat : _mediaTypes) {
|
||||
for (auto& extension : supportedFormat.mediaType.extensions) {
|
||||
if (extension == detectedExtension) {
|
||||
return supportedFormat.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return INVALID_ID;
|
||||
}
|
||||
|
||||
MediaTypeLibrary::ID MediaTypeLibrary::findMediaTypeForWebID(const std::string& webMediaType) const {
|
||||
// Check web media type
|
||||
if (webMediaType != "") {
|
||||
for (auto& supportedFormat : _mediaTypes) {
|
||||
for (auto& candidateWebMediaType : supportedFormat.mediaType.webMediaTypes) {
|
||||
if (candidateWebMediaType == webMediaType) {
|
||||
return supportedFormat.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return INVALID_ID;
|
||||
}
|
90
libraries/shared/src/shared/MediaTypeLibrary.h
Normal file
90
libraries/shared/src/shared/MediaTypeLibrary.h
Normal file
|
@ -0,0 +1,90 @@
|
|||
//
|
||||
// MediaTypeLibrary.h
|
||||
// libraries/shared/src/shared
|
||||
//
|
||||
// Created by Sabrina Shanman on 2018/11/28.
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_MediaTypeLibrary_h
|
||||
#define hifi_MediaTypeLibrary_h
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
|
||||
#include "HifiTypes.h"
|
||||
|
||||
// A short sequence of bytes, typically at the beginning of the file, which identifies the file format
|
||||
class FileSignature {
|
||||
public:
|
||||
FileSignature(const std::string& bytes, int byteOffset) :
|
||||
bytes(bytes),
|
||||
byteOffset(byteOffset) {
|
||||
}
|
||||
FileSignature(const FileSignature& fileSignature) :
|
||||
bytes(fileSignature.bytes),
|
||||
byteOffset(fileSignature.byteOffset) {
|
||||
}
|
||||
|
||||
std::string bytes;
|
||||
int byteOffset;
|
||||
};
|
||||
|
||||
// A named file extension with a list of known ways to positively identify the file type
|
||||
class MediaType {
|
||||
public:
|
||||
MediaType(const std::string& name) :
|
||||
name(name) {
|
||||
}
|
||||
MediaType() {};
|
||||
MediaType(const MediaType& mediaType) :
|
||||
name(mediaType.name),
|
||||
extensions(mediaType.extensions),
|
||||
webMediaTypes(mediaType.webMediaTypes),
|
||||
fileSignatures(mediaType.fileSignatures) {
|
||||
}
|
||||
|
||||
static MediaType NONE;
|
||||
|
||||
std::string name;
|
||||
std::vector<std::string> extensions;
|
||||
std::vector<std::string> webMediaTypes;
|
||||
std::vector<FileSignature> fileSignatures;
|
||||
};
|
||||
|
||||
class MediaTypeLibrary {
|
||||
public:
|
||||
using ID = unsigned int;
|
||||
static const ID INVALID_ID { 0 };
|
||||
|
||||
ID registerMediaType(const MediaType& mediaType);
|
||||
void unregisterMediaType(const ID& id);
|
||||
|
||||
MediaType getMediaType(const ID& id) const;
|
||||
|
||||
ID findMediaTypeForData(const hifi::ByteArray& data) const;
|
||||
ID findMediaTypeForURL(const hifi::URL& url) const;
|
||||
ID findMediaTypeForWebID(const std::string& webMediaType) const;
|
||||
|
||||
protected:
|
||||
ID nextID { 1 };
|
||||
|
||||
class Entry {
|
||||
public:
|
||||
Entry(const ID& id, const MediaType& mediaType) :
|
||||
id(id),
|
||||
mediaType(mediaType) {
|
||||
}
|
||||
ID id;
|
||||
MediaType mediaType;
|
||||
};
|
||||
|
||||
std::vector<Entry> _mediaTypes;
|
||||
};
|
||||
|
||||
#endif // hifi_MeidaTypeLibrary_h
|
Loading…
Reference in a new issue