mirror of
https://github.com/overte-org/overte.git
synced 2025-08-06 00:40:06 +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
|
import "../../stylesUit" 1.0
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: diagnosingScreen
|
id: root
|
||||||
|
|
||||||
visible: false
|
visible: false
|
||||||
|
|
||||||
property var avatarDoctor: null
|
property var avatarDoctor: null
|
||||||
property var errors: []
|
property var errors: []
|
||||||
|
|
||||||
|
property int minimumDiagnoseTimeMS: 1000
|
||||||
|
|
||||||
signal doneDiagnosing
|
signal doneDiagnosing
|
||||||
|
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
if (!diagnosingScreen.visible) {
|
if (root.avatarDoctor !== null) {
|
||||||
//if (debugDelay.running) {
|
root.avatarDoctor.complete.disconnect(_private.avatarDoctorComplete);
|
||||||
// debugDelay.stop();
|
root.avatarDoctor = null;
|
||||||
//}
|
}
|
||||||
|
if (doneTimer.running) {
|
||||||
|
doneTimer.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!root.visible) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//debugDelay.start();
|
|
||||||
avatarDoctor = AvatarPackagerCore.currentAvatarProject.diagnose();
|
root.avatarDoctor = AvatarPackagerCore.currentAvatarProject.diagnose();
|
||||||
avatarDoctor.complete.connect(function(errors) {
|
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));
|
console.warn("avatarDoctor.complete " + JSON.stringify(errors));
|
||||||
diagnosingScreen.errors = errors;
|
root.errors = errors;
|
||||||
AvatarPackagerCore.currentAvatarProject.hasErrors = errors.length > 0;
|
AvatarPackagerCore.currentAvatarProject.hasErrors = errors.length > 0;
|
||||||
AvatarPackagerCore.addCurrentProjectToRecentProjects();
|
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();
|
doneTimer.start();
|
||||||
});
|
}
|
||||||
avatarDoctor.startDiagnosing();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: doneTimer
|
id: doneTimer
|
||||||
interval: 1
|
|
||||||
repeat: false
|
repeat: false
|
||||||
running: false
|
running: false
|
||||||
onTriggered: {
|
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 {
|
property var footer: Item {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.rightMargin: 17
|
anchors.rightMargin: 17
|
||||||
|
|
|
@ -32,7 +32,7 @@ Item {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
avatarProject.reset();
|
avatarProject.reset();
|
||||||
avatarPackager.state = AvatarPackagerState.project;
|
avatarPackager.state = AvatarPackagerState.avatarDoctorDiagnose;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
|
|
||||||
#include "AvatarDoctor.h"
|
#include "AvatarDoctor.h"
|
||||||
#include <model-networking/ModelCache.h>
|
#include <model-networking/ModelCache.h>
|
||||||
|
#include <AvatarConstants.h>
|
||||||
|
#include <ResourceManager.h>
|
||||||
|
|
||||||
AvatarDoctor::AvatarDoctor(QUrl avatarFSTFileUrl) :
|
AvatarDoctor::AvatarDoctor(QUrl avatarFSTFileUrl) :
|
||||||
_avatarFSTFileUrl(std::move(avatarFSTFileUrl)) {
|
_avatarFSTFileUrl(std::move(avatarFSTFileUrl)) {
|
||||||
|
@ -18,63 +20,149 @@ AvatarDoctor::AvatarDoctor(QUrl avatarFSTFileUrl) :
|
||||||
|
|
||||||
void AvatarDoctor::startDiagnosing() {
|
void AvatarDoctor::startDiagnosing() {
|
||||||
_errors.clear();
|
_errors.clear();
|
||||||
|
|
||||||
|
_externalTextureCount = 0;
|
||||||
|
_checkedTextureCount = 0;
|
||||||
|
_missingTextureCount = 0;
|
||||||
|
_unsupportedTextureCount = 0;
|
||||||
|
|
||||||
const auto resource = DependencyManager::get<ModelCache>()->getGeometryResource(_avatarFSTFileUrl);
|
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
|
// MODEL
|
||||||
if (!success) {
|
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());
|
emit complete(getErrors());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const auto model = resource.data();
|
||||||
const auto avatarModel = resource.data()->getHFMModel();
|
const auto avatarModel = resource.data()->getHFMModel();
|
||||||
if (!avatarModel.originalURL.endsWith(".fbx")) {
|
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());
|
emit complete(getErrors());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// RIG
|
// RIG
|
||||||
if (avatarModel.joints.isEmpty()) {
|
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 {
|
else {
|
||||||
if (avatarModel.joints.length() > 256) {
|
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
|
// Avatar does not have Hips bone mapped
|
||||||
if (!avatarModel.getJointNames().contains("Hips")) {
|
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")) {
|
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")) {
|
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
|
// SCALE
|
||||||
const float DEFAULT_HEIGHT = 1.75f;
|
const float RECOMMENDED_MIN_HEIGHT = DEFAULT_AVATAR_HEIGHT * 0.25f;
|
||||||
const float RECOMMENDED_MIN_HEIGHT = DEFAULT_HEIGHT * 0.25;
|
const float RECOMMENDED_MAX_HEIGHT = DEFAULT_AVATAR_HEIGHT * 1.5f;
|
||||||
const float RECOMMENDED_MAX_HEIGHT = DEFAULT_HEIGHT * 1.5;
|
|
||||||
|
const float avatarHeight = avatarModel.bindExtents.largestDimension();
|
||||||
|
|
||||||
float avatarHeight = avatarModel.getMeshExtents().largestDimension();
|
qDebug() << "avatarHeight" << avatarHeight;
|
||||||
|
qDebug() << "defined Scale =" << model->getMapping()["scale"].toFloat();
|
||||||
qWarning() << "avatarHeight" << avatarHeight;
|
|
||||||
if (avatarHeight < RECOMMENDED_MIN_HEIGHT) {
|
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
|
// 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) {
|
if (resource) {
|
||||||
|
@ -84,7 +172,7 @@ void AvatarDoctor::startDiagnosing() {
|
||||||
connect(resource.data(), &GeometryResource::finished, this, resourceLoaded);
|
connect(resource.data(), &GeometryResource::finished, this, resourceLoaded);
|
||||||
}
|
}
|
||||||
} else {
|
} 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());
|
emit complete(getErrors());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,11 @@ signals:
|
||||||
private:
|
private:
|
||||||
QUrl _avatarFSTFileUrl;
|
QUrl _avatarFSTFileUrl;
|
||||||
QVector<AvatarDiagnosticResult> _errors;
|
QVector<AvatarDiagnosticResult> _errors;
|
||||||
|
|
||||||
|
int _externalTextureCount = 0;
|
||||||
|
int _checkedTextureCount = 0;
|
||||||
|
int _missingTextureCount = 0;
|
||||||
|
int _unsupportedTextureCount = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_AvatarDoctor_h
|
#endif // hifi_AvatarDoctor_h
|
||||||
|
|
|
@ -244,9 +244,7 @@ MarketplaceItemUploader* AvatarProject::upload(bool updateExisting) {
|
||||||
}
|
}
|
||||||
|
|
||||||
AvatarDoctor* AvatarProject::diagnose() {
|
AvatarDoctor* AvatarProject::diagnose() {
|
||||||
auto avatarDoctor = new AvatarDoctor(QUrl(getFSTPath()));
|
return new AvatarDoctor(QUrl(getFSTPath()));
|
||||||
|
|
||||||
return avatarDoctor;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AvatarProject::openInInventory() const {
|
void AvatarProject::openInInventory() const {
|
||||||
|
|
Loading…
Reference in a new issue