mirror of
https://github.com/overte-org/overte.git
synced 2025-08-07 10:09:46 +02:00
Fix crash with models from ReadyPlayerMe by adding extra validation.
This also adds test code for the GLTF loader.
This commit is contained in:
parent
918452d02c
commit
cd132246e6
5 changed files with 202 additions and 0 deletions
|
@ -342,6 +342,26 @@ public:
|
||||||
FlowData flowData;
|
FlowData flowData;
|
||||||
|
|
||||||
void debugDump();
|
void debugDump();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the number of warnings that were generated when loading this model.
|
||||||
|
*
|
||||||
|
* These may indicate non-compliance with the spec, or usage of deprecated functionality.
|
||||||
|
* This function is intended to be used for testing.
|
||||||
|
*
|
||||||
|
* @return Count
|
||||||
|
*/
|
||||||
|
int loadWarningCount = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the number of errors that were generated when loading this model.
|
||||||
|
*
|
||||||
|
* Errors indicate the model is probably broken and unusable.
|
||||||
|
*
|
||||||
|
* @return Count
|
||||||
|
*/
|
||||||
|
int loadErrorCount = 0;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1498,6 +1498,32 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::VariantHash&
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int c = 0; c < clusterJoints.size(); ++c) {
|
for (int c = 0; c < clusterJoints.size(); ++c) {
|
||||||
|
if (mesh.clusterIndices.length() <= prevMeshClusterIndexCount + c) {
|
||||||
|
qCWarning(modelformat) << "Trying to write past end of clusterIndices at" << prevMeshClusterIndexCount + c;
|
||||||
|
hfmModel.loadErrorCount++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( clusterJoints.length() <= c) {
|
||||||
|
qCWarning(modelformat) << "Trying to read past end of clusterJoints at" << c;
|
||||||
|
hfmModel.loadErrorCount++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( _file.skins.length() <= node.skin ) {
|
||||||
|
qCWarning(modelformat) << "Trying to read past end of _file.skins at" << node.skin;
|
||||||
|
hfmModel.loadErrorCount++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( _file.skins[node.skin].joints.length() <= clusterJoints[c]) {
|
||||||
|
qCWarning(modelformat) << "Trying to read past end of _file.skins[node.skin].joints at" << clusterJoints[c]
|
||||||
|
<< "; there are only" << _file.skins[node.skin].joints.length() << "for skin" << node.skin;
|
||||||
|
hfmModel.loadErrorCount++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
mesh.clusterIndices[prevMeshClusterIndexCount + c] =
|
mesh.clusterIndices[prevMeshClusterIndexCount + c] =
|
||||||
originalToNewNodeIndexMap[_file.skins[node.skin].joints[clusterJoints[c]]];
|
originalToNewNodeIndexMap[_file.skins[node.skin].joints[clusterJoints[c]]];
|
||||||
}
|
}
|
||||||
|
|
10
tests/model-serializers/CMakeLists.txt
Normal file
10
tests/model-serializers/CMakeLists.txt
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
|
||||||
|
# Declare dependencies
|
||||||
|
macro (setup_testcase_dependencies)
|
||||||
|
# link in the shared libraries
|
||||||
|
link_hifi_libraries(shared test-utils model-serializers networking model-networking hfm graphics gpu image)
|
||||||
|
|
||||||
|
package_libraries_for_deployment()
|
||||||
|
endmacro ()
|
||||||
|
|
||||||
|
setup_hifi_testcase(Script Network)
|
119
tests/model-serializers/src/ModelSerializersTests.cpp
Normal file
119
tests/model-serializers/src/ModelSerializersTests.cpp
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
//
|
||||||
|
// AABoxCubeTests.h
|
||||||
|
// tests/octree/src
|
||||||
|
//
|
||||||
|
// Created by Brad Hefta-Gaub on 06/04/2014.
|
||||||
|
// Copyright 2014 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 "ModelSerializersTests.h"
|
||||||
|
#include "GLTFSerializer.h"
|
||||||
|
#include "FBXSerializer.h"
|
||||||
|
#include "OBJSerializer.h"
|
||||||
|
|
||||||
|
#include "Gzip.h"
|
||||||
|
#include "model-networking/ModelLoader.h"
|
||||||
|
#include <hfm/ModelFormatRegistry.h>
|
||||||
|
#include "DependencyManager.h"
|
||||||
|
#include "ResourceManager.h"
|
||||||
|
#include "AssetClient.h"
|
||||||
|
#include "LimitedNodeList.h"
|
||||||
|
#include "NodeList.h"
|
||||||
|
|
||||||
|
#include <QUrl>
|
||||||
|
#include <QNetworkAccessManager>
|
||||||
|
#include <QNetworkRequest>
|
||||||
|
#include <QNetworkReply>
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
|
||||||
|
QTEST_MAIN(ModelSerializersTests)
|
||||||
|
|
||||||
|
void ModelSerializersTests::initTestCase() {
|
||||||
|
qRegisterMetaType<QNetworkReply*>("QNetworkReply*");
|
||||||
|
|
||||||
|
DependencyManager::registerInheritance<LimitedNodeList, NodeList>();
|
||||||
|
DependencyManager::set<NodeList>(NodeType::Agent, INVALID_PORT);
|
||||||
|
|
||||||
|
DependencyManager::set<ModelFormatRegistry>(); // ModelFormatRegistry must be defined before ModelCache. See the ModelCache constructor.
|
||||||
|
DependencyManager::set<ResourceManager>();
|
||||||
|
DependencyManager::set<AssetClient>();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
auto modelFormatRegistry = DependencyManager::get<ModelFormatRegistry>();
|
||||||
|
modelFormatRegistry->addFormat(FBXSerializer());
|
||||||
|
modelFormatRegistry->addFormat(OBJSerializer());
|
||||||
|
modelFormatRegistry->addFormat(GLTFSerializer());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelSerializersTests::loadGLTF_data() {
|
||||||
|
QTest::addColumn<QString>("filename");
|
||||||
|
QTest::addColumn<bool>("expectWarnings");
|
||||||
|
QTest::addColumn<bool>("expectErrors");
|
||||||
|
|
||||||
|
QTest::newRow("crash1") << "635d84711260644e7e393e0b.glb.gz" << false << true;
|
||||||
|
QTest::newRow("crash2") << "dude.glb.gz" << false << true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelSerializersTests::loadGLTF() {
|
||||||
|
QFETCH(QString, filename);
|
||||||
|
QFETCH(bool, expectWarnings);
|
||||||
|
QFETCH(bool, expectErrors);
|
||||||
|
|
||||||
|
|
||||||
|
QFile gltf_file(filename);
|
||||||
|
QVERIFY(gltf_file.open(QIODevice::ReadOnly));
|
||||||
|
|
||||||
|
QByteArray data = gltf_file.readAll();
|
||||||
|
QByteArray uncompressedData;
|
||||||
|
QUrl url("https://example.com");
|
||||||
|
|
||||||
|
qInfo() << "URL: " << url;
|
||||||
|
|
||||||
|
if (filename.toLower().endsWith(".gz")) {
|
||||||
|
url.setPath("/" + filename.chopped(3));
|
||||||
|
|
||||||
|
if (gunzip(data, uncompressedData)) {
|
||||||
|
qInfo() << "Uncompressed into" << uncompressedData.length();
|
||||||
|
} else {
|
||||||
|
qCritical() << "Failed to uncompress";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
url.setPath("/" + filename);
|
||||||
|
uncompressedData = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ModelLoader loader;
|
||||||
|
QMultiHash<QString, QVariant> serializerMapping;
|
||||||
|
std::string webMediaType;
|
||||||
|
|
||||||
|
serializerMapping.insert("combineParts", true);
|
||||||
|
serializerMapping.insert("deduplicateIndices", true);
|
||||||
|
|
||||||
|
qInfo() << "Loading model from" << uncompressedData.length() << "bytes data, url" << url;
|
||||||
|
|
||||||
|
// Check that we can find a serializer for this
|
||||||
|
auto serializer = DependencyManager::get<ModelFormatRegistry>()->getSerializerForMediaType(uncompressedData, url, webMediaType);
|
||||||
|
QVERIFY(serializer);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
hfm::Model::Pointer model = loader.load(uncompressedData, serializerMapping, url, webMediaType);
|
||||||
|
QVERIFY(model);
|
||||||
|
QVERIFY(!model->meshes.empty());
|
||||||
|
QVERIFY(!model->joints.empty());
|
||||||
|
|
||||||
|
qInfo() << "Model was loaded with" << model->meshes.count() << "meshes and" << model->joints.count() << "joints. Found" << model->loadWarningCount << "warnings and" << model->loadErrorCount << "errors";
|
||||||
|
|
||||||
|
// Some models we test are expected to be broken. We're testing that we can load the model without blowing up,
|
||||||
|
// so loading it with errors is still a successful test.
|
||||||
|
QVERIFY(expectWarnings == (model->loadWarningCount>0));
|
||||||
|
QVERIFY(expectErrors == (model->loadErrorCount>0));
|
||||||
|
}
|
27
tests/model-serializers/src/ModelSerializersTests.h
Normal file
27
tests/model-serializers/src/ModelSerializersTests.h
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
//
|
||||||
|
// ModelSerializersTests.h
|
||||||
|
// tests/model-serializers/src
|
||||||
|
//
|
||||||
|
// Created by Dale Glass on 20/11/2022.
|
||||||
|
// Copyright 2022 Overte e.V.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef overte_ModelSerializersTests_h
|
||||||
|
#define overte_ModelSerializersTests_h
|
||||||
|
|
||||||
|
#include <QtTest/QtTest>
|
||||||
|
|
||||||
|
class ModelSerializersTests : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void initTestCase();
|
||||||
|
void loadGLTF_data();
|
||||||
|
void loadGLTF();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // overte_ModelSerializersTests_h
|
Loading…
Reference in a new issue