mirror of
https://github.com/overte-org/overte.git
synced 2025-08-10 02:03:11 +02:00
ResourceCache, NetworkGeometry and Model refactoring and optimizations.
* Removed validation logic from Resource class, Qt does this internally and is more standards compliant. This should result in more accurate caching and faster resource fetching when cache is stale and validation fails. * Added loaded and failed slots to Resource class, so it does not have to be polled. * NetworkGeometry now uses multiple Resource objects to download the fst/mapping file and the fbx/obj models. * NetworkGeometry is no longer a subclass of Resource * NetworkGeometry now has signals for success and failure, you no longer have to poll it to determine when loading is complete (except for textures *sigh*) Some functionality was removed * NetworkGeometry no longer has a fallback * NetworkGeometry no longer loads LODs or has lod logic. * The number of FBXGeometry copies is greatly reduced. * Model::setURL no supports fallback URL, delayLoad or retainCurrent option. This can result in a pop when switching avatars, and there's no longer a default if avatar loading fails.
This commit is contained in:
parent
f2fd7dbd3c
commit
1b3d7fabc8
20 changed files with 682 additions and 833 deletions
|
@ -106,16 +106,17 @@ bool ModelPackager::loadModel() {
|
||||||
}
|
}
|
||||||
qCDebug(interfaceapp) << "Reading FBX file : " << _fbxInfo.filePath();
|
qCDebug(interfaceapp) << "Reading FBX file : " << _fbxInfo.filePath();
|
||||||
QByteArray fbxContents = fbx.readAll();
|
QByteArray fbxContents = fbx.readAll();
|
||||||
_geometry = readFBX(fbxContents, QVariantHash(), _fbxInfo.filePath());
|
|
||||||
|
_geometry.reset(readFBX(fbxContents, QVariantHash(), _fbxInfo.filePath()));
|
||||||
|
|
||||||
// make sure we have some basic mappings
|
// make sure we have some basic mappings
|
||||||
populateBasicMapping(_mapping, _fbxInfo.filePath(), _geometry);
|
populateBasicMapping(_mapping, _fbxInfo.filePath(), *_geometry);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ModelPackager::editProperties() {
|
bool ModelPackager::editProperties() {
|
||||||
// open the dialog to configure the rest
|
// open the dialog to configure the rest
|
||||||
ModelPropertiesDialog properties(_modelType, _mapping, _modelFile.path(), _geometry);
|
ModelPropertiesDialog properties(_modelType, _mapping, _modelFile.path(), *_geometry);
|
||||||
if (properties.exec() == QDialog::Rejected) {
|
if (properties.exec() == QDialog::Rejected) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -339,7 +340,7 @@ void ModelPackager::populateBasicMapping(QVariantHash& mapping, QString filename
|
||||||
|
|
||||||
void ModelPackager::listTextures() {
|
void ModelPackager::listTextures() {
|
||||||
_textures.clear();
|
_textures.clear();
|
||||||
foreach (FBXMesh mesh, _geometry.meshes) {
|
foreach (FBXMesh mesh, _geometry->meshes) {
|
||||||
foreach (FBXMeshPart part, mesh.parts) {
|
foreach (FBXMeshPart part, mesh.parts) {
|
||||||
if (!part.diffuseTexture.filename.isEmpty() && part.diffuseTexture.content.isEmpty() &&
|
if (!part.diffuseTexture.filename.isEmpty() && part.diffuseTexture.content.isEmpty() &&
|
||||||
!_textures.contains(part.diffuseTexture.filename)) {
|
!_textures.contains(part.diffuseTexture.filename)) {
|
||||||
|
|
|
@ -39,11 +39,11 @@ private:
|
||||||
QString _texDir;
|
QString _texDir;
|
||||||
|
|
||||||
QVariantHash _mapping;
|
QVariantHash _mapping;
|
||||||
FBXGeometry _geometry;
|
std::unique_ptr<FBXGeometry> _geometry;
|
||||||
QStringList _textures;
|
QStringList _textures;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif // hifi_ModelPackager_h
|
#endif // hifi_ModelPackager_h
|
||||||
|
|
|
@ -196,7 +196,6 @@ void Avatar::simulate(float deltaTime) {
|
||||||
PerformanceTimer perfTimer("hand");
|
PerformanceTimer perfTimer("hand");
|
||||||
getHand()->simulate(deltaTime, false);
|
getHand()->simulate(deltaTime, false);
|
||||||
}
|
}
|
||||||
_skeletonModel.setLODDistance(getLODDistance());
|
|
||||||
|
|
||||||
if (!_shouldRenderBillboard && inViewFrustum) {
|
if (!_shouldRenderBillboard && inViewFrustum) {
|
||||||
{
|
{
|
||||||
|
@ -562,24 +561,22 @@ glm::quat Avatar::computeRotationFromBodyToWorldUp(float proportion) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Avatar::fixupModelsInScene() {
|
void Avatar::fixupModelsInScene() {
|
||||||
if (!(_skeletonModel.isRenderable() && getHead()->getFaceModel().isRenderable())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check to see if when we added our models to the scene they were ready, if they were not ready, then
|
// check to see if when we added our models to the scene they were ready, if they were not ready, then
|
||||||
// fix them up in the scene
|
// fix them up in the scene
|
||||||
render::ScenePointer scene = Application::getInstance()->getMain3DScene();
|
render::ScenePointer scene = Application::getInstance()->getMain3DScene();
|
||||||
render::PendingChanges pendingChanges;
|
render::PendingChanges pendingChanges;
|
||||||
if (_skeletonModel.needsFixupInScene()) {
|
if (_skeletonModel.isRenderable() && _skeletonModel.needsFixupInScene()) {
|
||||||
_skeletonModel.removeFromScene(scene, pendingChanges);
|
_skeletonModel.removeFromScene(scene, pendingChanges);
|
||||||
_skeletonModel.addToScene(scene, pendingChanges);
|
_skeletonModel.addToScene(scene, pendingChanges);
|
||||||
}
|
}
|
||||||
if (getHead()->getFaceModel().needsFixupInScene()) {
|
Model& faceModel = getHead()->getFaceModel();
|
||||||
getHead()->getFaceModel().removeFromScene(scene, pendingChanges);
|
if (faceModel.isRenderable() && faceModel.needsFixupInScene()) {
|
||||||
getHead()->getFaceModel().addToScene(scene, pendingChanges);
|
faceModel.removeFromScene(scene, pendingChanges);
|
||||||
|
faceModel.addToScene(scene, pendingChanges);
|
||||||
}
|
}
|
||||||
for (auto attachmentModel : _attachmentModels) {
|
for (auto attachmentModel : _attachmentModels) {
|
||||||
if (attachmentModel->needsFixupInScene()) {
|
if (attachmentModel->isRenderable() && attachmentModel->needsFixupInScene()) {
|
||||||
attachmentModel->removeFromScene(scene, pendingChanges);
|
attachmentModel->removeFromScene(scene, pendingChanges);
|
||||||
attachmentModel->addToScene(scene, pendingChanges);
|
attachmentModel->addToScene(scene, pendingChanges);
|
||||||
}
|
}
|
||||||
|
@ -621,11 +618,8 @@ void Avatar::simulateAttachments(float deltaTime) {
|
||||||
int jointIndex = getJointIndex(attachment.jointName);
|
int jointIndex = getJointIndex(attachment.jointName);
|
||||||
glm::vec3 jointPosition;
|
glm::vec3 jointPosition;
|
||||||
glm::quat jointRotation;
|
glm::quat jointRotation;
|
||||||
if (!isMyAvatar()) {
|
|
||||||
model->setLODDistance(getLODDistance());
|
|
||||||
}
|
|
||||||
if (_skeletonModel.getJointPositionInWorldFrame(jointIndex, jointPosition) &&
|
if (_skeletonModel.getJointPositionInWorldFrame(jointIndex, jointPosition) &&
|
||||||
_skeletonModel.getJointCombinedRotation(jointIndex, jointRotation)) {
|
_skeletonModel.getJointCombinedRotation(jointIndex, jointRotation)) {
|
||||||
model->setTranslation(jointPosition + jointRotation * attachment.translation * _scale);
|
model->setTranslation(jointPosition + jointRotation * attachment.translation * _scale);
|
||||||
model->setRotation(jointRotation * attachment.rotation);
|
model->setRotation(jointRotation * attachment.rotation);
|
||||||
model->setScaleToFit(true, _scale * attachment.scale, true); // hack to force rescale
|
model->setScaleToFit(true, _scale * attachment.scale, true); // hack to force rescale
|
||||||
|
@ -978,12 +972,12 @@ void Avatar::scaleVectorRelativeToPosition(glm::vec3 &positionToScale) const {
|
||||||
|
|
||||||
void Avatar::setFaceModelURL(const QUrl& faceModelURL) {
|
void Avatar::setFaceModelURL(const QUrl& faceModelURL) {
|
||||||
AvatarData::setFaceModelURL(faceModelURL);
|
AvatarData::setFaceModelURL(faceModelURL);
|
||||||
getHead()->getFaceModel().setURL(_faceModelURL, AvatarData::defaultFullAvatarModelUrl(), true, !isMyAvatar());
|
getHead()->getFaceModel().setURL(_faceModelURL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
||||||
AvatarData::setSkeletonModelURL(skeletonModelURL);
|
AvatarData::setSkeletonModelURL(skeletonModelURL);
|
||||||
_skeletonModel.setURL(_skeletonModelURL, AvatarData::defaultFullAvatarModelUrl(), true, !isMyAvatar());
|
_skeletonModel.setURL(_skeletonModelURL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Avatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) {
|
void Avatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) {
|
||||||
|
|
|
@ -233,9 +233,6 @@ void Head::simulate(float deltaTime, bool isMine, bool billboard) {
|
||||||
_saccade = glm::vec3();
|
_saccade = glm::vec3();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isMine) {
|
|
||||||
_faceModel.setLODDistance(static_cast<Avatar*>(_owningAvatar)->getLODDistance());
|
|
||||||
}
|
|
||||||
_leftEyePosition = _rightEyePosition = getPosition();
|
_leftEyePosition = _rightEyePosition = getPosition();
|
||||||
if (!billboard) {
|
if (!billboard) {
|
||||||
_faceModel.simulate(deltaTime);
|
_faceModel.simulate(deltaTime);
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
#include <QThreadPool>
|
#include <QThreadPool>
|
||||||
|
|
||||||
#include "AnimationCache.h"
|
#include "AnimationCache.h"
|
||||||
|
#include "AnimationLogging.h"
|
||||||
|
|
||||||
static int animationPointerMetaTypeId = qRegisterMetaType<AnimationPointer>();
|
static int animationPointerMetaTypeId = qRegisterMetaType<AnimationPointer>();
|
||||||
|
|
||||||
|
@ -62,11 +63,15 @@ void AnimationReader::run() {
|
||||||
QSharedPointer<Resource> animation = _animation.toStrongRef();
|
QSharedPointer<Resource> animation = _animation.toStrongRef();
|
||||||
if (!animation.isNull()) {
|
if (!animation.isNull()) {
|
||||||
QMetaObject::invokeMethod(animation.data(), "setGeometry",
|
QMetaObject::invokeMethod(animation.data(), "setGeometry",
|
||||||
Q_ARG(const FBXGeometry&, readFBX(_reply->readAll(), QVariantHash(), _reply->property("url").toString())));
|
Q_ARG(FBXGeometry*, readFBX(_reply->readAll(), QVariantHash(), _reply->property("url").toString())));
|
||||||
}
|
}
|
||||||
_reply->deleteLater();
|
_reply->deleteLater();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Animation::isLoaded() const {
|
||||||
|
return _loaded && _geometry;
|
||||||
|
}
|
||||||
|
|
||||||
QStringList Animation::getJointNames() const {
|
QStringList Animation::getJointNames() const {
|
||||||
if (QThread::currentThread() != thread()) {
|
if (QThread::currentThread() != thread()) {
|
||||||
QStringList result;
|
QStringList result;
|
||||||
|
@ -75,7 +80,7 @@ QStringList Animation::getJointNames() const {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
QStringList names;
|
QStringList names;
|
||||||
foreach (const FBXJoint& joint, _geometry.joints) {
|
foreach (const FBXJoint& joint, _geometry->joints) {
|
||||||
names.append(joint.name);
|
names.append(joint.name);
|
||||||
}
|
}
|
||||||
return names;
|
return names;
|
||||||
|
@ -88,15 +93,15 @@ QVector<FBXAnimationFrame> Animation::getFrames() const {
|
||||||
Q_RETURN_ARG(QVector<FBXAnimationFrame>, result));
|
Q_RETURN_ARG(QVector<FBXAnimationFrame>, result));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
return _geometry.animationFrames;
|
return _geometry->animationFrames;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QVector<FBXAnimationFrame>& Animation::getFramesReference() const {
|
const QVector<FBXAnimationFrame>& Animation::getFramesReference() const {
|
||||||
return _geometry.animationFrames;
|
return _geometry->animationFrames;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Animation::setGeometry(const FBXGeometry& geometry) {
|
void Animation::setGeometry(FBXGeometry* geometry) {
|
||||||
_geometry = geometry;
|
_geometry.reset(geometry);
|
||||||
finishedLoading(true);
|
finishedLoading(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,10 @@ public:
|
||||||
|
|
||||||
Animation(const QUrl& url);
|
Animation(const QUrl& url);
|
||||||
|
|
||||||
const FBXGeometry& getGeometry() const { return _geometry; }
|
const FBXGeometry& getGeometry() const { return *_geometry; }
|
||||||
|
|
||||||
|
virtual bool isLoaded() const override;
|
||||||
|
|
||||||
|
|
||||||
Q_INVOKABLE QStringList getJointNames() const;
|
Q_INVOKABLE QStringList getJointNames() const;
|
||||||
|
|
||||||
|
@ -62,13 +65,13 @@ public:
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
Q_INVOKABLE void setGeometry(const FBXGeometry& geometry);
|
Q_INVOKABLE void setGeometry(FBXGeometry* geometry);
|
||||||
|
|
||||||
virtual void downloadFinished(QNetworkReply* reply);
|
virtual void downloadFinished(QNetworkReply* reply);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
FBXGeometry _geometry;
|
std::unique_ptr<FBXGeometry> _geometry;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ void RenderableZoneEntityItem::changeProperties(Lambda setNewProperties) {
|
||||||
|
|
||||||
_model = getModel();
|
_model = getModel();
|
||||||
_needsInitialSimulation = true;
|
_needsInitialSimulation = true;
|
||||||
_model->setURL(getCompoundShapeURL(), QUrl(), true, true);
|
_model->setURL(getCompoundShapeURL());
|
||||||
}
|
}
|
||||||
if (oldPosition != getPosition() ||
|
if (oldPosition != getPosition() ||
|
||||||
oldRotation != getRotation() ||
|
oldRotation != getRotation() ||
|
||||||
|
@ -85,7 +85,7 @@ void RenderableZoneEntityItem::initialSimulation() {
|
||||||
void RenderableZoneEntityItem::updateGeometry() {
|
void RenderableZoneEntityItem::updateGeometry() {
|
||||||
if (_model && !_model->isActive() && hasCompoundShapeURL()) {
|
if (_model && !_model->isActive() && hasCompoundShapeURL()) {
|
||||||
// Since we have a delayload, we need to update the geometry if it has been downloaded
|
// Since we have a delayload, we need to update the geometry if it has been downloaded
|
||||||
_model->setURL(getCompoundShapeURL(), QUrl(), true);
|
_model->setURL(getCompoundShapeURL());
|
||||||
}
|
}
|
||||||
if (_model && _model->isActive() && _needsInitialSimulation) {
|
if (_model && _model->isActive() && _needsInitialSimulation) {
|
||||||
initialSimulation();
|
initialSimulation();
|
||||||
|
|
|
@ -1373,12 +1373,12 @@ FBXLight extractLight(const FBXNode& object) {
|
||||||
|
|
||||||
|
|
||||||
#if USE_MODEL_MESH
|
#if USE_MODEL_MESH
|
||||||
void buildModelMesh(ExtractedMesh& extracted) {
|
void buildModelMesh(ExtractedMesh& extracted, const QString& url) {
|
||||||
static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex("buildModelMesh failed -- .*");
|
static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex("buildModelMesh failed -- .*");
|
||||||
|
|
||||||
if (extracted.mesh.vertices.size() == 0) {
|
if (extracted.mesh.vertices.size() == 0) {
|
||||||
extracted.mesh._mesh = model::Mesh();
|
extracted.mesh._mesh = model::Mesh();
|
||||||
qCDebug(modelformat) << "buildModelMesh failed -- no vertices";
|
qCDebug(modelformat) << "buildModelMesh failed -- no vertices, url = " << url;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
FBXMesh& fbxMesh = extracted.mesh;
|
FBXMesh& fbxMesh = extracted.mesh;
|
||||||
|
@ -1465,7 +1465,7 @@ void buildModelMesh(ExtractedMesh& extracted) {
|
||||||
|
|
||||||
if (! totalIndices) {
|
if (! totalIndices) {
|
||||||
extracted.mesh._mesh = model::Mesh();
|
extracted.mesh._mesh = model::Mesh();
|
||||||
qCDebug(modelformat) << "buildModelMesh failed -- no indices";
|
qCDebug(modelformat) << "buildModelMesh failed -- no indices, url = " << url;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1505,7 +1505,7 @@ void buildModelMesh(ExtractedMesh& extracted) {
|
||||||
mesh.setPartBuffer(pbv);
|
mesh.setPartBuffer(pbv);
|
||||||
} else {
|
} else {
|
||||||
extracted.mesh._mesh = model::Mesh();
|
extracted.mesh._mesh = model::Mesh();
|
||||||
qCDebug(modelformat) << "buildModelMesh failed -- no parts";
|
qCDebug(modelformat) << "buildModelMesh failed -- no parts, url = " << url;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1530,7 +1530,7 @@ QByteArray fileOnUrl(const QByteArray& filenameString, const QString& url) {
|
||||||
return filename;
|
return filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) {
|
FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) {
|
||||||
QHash<QString, ExtractedMesh> meshes;
|
QHash<QString, ExtractedMesh> meshes;
|
||||||
QHash<QString, QString> modelIDsToNames;
|
QHash<QString, QString> modelIDsToNames;
|
||||||
QHash<QString, int> meshIDsToMeshIndices;
|
QHash<QString, int> meshIDsToMeshIndices;
|
||||||
|
@ -1615,7 +1615,9 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping,
|
||||||
#if defined(DEBUG_FBXREADER)
|
#if defined(DEBUG_FBXREADER)
|
||||||
int unknown = 0;
|
int unknown = 0;
|
||||||
#endif
|
#endif
|
||||||
FBXGeometry geometry;
|
FBXGeometry* geometryPtr = new FBXGeometry;
|
||||||
|
FBXGeometry& geometry = *geometryPtr;
|
||||||
|
|
||||||
float unitScaleFactor = 1.0f;
|
float unitScaleFactor = 1.0f;
|
||||||
glm::vec3 ambientColor;
|
glm::vec3 ambientColor;
|
||||||
QString hifiGlobalNodeID;
|
QString hifiGlobalNodeID;
|
||||||
|
@ -2680,7 +2682,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping,
|
||||||
extracted.mesh.isEye = (maxJointIndex == geometry.leftEyeJointIndex || maxJointIndex == geometry.rightEyeJointIndex);
|
extracted.mesh.isEye = (maxJointIndex == geometry.leftEyeJointIndex || maxJointIndex == geometry.rightEyeJointIndex);
|
||||||
|
|
||||||
# if USE_MODEL_MESH
|
# if USE_MODEL_MESH
|
||||||
buildModelMesh(extracted);
|
buildModelMesh(extracted, url);
|
||||||
# endif
|
# endif
|
||||||
|
|
||||||
if (extracted.mesh.isEye) {
|
if (extracted.mesh.isEye) {
|
||||||
|
@ -2761,15 +2763,15 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return geometry;
|
return geometryPtr;
|
||||||
}
|
}
|
||||||
|
|
||||||
FBXGeometry readFBX(const QByteArray& model, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) {
|
FBXGeometry* readFBX(const QByteArray& model, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) {
|
||||||
QBuffer buffer(const_cast<QByteArray*>(&model));
|
QBuffer buffer(const_cast<QByteArray*>(&model));
|
||||||
buffer.open(QIODevice::ReadOnly);
|
buffer.open(QIODevice::ReadOnly);
|
||||||
return readFBX(&buffer, mapping, url, loadLightmaps, lightmapLevel);
|
return readFBX(&buffer, mapping, url, loadLightmaps, lightmapLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
FBXGeometry readFBX(QIODevice* device, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) {
|
FBXGeometry* readFBX(QIODevice* device, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) {
|
||||||
return extractFBXGeometry(parseFBX(device), mapping, url, loadLightmaps, lightmapLevel);
|
return extractFBXGeometry(parseFBX(device), mapping, url, loadLightmaps, lightmapLevel);
|
||||||
}
|
}
|
||||||
|
|
|
@ -272,10 +272,10 @@ Q_DECLARE_METATYPE(FBXGeometry)
|
||||||
|
|
||||||
/// Reads FBX geometry from the supplied model and mapping data.
|
/// Reads FBX geometry from the supplied model and mapping data.
|
||||||
/// \exception QString if an error occurs in parsing
|
/// \exception QString if an error occurs in parsing
|
||||||
FBXGeometry readFBX(const QByteArray& model, const QVariantHash& mapping, const QString& url = "", bool loadLightmaps = true, float lightmapLevel = 1.0f);
|
FBXGeometry* readFBX(const QByteArray& model, const QVariantHash& mapping, const QString& url = "", bool loadLightmaps = true, float lightmapLevel = 1.0f);
|
||||||
|
|
||||||
/// Reads FBX geometry from the supplied model and mapping data.
|
/// Reads FBX geometry from the supplied model and mapping data.
|
||||||
/// \exception QString if an error occurs in parsing
|
/// \exception QString if an error occurs in parsing
|
||||||
FBXGeometry readFBX(QIODevice* device, const QVariantHash& mapping, const QString& url = "", bool loadLightmaps = true, float lightmapLevel = 1.0f);
|
FBXGeometry* readFBX(QIODevice* device, const QVariantHash& mapping, const QString& url = "", bool loadLightmaps = true, float lightmapLevel = 1.0f);
|
||||||
|
|
||||||
#endif // hifi_FBXReader_h
|
#endif // hifi_FBXReader_h
|
||||||
|
|
|
@ -399,15 +399,16 @@ done:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
FBXGeometry OBJReader::readOBJ(const QByteArray& model, const QVariantHash& mapping) {
|
FBXGeometry* OBJReader::readOBJ(const QByteArray& model, const QVariantHash& mapping) {
|
||||||
QBuffer buffer(const_cast<QByteArray*>(&model));
|
QBuffer buffer(const_cast<QByteArray*>(&model));
|
||||||
buffer.open(QIODevice::ReadOnly);
|
buffer.open(QIODevice::ReadOnly);
|
||||||
return readOBJ(&buffer, mapping, nullptr);
|
return readOBJ(&buffer, mapping, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
FBXGeometry OBJReader::readOBJ(QIODevice* device, const QVariantHash& mapping, QUrl* url) {
|
FBXGeometry* OBJReader::readOBJ(QIODevice* device, const QVariantHash& mapping, QUrl* url) {
|
||||||
FBXGeometry geometry;
|
FBXGeometry* geometryPtr = new FBXGeometry();
|
||||||
|
FBXGeometry& geometry = *geometryPtr;
|
||||||
OBJTokenizer tokenizer(device);
|
OBJTokenizer tokenizer(device);
|
||||||
float scaleGuess = 1.0f;
|
float scaleGuess = 1.0f;
|
||||||
|
|
||||||
|
@ -545,7 +546,7 @@ FBXGeometry OBJReader::readOBJ(QIODevice* device, const QVariantHash& mapping, Q
|
||||||
qCDebug(modelformat) << "OBJ reader fail: " << e.what();
|
qCDebug(modelformat) << "OBJ reader fail: " << e.what();
|
||||||
}
|
}
|
||||||
|
|
||||||
return geometry;
|
return geometryPtr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -71,8 +71,8 @@ public:
|
||||||
QHash<QString, OBJMaterial> materials;
|
QHash<QString, OBJMaterial> materials;
|
||||||
|
|
||||||
QNetworkReply* request(QUrl& url, bool isTest);
|
QNetworkReply* request(QUrl& url, bool isTest);
|
||||||
FBXGeometry readOBJ(const QByteArray& model, const QVariantHash& mapping);
|
FBXGeometry* readOBJ(const QByteArray& model, const QVariantHash& mapping);
|
||||||
FBXGeometry readOBJ(QIODevice* device, const QVariantHash& mapping, QUrl* url);
|
FBXGeometry* readOBJ(QIODevice* device, const QVariantHash& mapping, QUrl* url);
|
||||||
private:
|
private:
|
||||||
QUrl* _url = nullptr;
|
QUrl* _url = nullptr;
|
||||||
|
|
||||||
|
|
|
@ -320,7 +320,6 @@ void Resource::attemptRequest() {
|
||||||
void Resource::finishedLoading(bool success) {
|
void Resource::finishedLoading(bool success) {
|
||||||
if (success) {
|
if (success) {
|
||||||
_loaded = true;
|
_loaded = true;
|
||||||
emit loaded();
|
|
||||||
} else {
|
} else {
|
||||||
_failedToLoad = true;
|
_failedToLoad = true;
|
||||||
}
|
}
|
||||||
|
@ -333,91 +332,26 @@ void Resource::reinsert() {
|
||||||
|
|
||||||
static const int REPLY_TIMEOUT_MS = 5000;
|
static const int REPLY_TIMEOUT_MS = 5000;
|
||||||
void Resource::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
|
void Resource::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
|
||||||
if (!_reply->isFinished()) {
|
_bytesReceived = bytesReceived;
|
||||||
_bytesReceived = bytesReceived;
|
_bytesTotal = bytesTotal;
|
||||||
_bytesTotal = bytesTotal;
|
_replyTimer->start(REPLY_TIMEOUT_MS);
|
||||||
_replyTimer->start(REPLY_TIMEOUT_MS);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_reply->disconnect(this);
|
|
||||||
_replyTimer->disconnect(this);
|
|
||||||
QNetworkReply* reply = _reply;
|
|
||||||
_reply = nullptr;
|
|
||||||
_replyTimer->deleteLater();
|
|
||||||
_replyTimer = nullptr;
|
|
||||||
ResourceCache::requestCompleted(this);
|
|
||||||
|
|
||||||
downloadFinished(reply);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Resource::handleReplyError() {
|
void Resource::handleReplyError() {
|
||||||
handleReplyError(_reply->error(), qDebug() << _reply->errorString());
|
handleReplyErrorInternal(_reply->error());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Resource::handleReplyTimeout() {
|
void Resource::handleReplyTimeout() {
|
||||||
handleReplyError(QNetworkReply::TimeoutError, qDebug() << "Timed out loading" << _reply->url() <<
|
handleReplyErrorInternal(QNetworkReply::TimeoutError);
|
||||||
"received" << _bytesReceived << "total" << _bytesTotal);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Resource::maybeRefresh() {
|
|
||||||
if (Q_LIKELY(NetworkAccessManager::getInstance().cache())) {
|
|
||||||
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
|
|
||||||
QVariant variant = reply->header(QNetworkRequest::LastModifiedHeader);
|
|
||||||
QNetworkCacheMetaData metaData = NetworkAccessManager::getInstance().cache()->metaData(_url);
|
|
||||||
if (variant.isValid() && variant.canConvert<QDateTime>() && metaData.isValid()) {
|
|
||||||
QDateTime lastModified = variant.value<QDateTime>();
|
|
||||||
QDateTime lastModifiedOld = metaData.lastModified();
|
|
||||||
if (lastModified.isValid() && lastModifiedOld.isValid() &&
|
|
||||||
lastModifiedOld >= lastModified) { // With >=, cache won't thrash in eventually-consistent cdn.
|
|
||||||
qCDebug(networking) << "Using cached version of" << _url.fileName();
|
|
||||||
// We don't need to update, return
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else if (!variant.isValid() || !variant.canConvert<QDateTime>() ||
|
|
||||||
!variant.value<QDateTime>().isValid() || variant.value<QDateTime>().isNull()) {
|
|
||||||
qCDebug(networking) << "Cannot determine when" << _url.fileName() << "was modified last, cached version might be outdated";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
qCDebug(networking) << "Loaded" << _url.fileName() << "from the disk cache but the network version is newer, refreshing.";
|
|
||||||
refresh();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Resource::makeRequest() {
|
void Resource::makeRequest() {
|
||||||
_reply = NetworkAccessManager::getInstance().get(_request);
|
_reply = NetworkAccessManager::getInstance().get(_request);
|
||||||
|
|
||||||
connect(_reply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(handleDownloadProgress(qint64,qint64)));
|
connect(_reply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(handleDownloadProgress(qint64,qint64)));
|
||||||
connect(_reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleReplyError()));
|
connect(_reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleReplyError()));
|
||||||
connect(_reply, SIGNAL(finished()), SLOT(handleReplyFinished()));
|
connect(_reply, SIGNAL(finished()), SLOT(handleReplyFinished()));
|
||||||
|
|
||||||
if (_reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool()) {
|
|
||||||
// If the file as been updated since it was cached, refresh it
|
|
||||||
QNetworkRequest request(_request);
|
|
||||||
request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork);
|
|
||||||
request.setAttribute(QNetworkRequest::CacheSaveControlAttribute, false);
|
|
||||||
QNetworkReply* reply = NetworkAccessManager::getInstance().head(request);
|
|
||||||
connect(reply, &QNetworkReply::finished, this, &Resource::maybeRefresh);
|
|
||||||
} else {
|
|
||||||
if (Q_LIKELY(NetworkAccessManager::getInstance().cache())) {
|
|
||||||
QNetworkCacheMetaData metaData = NetworkAccessManager::getInstance().cache()->metaData(_url);
|
|
||||||
bool needUpdate = false;
|
|
||||||
if (metaData.expirationDate().isNull() || metaData.expirationDate() <= QDateTime::currentDateTime()) {
|
|
||||||
// If the expiration date is NULL or in the past,
|
|
||||||
// put one far enough away that it won't be an issue.
|
|
||||||
metaData.setExpirationDate(QDateTime::currentDateTime().addYears(100));
|
|
||||||
needUpdate = true;
|
|
||||||
}
|
|
||||||
if (metaData.lastModified().isNull()) {
|
|
||||||
// If the lastModified date is NULL, set it to now.
|
|
||||||
metaData.setLastModified(QDateTime::currentDateTime());
|
|
||||||
needUpdate = true;
|
|
||||||
}
|
|
||||||
if (needUpdate) {
|
|
||||||
NetworkAccessManager::getInstance().cache()->updateMetaData(metaData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_replyTimer = new QTimer(this);
|
_replyTimer = new QTimer(this);
|
||||||
connect(_replyTimer, SIGNAL(timeout()), SLOT(handleReplyTimeout()));
|
connect(_replyTimer, SIGNAL(timeout()), SLOT(handleReplyTimeout()));
|
||||||
_replyTimer->setSingleShot(true);
|
_replyTimer->setSingleShot(true);
|
||||||
|
@ -425,7 +359,8 @@ void Resource::makeRequest() {
|
||||||
_bytesReceived = _bytesTotal = 0;
|
_bytesReceived = _bytesTotal = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Resource::handleReplyError(QNetworkReply::NetworkError error, QDebug debug) {
|
void Resource::handleReplyErrorInternal(QNetworkReply::NetworkError error) {
|
||||||
|
|
||||||
_reply->disconnect(this);
|
_reply->disconnect(this);
|
||||||
_replyTimer->disconnect(this);
|
_replyTimer->disconnect(this);
|
||||||
_reply->deleteLater();
|
_reply->deleteLater();
|
||||||
|
@ -433,7 +368,7 @@ void Resource::handleReplyError(QNetworkReply::NetworkError error, QDebug debug)
|
||||||
_replyTimer->deleteLater();
|
_replyTimer->deleteLater();
|
||||||
_replyTimer = nullptr;
|
_replyTimer = nullptr;
|
||||||
ResourceCache::requestCompleted(this);
|
ResourceCache::requestCompleted(this);
|
||||||
|
|
||||||
// retry for certain types of failures
|
// retry for certain types of failures
|
||||||
switch (error) {
|
switch (error) {
|
||||||
case QNetworkReply::RemoteHostClosedError:
|
case QNetworkReply::RemoteHostClosedError:
|
||||||
|
@ -444,26 +379,46 @@ void Resource::handleReplyError(QNetworkReply::NetworkError error, QDebug debug)
|
||||||
case QNetworkReply::UnknownNetworkError:
|
case QNetworkReply::UnknownNetworkError:
|
||||||
case QNetworkReply::UnknownProxyError:
|
case QNetworkReply::UnknownProxyError:
|
||||||
case QNetworkReply::UnknownContentError:
|
case QNetworkReply::UnknownContentError:
|
||||||
case QNetworkReply::ProtocolFailure: {
|
case QNetworkReply::ProtocolFailure: {
|
||||||
// retry with increasing delays
|
// retry with increasing delays
|
||||||
const int MAX_ATTEMPTS = 8;
|
const int MAX_ATTEMPTS = 8;
|
||||||
const int BASE_DELAY_MS = 1000;
|
const int BASE_DELAY_MS = 1000;
|
||||||
if (++_attempts < MAX_ATTEMPTS) {
|
if (++_attempts < MAX_ATTEMPTS) {
|
||||||
QTimer::singleShot(BASE_DELAY_MS * (int)pow(2.0, _attempts), this, SLOT(attemptRequest()));
|
QTimer::singleShot(BASE_DELAY_MS * (int)pow(2.0, _attempts), this, SLOT(attemptRequest()));
|
||||||
debug << "-- retrying...";
|
qCWarning(networking) << "error downloading url =" << _url.toDisplayString() << ", error =" << error << ", retrying (" << _attempts << "/" << MAX_ATTEMPTS << ")";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// fall through to final failure
|
// fall through to final failure
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
qCCritical(networking) << "error downloading, url =" << _url.toDisplayString() << ", error =" << error;
|
||||||
|
emit failed(error);
|
||||||
finishedLoading(false);
|
finishedLoading(false);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Resource::handleReplyFinished() {
|
void Resource::handleReplyFinished() {
|
||||||
qCDebug(networking) << "Got finished without download progress/error?" << _url;
|
|
||||||
handleDownloadProgress(0, 0);
|
bool fromCache = _reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool();
|
||||||
|
qCDebug(networking) << "success downloading url =" << _url.toDisplayString() << (fromCache ? "from cache" : "");
|
||||||
|
|
||||||
|
_reply->disconnect(this);
|
||||||
|
_replyTimer->disconnect(this);
|
||||||
|
QNetworkReply* reply = _reply;
|
||||||
|
_reply = nullptr;
|
||||||
|
_replyTimer->deleteLater();
|
||||||
|
_replyTimer = nullptr;
|
||||||
|
ResourceCache::requestCompleted(this);
|
||||||
|
|
||||||
|
finishedLoading(true);
|
||||||
|
emit loaded(*reply);
|
||||||
|
downloadFinished(reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Resource::downloadFinished(QNetworkReply* reply) {
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint qHash(const QPointer<QObject>& value, uint seed) {
|
uint qHash(const QPointer<QObject>& value, uint seed) {
|
||||||
|
|
|
@ -150,7 +150,7 @@ public:
|
||||||
float getLoadPriority();
|
float getLoadPriority();
|
||||||
|
|
||||||
/// Checks whether the resource has loaded.
|
/// Checks whether the resource has loaded.
|
||||||
bool isLoaded() const { return _loaded; }
|
virtual bool isLoaded() const { return _loaded; }
|
||||||
|
|
||||||
/// For loading resources, returns the number of bytes received.
|
/// For loading resources, returns the number of bytes received.
|
||||||
qint64 getBytesReceived() const { return _bytesReceived; }
|
qint64 getBytesReceived() const { return _bytesReceived; }
|
||||||
|
@ -174,21 +174,22 @@ public:
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
/// Fired when the resource has been loaded.
|
/// Fired when the resource has been loaded.
|
||||||
void loaded();
|
void loaded(QNetworkReply& request);
|
||||||
|
|
||||||
|
/// Fired when resource failed to load.
|
||||||
|
void failed(QNetworkReply::NetworkError error);
|
||||||
|
|
||||||
|
/// Fired when resource is refreshed.
|
||||||
void onRefresh();
|
void onRefresh();
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
void attemptRequest();
|
void attemptRequest();
|
||||||
|
|
||||||
/// Refreshes the resource if the last modified date on the network
|
|
||||||
/// is greater than the last modified date in the cache.
|
|
||||||
void maybeRefresh();
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void init();
|
virtual void init();
|
||||||
|
|
||||||
/// Called when the download has finished. The recipient should delete the reply when done with it.
|
/// Called when the download has finished. The recipient should delete the reply when done with it.
|
||||||
virtual void downloadFinished(QNetworkReply* reply) = 0;
|
virtual void downloadFinished(QNetworkReply* reply);
|
||||||
|
|
||||||
/// Should be called by subclasses when all the loading that will be done has been done.
|
/// Should be called by subclasses when all the loading that will be done has been done.
|
||||||
Q_INVOKABLE void finishedLoading(bool success);
|
Q_INVOKABLE void finishedLoading(bool success);
|
||||||
|
@ -216,7 +217,7 @@ private:
|
||||||
|
|
||||||
void makeRequest();
|
void makeRequest();
|
||||||
|
|
||||||
void handleReplyError(QNetworkReply::NetworkError error, QDebug debug);
|
void handleReplyErrorInternal(QNetworkReply::NetworkError error);
|
||||||
|
|
||||||
friend class ResourceCache;
|
friend class ResourceCache;
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
#include <QRunnable>
|
|
||||||
#include <QThreadPool>
|
#include <QThreadPool>
|
||||||
|
|
||||||
#include <FSTReader.h>
|
#include <FSTReader.h>
|
||||||
|
@ -50,6 +49,13 @@ GeometryCache::~GeometryCache() {
|
||||||
#endif //def WANT_DEBUG
|
#endif //def WANT_DEBUG
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QSharedPointer<Resource> GeometryCache::createResource(const QUrl& url, const QSharedPointer<Resource>& fallback,
|
||||||
|
bool delayLoad, const void* extra) {
|
||||||
|
// NetworkGeometry is no longer a subclass of Resource, but requires this method because, it is pure virtual.
|
||||||
|
assert(false);
|
||||||
|
return QSharedPointer<Resource>();
|
||||||
|
}
|
||||||
|
|
||||||
const int NUM_VERTICES_PER_TRIANGLE = 3;
|
const int NUM_VERTICES_PER_TRIANGLE = 3;
|
||||||
const int NUM_TRIANGLES_PER_QUAD = 2;
|
const int NUM_TRIANGLES_PER_QUAD = 2;
|
||||||
const int NUM_VERTICES_PER_TRIANGULATED_QUAD = NUM_VERTICES_PER_TRIANGLE * NUM_TRIANGLES_PER_QUAD;
|
const int NUM_VERTICES_PER_TRIANGULATED_QUAD = NUM_VERTICES_PER_TRIANGLE * NUM_TRIANGLES_PER_QUAD;
|
||||||
|
@ -1643,19 +1649,6 @@ void GeometryCache::renderLine(gpu::Batch& batch, const glm::vec2& p1, const glm
|
||||||
batch.draw(gpu::LINES, 2, 0);
|
batch.draw(gpu::LINES, 2, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
QSharedPointer<NetworkGeometry> GeometryCache::getGeometry(const QUrl& url, const QUrl& fallback, bool delayLoad) {
|
|
||||||
return getResource(url, fallback, delayLoad, NULL).staticCast<NetworkGeometry>();
|
|
||||||
}
|
|
||||||
|
|
||||||
QSharedPointer<Resource> GeometryCache::createResource(const QUrl& url, const QSharedPointer<Resource>& fallback,
|
|
||||||
bool delayLoad, const void* extra) {
|
|
||||||
QSharedPointer<NetworkGeometry> geometry(new NetworkGeometry(url, fallback.staticCast<NetworkGeometry>(), delayLoad),
|
|
||||||
&Resource::allReferencesCleared);
|
|
||||||
geometry->setLODParent(geometry);
|
|
||||||
return geometry.staticCast<Resource>();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GeometryCache::useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend) {
|
void GeometryCache::useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend) {
|
||||||
if (!_standardDrawPipeline) {
|
if (!_standardDrawPipeline) {
|
||||||
auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(standardTransformPNTC_vert)));
|
auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(standardTransformPNTC_vert)));
|
||||||
|
@ -1685,33 +1678,82 @@ void GeometryCache::useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const float NetworkGeometry::NO_HYSTERESIS = -1.0f;
|
GeometryReader::GeometryReader(const QUrl& url, QNetworkReply* reply, const QVariantHash& mapping) :
|
||||||
|
_url(url),
|
||||||
|
_reply(reply),
|
||||||
|
_mapping(mapping) {
|
||||||
|
}
|
||||||
|
|
||||||
NetworkGeometry::NetworkGeometry(const QUrl& url, const QSharedPointer<NetworkGeometry>& fallback, bool delayLoad,
|
void GeometryReader::run() {
|
||||||
const QVariantHash& mapping, const QUrl& textureBase) :
|
try {
|
||||||
Resource(url, delayLoad),
|
if (!_reply) {
|
||||||
_mapping(mapping),
|
throw QString("Reply is NULL ?!");
|
||||||
_textureBase(textureBase.isValid() ? textureBase : url),
|
}
|
||||||
_fallback(fallback)
|
QString urlname = _url.path().toLower();
|
||||||
{
|
bool urlValid = true;
|
||||||
|
urlValid &= !urlname.isEmpty();
|
||||||
if (url.isEmpty()) {
|
urlValid &= !_url.path().isEmpty();
|
||||||
// make the minimal amount of dummy geometry to satisfy Model
|
urlValid &= _url.path().toLower().endsWith(".fbx") || _url.path().toLower().endsWith(".obj");
|
||||||
FBXJoint joint = { false, QVector<int>(), -1, 0.0f, 0.0f, glm::vec3(), glm::mat4(), glm::quat(), glm::quat(),
|
|
||||||
glm::quat(), glm::mat4(), glm::mat4(), glm::vec3(), glm::vec3(), glm::quat(), glm::quat(),
|
if (urlValid) {
|
||||||
glm::mat4(), QString(""), false};
|
// Let's read the binaries from the network
|
||||||
_geometry.joints.append(joint);
|
FBXGeometry* fbxgeo = nullptr;
|
||||||
_geometry.leftEyeJointIndex = -1;
|
if (_url.path().toLower().endsWith(".fbx")) {
|
||||||
_geometry.rightEyeJointIndex = -1;
|
const bool grabLightmaps = true;
|
||||||
_geometry.neckJointIndex = -1;
|
const float lightmapLevel = 1.0f;
|
||||||
_geometry.rootJointIndex = -1;
|
fbxgeo = readFBX(_reply, _mapping, _url.path(), grabLightmaps, lightmapLevel);
|
||||||
_geometry.leanJointIndex = -1;
|
} else if (_url.path().toLower().endsWith(".obj")) {
|
||||||
_geometry.headJointIndex = -1;
|
fbxgeo = OBJReader().readOBJ(_reply, _mapping, &_url);
|
||||||
_geometry.leftHandJointIndex = -1;
|
} else {
|
||||||
_geometry.rightHandJointIndex = -1;
|
QString errorStr("usupported format");
|
||||||
|
emit onError(NetworkGeometry::ModelParseError, errorStr);
|
||||||
|
}
|
||||||
|
emit onSuccess(fbxgeo);
|
||||||
|
} else {
|
||||||
|
throw QString("url is invalid");
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (const QString& error) {
|
||||||
|
qCDebug(renderutils) << "Error reading " << _url << ": " << error;
|
||||||
|
emit onError(NetworkGeometry::ModelParseError, error);
|
||||||
}
|
}
|
||||||
|
_reply->deleteLater();
|
||||||
connect(this, &Resource::loaded, this, &NetworkGeometry::replaceTexturesWithPendingChanges);
|
}
|
||||||
|
|
||||||
|
NetworkGeometry::NetworkGeometry(const QUrl& url, bool delayLoad, const QVariantHash& mapping, const QUrl& textureBaseUrl) :
|
||||||
|
_url(url),
|
||||||
|
_mapping(mapping),
|
||||||
|
_textureBaseUrl(textureBaseUrl) {
|
||||||
|
|
||||||
|
if (delayLoad) {
|
||||||
|
_state = DelayState;
|
||||||
|
} else {
|
||||||
|
attemptRequestInternal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NetworkGeometry::~NetworkGeometry() {
|
||||||
|
if (_resource) {
|
||||||
|
_resource->deleteLater();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkGeometry::attemptRequest() {
|
||||||
|
if (_state == DelayState) {
|
||||||
|
attemptRequestInternal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkGeometry::attemptRequestInternal() {
|
||||||
|
if (_url.path().toLower().endsWith(".fst")) {
|
||||||
|
requestMapping(_url);
|
||||||
|
} else {
|
||||||
|
requestModel(_url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NetworkGeometry::isLoaded() const {
|
||||||
|
return _state == SuccessState;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NetworkGeometry::isLoadedWithTextures() const {
|
bool NetworkGeometry::isLoadedWithTextures() const {
|
||||||
|
@ -1719,12 +1761,12 @@ bool NetworkGeometry::isLoadedWithTextures() const {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!_isLoadedWithTextures) {
|
if (!_isLoadedWithTextures) {
|
||||||
foreach (const NetworkMesh& mesh, _meshes) {
|
for (auto&& mesh : _meshes) {
|
||||||
foreach (const NetworkMeshPart& part, mesh.parts) {
|
for (auto && part : mesh->_parts) {
|
||||||
if ((part.diffuseTexture && !part.diffuseTexture->isLoaded()) ||
|
if ((part->diffuseTexture && !part->diffuseTexture->isLoaded()) ||
|
||||||
(part.normalTexture && !part.normalTexture->isLoaded()) ||
|
(part->normalTexture && !part->normalTexture->isLoaded()) ||
|
||||||
(part.specularTexture && !part.specularTexture->isLoaded()) ||
|
(part->specularTexture && !part->specularTexture->isLoaded()) ||
|
||||||
(part.emissiveTexture && !part.emissiveTexture->isLoaded())) {
|
(part->emissiveTexture && !part->emissiveTexture->isLoaded())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1734,183 +1776,38 @@ bool NetworkGeometry::isLoadedWithTextures() const {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
QSharedPointer<NetworkGeometry> NetworkGeometry::getLODOrFallback(float distance, float& hysteresis, bool delayLoad) const {
|
|
||||||
if (_lodParent.data() != this) {
|
|
||||||
return _lodParent.data()->getLODOrFallback(distance, hysteresis, delayLoad);
|
|
||||||
}
|
|
||||||
if (_failedToLoad && _fallback) {
|
|
||||||
return _fallback;
|
|
||||||
}
|
|
||||||
QSharedPointer<NetworkGeometry> lod = _lodParent;
|
|
||||||
float lodDistance = 0.0f;
|
|
||||||
QMap<float, QSharedPointer<NetworkGeometry> >::const_iterator it = _lods.upperBound(distance);
|
|
||||||
if (it != _lods.constBegin()) {
|
|
||||||
it = it - 1;
|
|
||||||
lod = it.value();
|
|
||||||
lodDistance = it.key();
|
|
||||||
}
|
|
||||||
if (hysteresis != NO_HYSTERESIS && hysteresis != lodDistance) {
|
|
||||||
// if we previously selected a different distance, make sure we've moved far enough to justify switching
|
|
||||||
const float HYSTERESIS_PROPORTION = 0.1f;
|
|
||||||
if (glm::abs(distance - qMax(hysteresis, lodDistance)) / fabsf(hysteresis - lodDistance) < HYSTERESIS_PROPORTION) {
|
|
||||||
lod = _lodParent;
|
|
||||||
lodDistance = 0.0f;
|
|
||||||
it = _lods.upperBound(hysteresis);
|
|
||||||
if (it != _lods.constBegin()) {
|
|
||||||
it = it - 1;
|
|
||||||
lod = it.value();
|
|
||||||
lodDistance = it.key();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (lod && lod->isLoaded()) {
|
|
||||||
hysteresis = lodDistance;
|
|
||||||
return lod;
|
|
||||||
}
|
|
||||||
// if the ideal LOD isn't loaded, we need to make sure it's started to load, and possibly return the closest loaded one
|
|
||||||
if (!delayLoad) {
|
|
||||||
lod->ensureLoading();
|
|
||||||
}
|
|
||||||
float closestDistance = FLT_MAX;
|
|
||||||
if (isLoaded()) {
|
|
||||||
lod = _lodParent;
|
|
||||||
closestDistance = distance;
|
|
||||||
}
|
|
||||||
for (it = _lods.constBegin(); it != _lods.constEnd(); it++) {
|
|
||||||
float distanceToLOD = glm::abs(distance - it.key());
|
|
||||||
if (it.value()->isLoaded() && distanceToLOD < closestDistance) {
|
|
||||||
lod = it.value();
|
|
||||||
closestDistance = distanceToLOD;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
hysteresis = NO_HYSTERESIS;
|
|
||||||
return lod;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint qHash(const QWeakPointer<Animation>& animation, uint seed = 0) {
|
|
||||||
return qHash(animation.data(), seed);
|
|
||||||
}
|
|
||||||
|
|
||||||
QVector<int> NetworkGeometry::getJointMappings(const AnimationPointer& animation) {
|
|
||||||
QVector<int> mappings = _jointMappings.value(animation);
|
|
||||||
if (mappings.isEmpty() && isLoaded() && animation && animation->isLoaded()) {
|
|
||||||
const FBXGeometry& animationGeometry = animation->getGeometry();
|
|
||||||
for (int i = 0; i < animationGeometry.joints.size(); i++) {
|
|
||||||
mappings.append(_geometry.jointIndices.value(animationGeometry.joints.at(i).name) - 1);
|
|
||||||
}
|
|
||||||
_jointMappings.insert(animation, mappings);
|
|
||||||
}
|
|
||||||
return mappings;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkGeometry::setLoadPriority(const QPointer<QObject>& owner, float priority) {
|
|
||||||
Resource::setLoadPriority(owner, priority);
|
|
||||||
|
|
||||||
for (int i = 0; i < _meshes.size(); i++) {
|
|
||||||
NetworkMesh& mesh = _meshes[i];
|
|
||||||
for (int j = 0; j < mesh.parts.size(); j++) {
|
|
||||||
NetworkMeshPart& part = mesh.parts[j];
|
|
||||||
if (part.diffuseTexture) {
|
|
||||||
part.diffuseTexture->setLoadPriority(owner, priority);
|
|
||||||
}
|
|
||||||
if (part.normalTexture) {
|
|
||||||
part.normalTexture->setLoadPriority(owner, priority);
|
|
||||||
}
|
|
||||||
if (part.specularTexture) {
|
|
||||||
part.specularTexture->setLoadPriority(owner, priority);
|
|
||||||
}
|
|
||||||
if (part.emissiveTexture) {
|
|
||||||
part.emissiveTexture->setLoadPriority(owner, priority);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkGeometry::setLoadPriorities(const QHash<QPointer<QObject>, float>& priorities) {
|
|
||||||
Resource::setLoadPriorities(priorities);
|
|
||||||
|
|
||||||
for (int i = 0; i < _meshes.size(); i++) {
|
|
||||||
NetworkMesh& mesh = _meshes[i];
|
|
||||||
for (int j = 0; j < mesh.parts.size(); j++) {
|
|
||||||
NetworkMeshPart& part = mesh.parts[j];
|
|
||||||
if (part.diffuseTexture) {
|
|
||||||
part.diffuseTexture->setLoadPriorities(priorities);
|
|
||||||
}
|
|
||||||
if (part.normalTexture) {
|
|
||||||
part.normalTexture->setLoadPriorities(priorities);
|
|
||||||
}
|
|
||||||
if (part.specularTexture) {
|
|
||||||
part.specularTexture->setLoadPriorities(priorities);
|
|
||||||
}
|
|
||||||
if (part.emissiveTexture) {
|
|
||||||
part.emissiveTexture->setLoadPriorities(priorities);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkGeometry::clearLoadPriority(const QPointer<QObject>& owner) {
|
|
||||||
Resource::clearLoadPriority(owner);
|
|
||||||
|
|
||||||
for (int i = 0; i < _meshes.size(); i++) {
|
|
||||||
NetworkMesh& mesh = _meshes[i];
|
|
||||||
for (int j = 0; j < mesh.parts.size(); j++) {
|
|
||||||
NetworkMeshPart& part = mesh.parts[j];
|
|
||||||
if (part.diffuseTexture) {
|
|
||||||
part.diffuseTexture->clearLoadPriority(owner);
|
|
||||||
}
|
|
||||||
if (part.normalTexture) {
|
|
||||||
part.normalTexture->clearLoadPriority(owner);
|
|
||||||
}
|
|
||||||
if (part.specularTexture) {
|
|
||||||
part.specularTexture->clearLoadPriority(owner);
|
|
||||||
}
|
|
||||||
if (part.emissiveTexture) {
|
|
||||||
part.emissiveTexture->clearLoadPriority(owner);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkGeometry::setTextureWithNameToURL(const QString& name, const QUrl& url) {
|
void NetworkGeometry::setTextureWithNameToURL(const QString& name, const QUrl& url) {
|
||||||
if (_meshes.size() > 0) {
|
if (_meshes.size() > 0) {
|
||||||
auto textureCache = DependencyManager::get<TextureCache>();
|
auto textureCache = DependencyManager::get<TextureCache>();
|
||||||
for (int i = 0; i < _meshes.size(); i++) {
|
for (size_t i = 0; i < _meshes.size(); i++) {
|
||||||
NetworkMesh& mesh = _meshes[i];
|
NetworkMesh& mesh = *(_meshes[i].get());
|
||||||
for (int j = 0; j < mesh.parts.size(); j++) {
|
for (size_t j = 0; j < mesh._parts.size(); j++) {
|
||||||
NetworkMeshPart& part = mesh.parts[j];
|
NetworkMeshPart& part = *(mesh._parts[j].get());
|
||||||
|
|
||||||
QSharedPointer<NetworkTexture> matchingTexture = QSharedPointer<NetworkTexture>();
|
QSharedPointer<NetworkTexture> matchingTexture = QSharedPointer<NetworkTexture>();
|
||||||
if (part.diffuseTextureName == name) {
|
if (part.diffuseTextureName == name) {
|
||||||
part.diffuseTexture = textureCache->getTexture(url, DEFAULT_TEXTURE, _geometry.meshes[i].isEye);
|
part.diffuseTexture = textureCache->getTexture(url, DEFAULT_TEXTURE, _geometry->meshes[i].isEye);
|
||||||
part.diffuseTexture->setLoadPriorities(_loadPriorities);
|
|
||||||
} else if (part.normalTextureName == name) {
|
} else if (part.normalTextureName == name) {
|
||||||
part.normalTexture = textureCache->getTexture(url);
|
part.normalTexture = textureCache->getTexture(url);
|
||||||
part.normalTexture->setLoadPriorities(_loadPriorities);
|
|
||||||
} else if (part.specularTextureName == name) {
|
} else if (part.specularTextureName == name) {
|
||||||
part.specularTexture = textureCache->getTexture(url);
|
part.specularTexture = textureCache->getTexture(url);
|
||||||
part.specularTexture->setLoadPriorities(_loadPriorities);
|
|
||||||
} else if (part.emissiveTextureName == name) {
|
} else if (part.emissiveTextureName == name) {
|
||||||
part.emissiveTexture = textureCache->getTexture(url);
|
part.emissiveTexture = textureCache->getTexture(url);
|
||||||
part.emissiveTexture->setLoadPriorities(_loadPriorities);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
qCDebug(renderutils) << "Adding a name url pair to pending" << name << url;
|
qCWarning(renderutils) << "Ignoring setTextureWirthNameToURL() geometry not ready." << name << url;
|
||||||
// we don't have meshes downloaded yet, so hold this texture as pending
|
|
||||||
_pendingTextureChanges.insert(name, url);
|
|
||||||
}
|
}
|
||||||
_isLoadedWithTextures = false;
|
_isLoadedWithTextures = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList NetworkGeometry::getTextureNames() const {
|
QStringList NetworkGeometry::getTextureNames() const {
|
||||||
QStringList result;
|
QStringList result;
|
||||||
for (int i = 0; i < _meshes.size(); i++) {
|
for (size_t i = 0; i < _meshes.size(); i++) {
|
||||||
const NetworkMesh& mesh = _meshes[i];
|
const NetworkMesh& mesh = *(_meshes[i].get());
|
||||||
for (int j = 0; j < mesh.parts.size(); j++) {
|
for (size_t j = 0; j < mesh._parts.size(); j++) {
|
||||||
const NetworkMeshPart& part = mesh.parts[j];
|
const NetworkMeshPart& part = *(mesh._parts[j].get());
|
||||||
|
|
||||||
if (!part.diffuseTextureName.isEmpty() && part.diffuseTexture) {
|
if (!part.diffuseTextureName.isEmpty() && part.diffuseTexture) {
|
||||||
QString textureURL = part.diffuseTexture->getURL().toString();
|
QString textureURL = part.diffuseTexture->getURL().toString();
|
||||||
result << part.diffuseTextureName + ":" + textureURL;
|
result << part.diffuseTextureName + ":" + textureURL;
|
||||||
|
@ -1935,320 +1832,259 @@ QStringList NetworkGeometry::getTextureNames() const {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkGeometry::replaceTexturesWithPendingChanges() {
|
void NetworkGeometry::requestMapping(const QUrl& url) {
|
||||||
QHash<QString, QUrl>::Iterator it = _pendingTextureChanges.begin();
|
_state = RequestMappingState;
|
||||||
|
if (_resource) {
|
||||||
while (it != _pendingTextureChanges.end()) {
|
_resource->deleteLater();
|
||||||
setTextureWithNameToURL(it.key(), it.value());
|
|
||||||
it = _pendingTextureChanges.erase(it);
|
|
||||||
}
|
}
|
||||||
|
_resource = new Resource(url, false);
|
||||||
|
connect(_resource, SIGNAL(loaded(QNetworkReply&)), SLOT(mappingRequestDone(QNetworkReply&)));
|
||||||
|
connect(_resource, SIGNAL(failed(QNetworkReply::NetworkError)), SLOT(mappingRequestError(QNetworkReply::NetworkError)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reads geometry in a worker thread.
|
void NetworkGeometry::requestModel(const QUrl& url) {
|
||||||
class GeometryReader : public QRunnable {
|
_state = RequestModelState;
|
||||||
public:
|
if (_resource) {
|
||||||
|
_resource->deleteLater();
|
||||||
GeometryReader(const QWeakPointer<Resource>& geometry, const QUrl& url,
|
}
|
||||||
QNetworkReply* reply, const QVariantHash& mapping);
|
_resource = new Resource(url, false);
|
||||||
|
connect(_resource, SIGNAL(loaded(QNetworkReply&)), SLOT(modelRequestDone(QNetworkReply&)));
|
||||||
virtual void run();
|
connect(_resource, SIGNAL(failed(QNetworkReply::NetworkError)), SLOT(modelRequestError(QNetworkReply::NetworkError)));
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
QWeakPointer<Resource> _geometry;
|
|
||||||
QUrl _url;
|
|
||||||
QNetworkReply* _reply;
|
|
||||||
QVariantHash _mapping;
|
|
||||||
};
|
|
||||||
|
|
||||||
GeometryReader::GeometryReader(const QWeakPointer<Resource>& geometry, const QUrl& url,
|
|
||||||
QNetworkReply* reply, const QVariantHash& mapping) :
|
|
||||||
_geometry(geometry),
|
|
||||||
_url(url),
|
|
||||||
_reply(reply),
|
|
||||||
_mapping(mapping) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GeometryReader::run() {
|
void NetworkGeometry::mappingRequestDone(QNetworkReply& reply) {
|
||||||
QSharedPointer<Resource> geometry = _geometry.toStrongRef();
|
assert(_state == RequestMappingState);
|
||||||
if (geometry.isNull()) {
|
|
||||||
_reply->deleteLater();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
if (!_reply) {
|
|
||||||
throw QString("Reply is NULL ?!");
|
|
||||||
}
|
|
||||||
QString urlname = _url.path().toLower();
|
|
||||||
bool urlValid = true;
|
|
||||||
urlValid &= !urlname.isEmpty();
|
|
||||||
urlValid &= !_url.path().isEmpty();
|
|
||||||
urlValid &= _url.path().toLower().endsWith(".fbx")
|
|
||||||
|| _url.path().toLower().endsWith(".obj")
|
|
||||||
|| _url.path().toLower().endsWith(".svo");
|
|
||||||
|
|
||||||
if (urlValid) {
|
// parse the mapping file
|
||||||
// Let's read the binaries from the network
|
_mapping = FSTReader::readMapping(reply.readAll());
|
||||||
FBXGeometry fbxgeo;
|
|
||||||
if (_url.path().toLower().endsWith(".fbx")) {
|
QUrl replyUrl = reply.url();
|
||||||
bool grabLightmaps = true;
|
QString modelUrlStr = _mapping.value("filename").toString();
|
||||||
float lightmapLevel = 1.0f;
|
if (modelUrlStr.isNull()) {
|
||||||
// HACK: For monday 12/01/2014 we need to kill lighmaps loading in starchamber...
|
qCDebug(renderutils) << "Mapping file " << _url << "has no \"filename\" entry";
|
||||||
if (_url.path().toLower().endsWith("loungev4_11-18.fbx")) {
|
emit onFailure(*this, MissingFilenameInMapping);
|
||||||
grabLightmaps = false;
|
} else {
|
||||||
} else if (_url.path().toLower().endsWith("apt8_reboot.fbx")) {
|
// read _textureBase from mapping file, if present
|
||||||
lightmapLevel = 4.0f;
|
QString texdir = _mapping.value("texdir").toString();
|
||||||
} else if (_url.path().toLower().endsWith("palaceoforinthilian4.fbx")) {
|
if (!texdir.isNull()) {
|
||||||
lightmapLevel = 3.5f;
|
if (!texdir.endsWith('/')) {
|
||||||
}
|
texdir += '/';
|
||||||
fbxgeo = readFBX(_reply, _mapping, _url.path(), grabLightmaps, lightmapLevel);
|
|
||||||
} else if (_url.path().toLower().endsWith(".obj")) {
|
|
||||||
fbxgeo = OBJReader().readOBJ(_reply, _mapping, &_url);
|
|
||||||
}
|
}
|
||||||
QMetaObject::invokeMethod(geometry.data(), "setGeometry", Q_ARG(const FBXGeometry&, fbxgeo));
|
_textureBaseUrl = replyUrl.resolved(texdir);
|
||||||
} else {
|
|
||||||
throw QString("url is invalid");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (const QString& error) {
|
QUrl modelUrl = replyUrl.resolved(modelUrlStr);
|
||||||
qCDebug(renderutils) << "Error reading " << _url << ": " << error;
|
requestModel(modelUrl);
|
||||||
QMetaObject::invokeMethod(geometry.data(), "finishedLoading", Q_ARG(bool, false));
|
|
||||||
}
|
|
||||||
_reply->deleteLater();
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkGeometry::init() {
|
|
||||||
_mapping = QVariantHash();
|
|
||||||
_geometry = FBXGeometry();
|
|
||||||
_meshes.clear();
|
|
||||||
_lods.clear();
|
|
||||||
_pendingTextureChanges.clear();
|
|
||||||
_request.setUrl(_url);
|
|
||||||
Resource::init();
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkGeometry::downloadFinished(QNetworkReply* reply) {
|
|
||||||
QUrl url = reply->url();
|
|
||||||
if (url.path().toLower().endsWith(".fst")) {
|
|
||||||
// it's a mapping file; parse it and get the mesh filename
|
|
||||||
_mapping = FSTReader::readMapping(reply->readAll());
|
|
||||||
reply->deleteLater();
|
|
||||||
QString filename = _mapping.value("filename").toString();
|
|
||||||
if (filename.isNull()) {
|
|
||||||
qCDebug(renderutils) << "Mapping file " << url << " has no filename.";
|
|
||||||
finishedLoading(false);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
QString texdir = _mapping.value("texdir").toString();
|
|
||||||
if (!texdir.isNull()) {
|
|
||||||
if (!texdir.endsWith('/')) {
|
|
||||||
texdir += '/';
|
|
||||||
}
|
|
||||||
_textureBase = url.resolved(texdir);
|
|
||||||
}
|
|
||||||
QVariantHash lods = _mapping.value("lod").toHash();
|
|
||||||
for (QVariantHash::const_iterator it = lods.begin(); it != lods.end(); it++) {
|
|
||||||
auto geometry = QSharedPointer<NetworkGeometry>::create(url.resolved(it.key()),
|
|
||||||
QSharedPointer<NetworkGeometry>(), true, _mapping, _textureBase);
|
|
||||||
geometry->setSelf(geometry.staticCast<Resource>());
|
|
||||||
geometry->setLODParent(_lodParent);
|
|
||||||
_lods.insert(it.value().toFloat(), geometry);
|
|
||||||
}
|
|
||||||
_request.setUrl(url.resolved(filename));
|
|
||||||
|
|
||||||
// make the request immediately only if we have no LODs to switch between
|
|
||||||
_startedLoading = false;
|
|
||||||
if (_lods.isEmpty()) {
|
|
||||||
attemptRequest();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// send the reader off to the thread pool
|
|
||||||
QThreadPool::globalInstance()->start(new GeometryReader(_self, url, reply, _mapping));
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkGeometry::reinsert() {
|
|
||||||
Resource::reinsert();
|
|
||||||
|
|
||||||
_lodParent = qWeakPointerCast<NetworkGeometry, Resource>(_self);
|
|
||||||
foreach (const QSharedPointer<NetworkGeometry>& lod, _lods) {
|
|
||||||
lod->setLODParent(_lodParent);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkGeometry::setGeometry(const FBXGeometry& geometry) {
|
void NetworkGeometry::mappingRequestError(QNetworkReply::NetworkError error) {
|
||||||
_geometry = geometry;
|
assert(_state == RequestMappingState);
|
||||||
|
_state = ErrorState;
|
||||||
|
emit onFailure(*this, MappingRequestError);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkGeometry::modelRequestDone(QNetworkReply& reply) {
|
||||||
|
assert(_state == RequestModelState);
|
||||||
|
|
||||||
|
_state = ParsingModelState;
|
||||||
|
|
||||||
|
// asynchronously parse the model file.
|
||||||
|
GeometryReader* geometryReader = new GeometryReader(reply.url(), &reply, _mapping);
|
||||||
|
connect(geometryReader, SIGNAL(onSuccess(FBXGeometry*)), SLOT(modelParseSuccess(FBXGeometry*)));
|
||||||
|
connect(geometryReader, SIGNAL(onError(int, QString)), SLOT(modelParseError(int, QString)));
|
||||||
|
|
||||||
|
QThreadPool::globalInstance()->start(geometryReader);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkGeometry::modelRequestError(QNetworkReply::NetworkError error) {
|
||||||
|
assert(_state == RequestModelState);
|
||||||
|
_state = ErrorState;
|
||||||
|
emit onFailure(*this, ModelRequestError);
|
||||||
|
}
|
||||||
|
|
||||||
|
static NetworkMesh* buildNetworkMesh(const FBXMesh& mesh, const QUrl& textureBaseUrl) {
|
||||||
auto textureCache = DependencyManager::get<TextureCache>();
|
auto textureCache = DependencyManager::get<TextureCache>();
|
||||||
|
NetworkMesh* networkMesh = new NetworkMesh();
|
||||||
foreach (const FBXMesh& mesh, _geometry.meshes) {
|
|
||||||
NetworkMesh networkMesh;
|
int totalIndices = 0;
|
||||||
|
bool checkForTexcoordLightmap = false;
|
||||||
int totalIndices = 0;
|
|
||||||
bool checkForTexcoordLightmap = false;
|
// process network parts
|
||||||
foreach (const FBXMeshPart& part, mesh.parts) {
|
foreach (const FBXMeshPart& part, mesh.parts) {
|
||||||
NetworkMeshPart networkPart;
|
NetworkMeshPart* networkPart = new NetworkMeshPart();
|
||||||
if (!part.diffuseTexture.filename.isEmpty()) {
|
|
||||||
networkPart.diffuseTexture = textureCache->getTexture(
|
if (!part.diffuseTexture.filename.isEmpty()) {
|
||||||
_textureBase.resolved(QUrl(part.diffuseTexture.filename)), DEFAULT_TEXTURE,
|
networkPart->diffuseTexture = textureCache->getTexture(textureBaseUrl.resolved(QUrl(part.diffuseTexture.filename)), DEFAULT_TEXTURE,
|
||||||
mesh.isEye, part.diffuseTexture.content);
|
mesh.isEye, part.diffuseTexture.content);
|
||||||
networkPart.diffuseTextureName = part.diffuseTexture.name;
|
networkPart->diffuseTextureName = part.diffuseTexture.name;
|
||||||
networkPart.diffuseTexture->setLoadPriorities(_loadPriorities);
|
|
||||||
}
|
|
||||||
if (!part.normalTexture.filename.isEmpty()) {
|
|
||||||
networkPart.normalTexture = textureCache->getTexture(
|
|
||||||
_textureBase.resolved(QUrl(part.normalTexture.filename)), NORMAL_TEXTURE,
|
|
||||||
false, part.normalTexture.content);
|
|
||||||
networkPart.normalTextureName = part.normalTexture.name;
|
|
||||||
networkPart.normalTexture->setLoadPriorities(_loadPriorities);
|
|
||||||
}
|
|
||||||
if (!part.specularTexture.filename.isEmpty()) {
|
|
||||||
networkPart.specularTexture = textureCache->getTexture(
|
|
||||||
_textureBase.resolved(QUrl(part.specularTexture.filename)), SPECULAR_TEXTURE,
|
|
||||||
false, part.specularTexture.content);
|
|
||||||
networkPart.specularTextureName = part.specularTexture.name;
|
|
||||||
networkPart.specularTexture->setLoadPriorities(_loadPriorities);
|
|
||||||
}
|
|
||||||
if (!part.emissiveTexture.filename.isEmpty()) {
|
|
||||||
networkPart.emissiveTexture = textureCache->getTexture(
|
|
||||||
_textureBase.resolved(QUrl(part.emissiveTexture.filename)), EMISSIVE_TEXTURE,
|
|
||||||
false, part.emissiveTexture.content);
|
|
||||||
networkPart.emissiveTextureName = part.emissiveTexture.name;
|
|
||||||
networkPart.emissiveTexture->setLoadPriorities(_loadPriorities);
|
|
||||||
checkForTexcoordLightmap = true;
|
|
||||||
}
|
|
||||||
networkMesh.parts.append(networkPart);
|
|
||||||
|
|
||||||
totalIndices += (part.quadIndices.size() + part.triangleIndices.size());
|
|
||||||
}
|
}
|
||||||
|
if (!part.normalTexture.filename.isEmpty()) {
|
||||||
{
|
networkPart->normalTexture = textureCache->getTexture(textureBaseUrl.resolved(QUrl(part.normalTexture.filename)), NORMAL_TEXTURE,
|
||||||
networkMesh._indexBuffer = std::make_shared<gpu::Buffer>();
|
false, part.normalTexture.content);
|
||||||
networkMesh._indexBuffer->resize(totalIndices * sizeof(int));
|
networkPart->normalTextureName = part.normalTexture.name;
|
||||||
int offset = 0;
|
|
||||||
foreach(const FBXMeshPart& part, mesh.parts) {
|
|
||||||
networkMesh._indexBuffer->setSubData(offset, part.quadIndices.size() * sizeof(int),
|
|
||||||
(gpu::Byte*) part.quadIndices.constData());
|
|
||||||
offset += part.quadIndices.size() * sizeof(int);
|
|
||||||
networkMesh._indexBuffer->setSubData(offset, part.triangleIndices.size() * sizeof(int),
|
|
||||||
(gpu::Byte*) part.triangleIndices.constData());
|
|
||||||
offset += part.triangleIndices.size() * sizeof(int);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (!part.specularTexture.filename.isEmpty()) {
|
||||||
{
|
networkPart->specularTexture = textureCache->getTexture(textureBaseUrl.resolved(QUrl(part.specularTexture.filename)), SPECULAR_TEXTURE,
|
||||||
networkMesh._vertexBuffer = std::make_shared<gpu::Buffer>();
|
false, part.specularTexture.content);
|
||||||
// if we don't need to do any blending, the positions/normals can be static
|
networkPart->specularTextureName = part.specularTexture.name;
|
||||||
if (mesh.blendshapes.isEmpty()) {
|
|
||||||
int normalsOffset = mesh.vertices.size() * sizeof(glm::vec3);
|
|
||||||
int tangentsOffset = normalsOffset + mesh.normals.size() * sizeof(glm::vec3);
|
|
||||||
int colorsOffset = tangentsOffset + mesh.tangents.size() * sizeof(glm::vec3);
|
|
||||||
int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
|
|
||||||
int texCoords1Offset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2);
|
|
||||||
int clusterIndicesOffset = texCoords1Offset + mesh.texCoords1.size() * sizeof(glm::vec2);
|
|
||||||
int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4);
|
|
||||||
|
|
||||||
networkMesh._vertexBuffer->resize(clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4));
|
|
||||||
|
|
||||||
networkMesh._vertexBuffer->setSubData(0, mesh.vertices.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.vertices.constData());
|
|
||||||
networkMesh._vertexBuffer->setSubData(normalsOffset, mesh.normals.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.normals.constData());
|
|
||||||
networkMesh._vertexBuffer->setSubData(tangentsOffset,
|
|
||||||
mesh.tangents.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.tangents.constData());
|
|
||||||
networkMesh._vertexBuffer->setSubData(colorsOffset, mesh.colors.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.colors.constData());
|
|
||||||
networkMesh._vertexBuffer->setSubData(texCoordsOffset,
|
|
||||||
mesh.texCoords.size() * sizeof(glm::vec2), (gpu::Byte*) mesh.texCoords.constData());
|
|
||||||
networkMesh._vertexBuffer->setSubData(texCoords1Offset,
|
|
||||||
mesh.texCoords1.size() * sizeof(glm::vec2), (gpu::Byte*) mesh.texCoords1.constData());
|
|
||||||
networkMesh._vertexBuffer->setSubData(clusterIndicesOffset,
|
|
||||||
mesh.clusterIndices.size() * sizeof(glm::vec4), (gpu::Byte*) mesh.clusterIndices.constData());
|
|
||||||
networkMesh._vertexBuffer->setSubData(clusterWeightsOffset,
|
|
||||||
mesh.clusterWeights.size() * sizeof(glm::vec4), (gpu::Byte*) mesh.clusterWeights.constData());
|
|
||||||
|
|
||||||
// otherwise, at least the cluster indices/weights can be static
|
|
||||||
networkMesh._vertexStream = std::make_shared<gpu::BufferStream>();
|
|
||||||
networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, 0, sizeof(glm::vec3));
|
|
||||||
if (mesh.normals.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, normalsOffset, sizeof(glm::vec3));
|
|
||||||
if (mesh.tangents.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, tangentsOffset, sizeof(glm::vec3));
|
|
||||||
if (mesh.colors.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, colorsOffset, sizeof(glm::vec3));
|
|
||||||
if (mesh.texCoords.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, texCoordsOffset, sizeof(glm::vec2));
|
|
||||||
if (mesh.texCoords1.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, texCoords1Offset, sizeof(glm::vec2));
|
|
||||||
if (mesh.clusterIndices.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, clusterIndicesOffset, sizeof(glm::vec4));
|
|
||||||
if (mesh.clusterWeights.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, clusterWeightsOffset, sizeof(glm::vec4));
|
|
||||||
|
|
||||||
int channelNum = 0;
|
|
||||||
networkMesh._vertexFormat = std::make_shared<gpu::Stream::Format>();
|
|
||||||
networkMesh._vertexFormat->setAttribute(gpu::Stream::POSITION, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0);
|
|
||||||
if (mesh.normals.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::NORMAL, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ));
|
|
||||||
if (mesh.tangents.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::TANGENT, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ));
|
|
||||||
if (mesh.colors.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::COLOR, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RGB));
|
|
||||||
if (mesh.texCoords.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::TEXCOORD, channelNum++, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV));
|
|
||||||
if (mesh.texCoords1.size()) {
|
|
||||||
networkMesh._vertexFormat->setAttribute(gpu::Stream::TEXCOORD1, channelNum++, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV));
|
|
||||||
} else if (checkForTexcoordLightmap && mesh.texCoords.size()) {
|
|
||||||
// need lightmap texcoord UV but doesn't have uv#1 so just reuse the same channel
|
|
||||||
networkMesh._vertexFormat->setAttribute(gpu::Stream::TEXCOORD1, channelNum - 1, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV));
|
|
||||||
}
|
|
||||||
if (mesh.clusterIndices.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_INDEX, channelNum++, gpu::Element(gpu::VEC4, gpu::NFLOAT, gpu::XYZW));
|
|
||||||
if (mesh.clusterWeights.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT, channelNum++, gpu::Element(gpu::VEC4, gpu::NFLOAT, gpu::XYZW));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
int colorsOffset = mesh.tangents.size() * sizeof(glm::vec3);
|
|
||||||
int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
|
|
||||||
int clusterIndicesOffset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2);
|
|
||||||
int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4);
|
|
||||||
|
|
||||||
networkMesh._vertexBuffer->resize(clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4));
|
|
||||||
networkMesh._vertexBuffer->setSubData(0, mesh.tangents.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.tangents.constData());
|
|
||||||
networkMesh._vertexBuffer->setSubData(colorsOffset, mesh.colors.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.colors.constData());
|
|
||||||
networkMesh._vertexBuffer->setSubData(texCoordsOffset,
|
|
||||||
mesh.texCoords.size() * sizeof(glm::vec2), (gpu::Byte*) mesh.texCoords.constData());
|
|
||||||
networkMesh._vertexBuffer->setSubData(clusterIndicesOffset,
|
|
||||||
mesh.clusterIndices.size() * sizeof(glm::vec4), (gpu::Byte*) mesh.clusterIndices.constData());
|
|
||||||
networkMesh._vertexBuffer->setSubData(clusterWeightsOffset,
|
|
||||||
mesh.clusterWeights.size() * sizeof(glm::vec4), (gpu::Byte*) mesh.clusterWeights.constData());
|
|
||||||
|
|
||||||
networkMesh._vertexStream = std::make_shared<gpu::BufferStream>();
|
|
||||||
if (mesh.tangents.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, 0, sizeof(glm::vec3));
|
|
||||||
if (mesh.colors.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, colorsOffset, sizeof(glm::vec3));
|
|
||||||
if (mesh.texCoords.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, texCoordsOffset, sizeof(glm::vec2));
|
|
||||||
if (mesh.clusterIndices.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, clusterIndicesOffset, sizeof(glm::vec4));
|
|
||||||
if (mesh.clusterWeights.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, clusterWeightsOffset, sizeof(glm::vec4));
|
|
||||||
|
|
||||||
int channelNum = 0;
|
|
||||||
networkMesh._vertexFormat = std::make_shared<gpu::Stream::Format>();
|
|
||||||
networkMesh._vertexFormat->setAttribute(gpu::Stream::POSITION, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ));
|
|
||||||
if (mesh.normals.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::NORMAL, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ));
|
|
||||||
if (mesh.tangents.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::TANGENT, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ));
|
|
||||||
if (mesh.colors.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::COLOR, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RGB));
|
|
||||||
if (mesh.texCoords.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::TEXCOORD, channelNum++, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV));
|
|
||||||
if (mesh.clusterIndices.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_INDEX, channelNum++, gpu::Element(gpu::VEC4, gpu::NFLOAT, gpu::XYZW));
|
|
||||||
if (mesh.clusterWeights.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT, channelNum++, gpu::Element(gpu::VEC4, gpu::NFLOAT, gpu::XYZW));
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (!part.emissiveTexture.filename.isEmpty()) {
|
||||||
_meshes.append(networkMesh);
|
networkPart->emissiveTexture = textureCache->getTexture(textureBaseUrl.resolved(QUrl(part.emissiveTexture.filename)), EMISSIVE_TEXTURE,
|
||||||
|
false, part.emissiveTexture.content);
|
||||||
|
networkPart->emissiveTextureName = part.emissiveTexture.name;
|
||||||
|
checkForTexcoordLightmap = true;
|
||||||
|
}
|
||||||
|
networkMesh->_parts.emplace_back(networkPart);
|
||||||
|
totalIndices += (part.quadIndices.size() + part.triangleIndices.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
finishedLoading(true);
|
// initialize index buffer
|
||||||
|
{
|
||||||
|
networkMesh->_indexBuffer = std::make_shared<gpu::Buffer>();
|
||||||
|
networkMesh->_indexBuffer->resize(totalIndices * sizeof(int));
|
||||||
|
int offset = 0;
|
||||||
|
foreach(const FBXMeshPart& part, mesh.parts) {
|
||||||
|
networkMesh->_indexBuffer->setSubData(offset, part.quadIndices.size() * sizeof(int),
|
||||||
|
(gpu::Byte*) part.quadIndices.constData());
|
||||||
|
offset += part.quadIndices.size() * sizeof(int);
|
||||||
|
networkMesh->_indexBuffer->setSubData(offset, part.triangleIndices.size() * sizeof(int),
|
||||||
|
(gpu::Byte*) part.triangleIndices.constData());
|
||||||
|
offset += part.triangleIndices.size() * sizeof(int);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize vertex buffer
|
||||||
|
{
|
||||||
|
networkMesh->_vertexBuffer = std::make_shared<gpu::Buffer>();
|
||||||
|
// if we don't need to do any blending, the positions/normals can be static
|
||||||
|
if (mesh.blendshapes.isEmpty()) {
|
||||||
|
int normalsOffset = mesh.vertices.size() * sizeof(glm::vec3);
|
||||||
|
int tangentsOffset = normalsOffset + mesh.normals.size() * sizeof(glm::vec3);
|
||||||
|
int colorsOffset = tangentsOffset + mesh.tangents.size() * sizeof(glm::vec3);
|
||||||
|
int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
|
||||||
|
int texCoords1Offset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2);
|
||||||
|
int clusterIndicesOffset = texCoords1Offset + mesh.texCoords1.size() * sizeof(glm::vec2);
|
||||||
|
int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4);
|
||||||
|
|
||||||
|
networkMesh->_vertexBuffer->resize(clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4));
|
||||||
|
|
||||||
|
networkMesh->_vertexBuffer->setSubData(0, mesh.vertices.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.vertices.constData());
|
||||||
|
networkMesh->_vertexBuffer->setSubData(normalsOffset, mesh.normals.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.normals.constData());
|
||||||
|
networkMesh->_vertexBuffer->setSubData(tangentsOffset,
|
||||||
|
mesh.tangents.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.tangents.constData());
|
||||||
|
networkMesh->_vertexBuffer->setSubData(colorsOffset, mesh.colors.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.colors.constData());
|
||||||
|
networkMesh->_vertexBuffer->setSubData(texCoordsOffset,
|
||||||
|
mesh.texCoords.size() * sizeof(glm::vec2), (gpu::Byte*) mesh.texCoords.constData());
|
||||||
|
networkMesh->_vertexBuffer->setSubData(texCoords1Offset,
|
||||||
|
mesh.texCoords1.size() * sizeof(glm::vec2), (gpu::Byte*) mesh.texCoords1.constData());
|
||||||
|
networkMesh->_vertexBuffer->setSubData(clusterIndicesOffset,
|
||||||
|
mesh.clusterIndices.size() * sizeof(glm::vec4), (gpu::Byte*) mesh.clusterIndices.constData());
|
||||||
|
networkMesh->_vertexBuffer->setSubData(clusterWeightsOffset,
|
||||||
|
mesh.clusterWeights.size() * sizeof(glm::vec4), (gpu::Byte*) mesh.clusterWeights.constData());
|
||||||
|
|
||||||
|
// otherwise, at least the cluster indices/weights can be static
|
||||||
|
networkMesh->_vertexStream = std::make_shared<gpu::BufferStream>();
|
||||||
|
networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, 0, sizeof(glm::vec3));
|
||||||
|
if (mesh.normals.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, normalsOffset, sizeof(glm::vec3));
|
||||||
|
if (mesh.tangents.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, tangentsOffset, sizeof(glm::vec3));
|
||||||
|
if (mesh.colors.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, colorsOffset, sizeof(glm::vec3));
|
||||||
|
if (mesh.texCoords.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, texCoordsOffset, sizeof(glm::vec2));
|
||||||
|
if (mesh.texCoords1.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, texCoords1Offset, sizeof(glm::vec2));
|
||||||
|
if (mesh.clusterIndices.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, clusterIndicesOffset, sizeof(glm::vec4));
|
||||||
|
if (mesh.clusterWeights.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, clusterWeightsOffset, sizeof(glm::vec4));
|
||||||
|
|
||||||
|
int channelNum = 0;
|
||||||
|
networkMesh->_vertexFormat = std::make_shared<gpu::Stream::Format>();
|
||||||
|
networkMesh->_vertexFormat->setAttribute(gpu::Stream::POSITION, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0);
|
||||||
|
if (mesh.normals.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::NORMAL, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ));
|
||||||
|
if (mesh.tangents.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::TANGENT, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ));
|
||||||
|
if (mesh.colors.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::COLOR, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RGB));
|
||||||
|
if (mesh.texCoords.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::TEXCOORD, channelNum++, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV));
|
||||||
|
if (mesh.texCoords1.size()) {
|
||||||
|
networkMesh->_vertexFormat->setAttribute(gpu::Stream::TEXCOORD1, channelNum++, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV));
|
||||||
|
} else if (checkForTexcoordLightmap && mesh.texCoords.size()) {
|
||||||
|
// need lightmap texcoord UV but doesn't have uv#1 so just reuse the same channel
|
||||||
|
networkMesh->_vertexFormat->setAttribute(gpu::Stream::TEXCOORD1, channelNum - 1, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV));
|
||||||
|
}
|
||||||
|
if (mesh.clusterIndices.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_INDEX, channelNum++, gpu::Element(gpu::VEC4, gpu::NFLOAT, gpu::XYZW));
|
||||||
|
if (mesh.clusterWeights.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT, channelNum++, gpu::Element(gpu::VEC4, gpu::NFLOAT, gpu::XYZW));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
int colorsOffset = mesh.tangents.size() * sizeof(glm::vec3);
|
||||||
|
int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
|
||||||
|
int clusterIndicesOffset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2);
|
||||||
|
int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4);
|
||||||
|
|
||||||
|
networkMesh->_vertexBuffer->resize(clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4));
|
||||||
|
networkMesh->_vertexBuffer->setSubData(0, mesh.tangents.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.tangents.constData());
|
||||||
|
networkMesh->_vertexBuffer->setSubData(colorsOffset, mesh.colors.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.colors.constData());
|
||||||
|
networkMesh->_vertexBuffer->setSubData(texCoordsOffset,
|
||||||
|
mesh.texCoords.size() * sizeof(glm::vec2), (gpu::Byte*) mesh.texCoords.constData());
|
||||||
|
networkMesh->_vertexBuffer->setSubData(clusterIndicesOffset,
|
||||||
|
mesh.clusterIndices.size() * sizeof(glm::vec4), (gpu::Byte*) mesh.clusterIndices.constData());
|
||||||
|
networkMesh->_vertexBuffer->setSubData(clusterWeightsOffset,
|
||||||
|
mesh.clusterWeights.size() * sizeof(glm::vec4), (gpu::Byte*) mesh.clusterWeights.constData());
|
||||||
|
|
||||||
|
networkMesh->_vertexStream = std::make_shared<gpu::BufferStream>();
|
||||||
|
if (mesh.tangents.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, 0, sizeof(glm::vec3));
|
||||||
|
if (mesh.colors.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, colorsOffset, sizeof(glm::vec3));
|
||||||
|
if (mesh.texCoords.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, texCoordsOffset, sizeof(glm::vec2));
|
||||||
|
if (mesh.clusterIndices.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, clusterIndicesOffset, sizeof(glm::vec4));
|
||||||
|
if (mesh.clusterWeights.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, clusterWeightsOffset, sizeof(glm::vec4));
|
||||||
|
|
||||||
|
int channelNum = 0;
|
||||||
|
networkMesh->_vertexFormat = std::make_shared<gpu::Stream::Format>();
|
||||||
|
networkMesh->_vertexFormat->setAttribute(gpu::Stream::POSITION, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ));
|
||||||
|
if (mesh.normals.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::NORMAL, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ));
|
||||||
|
if (mesh.tangents.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::TANGENT, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ));
|
||||||
|
if (mesh.colors.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::COLOR, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RGB));
|
||||||
|
if (mesh.texCoords.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::TEXCOORD, channelNum++, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV));
|
||||||
|
if (mesh.clusterIndices.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_INDEX, channelNum++, gpu::Element(gpu::VEC4, gpu::NFLOAT, gpu::XYZW));
|
||||||
|
if (mesh.clusterWeights.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT, channelNum++, gpu::Element(gpu::VEC4, gpu::NFLOAT, gpu::XYZW));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return networkMesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkGeometry::modelParseSuccess(FBXGeometry* geometry) {
|
||||||
|
// assume owner ship of geometry pointer
|
||||||
|
_geometry.reset(geometry);
|
||||||
|
|
||||||
|
foreach(const FBXMesh& mesh, _geometry->meshes) {
|
||||||
|
_meshes.emplace_back(buildNetworkMesh(mesh, _textureBaseUrl));
|
||||||
|
}
|
||||||
|
|
||||||
|
_state = SuccessState;
|
||||||
|
emit onSuccess(*this, *_geometry.get());
|
||||||
|
|
||||||
|
delete _resource;
|
||||||
|
_resource = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkGeometry::modelParseError(int error, QString str) {
|
||||||
|
_state = ErrorState;
|
||||||
|
emit onFailure(*this, (NetworkGeometry::Error)error);
|
||||||
|
|
||||||
|
delete _resource;
|
||||||
|
_resource = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NetworkMeshPart::isTranslucent() const {
|
bool NetworkMeshPart::isTranslucent() const {
|
||||||
return diffuseTexture && diffuseTexture->isTranslucent();
|
return diffuseTexture && diffuseTexture->isTranslucent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool NetworkMesh::isPartTranslucent(const FBXMesh& fbxMesh, int partIndex) const {
|
bool NetworkMesh::isPartTranslucent(const FBXMesh& fbxMesh, int partIndex) const {
|
||||||
assert(partIndex >= 0);
|
assert(partIndex >= 0);
|
||||||
assert(partIndex < parts.size());
|
assert((size_t)partIndex < _parts.size());
|
||||||
return (parts.at(partIndex).isTranslucent() || fbxMesh.parts.at(partIndex).opacity != 1.0f);
|
return (_parts.at(partIndex)->isTranslucent() || fbxMesh.parts.at(partIndex).opacity != 1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
int NetworkMesh::getTranslucentPartCount(const FBXMesh& fbxMesh) const {
|
int NetworkMesh::getTranslucentPartCount(const FBXMesh& fbxMesh) const {
|
||||||
int count = 0;
|
int count = 0;
|
||||||
for (int i = 0; i < parts.size(); i++) {
|
|
||||||
|
for (size_t i = 0; i < _parts.size(); i++) {
|
||||||
if (isPartTranslucent(fbxMesh, i)) {
|
if (isPartTranslucent(fbxMesh, i)) {
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
#define hifi_GeometryCache_h
|
#define hifi_GeometryCache_h
|
||||||
|
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
|
#include <QRunnable>
|
||||||
|
|
||||||
#include <DependencyManager.h>
|
#include <DependencyManager.h>
|
||||||
#include <ResourceCache.h>
|
#include <ResourceCache.h>
|
||||||
|
@ -129,6 +130,9 @@ public:
|
||||||
int allocateID() { return _nextID++; }
|
int allocateID() { return _nextID++; }
|
||||||
static const int UNKNOWN_ID;
|
static const int UNKNOWN_ID;
|
||||||
|
|
||||||
|
virtual QSharedPointer<Resource> createResource(const QUrl& url, const QSharedPointer<Resource>& fallback,
|
||||||
|
bool delayLoad, const void* extra);
|
||||||
|
|
||||||
void renderSphere(gpu::Batch& batch, float radius, int slices, int stacks, const glm::vec3& color, bool solid = true, int id = UNKNOWN_ID)
|
void renderSphere(gpu::Batch& batch, float radius, int slices, int stacks, const glm::vec3& color, bool solid = true, int id = UNKNOWN_ID)
|
||||||
{ renderSphere(batch, radius, slices, stacks, glm::vec4(color, 1.0f), solid, id); }
|
{ renderSphere(batch, radius, slices, stacks, glm::vec4(color, 1.0f), solid, id); }
|
||||||
|
|
||||||
|
@ -208,11 +212,6 @@ public:
|
||||||
/// Set a batch to the simple pipeline, returning the previous pipeline
|
/// Set a batch to the simple pipeline, returning the previous pipeline
|
||||||
void useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend = false);
|
void useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend = false);
|
||||||
|
|
||||||
protected:
|
|
||||||
|
|
||||||
virtual QSharedPointer<Resource> createResource(const QUrl& url,
|
|
||||||
const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
GeometryCache();
|
GeometryCache();
|
||||||
virtual ~GeometryCache();
|
virtual ~GeometryCache();
|
||||||
|
@ -305,70 +304,104 @@ private:
|
||||||
QHash<QUrl, QWeakPointer<NetworkGeometry> > _networkGeometry;
|
QHash<QUrl, QWeakPointer<NetworkGeometry> > _networkGeometry;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Geometry loaded from the network.
|
class NetworkGeometry : public QObject {
|
||||||
class NetworkGeometry : public Resource {
|
|
||||||
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.
|
||||||
/// A hysteresis value indicating that we have no state memory.
|
// if delayLoad is true, the url will not be immediately downloaded.
|
||||||
static const float NO_HYSTERESIS;
|
// use the attemptRequest method to initiate the download.
|
||||||
|
NetworkGeometry(const QUrl& url, bool delayLoad, const QVariantHash& mapping, const QUrl& textureBaseUrl = QUrl());
|
||||||
NetworkGeometry(const QUrl& url, const QSharedPointer<NetworkGeometry>& fallback, bool delayLoad,
|
~NetworkGeometry();
|
||||||
const QVariantHash& mapping = QVariantHash(), const QUrl& textureBase = QUrl());
|
|
||||||
|
|
||||||
/// Checks whether the geometry and its textures are loaded.
|
const QUrl& getURL() const { return _url; }
|
||||||
|
|
||||||
|
void attemptRequest();
|
||||||
|
|
||||||
|
// true when the geometry is loaded (but maybe not it's associated textures)
|
||||||
|
bool isLoaded() const;
|
||||||
|
|
||||||
|
// true when the requested geometry and its textures are loaded.
|
||||||
bool isLoadedWithTextures() const;
|
bool isLoadedWithTextures() const;
|
||||||
|
|
||||||
/// Returns a pointer to the geometry appropriate for the specified distance.
|
// WARNING: only valid when isLoaded returns true.
|
||||||
/// \param hysteresis a hysteresis parameter that prevents rapid model switching
|
const FBXGeometry& getFBXGeometry() const { return *_geometry; }
|
||||||
QSharedPointer<NetworkGeometry> getLODOrFallback(float distance, float& hysteresis, bool delayLoad = false) const;
|
const std::vector<std::unique_ptr<NetworkMesh>>& getMeshes() const { return _meshes; }
|
||||||
|
|
||||||
const FBXGeometry& getFBXGeometry() const { return _geometry; }
|
|
||||||
const QVector<NetworkMesh>& getMeshes() const { return _meshes; }
|
|
||||||
|
|
||||||
QVector<int> getJointMappings(const AnimationPointer& animation);
|
|
||||||
|
|
||||||
virtual void setLoadPriority(const QPointer<QObject>& owner, float priority);
|
|
||||||
virtual void setLoadPriorities(const QHash<QPointer<QObject>, float>& priorities);
|
|
||||||
virtual void clearLoadPriority(const QPointer<QObject>& owner);
|
|
||||||
|
|
||||||
void setTextureWithNameToURL(const QString& name, const QUrl& url);
|
void setTextureWithNameToURL(const QString& name, const QUrl& url);
|
||||||
QStringList getTextureNames() const;
|
QStringList getTextureNames() const;
|
||||||
|
|
||||||
|
enum Error {
|
||||||
|
MissingFilenameInMapping = 0,
|
||||||
|
MappingRequestError,
|
||||||
|
ModelRequestError,
|
||||||
|
ModelParseError
|
||||||
|
};
|
||||||
|
|
||||||
|
signals:
|
||||||
|
// Fired when everything has downloaded and parsed successfully.
|
||||||
|
void onSuccess(NetworkGeometry& networkGeometry, FBXGeometry& fbxGeometry);
|
||||||
|
|
||||||
|
// Fired when something went wrong.
|
||||||
|
void onFailure(NetworkGeometry& networkGeometry, Error error);
|
||||||
|
|
||||||
|
protected slots:
|
||||||
|
void mappingRequestDone(QNetworkReply& reply);
|
||||||
|
void mappingRequestError(QNetworkReply::NetworkError error);
|
||||||
|
|
||||||
|
void modelRequestDone(QNetworkReply& reply);
|
||||||
|
void modelRequestError(QNetworkReply::NetworkError error);
|
||||||
|
|
||||||
|
void modelParseSuccess(FBXGeometry* geometry);
|
||||||
|
void modelParseError(int error, QString str);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
void attemptRequestInternal();
|
||||||
|
void requestMapping(const QUrl& url);
|
||||||
|
void requestModel(const QUrl& url);
|
||||||
|
|
||||||
virtual void init();
|
enum State { DelayState,
|
||||||
virtual void downloadFinished(QNetworkReply* reply);
|
RequestMappingState,
|
||||||
virtual void reinsert();
|
RequestModelState,
|
||||||
|
ParsingModelState,
|
||||||
Q_INVOKABLE void setGeometry(const FBXGeometry& geometry);
|
SuccessState,
|
||||||
|
ErrorState };
|
||||||
private slots:
|
State _state;
|
||||||
void replaceTexturesWithPendingChanges();
|
|
||||||
private:
|
QUrl _url;
|
||||||
|
|
||||||
friend class GeometryCache;
|
|
||||||
|
|
||||||
void setLODParent(const QWeakPointer<NetworkGeometry>& lodParent) { _lodParent = lodParent; }
|
|
||||||
|
|
||||||
QVariantHash _mapping;
|
QVariantHash _mapping;
|
||||||
QUrl _textureBase;
|
QUrl _textureBaseUrl;
|
||||||
QSharedPointer<NetworkGeometry> _fallback;
|
|
||||||
|
|
||||||
QMap<float, QSharedPointer<NetworkGeometry> > _lods;
|
|
||||||
FBXGeometry _geometry;
|
|
||||||
QVector<NetworkMesh> _meshes;
|
|
||||||
|
|
||||||
QWeakPointer<NetworkGeometry> _lodParent;
|
|
||||||
|
|
||||||
QHash<QWeakPointer<Animation>, QVector<int> > _jointMappings;
|
|
||||||
|
|
||||||
QHash<QString, QUrl> _pendingTextureChanges;
|
|
||||||
|
|
||||||
|
Resource* _resource = nullptr;
|
||||||
|
std::unique_ptr<FBXGeometry> _geometry;
|
||||||
|
std::vector<std::unique_ptr<NetworkMesh>> _meshes;
|
||||||
|
|
||||||
|
// cache for isLoadedWithTextures()
|
||||||
mutable bool _isLoadedWithTextures = false;
|
mutable bool _isLoadedWithTextures = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Reads geometry in a worker thread.
|
||||||
|
class GeometryReader : public QObject, public QRunnable {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
GeometryReader(const QUrl& url, QNetworkReply* reply, const QVariantHash& mapping);
|
||||||
|
|
||||||
|
virtual void run();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void onSuccess(FBXGeometry* geometry);
|
||||||
|
void onError(int error, QString str);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
QWeakPointer<Resource> _geometry;
|
||||||
|
QUrl _url;
|
||||||
|
QNetworkReply* _reply;
|
||||||
|
QVariantHash _mapping;
|
||||||
|
};
|
||||||
|
|
||||||
/// The state associated with a single mesh part.
|
/// The state associated with a single mesh part.
|
||||||
class NetworkMeshPart {
|
class NetworkMeshPart {
|
||||||
public:
|
public:
|
||||||
|
@ -394,9 +427,9 @@ public:
|
||||||
gpu::BufferStreamPointer _vertexStream;
|
gpu::BufferStreamPointer _vertexStream;
|
||||||
|
|
||||||
gpu::Stream::FormatPointer _vertexFormat;
|
gpu::Stream::FormatPointer _vertexFormat;
|
||||||
|
|
||||||
QVector<NetworkMeshPart> parts;
|
std::vector<std::unique_ptr<NetworkMeshPart>> _parts;
|
||||||
|
|
||||||
int getTranslucentPartCount(const FBXMesh& fbxMesh) const;
|
int getTranslucentPartCount(const FBXMesh& fbxMesh) const;
|
||||||
bool isPartTranslucent(const FBXMesh& fbxMesh, int partIndex) const;
|
bool isPartTranslucent(const FBXMesh& fbxMesh, int partIndex) const;
|
||||||
};
|
};
|
||||||
|
|
|
@ -48,6 +48,8 @@
|
||||||
#include "model_lightmap_specular_map_frag.h"
|
#include "model_lightmap_specular_map_frag.h"
|
||||||
#include "model_translucent_frag.h"
|
#include "model_translucent_frag.h"
|
||||||
|
|
||||||
|
#include "RenderUtilsLogging.h"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
static int modelPointerTypeId = qRegisterMetaType<QPointer<Model> >();
|
static int modelPointerTypeId = qRegisterMetaType<QPointer<Model> >();
|
||||||
|
@ -66,7 +68,6 @@ Model::Model(RigPointer rig, QObject* parent) :
|
||||||
_snappedToRegistrationPoint(false),
|
_snappedToRegistrationPoint(false),
|
||||||
_showTrueJointTransforms(true),
|
_showTrueJointTransforms(true),
|
||||||
_cauterizeBones(false),
|
_cauterizeBones(false),
|
||||||
_lodDistance(0.0f),
|
|
||||||
_pupilDilation(0.0f),
|
_pupilDilation(0.0f),
|
||||||
_url(HTTP_INVALID_COM),
|
_url(HTTP_INVALID_COM),
|
||||||
_urlAsString(HTTP_INVALID_COM),
|
_urlAsString(HTTP_INVALID_COM),
|
||||||
|
@ -234,7 +235,7 @@ QVector<JointState> Model::createJointStates(const FBXGeometry& geometry) {
|
||||||
};
|
};
|
||||||
|
|
||||||
void Model::initJointTransforms() {
|
void Model::initJointTransforms() {
|
||||||
if (!_geometry) {
|
if (!_geometry || !_geometry->isLoaded()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||||
|
@ -368,8 +369,10 @@ void Model::init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Model::reset() {
|
void Model::reset() {
|
||||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
if (_geometry && _geometry->isLoaded()) {
|
||||||
_rig->reset(geometry.joints);
|
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||||
|
_rig->reset(geometry.joints);
|
||||||
|
}
|
||||||
_meshGroupsKnown = false;
|
_meshGroupsKnown = false;
|
||||||
_readyWhenAdded = false; // in case any of our users are using scenes
|
_readyWhenAdded = false; // in case any of our users are using scenes
|
||||||
invalidCalculatedMeshBoxes(); // if we have to reload, we need to assume our mesh boxes are all invalid
|
invalidCalculatedMeshBoxes(); // if we have to reload, we need to assume our mesh boxes are all invalid
|
||||||
|
@ -378,68 +381,23 @@ void Model::reset() {
|
||||||
bool Model::updateGeometry() {
|
bool Model::updateGeometry() {
|
||||||
PROFILE_RANGE(__FUNCTION__);
|
PROFILE_RANGE(__FUNCTION__);
|
||||||
bool needFullUpdate = false;
|
bool needFullUpdate = false;
|
||||||
|
|
||||||
bool needToRebuild = false;
|
bool needToRebuild = false;
|
||||||
if (_nextGeometry) {
|
|
||||||
_nextGeometry = _nextGeometry->getLODOrFallback(_lodDistance, _nextLODHysteresis);
|
if (!_geometry || !_geometry->isLoaded()) {
|
||||||
_nextGeometry->setLoadPriority(this, -_lodDistance);
|
|
||||||
_nextGeometry->ensureLoading();
|
|
||||||
if (_nextGeometry->isLoaded()) {
|
|
||||||
applyNextGeometry();
|
|
||||||
needToRebuild = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!_geometry) {
|
|
||||||
// geometry is not ready
|
// geometry is not ready
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QSharedPointer<NetworkGeometry> geometry = _geometry->getLODOrFallback(_lodDistance, _lodHysteresis);
|
_needsReload = false;
|
||||||
if (_geometry != geometry) {
|
|
||||||
|
|
||||||
// NOTE: it is theoretically impossible to reach here after passing through the applyNextGeometry() call above.
|
QSharedPointer<NetworkGeometry> geometry = _geometry;
|
||||||
// Which means we don't need to worry about calling deleteGeometry() below immediately after creating new geometry.
|
if (_rig->jointStatesEmpty()) {
|
||||||
|
|
||||||
const FBXGeometry& newGeometry = geometry->getFBXGeometry();
|
|
||||||
QVector<JointState> newJointStates = createJointStates(newGeometry);
|
|
||||||
|
|
||||||
if (! _rig->jointStatesEmpty()) {
|
|
||||||
// copy the existing joint states
|
|
||||||
const FBXGeometry& oldGeometry = _geometry->getFBXGeometry();
|
|
||||||
for (QHash<QString, int>::const_iterator it = oldGeometry.jointIndices.constBegin();
|
|
||||||
it != oldGeometry.jointIndices.constEnd(); it++) {
|
|
||||||
int oldIndex = it.value() - 1;
|
|
||||||
int newIndex = newGeometry.getJointIndex(it.key());
|
|
||||||
if (newIndex != -1) {
|
|
||||||
newJointStates[newIndex].copyState(_rig->getJointState(oldIndex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteGeometry();
|
|
||||||
_dilatedTextures.clear();
|
|
||||||
if (!geometry) {
|
|
||||||
std::cout << "WARNING: no geometry in Model::updateGeometry\n";
|
|
||||||
}
|
|
||||||
setGeometry(geometry);
|
|
||||||
|
|
||||||
_meshGroupsKnown = false;
|
|
||||||
_readyWhenAdded = false; // in case any of our users are using scenes
|
|
||||||
invalidCalculatedMeshBoxes(); // if we have to reload, we need to assume our mesh boxes are all invalid
|
|
||||||
initJointStates(newJointStates);
|
|
||||||
needToRebuild = true;
|
|
||||||
} else if (_rig->jointStatesEmpty()) {
|
|
||||||
const FBXGeometry& fbxGeometry = geometry->getFBXGeometry();
|
const FBXGeometry& fbxGeometry = geometry->getFBXGeometry();
|
||||||
if (fbxGeometry.joints.size() > 0) {
|
if (fbxGeometry.joints.size() > 0) {
|
||||||
initJointStates(createJointStates(fbxGeometry));
|
initJointStates(createJointStates(fbxGeometry));
|
||||||
needToRebuild = true;
|
needToRebuild = true;
|
||||||
}
|
}
|
||||||
} else if (!geometry->isLoaded()) {
|
|
||||||
deleteGeometry();
|
|
||||||
_dilatedTextures.clear();
|
|
||||||
}
|
}
|
||||||
_geometry->setLoadPriority(this, -_lodDistance);
|
|
||||||
_geometry->ensureLoading();
|
|
||||||
|
|
||||||
if (needToRebuild) {
|
if (needToRebuild) {
|
||||||
const FBXGeometry& fbxGeometry = geometry->getFBXGeometry();
|
const FBXGeometry& fbxGeometry = geometry->getFBXGeometry();
|
||||||
|
@ -454,7 +412,7 @@ bool Model::updateGeometry() {
|
||||||
buffer->resize((mesh.vertices.size() + mesh.normals.size()) * sizeof(glm::vec3));
|
buffer->resize((mesh.vertices.size() + mesh.normals.size()) * sizeof(glm::vec3));
|
||||||
buffer->setSubData(0, mesh.vertices.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.vertices.constData());
|
buffer->setSubData(0, mesh.vertices.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.vertices.constData());
|
||||||
buffer->setSubData(mesh.vertices.size() * sizeof(glm::vec3),
|
buffer->setSubData(mesh.vertices.size() * sizeof(glm::vec3),
|
||||||
mesh.normals.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.normals.constData());
|
mesh.normals.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.normals.constData());
|
||||||
}
|
}
|
||||||
_blendedVertexBuffers.push_back(buffer);
|
_blendedVertexBuffers.push_back(buffer);
|
||||||
}
|
}
|
||||||
|
@ -1069,53 +1027,36 @@ int Model::getLastFreeJointIndex(int jointIndex) const {
|
||||||
return (isActive() && jointIndex != -1) ? _geometry->getFBXGeometry().joints.at(jointIndex).freeLineage.last() : -1;
|
return (isActive() && jointIndex != -1) ? _geometry->getFBXGeometry().joints.at(jointIndex).freeLineage.last() : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Model::setURL(const QUrl& url, const QUrl& fallback, bool retainCurrent, bool delayLoad) {
|
void Model::setURL(const QUrl& url) {
|
||||||
|
|
||||||
// don't recreate the geometry if it's the same URL
|
// don't recreate the geometry if it's the same URL
|
||||||
if (_url == url && _geometry && _geometry->getURL() == url) {
|
if (_url == url && _geometry && _geometry->getURL() == url) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_readyWhenAdded = false; // reset out render items.
|
|
||||||
_needsReload = true;
|
|
||||||
invalidCalculatedMeshBoxes();
|
|
||||||
|
|
||||||
_url = url;
|
_url = url;
|
||||||
_urlAsString = _url.toString();
|
_urlAsString = _url.toString();
|
||||||
|
|
||||||
|
{
|
||||||
|
render::PendingChanges pendingChanges;
|
||||||
|
render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene();
|
||||||
|
removeFromScene(scene, pendingChanges);
|
||||||
|
scene->enqueuePendingChanges(pendingChanges);
|
||||||
|
}
|
||||||
|
|
||||||
|
_needsReload = true;
|
||||||
|
_meshGroupsKnown = false;
|
||||||
|
invalidCalculatedMeshBoxes();
|
||||||
|
deleteGeometry();
|
||||||
|
|
||||||
|
_geometry.reset(new NetworkGeometry(url, false, QVariantHash()));
|
||||||
onInvalidate();
|
onInvalidate();
|
||||||
|
|
||||||
// if so instructed, keep the current geometry until the new one is loaded
|
|
||||||
_nextGeometry = DependencyManager::get<GeometryCache>()->getGeometry(url, fallback, delayLoad);
|
|
||||||
_nextLODHysteresis = NetworkGeometry::NO_HYSTERESIS;
|
|
||||||
if (!retainCurrent || !isActive() || (_nextGeometry && _nextGeometry->isLoaded())) {
|
|
||||||
applyNextGeometry();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Model::geometryRefreshed() {
|
|
||||||
QObject* sender = QObject::sender();
|
|
||||||
|
|
||||||
if (sender == _geometry) {
|
|
||||||
_readyWhenAdded = false; // reset out render items.
|
|
||||||
_needsReload = true;
|
|
||||||
invalidCalculatedMeshBoxes();
|
|
||||||
|
|
||||||
onInvalidate();
|
|
||||||
|
|
||||||
// if so instructed, keep the current geometry until the new one is loaded
|
|
||||||
_nextGeometry = DependencyManager::get<GeometryCache>()->getGeometry(_url);
|
|
||||||
_nextLODHysteresis = NetworkGeometry::NO_HYSTERESIS;
|
|
||||||
applyNextGeometry();
|
|
||||||
} else {
|
|
||||||
sender->disconnect(this, SLOT(geometryRefreshed()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const QSharedPointer<NetworkGeometry> Model::getCollisionGeometry(bool delayLoad)
|
const QSharedPointer<NetworkGeometry> Model::getCollisionGeometry(bool delayLoad)
|
||||||
{
|
{
|
||||||
if (_collisionGeometry.isNull() && !_collisionUrl.isEmpty()) {
|
if (_collisionGeometry.isNull() && !_collisionUrl.isEmpty()) {
|
||||||
_collisionGeometry = DependencyManager::get<GeometryCache>()->getGeometry(_collisionUrl, QUrl(), delayLoad);
|
_collisionGeometry.reset(new NetworkGeometry(_collisionUrl, delayLoad, QVariantHash()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_collisionGeometry && _collisionGeometry->isLoaded()) {
|
if (_collisionGeometry && _collisionGeometry->isLoaded()) {
|
||||||
|
@ -1130,7 +1071,7 @@ void Model::setCollisionModelURL(const QUrl& url) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_collisionUrl = url;
|
_collisionUrl = url;
|
||||||
_collisionGeometry = DependencyManager::get<GeometryCache>()->getGeometry(url, QUrl(), true);
|
_collisionGeometry.reset(new NetworkGeometry(url, false, QVariantHash()));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Model::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position) const {
|
bool Model::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position) const {
|
||||||
|
@ -1461,46 +1402,23 @@ void Model::setGeometry(const QSharedPointer<NetworkGeometry>& newGeometry) {
|
||||||
if (_geometry == newGeometry) {
|
if (_geometry == newGeometry) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_geometry) {
|
|
||||||
_geometry->disconnect(_geometry.data(), &Resource::onRefresh, this, &Model::geometryRefreshed);
|
|
||||||
}
|
|
||||||
_geometry = newGeometry;
|
_geometry = newGeometry;
|
||||||
QObject::connect(_geometry.data(), &Resource::onRefresh, this, &Model::geometryRefreshed);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Model::applyNextGeometry() {
|
|
||||||
// delete our local geometry and custom textures
|
|
||||||
deleteGeometry();
|
|
||||||
_dilatedTextures.clear();
|
|
||||||
_lodHysteresis = _nextLODHysteresis;
|
|
||||||
|
|
||||||
// we retain a reference to the base geometry so that its reference count doesn't fall to zero
|
|
||||||
setGeometry(_nextGeometry);
|
|
||||||
|
|
||||||
_meshGroupsKnown = false;
|
|
||||||
_readyWhenAdded = false; // in case any of our users are using scenes
|
|
||||||
_needsReload = false; // we are loaded now!
|
|
||||||
invalidCalculatedMeshBoxes();
|
|
||||||
_nextGeometry.reset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Model::deleteGeometry() {
|
void Model::deleteGeometry() {
|
||||||
_blendedVertexBuffers.clear();
|
_blendedVertexBuffers.clear();
|
||||||
_rig->clearJointStates();
|
_rig->clearJointStates();
|
||||||
_meshStates.clear();
|
_meshStates.clear();
|
||||||
|
|
||||||
_rig->deleteAnimations();
|
_rig->deleteAnimations();
|
||||||
|
|
||||||
if (_geometry) {
|
|
||||||
_geometry->clearLoadPriority(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
_blendedBlendshapeCoefficients.clear();
|
_blendedBlendshapeCoefficients.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
AABox Model::getPartBounds(int meshIndex, int partIndex) {
|
AABox Model::getPartBounds(int meshIndex, int partIndex) {
|
||||||
|
|
||||||
|
if (!_geometry || !_geometry->isLoaded()) {
|
||||||
|
return AABox();
|
||||||
|
}
|
||||||
|
|
||||||
if (meshIndex < _meshStates.size()) {
|
if (meshIndex < _meshStates.size()) {
|
||||||
const MeshState& state = _meshStates.at(meshIndex);
|
const MeshState& state = _meshStates.at(meshIndex);
|
||||||
bool isSkinned = state.clusterMatrices.size() > 1;
|
bool isSkinned = state.clusterMatrices.size() > 1;
|
||||||
|
@ -1531,7 +1449,7 @@ AABox Model::getPartBounds(int meshIndex, int partIndex) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool translucent) {
|
void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool translucent) {
|
||||||
// PROFILE_RANGE(__FUNCTION__);
|
// PROFILE_RANGE(__FUNCTION__);
|
||||||
PerformanceTimer perfTimer("Model::renderPart");
|
PerformanceTimer perfTimer("Model::renderPart");
|
||||||
if (!_readyWhenAdded) {
|
if (!_readyWhenAdded) {
|
||||||
return; // bail asap
|
return; // bail asap
|
||||||
|
@ -1557,14 +1475,14 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool tran
|
||||||
auto alphaThreshold = args->_alphaThreshold; //translucent ? TRANSPARENT_ALPHA_THRESHOLD : OPAQUE_ALPHA_THRESHOLD; // FIX ME
|
auto alphaThreshold = args->_alphaThreshold; //translucent ? TRANSPARENT_ALPHA_THRESHOLD : OPAQUE_ALPHA_THRESHOLD; // FIX ME
|
||||||
|
|
||||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||||
const QVector<NetworkMesh>& networkMeshes = _geometry->getMeshes();
|
const std::vector<std::unique_ptr<NetworkMesh>>& networkMeshes = _geometry->getMeshes();
|
||||||
|
|
||||||
// guard against partially loaded meshes
|
// guard against partially loaded meshes
|
||||||
if (meshIndex >= networkMeshes.size() || meshIndex >= geometry.meshes.size() || meshIndex >= _meshStates.size() ) {
|
if (meshIndex >= (int)networkMeshes.size() || meshIndex >= (int)geometry.meshes.size() || meshIndex >= (int)_meshStates.size() ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NetworkMesh& networkMesh = networkMeshes.at(meshIndex);
|
const NetworkMesh& networkMesh = *(networkMeshes.at(meshIndex).get());
|
||||||
const FBXMesh& mesh = geometry.meshes.at(meshIndex);
|
const FBXMesh& mesh = geometry.meshes.at(meshIndex);
|
||||||
const MeshState& state = _meshStates.at(meshIndex);
|
const MeshState& state = _meshStates.at(meshIndex);
|
||||||
|
|
||||||
|
@ -1614,8 +1532,7 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool tran
|
||||||
|
|
||||||
// if our index is ever out of range for either meshes or networkMeshes, then skip it, and set our _meshGroupsKnown
|
// if our index is ever out of range for either meshes or networkMeshes, then skip it, and set our _meshGroupsKnown
|
||||||
// to false to rebuild out mesh groups.
|
// to false to rebuild out mesh groups.
|
||||||
|
if (meshIndex < 0 || meshIndex >= (int)networkMeshes.size() || meshIndex > geometry.meshes.size()) {
|
||||||
if (meshIndex < 0 || meshIndex >= networkMeshes.size() || meshIndex > geometry.meshes.size()) {
|
|
||||||
_meshGroupsKnown = false; // regenerate these lists next time around.
|
_meshGroupsKnown = false; // regenerate these lists next time around.
|
||||||
_readyWhenAdded = false; // in case any of our users are using scenes
|
_readyWhenAdded = false; // in case any of our users are using scenes
|
||||||
invalidCalculatedMeshBoxes(); // if we have to reload, we need to assume our mesh boxes are all invalid
|
invalidCalculatedMeshBoxes(); // if we have to reload, we need to assume our mesh boxes are all invalid
|
||||||
|
@ -1669,11 +1586,11 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool tran
|
||||||
}
|
}
|
||||||
|
|
||||||
// guard against partially loaded meshes
|
// guard against partially loaded meshes
|
||||||
if (partIndex >= networkMesh.parts.size() || partIndex >= mesh.parts.size()) {
|
if (partIndex >= (int)networkMesh._parts.size() || partIndex >= mesh.parts.size()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NetworkMeshPart& networkPart = networkMesh.parts.at(partIndex);
|
const NetworkMeshPart& networkPart = *(networkMesh._parts.at(partIndex).get());
|
||||||
const FBXMeshPart& part = mesh.parts.at(partIndex);
|
const FBXMeshPart& part = mesh.parts.at(partIndex);
|
||||||
model::MaterialPointer material = part._material;
|
model::MaterialPointer material = part._material;
|
||||||
|
|
||||||
|
@ -1790,10 +1707,10 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool tran
|
||||||
|
|
||||||
void Model::segregateMeshGroups() {
|
void Model::segregateMeshGroups() {
|
||||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||||
const QVector<NetworkMesh>& networkMeshes = _geometry->getMeshes();
|
const std::vector<std::unique_ptr<NetworkMesh>>& networkMeshes = _geometry->getMeshes();
|
||||||
|
|
||||||
// all of our mesh vectors must match in size
|
// all of our mesh vectors must match in size
|
||||||
if (networkMeshes.size() != geometry.meshes.size() ||
|
if ((int)networkMeshes.size() != geometry.meshes.size() ||
|
||||||
geometry.meshes.size() != _meshStates.size()) {
|
geometry.meshes.size() != _meshStates.size()) {
|
||||||
qDebug() << "WARNING!!!! Mesh Sizes don't match! We will not segregate mesh groups yet.";
|
qDebug() << "WARNING!!!! Mesh Sizes don't match! We will not segregate mesh groups yet.";
|
||||||
return;
|
return;
|
||||||
|
@ -1803,12 +1720,12 @@ void Model::segregateMeshGroups() {
|
||||||
_opaqueRenderItems.clear();
|
_opaqueRenderItems.clear();
|
||||||
|
|
||||||
// Run through all of the meshes, and place them into their segregated, but unsorted buckets
|
// Run through all of the meshes, and place them into their segregated, but unsorted buckets
|
||||||
for (int i = 0; i < networkMeshes.size(); i++) {
|
for (int i = 0; i < (int)networkMeshes.size(); i++) {
|
||||||
const NetworkMesh& networkMesh = networkMeshes.at(i);
|
const NetworkMesh& networkMesh = *(networkMeshes.at(i).get());
|
||||||
const FBXMesh& mesh = geometry.meshes.at(i);
|
const FBXMesh& mesh = geometry.meshes.at(i);
|
||||||
const MeshState& state = _meshStates.at(i);
|
const MeshState& state = _meshStates.at(i);
|
||||||
|
|
||||||
bool translucentMesh = networkMesh.getTranslucentPartCount(mesh) == networkMesh.parts.size();
|
bool translucentMesh = networkMesh.getTranslucentPartCount(mesh) == (int)networkMesh._parts.size();
|
||||||
bool hasTangents = !mesh.tangents.isEmpty();
|
bool hasTangents = !mesh.tangents.isEmpty();
|
||||||
bool hasSpecular = mesh.hasSpecularTexture();
|
bool hasSpecular = mesh.hasSpecularTexture();
|
||||||
bool hasLightmap = mesh.hasEmissiveTexture();
|
bool hasLightmap = mesh.hasEmissiveTexture();
|
||||||
|
|
|
@ -68,11 +68,7 @@ public:
|
||||||
|
|
||||||
|
|
||||||
/// Sets the URL of the model to render.
|
/// Sets the URL of the model to render.
|
||||||
/// \param fallback the URL of a fallback model to render if the requested model fails to load
|
Q_INVOKABLE void setURL(const QUrl& url);
|
||||||
/// \param retainCurrent if true, keep rendering the current model until the new one is loaded
|
|
||||||
/// \param delayLoad if true, don't load the model immediately; wait until actually requested
|
|
||||||
Q_INVOKABLE void setURL(const QUrl& url, const QUrl& fallback = QUrl(),
|
|
||||||
bool retainCurrent = false, bool delayLoad = false);
|
|
||||||
const QUrl& getURL() const { return _url; }
|
const QUrl& getURL() const { return _url; }
|
||||||
const QString& getURLAsString() const { return _urlAsString; }
|
const QString& getURLAsString() const { return _urlAsString; }
|
||||||
|
|
||||||
|
@ -89,7 +85,7 @@ public:
|
||||||
render::Item::Status::Getters& statusGetters);
|
render::Item::Status::Getters& statusGetters);
|
||||||
void removeFromScene(std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges);
|
void removeFromScene(std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges);
|
||||||
void renderSetup(RenderArgs* args);
|
void renderSetup(RenderArgs* args);
|
||||||
bool isRenderable() const { return !_meshStates.isEmpty() || (isActive() && _geometry->getMeshes().isEmpty()); }
|
bool isRenderable() const { return !_meshStates.isEmpty() || (isActive() && _geometry->getMeshes().empty()); }
|
||||||
|
|
||||||
bool isVisible() const { return _isVisible; }
|
bool isVisible() const { return _isVisible; }
|
||||||
|
|
||||||
|
@ -141,14 +137,11 @@ public:
|
||||||
const QUrl& getCollisionURL() const { return _collisionUrl; }
|
const QUrl& getCollisionURL() const { return _collisionUrl; }
|
||||||
|
|
||||||
/// Returns a reference to the shared collision geometry.
|
/// Returns a reference to the shared collision geometry.
|
||||||
const QSharedPointer<NetworkGeometry> getCollisionGeometry(bool delayLoad = true);
|
const QSharedPointer<NetworkGeometry> getCollisionGeometry(bool delayLoad = false);
|
||||||
|
|
||||||
void setOffset(const glm::vec3& offset);
|
void setOffset(const glm::vec3& offset);
|
||||||
const glm::vec3& getOffset() const { return _offset; }
|
const glm::vec3& getOffset() const { return _offset; }
|
||||||
|
|
||||||
/// Sets the distance parameter used for LOD computations.
|
|
||||||
void setLODDistance(float distance) { _lodDistance = distance; }
|
|
||||||
|
|
||||||
void setScaleToFit(bool scaleToFit, float largestDimension = 0.0f, bool forceRescale = false);
|
void setScaleToFit(bool scaleToFit, float largestDimension = 0.0f, bool forceRescale = false);
|
||||||
bool getScaleToFit() const { return _scaleToFit; } /// is scale to fit enabled
|
bool getScaleToFit() const { return _scaleToFit; } /// is scale to fit enabled
|
||||||
|
|
||||||
|
@ -309,20 +302,12 @@ protected:
|
||||||
// hook for derived classes to be notified when setUrl invalidates the current model.
|
// hook for derived classes to be notified when setUrl invalidates the current model.
|
||||||
virtual void onInvalidate() {};
|
virtual void onInvalidate() {};
|
||||||
|
|
||||||
void geometryRefreshed();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
void applyNextGeometry();
|
|
||||||
void deleteGeometry();
|
void deleteGeometry();
|
||||||
QVector<JointState> createJointStates(const FBXGeometry& geometry);
|
QVector<JointState> createJointStates(const FBXGeometry& geometry);
|
||||||
void initJointTransforms();
|
void initJointTransforms();
|
||||||
|
|
||||||
QSharedPointer<NetworkGeometry> _nextGeometry;
|
|
||||||
float _lodDistance;
|
|
||||||
float _lodHysteresis;
|
|
||||||
float _nextLODHysteresis;
|
|
||||||
|
|
||||||
QSharedPointer<NetworkGeometry> _collisionGeometry;
|
QSharedPointer<NetworkGeometry> _collisionGeometry;
|
||||||
|
|
||||||
float _pupilDilation;
|
float _pupilDilation;
|
||||||
|
|
95
tests/networking/src/ResourceTests.cpp
Normal file
95
tests/networking/src/ResourceTests.cpp
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
//
|
||||||
|
// ResoruceTests.cpp
|
||||||
|
//
|
||||||
|
// Copyright 2015 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <QNetworkDiskCache>
|
||||||
|
|
||||||
|
#include "ResourceCache.h"
|
||||||
|
#include "NetworkAccessManager.h"
|
||||||
|
#include "DependencyManager.h"
|
||||||
|
|
||||||
|
#include "ResourceTests.h"
|
||||||
|
|
||||||
|
QTEST_MAIN(ResourceTests)
|
||||||
|
|
||||||
|
void ResourceTests::initTestCase() {
|
||||||
|
|
||||||
|
auto resourceCacheSharedItems = DependencyManager::set<ResourceCacheSharedItems>();
|
||||||
|
|
||||||
|
const qint64 MAXIMUM_CACHE_SIZE = 1024 * 1024 * 1024; // 1GB
|
||||||
|
|
||||||
|
// set up the file cache
|
||||||
|
//QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
|
||||||
|
QString cachePath = "./resourceTestCache";
|
||||||
|
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
||||||
|
QNetworkDiskCache* cache = new QNetworkDiskCache();
|
||||||
|
cache->setMaximumCacheSize(MAXIMUM_CACHE_SIZE);
|
||||||
|
cache->setCacheDirectory(cachePath);
|
||||||
|
cache->clear(); // clear the cache
|
||||||
|
networkAccessManager.setCache(cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Resource* resource = nullptr;
|
||||||
|
|
||||||
|
|
||||||
|
static bool waitForSignal(QObject *sender, const char *signal, int timeout = 1000) {
|
||||||
|
QEventLoop loop;
|
||||||
|
QTimer timer;
|
||||||
|
timer.setInterval(timeout);
|
||||||
|
timer.setSingleShot(true);
|
||||||
|
|
||||||
|
loop.connect(sender, signal, SLOT(quit()));
|
||||||
|
loop.connect(&timer, SIGNAL(timeout()), SLOT(quit()));
|
||||||
|
timer.start();
|
||||||
|
loop.exec();
|
||||||
|
|
||||||
|
return timer.isActive();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourceTests::downloadFirst() {
|
||||||
|
|
||||||
|
// download the Mery fst file
|
||||||
|
QUrl meryUrl = QUrl("http://hifi-public.s3.amazonaws.com/marketplace/contents/e21c0b95-e502-4d15-8c41-ea2fc40f1125/3585ddf674869a67d31d5964f7b52de1.fst");
|
||||||
|
resource = new Resource(meryUrl, false);
|
||||||
|
|
||||||
|
const int timeout = 1000;
|
||||||
|
QEventLoop loop;
|
||||||
|
QTimer timer;
|
||||||
|
timer.setInterval(timeout);
|
||||||
|
timer.setSingleShot(true);
|
||||||
|
|
||||||
|
loop.connect(resource, SIGNAL(loaded(QNetworkReply&)), SLOT(quit()));
|
||||||
|
loop.connect(resource, SIGNAL(failed(QNetworkReply::NetworkError)), SLOT(quit()));
|
||||||
|
loop.connect(&timer, SIGNAL(timeout()), SLOT(quit()));
|
||||||
|
timer.start();
|
||||||
|
loop.exec();
|
||||||
|
|
||||||
|
QVERIFY(resource->isLoaded());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourceTests::downloadAgain() {
|
||||||
|
|
||||||
|
// download the Mery fst file
|
||||||
|
QUrl meryUrl = QUrl("http://hifi-public.s3.amazonaws.com/marketplace/contents/e21c0b95-e502-4d15-8c41-ea2fc40f1125/3585ddf674869a67d31d5964f7b52de1.fst");
|
||||||
|
resource = new Resource(meryUrl, false);
|
||||||
|
|
||||||
|
const int timeout = 1000;
|
||||||
|
QEventLoop loop;
|
||||||
|
QTimer timer;
|
||||||
|
timer.setInterval(timeout);
|
||||||
|
timer.setSingleShot(true);
|
||||||
|
|
||||||
|
loop.connect(resource, SIGNAL(loaded(QNetworkReply&)), SLOT(quit()));
|
||||||
|
loop.connect(resource, SIGNAL(failed(QNetworkReply::NetworkError)), SLOT(quit()));
|
||||||
|
loop.connect(&timer, SIGNAL(timeout()), SLOT(quit()));
|
||||||
|
timer.start();
|
||||||
|
loop.exec();
|
||||||
|
|
||||||
|
QVERIFY(resource->isLoaded());
|
||||||
|
|
||||||
|
}
|
23
tests/networking/src/ResourceTests.h
Normal file
23
tests/networking/src/ResourceTests.h
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
//
|
||||||
|
// ResourceTests.h
|
||||||
|
//
|
||||||
|
// Copyright 2015 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef hifi_ResourceTests_h
|
||||||
|
#define hifi_ResourceTests_h
|
||||||
|
|
||||||
|
#include <QtTest/QtTest>
|
||||||
|
|
||||||
|
class ResourceTests : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
private slots:
|
||||||
|
void initTestCase();
|
||||||
|
void downloadFirst();
|
||||||
|
void downloadAgain();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // hifi_ResourceTests_h
|
|
@ -36,15 +36,16 @@ bool vhacd::VHACDUtil::loadFBX(const QString filename, FBXGeometry& result) {
|
||||||
std::cout << "Reading FBX.....\n";
|
std::cout << "Reading FBX.....\n";
|
||||||
|
|
||||||
QByteArray fbxContents = fbx.readAll();
|
QByteArray fbxContents = fbx.readAll();
|
||||||
|
FBXGeometry* geom;
|
||||||
if (filename.toLower().endsWith(".obj")) {
|
if (filename.toLower().endsWith(".obj")) {
|
||||||
result = OBJReader().readOBJ(fbxContents, QVariantHash());
|
geom = OBJReader().readOBJ(fbxContents, QVariantHash());
|
||||||
} else if (filename.toLower().endsWith(".fbx")) {
|
} else if (filename.toLower().endsWith(".fbx")) {
|
||||||
result = readFBX(fbxContents, QVariantHash(), filename);
|
geom = readFBX(fbxContents, QVariantHash(), filename);
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "unknown file extension";
|
qDebug() << "unknown file extension";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
result = *geom;
|
||||||
|
|
||||||
reSortFBXGeometryMeshes(result);
|
reSortFBXGeometryMeshes(result);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue