mirror of
https://github.com/lubosz/overte.git
synced 2025-04-16 19:59:20 +02:00
Better scale and texture checks
This commit is contained in:
parent
3026fd625a
commit
556a55ff16
5 changed files with 151 additions and 58 deletions
|
@ -4,39 +4,59 @@ import "../../controlsUit" 1.0 as HifiControls
|
|||
import "../../stylesUit" 1.0
|
||||
|
||||
Item {
|
||||
id: diagnosingScreen
|
||||
id: root
|
||||
|
||||
visible: false
|
||||
|
||||
property var avatarDoctor: null
|
||||
property var errors: []
|
||||
|
||||
property int minimumDiagnoseTimeMS: 1000
|
||||
|
||||
signal doneDiagnosing
|
||||
|
||||
onVisibleChanged: {
|
||||
if (!diagnosingScreen.visible) {
|
||||
//if (debugDelay.running) {
|
||||
// debugDelay.stop();
|
||||
//}
|
||||
if (root.avatarDoctor !== null) {
|
||||
root.avatarDoctor.complete.disconnect(_private.avatarDoctorComplete);
|
||||
root.avatarDoctor = null;
|
||||
}
|
||||
if (doneTimer.running) {
|
||||
doneTimer.stop();
|
||||
}
|
||||
|
||||
if (!root.visible) {
|
||||
return;
|
||||
}
|
||||
//debugDelay.start();
|
||||
avatarDoctor = AvatarPackagerCore.currentAvatarProject.diagnose();
|
||||
avatarDoctor.complete.connect(function(errors) {
|
||||
|
||||
root.avatarDoctor = AvatarPackagerCore.currentAvatarProject.diagnose();
|
||||
root.avatarDoctor.complete.connect(this, _private.avatarDoctorComplete);
|
||||
_private.startTime = Date.now();
|
||||
root.avatarDoctor.startDiagnosing();
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: _private
|
||||
property real startTime: 0
|
||||
|
||||
function avatarDoctorComplete(errors) {
|
||||
if (!root.visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.warn("avatarDoctor.complete " + JSON.stringify(errors));
|
||||
diagnosingScreen.errors = errors;
|
||||
root.errors = errors;
|
||||
AvatarPackagerCore.currentAvatarProject.hasErrors = errors.length > 0;
|
||||
AvatarPackagerCore.addCurrentProjectToRecentProjects();
|
||||
|
||||
// FIXME: can't seem to change state here so do it with a timer instead
|
||||
let timeSpendDiagnosingMS = Date.now() - _private.startTime;
|
||||
let timeLeftMS = root.minimumDiagnoseTimeMS - timeSpendDiagnosingMS;
|
||||
doneTimer.interval = timeLeftMS < 0 ? 0 : timeLeftMS;
|
||||
doneTimer.start();
|
||||
});
|
||||
avatarDoctor.startDiagnosing();
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: doneTimer
|
||||
interval: 1
|
||||
repeat: false
|
||||
running: false
|
||||
onTriggered: {
|
||||
|
@ -44,24 +64,6 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Timer {
|
||||
id: debugDelay
|
||||
interval: 5000
|
||||
repeat: false
|
||||
running: false
|
||||
onTriggered: {
|
||||
if (Math.random() > 0.5) {
|
||||
// ERROR
|
||||
avatarPackager.state = AvatarPackagerState.avatarDoctorErrorReport;
|
||||
} else {
|
||||
// SUCCESS
|
||||
avatarPackager.state = AvatarPackagerState.project;
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
property var footer: Item {
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: 17
|
||||
|
|
|
@ -32,7 +32,7 @@ Item {
|
|||
return;
|
||||
}
|
||||
avatarProject.reset();
|
||||
avatarPackager.state = AvatarPackagerState.project;
|
||||
avatarPackager.state = AvatarPackagerState.avatarDoctorDiagnose;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
|
||||
#include "AvatarDoctor.h"
|
||||
#include <model-networking/ModelCache.h>
|
||||
#include <AvatarConstants.h>
|
||||
#include <ResourceManager.h>
|
||||
|
||||
AvatarDoctor::AvatarDoctor(QUrl avatarFSTFileUrl) :
|
||||
_avatarFSTFileUrl(std::move(avatarFSTFileUrl)) {
|
||||
|
@ -18,63 +20,149 @@ AvatarDoctor::AvatarDoctor(QUrl avatarFSTFileUrl) :
|
|||
|
||||
void AvatarDoctor::startDiagnosing() {
|
||||
_errors.clear();
|
||||
|
||||
_externalTextureCount = 0;
|
||||
_checkedTextureCount = 0;
|
||||
_missingTextureCount = 0;
|
||||
_unsupportedTextureCount = 0;
|
||||
|
||||
const auto resource = DependencyManager::get<ModelCache>()->getGeometryResource(_avatarFSTFileUrl);
|
||||
const auto resourceLoaded = [this, resource](bool success) {
|
||||
resource->refresh();
|
||||
const QUrl DEFAULT_URL = QUrl("https://docs.highfidelity.com/create/avatars/create-avatars.html#create-your-own-avatar");
|
||||
const auto resourceLoaded = [this, resource, DEFAULT_URL](bool success) {
|
||||
// MODEL
|
||||
if (!success) {
|
||||
_errors.push_back({ "Model file cannot be opened", QUrl("http://www.highfidelity.com/docs") });
|
||||
_errors.push_back({ "Model file cannot be opened", DEFAULT_URL });
|
||||
emit complete(getErrors());
|
||||
return;
|
||||
}
|
||||
const auto model = resource.data();
|
||||
const auto avatarModel = resource.data()->getHFMModel();
|
||||
if (!avatarModel.originalURL.endsWith(".fbx")) {
|
||||
_errors.push_back({ "Unsupported avatar model format", QUrl("http://www.highfidelity.com/docs") });
|
||||
_errors.push_back({ "Unsupported avatar model format", DEFAULT_URL });
|
||||
emit complete(getErrors());
|
||||
return;
|
||||
}
|
||||
|
||||
// RIG
|
||||
if (avatarModel.joints.isEmpty()) {
|
||||
_errors.push_back({ "Avatar has no rig", QUrl("http://www.highfidelity.com/docs") });
|
||||
_errors.push_back({ "Avatar has no rig", DEFAULT_URL });
|
||||
}
|
||||
else {
|
||||
if (avatarModel.joints.length() > 256) {
|
||||
_errors.push_back({ "Avatar has over 256 bones", QUrl("http://www.highfidelity.com/docs") });
|
||||
_errors.push_back({ "Avatar has over 256 bones", DEFAULT_URL });
|
||||
}
|
||||
// Avatar does not have Hips bone mapped
|
||||
if (!avatarModel.getJointNames().contains("Hips")) {
|
||||
_errors.push_back({ "Hips are not mapped", QUrl("http://www.highfidelity.com/docs") });
|
||||
_errors.push_back({ "Hips are not mapped", DEFAULT_URL });
|
||||
}
|
||||
if (!avatarModel.getJointNames().contains("Spine")) {
|
||||
_errors.push_back({ "Spine is not mapped", QUrl("http://www.highfidelity.com/docs") });
|
||||
_errors.push_back({ "Spine is not mapped", DEFAULT_URL });
|
||||
}
|
||||
if (!avatarModel.getJointNames().contains("Head")) {
|
||||
_errors.push_back({ "Head is not mapped", QUrl("http://www.highfidelity.com/docs") });
|
||||
_errors.push_back({ "Head is not mapped", DEFAULT_URL });
|
||||
}
|
||||
}
|
||||
|
||||
// SCALE
|
||||
const float DEFAULT_HEIGHT = 1.75f;
|
||||
const float RECOMMENDED_MIN_HEIGHT = DEFAULT_HEIGHT * 0.25;
|
||||
const float RECOMMENDED_MAX_HEIGHT = DEFAULT_HEIGHT * 1.5;
|
||||
const float RECOMMENDED_MIN_HEIGHT = DEFAULT_AVATAR_HEIGHT * 0.25f;
|
||||
const float RECOMMENDED_MAX_HEIGHT = DEFAULT_AVATAR_HEIGHT * 1.5f;
|
||||
|
||||
const float avatarHeight = avatarModel.bindExtents.largestDimension();
|
||||
|
||||
float avatarHeight = avatarModel.getMeshExtents().largestDimension();
|
||||
|
||||
qWarning() << "avatarHeight" << avatarHeight;
|
||||
qDebug() << "avatarHeight" << avatarHeight;
|
||||
qDebug() << "defined Scale =" << model->getMapping()["scale"].toFloat();
|
||||
if (avatarHeight < RECOMMENDED_MIN_HEIGHT) {
|
||||
_errors.push_back({ "Avatar is possibly smaller then expected.", QUrl("http://www.highfidelity.com/docs") });
|
||||
_errors.push_back({ "Avatar is possibly smaller then expected.", DEFAULT_URL });
|
||||
} else if (avatarHeight > RECOMMENDED_MAX_HEIGHT) {
|
||||
_errors.push_back({ "Avatar is possibly larger then expected.", DEFAULT_URL });
|
||||
}
|
||||
else if (avatarHeight > RECOMMENDED_MAX_HEIGHT) {
|
||||
_errors.push_back({ "Avatar is possibly larger then expected.", QUrl("http://www.highfidelity.com/docs") });
|
||||
}
|
||||
|
||||
// BLENDSHAPES
|
||||
|
||||
// TEXTURES
|
||||
//avatarModel.materials.
|
||||
QStringList externalTextures{};
|
||||
QSet<QString> textureNames{};
|
||||
auto addTextureToList = [&externalTextures](hfm::Texture texture) mutable {
|
||||
if (!texture.filename.isEmpty() && texture.content.isEmpty() && !externalTextures.contains(texture.name)) {
|
||||
externalTextures << texture.name;
|
||||
}
|
||||
};
|
||||
|
||||
foreach(const HFMMaterial material, avatarModel.materials) {
|
||||
addTextureToList(material.normalTexture);
|
||||
addTextureToList(material.albedoTexture);
|
||||
addTextureToList(material.opacityTexture);
|
||||
addTextureToList(material.glossTexture);
|
||||
addTextureToList(material.roughnessTexture);
|
||||
addTextureToList(material.specularTexture);
|
||||
addTextureToList(material.metallicTexture);
|
||||
addTextureToList(material.emissiveTexture);
|
||||
addTextureToList(material.occlusionTexture);
|
||||
addTextureToList(material.scatteringTexture);
|
||||
addTextureToList(material.lightmapTexture);
|
||||
}
|
||||
|
||||
if (!externalTextures.empty()) {
|
||||
// Check External Textures:
|
||||
auto modelTexturesURLs = model->getTextures();
|
||||
_externalTextureCount = externalTextures.length();
|
||||
foreach(const QString textureKey, externalTextures) {
|
||||
if (!modelTexturesURLs.contains(textureKey)) {
|
||||
_missingTextureCount++;
|
||||
_checkedTextureCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
emit complete(getErrors());
|
||||
const QUrl textureURL = modelTexturesURLs[textureKey].toUrl();
|
||||
|
||||
auto textureResource = DependencyManager::get<TextureCache>()->getTexture(textureURL);
|
||||
auto checkTextureLoadingComplete = [this, DEFAULT_URL] () mutable {
|
||||
qDebug() << "checkTextureLoadingComplete" << _checkedTextureCount << "/" << _externalTextureCount;
|
||||
|
||||
if (_checkedTextureCount == _externalTextureCount) {
|
||||
if (_missingTextureCount == 1) {
|
||||
_errors.push_back({ tr("Missing %n texture(s).","", _missingTextureCount), DEFAULT_URL });
|
||||
}
|
||||
if (_unsupportedTextureCount > 0) {
|
||||
_errors.push_back({ tr("%n unsupported texture(s) found.", "", _unsupportedTextureCount), DEFAULT_URL });
|
||||
}
|
||||
emit complete(getErrors());
|
||||
}
|
||||
};
|
||||
|
||||
auto textureLoaded = [this, textureResource, checkTextureLoadingComplete] (bool success) mutable {
|
||||
if (!success) {
|
||||
auto normalizedURL = DependencyManager::get<ResourceManager>()->normalizeURL(textureResource->getURL());
|
||||
if (normalizedURL.isLocalFile()) {
|
||||
QFile textureFile(normalizedURL.toLocalFile());
|
||||
if (textureFile.exists()) {
|
||||
_unsupportedTextureCount++;
|
||||
} else {
|
||||
_missingTextureCount++;
|
||||
}
|
||||
} else {
|
||||
_missingTextureCount++;
|
||||
}
|
||||
}
|
||||
_checkedTextureCount++;
|
||||
checkTextureLoadingComplete();
|
||||
};
|
||||
|
||||
if (textureResource) {
|
||||
textureResource->refresh();
|
||||
if (textureResource->isLoaded()) {
|
||||
textureLoaded(!textureResource->isFailed());
|
||||
} else {
|
||||
connect(textureResource.data(), &NetworkTexture::finished, this, textureLoaded);
|
||||
}
|
||||
} else {
|
||||
_missingTextureCount++;
|
||||
_checkedTextureCount++;
|
||||
checkTextureLoadingComplete();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
emit complete(getErrors());
|
||||
}
|
||||
};
|
||||
|
||||
if (resource) {
|
||||
|
@ -84,7 +172,7 @@ void AvatarDoctor::startDiagnosing() {
|
|||
connect(resource.data(), &GeometryResource::finished, this, resourceLoaded);
|
||||
}
|
||||
} else {
|
||||
_errors.push_back({ "Model file cannot be opened", QUrl("http://www.highfidelity.com/docs") });
|
||||
_errors.push_back({ "Model file cannot be opened", DEFAULT_URL });
|
||||
emit complete(getErrors());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,6 +45,11 @@ signals:
|
|||
private:
|
||||
QUrl _avatarFSTFileUrl;
|
||||
QVector<AvatarDiagnosticResult> _errors;
|
||||
|
||||
int _externalTextureCount = 0;
|
||||
int _checkedTextureCount = 0;
|
||||
int _missingTextureCount = 0;
|
||||
int _unsupportedTextureCount = 0;
|
||||
};
|
||||
|
||||
#endif // hifi_AvatarDoctor_h
|
||||
|
|
|
@ -244,9 +244,7 @@ MarketplaceItemUploader* AvatarProject::upload(bool updateExisting) {
|
|||
}
|
||||
|
||||
AvatarDoctor* AvatarProject::diagnose() {
|
||||
auto avatarDoctor = new AvatarDoctor(QUrl(getFSTPath()));
|
||||
|
||||
return avatarDoctor;
|
||||
return new AvatarDoctor(QUrl(getFSTPath()));
|
||||
}
|
||||
|
||||
void AvatarProject::openInInventory() const {
|
||||
|
|
Loading…
Reference in a new issue