cleaning up oven

This commit is contained in:
SamGondelman 2019-02-20 14:14:15 -08:00 committed by sabrina-shanman
parent 86c948f116
commit 270b96aa8d
9 changed files with 431 additions and 288 deletions

View file

@ -25,6 +25,9 @@ public:
JSBaker(const QUrl& jsURL, const QString& bakedOutputDir);
static bool bakeJS(const QByteArray& inputFile, QByteArray& outputFile);
QString getJSPath() const { return _jsURL.fileName(); }
QString getBakedJSFilePath() const { return _bakedJSFilePath; }
public slots:
virtual void bake() override;

View file

@ -32,11 +32,12 @@
#endif
ModelBaker::ModelBaker(const QUrl& inputModelURL, TextureBakerThreadGetter inputTextureThreadGetter,
const QString& bakedOutputDirectory, const QString& originalOutputDirectory) :
const QString& bakedOutputDirectory, const QString& originalOutputDirectory, bool hasBeenBaked) :
_modelURL(inputModelURL),
_bakedOutputDir(bakedOutputDirectory),
_originalOutputDir(originalOutputDirectory),
_textureThreadGetter(inputTextureThreadGetter)
_textureThreadGetter(inputTextureThreadGetter),
_hasBeenBaked(hasBeenBaked)
{
auto tempDir = PathUtils::generateTemporaryDir();

View file

@ -30,16 +30,19 @@
using TextureBakerThreadGetter = std::function<QThread*()>;
using GetMaterialIDCallback = std::function <int(int)>;
static const QString BAKED_FBX_EXTENSION = ".baked.fbx";
static const QString BAKEABLE_MODEL_FBX_EXTENSION { ".fbx" };
static const QString BAKEABLE_MODEL_OBJ_EXTENSION { ".obj" };
static const QString FST_EXTENSION { ".fst" };
static const QString BAKED_FST_EXTENSION { ".baked.fst" };
static const QString FBX_EXTENSION { ".fbx" };
static const QString BAKED_FBX_EXTENSION { ".baked.fbx" };
static const QString OBJ_EXTENSION { ".obj" };
static const QString GLTF_EXTENSION { ".gltf" };
class ModelBaker : public Baker {
Q_OBJECT
public:
ModelBaker(const QUrl& inputModelURL, TextureBakerThreadGetter inputTextureThreadGetter,
const QString& bakedOutputDirectory, const QString& originalOutputDirectory = "");
const QString& bakedOutputDirectory, const QString& originalOutputDirectory = "", bool hasBeenBaked = false);
virtual ~ModelBaker();
void initializeOutputDirs();
@ -59,7 +62,7 @@ protected:
void texturesFinished();
void embedTextureMetaData();
void exportScene();
FBXNode _rootNode;
QHash<QByteArray, QByteArray> _textureContentMap;
QUrl _modelURL;
@ -79,12 +82,14 @@ private:
void bakeTexture(const QUrl & textureURL, image::TextureUsage::Type textureType, const QDir & outputDir,
const QString & bakedFilename, const QByteArray & textureContent);
QString texturePathRelativeToModel(QUrl modelURL, QUrl textureURL);
TextureBakerThreadGetter _textureThreadGetter;
QMultiHash<QUrl, QSharedPointer<TextureBaker>> _bakingTextures;
QHash<QString, int> _textureNameMatchCount;
QHash<QUrl, QString> _remappedTexturePaths;
bool _pendingErrorEmission{ false };
bool _pendingErrorEmission { false };
bool _hasBeenBaked { false };
};
#endif // hifi_ModelBaker_h

View file

@ -14,33 +14,26 @@
#include "../FBXBaker.h"
#include "../OBJBaker.h"
QUrl getBakeableModelURL(const QUrl& url, bool shouldRebakeOriginals) {
// Check if the file pointed to by this URL is a bakeable model, by comparing extensions
auto modelFileName = url.fileName();
// Check if the file pointed to by this URL is a bakeable model, by comparing extensions
QUrl getBakeableModelURL(const QUrl& url) {
static const std::vector<QString> extensionsToBake = {
FST_EXTENSION,
BAKED_FST_EXTENSION,
FBX_EXTENSION,
BAKED_FBX_EXTENSION,
OBJ_EXTENSION,
GLTF_EXTENSION
};
bool isBakedModel = modelFileName.endsWith(BAKED_FBX_EXTENSION, Qt::CaseInsensitive);
bool isBakeableFBX = modelFileName.endsWith(BAKEABLE_MODEL_FBX_EXTENSION, Qt::CaseInsensitive);
bool isBakeableOBJ = modelFileName.endsWith(BAKEABLE_MODEL_OBJ_EXTENSION, Qt::CaseInsensitive);
bool isBakeable = isBakeableFBX || isBakeableOBJ;
if (isBakeable || (shouldRebakeOriginals && isBakedModel)) {
if (isBakedModel) {
// Grab a URL to the original, that we assume is stored a directory up, in the "original" folder
// with just the fbx extension
qDebug() << "Inferring original URL for baked model URL" << url;
auto originalFileName = modelFileName;
originalFileName.replace(".baked", "");
qDebug() << "Original model URL must be present at" << url;
return url.resolved("../original/" + originalFileName);
} else {
// Grab a clean version of the URL without a query or fragment
return url.adjusted(QUrl::RemoveQuery | QUrl::RemoveFragment);
QUrl cleanURL = url.adjusted(QUrl::RemoveQuery | QUrl::RemoveFragment);
QString cleanURLString = cleanURL.fileName();
for (auto& extension : extensionsToBake) {
if (cleanURLString.endsWith(extension, Qt::CaseInsensitive)) {
return cleanURL;
}
}
qWarning() << "Unknown model type: " << modelFileName;
qWarning() << "Unknown model type: " << url.fileName();
return QUrl();
}
@ -59,11 +52,14 @@ std::unique_ptr<ModelBaker> getModelBaker(const QUrl& bakeableModelURL, TextureB
QString originalOutputDirectory = contentOutputPath + subDirName + "/original";
std::unique_ptr<ModelBaker> baker;
if (filename.endsWith(BAKEABLE_MODEL_FBX_EXTENSION, Qt::CaseInsensitive)) {
baker = std::make_unique<FBXBaker>(bakeableModelURL, inputTextureThreadGetter, bakedOutputDirectory, originalOutputDirectory);
} else if (filename.endsWith(BAKEABLE_MODEL_OBJ_EXTENSION, Qt::CaseInsensitive)) {
if (filename.endsWith(FST_EXTENSION, Qt::CaseInsensitive)) {
//baker = std::make_unique<FSTBaker>(bakeableModelURL, inputTextureThreadGetter, bakedOutputDirectory, originalOutputDirectory, filename.endsWith(BAKED_FST_EXTENSION, Qt::CaseInsensitive));
} else if (filename.endsWith(FBX_EXTENSION, Qt::CaseInsensitive)) {
baker = std::make_unique<FBXBaker>(bakeableModelURL, inputTextureThreadGetter, bakedOutputDirectory, originalOutputDirectory, filename.endsWith(BAKED_FBX_EXTENSION, Qt::CaseInsensitive));
} else if (filename.endsWith(OBJ_EXTENSION, Qt::CaseInsensitive)) {
baker = std::make_unique<OBJBaker>(bakeableModelURL, inputTextureThreadGetter, bakedOutputDirectory, originalOutputDirectory);
} else if (filename.endsWith(GLTF_EXTENSION, Qt::CaseInsensitive)) {
//baker = std::make_unique<GLTFBaker>(bakeableModelURL, inputTextureThreadGetter, bakedOutputDirectory, originalOutputDirectory);
} else {
qDebug() << "Could not create ModelBaker for url" << bakeableModelURL;
}

View file

@ -19,7 +19,7 @@
// Returns either the given model URL, or, if the model is baked and shouldRebakeOriginals is true,
// the guessed location of the original model
// Returns an empty URL if no bakeable URL found
QUrl getBakeableModelURL(const QUrl& url, bool shouldRebakeOriginals);
QUrl getBakeableModelURL(const QUrl& url);
// Assuming the URL is valid, gets the appropriate baker for the given URL, and creates the base directory where the baker's output will later be stored
// Returns an empty pointer if a baker could not be created

View file

@ -37,25 +37,16 @@ void BakerCLI::bakeFile(QUrl inputUrl, const QString& outputPath, const QString&
qDebug() << "Baking file type: " << type;
static const QString MODEL_EXTENSION { "fbx" };
static const QString MODEL_EXTENSION { "model" };
static const QString FBX_EXTENSION { "fbx" }; // legacy
static const QString MATERIAL_EXTENSION { "material" };
static const QString SCRIPT_EXTENSION { "js" };
// check what kind of baker we should be creating
bool isModel = type == MODEL_EXTENSION;
bool isScript = type == SCRIPT_EXTENSION;
// If the type doesn't match the above, we assume we have a texture, and the type specified is the
// texture usage type (albedo, cubemap, normals, etc.)
auto url = inputUrl.toDisplayString();
auto idx = url.lastIndexOf('.');
auto extension = idx >= 0 ? url.mid(idx + 1).toLower() : "";
bool isSupportedImage = QImageReader::supportedImageFormats().contains(extension.toLatin1());
_outputPath = outputPath;
// create our appropiate baker
if (isModel) {
QUrl bakeableModelURL = getBakeableModelURL(inputUrl, false);
if (type == MODEL_EXTENSION || type == FBX_EXTENSION) {
QUrl bakeableModelURL = getBakeableModelURL(inputUrl);
if (!bakeableModelURL.isEmpty()) {
auto getWorkerThreadCallback = []() -> QThread* {
return Oven::instance().getNextWorkerThread();
@ -65,35 +56,49 @@ void BakerCLI::bakeFile(QUrl inputUrl, const QString& outputPath, const QString&
_baker->moveToThread(Oven::instance().getNextWorkerThread());
}
}
} else if (isScript) {
} else if (type == SCRIPT_EXTENSION) {
_baker = std::unique_ptr<Baker> { new JSBaker(inputUrl, outputPath) };
_baker->moveToThread(Oven::instance().getNextWorkerThread());
} else if (isSupportedImage) {
static const std::unordered_map<QString, image::TextureUsage::Type> STRING_TO_TEXTURE_USAGE_TYPE_MAP {
{ "default", image::TextureUsage::DEFAULT_TEXTURE },
{ "strict", image::TextureUsage::STRICT_TEXTURE },
{ "albedo", image::TextureUsage::ALBEDO_TEXTURE },
{ "normal", image::TextureUsage::NORMAL_TEXTURE },
{ "bump", image::TextureUsage::BUMP_TEXTURE },
{ "specular", image::TextureUsage::SPECULAR_TEXTURE },
{ "metallic", image::TextureUsage::METALLIC_TEXTURE },
{ "roughness", image::TextureUsage::ROUGHNESS_TEXTURE },
{ "gloss", image::TextureUsage::GLOSS_TEXTURE },
{ "emissive", image::TextureUsage::EMISSIVE_TEXTURE },
{ "cube", image::TextureUsage::CUBE_TEXTURE },
{ "occlusion", image::TextureUsage::OCCLUSION_TEXTURE },
{ "scattering", image::TextureUsage::SCATTERING_TEXTURE },
{ "lightmap", image::TextureUsage::LIGHTMAP_TEXTURE },
};
auto it = STRING_TO_TEXTURE_USAGE_TYPE_MAP.find(type);
if (it == STRING_TO_TEXTURE_USAGE_TYPE_MAP.end()) {
qCDebug(model_baking) << "Unknown texture usage type:" << type;
QCoreApplication::exit(OVEN_STATUS_CODE_FAIL);
}
_baker = std::unique_ptr<Baker> { new TextureBaker(inputUrl, it->second, outputPath) };
_baker->moveToThread(Oven::instance().getNextWorkerThread());
} else if (type == MATERIAL_EXTENSION) {
//_baker = std::unique_ptr<Baker> { new MaterialBaker(inputUrl, outputPath) };
//_baker->moveToThread(Oven::instance().getNextWorkerThread());
} else {
// If the type doesn't match the above, we assume we have a texture, and the type specified is the
// texture usage type (albedo, cubemap, normals, etc.)
auto url = inputUrl.toDisplayString();
auto idx = url.lastIndexOf('.');
auto extension = idx >= 0 ? url.mid(idx + 1).toLower() : "";
if (QImageReader::supportedImageFormats().contains(extension.toLatin1())) {
static const std::unordered_map<QString, image::TextureUsage::Type> STRING_TO_TEXTURE_USAGE_TYPE_MAP {
{ "default", image::TextureUsage::DEFAULT_TEXTURE },
{ "strict", image::TextureUsage::STRICT_TEXTURE },
{ "albedo", image::TextureUsage::ALBEDO_TEXTURE },
{ "normal", image::TextureUsage::NORMAL_TEXTURE },
{ "bump", image::TextureUsage::BUMP_TEXTURE },
{ "specular", image::TextureUsage::SPECULAR_TEXTURE },
{ "metallic", image::TextureUsage::METALLIC_TEXTURE },
{ "roughness", image::TextureUsage::ROUGHNESS_TEXTURE },
{ "gloss", image::TextureUsage::GLOSS_TEXTURE },
{ "emissive", image::TextureUsage::EMISSIVE_TEXTURE },
{ "cube", image::TextureUsage::CUBE_TEXTURE },
{ "skybox", image::TextureUsage::CUBE_TEXTURE },
{ "occlusion", image::TextureUsage::OCCLUSION_TEXTURE },
{ "scattering", image::TextureUsage::SCATTERING_TEXTURE },
{ "lightmap", image::TextureUsage::LIGHTMAP_TEXTURE },
};
auto it = STRING_TO_TEXTURE_USAGE_TYPE_MAP.find(type);
if (it == STRING_TO_TEXTURE_USAGE_TYPE_MAP.end()) {
qCDebug(model_baking) << "Unknown texture usage type:" << type;
QCoreApplication::exit(OVEN_STATUS_CODE_FAIL);
}
_baker = std::unique_ptr<Baker> { new TextureBaker(inputUrl, it->second, outputPath) };
_baker->moveToThread(Oven::instance().getNextWorkerThread());
}
}
if (!_baker) {
qCDebug(model_baking) << "Failed to determine baker type for file" << inputUrl;
QCoreApplication::exit(OVEN_STATUS_CODE_FAIL);
return;

View file

@ -27,8 +27,7 @@ DomainBaker::DomainBaker(const QUrl& localModelFileURL, const QString& domainNam
bool shouldRebakeOriginals) :
_localEntitiesFileURL(localModelFileURL),
_domainName(domainName),
_baseOutputPath(baseOutputPath),
_shouldRebakeOriginals(shouldRebakeOriginals)
_baseOutputPath(baseOutputPath)
{
// make sure the destination path has a trailing slash
if (!destinationPath.toString().endsWith('/')) {
@ -145,11 +144,139 @@ void DomainBaker::loadLocalFile() {
}
}
const QString ENTITY_MODEL_URL_KEY = "modelURL";
const QString ENTITY_SKYBOX_KEY = "skybox";
const QString ENTITY_SKYBOX_URL_KEY = "url";
const QString ENTITY_KEYLIGHT_KEY = "keyLight";
const QString ENTITY_KEYLIGHT_AMBIENT_URL_KEY = "ambientURL";
void DomainBaker::addModelBaker(const QString& property, const QString& url, QJsonValueRef& jsonRef) {
// grab a QUrl for the model URL
QUrl bakeableModelURL = getBakeableModelURL(url);
if (!bakeableModelURL.isEmpty()) {
// setup a ModelBaker for this URL, as long as we don't already have one
if (!_modelBakers.contains(bakeableModelURL)) {
auto getWorkerThreadCallback = []() -> QThread* {
return Oven::instance().getNextWorkerThread();
};
QSharedPointer<ModelBaker> baker = QSharedPointer<ModelBaker>(getModelBaker(bakeableModelURL, getWorkerThreadCallback, _contentOutputPath).release(), &ModelBaker::deleteLater);
if (baker) {
// make sure our handler is called when the baker is done
connect(baker.data(), &Baker::finished, this, &DomainBaker::handleFinishedModelBaker);
// insert it into our bakers hash so we hold a strong pointer to it
_modelBakers.insert(bakeableModelURL, baker);
// move the baker to the baker thread
// and kickoff the bake
baker->moveToThread(Oven::instance().getNextWorkerThread());
QMetaObject::invokeMethod(baker.data(), "bake");
// keep track of the total number of baking entities
++_totalNumberOfSubBakes;
}
}
// add this QJsonValueRef to our multi hash so that we can easily re-write
// the model URL to the baked version once the baker is complete
_entitiesNeedingRewrite.insert(bakeableModelURL, { property, jsonRef });
}
}
void DomainBaker::addTextureBaker(const QString& property, const QString& url, image::TextureUsage::Type type, QJsonValueRef& jsonRef) {
auto idx = url.lastIndexOf('.');
auto extension = idx >= 0 ? url.mid(idx + 1).toLower() : "";
if (QImageReader::supportedImageFormats().contains(extension.toLatin1())) {
// grab a clean version of the URL without a query or fragment
QUrl textureURL = QUrl(url).adjusted(QUrl::RemoveQuery | QUrl::RemoveFragment);
// setup a texture baker for this URL, as long as we aren't baking a texture already
if (!_textureBakers.contains(textureURL)) {
// setup a baker for this texture
QSharedPointer<TextureBaker> textureBaker {
new TextureBaker(textureURL, type, _contentOutputPath),
&TextureBaker::deleteLater
};
// make sure our handler is called when the texture baker is done
connect(textureBaker.data(), &TextureBaker::finished, this, &DomainBaker::handleFinishedTextureBaker);
// insert it into our bakers hash so we hold a strong pointer to it
_textureBakers.insert(textureURL, textureBaker);
// move the baker to a worker thread and kickoff the bake
textureBaker->moveToThread(Oven::instance().getNextWorkerThread());
QMetaObject::invokeMethod(textureBaker.data(), "bake");
// keep track of the total number of baking entities
++_totalNumberOfSubBakes;
}
// add this QJsonValueRef to our multi hash so that it can re-write the texture URL
// to the baked version once the baker is complete
_entitiesNeedingRewrite.insert(textureURL, { property, jsonRef });
}
}
void DomainBaker::addScriptBaker(const QString& property, const QString& url, QJsonValueRef& jsonRef) {
// grab a clean version of the URL without a query or fragment
QUrl scriptURL = QUrl(url).adjusted(QUrl::RemoveQuery | QUrl::RemoveFragment);
// setup a texture baker for this URL, as long as we aren't baking a texture already
if (!_scriptBakers.contains(scriptURL)) {
// setup a baker for this texture
QSharedPointer<JSBaker> scriptBaker {
new JSBaker(scriptURL, _contentOutputPath),
&JSBaker::deleteLater
};
// make sure our handler is called when the texture baker is done
connect(scriptBaker.data(), &JSBaker::finished, this, &DomainBaker::handleFinishedScriptBaker);
// insert it into our bakers hash so we hold a strong pointer to it
_scriptBakers.insert(scriptURL, scriptBaker);
// move the baker to a worker thread and kickoff the bake
scriptBaker->moveToThread(Oven::instance().getNextWorkerThread());
QMetaObject::invokeMethod(scriptBaker.data(), "bake");
// keep track of the total number of baking entities
++_totalNumberOfSubBakes;
}
// add this QJsonValueRef to our multi hash so that it can re-write the texture URL
// to the baked version once the baker is complete
_entitiesNeedingRewrite.insert(scriptURL, { property, jsonRef });
}
// All the Entity Properties that can be baked
// ***************************************************************************************
// Models
const QString MODEL_URL_KEY = "modelURL";
const QString COMPOUND_SHAPE_URL_KEY = "compoundShapeURL";
const QString GRAP_KEY = "grab";
const QString EQUIPPABLE_INDICATOR_URL_KEY = "equippableIndicatorURL";
const QString ANIMATION_KEY = "animation";
const QString ANIMATION_URL_KEY = "url";
// Textures
const QString TEXTURES_KEY = "textures";
const QString IMAGE_URL_KEY = "imageURL";
const QString X_TEXTURE_URL_KEY = "xTextureURL";
const QString Y_TEXTURE_URL_KEY = "yTextureURL";
const QString Z_TEXTURE_URL_KEY = "zTextureURL";
const QString AMBIENT_LIGHT_KEY = "ambientLight";
const QString AMBIENT_URL_KEY = "ambientURL";
const QString SKYBOX_KEY = "skybox";
const QString SKYBOX_URL_KEY = "url";
// Scripts
const QString SCRIPT_KEY = "script";
const QString SERVER_SCRIPTS_KEY = "serverScripts";
// Materials
const QString MATERIAL_URL_KEY = "materialURL";
const QString MATERIAL_DATA_KEY = "materialData";
// ***************************************************************************************
void DomainBaker::enumerateEntities() {
qDebug() << "Enumerating" << _entities.size() << "entities from domain";
@ -159,65 +286,65 @@ void DomainBaker::enumerateEntities() {
if (it->isObject()) {
auto entity = it->toObject();
// check if this is an entity with a model URL or is a skybox texture
if (entity.contains(ENTITY_MODEL_URL_KEY)) {
// grab a QUrl for the model URL
QUrl bakeableModelURL = getBakeableModelURL(entity[ENTITY_MODEL_URL_KEY].toString(), _shouldRebakeOriginals);
if (!bakeableModelURL.isEmpty()) {
// setup a ModelBaker for this URL, as long as we don't already have one
if (!_modelBakers.contains(bakeableModelURL)) {
auto getWorkerThreadCallback = []() -> QThread* {
return Oven::instance().getNextWorkerThread();
};
QSharedPointer<ModelBaker> baker = QSharedPointer<ModelBaker>(getModelBaker(bakeableModelURL, getWorkerThreadCallback, _contentOutputPath).release(), &ModelBaker::deleteLater);
if (baker) {
// make sure our handler is called when the baker is done
connect(baker.data(), &Baker::finished, this, &DomainBaker::handleFinishedModelBaker);
// insert it into our bakers hash so we hold a strong pointer to it
_modelBakers.insert(bakeableModelURL, baker);
// move the baker to the baker thread
// and kickoff the bake
baker->moveToThread(Oven::instance().getNextWorkerThread());
QMetaObject::invokeMethod(baker.data(), "bake");
// keep track of the total number of baking entities
++_totalNumberOfSubBakes;
// add this QJsonValueRef to our multi hash so that we can easily re-write
// the model URL to the baked version once the baker is complete
_entitiesNeedingRewrite.insert(bakeableModelURL, *it);
}
}
}
} else {
// // We check now to see if we have either a texture for a skybox or a keylight, or both.
// if (entity.contains(ENTITY_SKYBOX_KEY)) {
// auto skyboxObject = entity[ENTITY_SKYBOX_KEY].toObject();
// if (skyboxObject.contains(ENTITY_SKYBOX_URL_KEY)) {
// // we have a URL to a skybox, grab it
// QUrl skyboxURL { skyboxObject[ENTITY_SKYBOX_URL_KEY].toString() };
//
// // setup a bake of the skybox
// bakeSkybox(skyboxURL, *it);
// }
// }
//
// if (entity.contains(ENTITY_KEYLIGHT_KEY)) {
// auto keyLightObject = entity[ENTITY_KEYLIGHT_KEY].toObject();
// if (keyLightObject.contains(ENTITY_KEYLIGHT_AMBIENT_URL_KEY)) {
// // we have a URL to a skybox, grab it
// QUrl skyboxURL { keyLightObject[ENTITY_KEYLIGHT_AMBIENT_URL_KEY].toString() };
//
// // setup a bake of the skybox
// bakeSkybox(skyboxURL, *it);
// }
// }
// Models
if (entity.contains(MODEL_URL_KEY)) {
addModelBaker(MODEL_URL_KEY, entity[MODEL_URL_KEY].toString(), *it);
}
if (entity.contains(COMPOUND_SHAPE_URL_KEY)) {
// TODO: handle compoundShapeURL
}
if (entity.contains(ANIMATION_KEY)) {
auto animationObject = entity[ANIMATION_KEY].toObject();
if (animationObject.contains(ANIMATION_URL_KEY)) {
addModelBaker(ANIMATION_KEY + "." + ANIMATION_URL_KEY, animationObject[ANIMATION_URL_KEY].toString(), *it);
}
}
if (entity.contains(GRAP_KEY)) {
auto grabObject = entity[GRAP_KEY].toObject();
if (grabObject.contains(EQUIPPABLE_INDICATOR_URL_KEY)) {
addModelBaker(GRAP_KEY + "." + EQUIPPABLE_INDICATOR_URL_KEY, grabObject[EQUIPPABLE_INDICATOR_URL_KEY].toString(), *it);
}
}
// Textures
if (entity.contains(TEXTURES_KEY)) {
// TODO: the textures property is treated differently for different entity types
}
if (entity.contains(IMAGE_URL_KEY)) {
addTextureBaker(IMAGE_URL_KEY, entity[IMAGE_URL_KEY].toString(), image::TextureUsage::DEFAULT_TEXTURE, *it);
}
if (entity.contains(X_TEXTURE_URL_KEY)) {
addTextureBaker(X_TEXTURE_URL_KEY, entity[X_TEXTURE_URL_KEY].toString(), image::TextureUsage::DEFAULT_TEXTURE, *it);
}
if (entity.contains(Y_TEXTURE_URL_KEY)) {
addTextureBaker(Y_TEXTURE_URL_KEY, entity[Y_TEXTURE_URL_KEY].toString(), image::TextureUsage::DEFAULT_TEXTURE, *it);
}
if (entity.contains(Z_TEXTURE_URL_KEY)) {
addTextureBaker(Z_TEXTURE_URL_KEY, entity[Z_TEXTURE_URL_KEY].toString(), image::TextureUsage::DEFAULT_TEXTURE, *it);
}
if (entity.contains(AMBIENT_LIGHT_KEY)) {
auto ambientLight = entity[AMBIENT_LIGHT_KEY].toObject();
if (ambientLight.contains(AMBIENT_URL_KEY)) {
addTextureBaker(AMBIENT_LIGHT_KEY + "." + AMBIENT_URL_KEY, ambientLight[AMBIENT_URL_KEY].toString(), image::TextureUsage::CUBE_TEXTURE, *it);
}
}
if (entity.contains(SKYBOX_KEY)) {
auto skybox = entity[SKYBOX_KEY].toObject();
if (skybox.contains(SKYBOX_URL_KEY)) {
addTextureBaker(SKYBOX_KEY + "." + SKYBOX_URL_KEY, skybox[SKYBOX_URL_KEY].toString(), image::TextureUsage::CUBE_TEXTURE, *it);
}
}
// Scripts
if (entity.contains(SCRIPT_KEY)) {
addScriptBaker(SCRIPT_KEY, entity[SCRIPT_KEY].toString(), *it);
}
if (entity.contains(SERVER_SCRIPTS_KEY)) {
// TODO: serverScripts can be multiple scripts, need to handle that
}
// Materials
// TODO
}
}
@ -225,48 +352,6 @@ void DomainBaker::enumerateEntities() {
emit bakeProgress(0, _totalNumberOfSubBakes);
}
void DomainBaker::bakeSkybox(QUrl skyboxURL, QJsonValueRef entity) {
auto skyboxFileName = skyboxURL.fileName();
static const QStringList BAKEABLE_SKYBOX_EXTENSIONS {
".jpg", ".png", ".gif", ".bmp", ".pbm", ".pgm", ".ppm", ".xbm", ".xpm", ".svg"
};
auto completeLowerExtension = skyboxFileName.mid(skyboxFileName.indexOf('.')).toLower();
if (BAKEABLE_SKYBOX_EXTENSIONS.contains(completeLowerExtension)) {
// grab a clean version of the URL without a query or fragment
skyboxURL = skyboxURL.adjusted(QUrl::RemoveQuery | QUrl::RemoveFragment);
// setup a texture baker for this URL, as long as we aren't baking a skybox already
if (!_skyboxBakers.contains(skyboxURL)) {
// setup a baker for this skybox
QSharedPointer<TextureBaker> skyboxBaker {
new TextureBaker(skyboxURL, image::TextureUsage::CUBE_TEXTURE, _contentOutputPath),
&TextureBaker::deleteLater
};
// make sure our handler is called when the skybox baker is done
connect(skyboxBaker.data(), &TextureBaker::finished, this, &DomainBaker::handleFinishedSkyboxBaker);
// insert it into our bakers hash so we hold a strong pointer to it
_skyboxBakers.insert(skyboxURL, skyboxBaker);
// move the baker to a worker thread and kickoff the bake
skyboxBaker->moveToThread(Oven::instance().getNextWorkerThread());
QMetaObject::invokeMethod(skyboxBaker.data(), "bake");
// keep track of the total number of baking entities
++_totalNumberOfSubBakes;
}
// add this QJsonValueRef to our multi hash so that it can re-write the skybox URL
// to the baked version once the baker is complete
_entitiesNeedingRewrite.insert(skyboxURL, entity);
}
}
void DomainBaker::handleFinishedModelBaker() {
auto baker = qobject_cast<ModelBaker*>(sender());
@ -275,62 +360,51 @@ void DomainBaker::handleFinishedModelBaker() {
// this FBXBaker is done and everything went according to plan
qDebug() << "Re-writing entity references to" << baker->getModelURL();
// enumerate the QJsonRef values for the URL of this FBX from our multi hash of
// setup a new URL using the prefix we were passed
auto relativeFBXFilePath = baker->getBakedModelFilePath().remove(_contentOutputPath);
if (relativeFBXFilePath.startsWith("/")) {
relativeFBXFilePath = relativeFBXFilePath.right(relativeFBXFilePath.length() - 1);
}
QUrl newURL = _destinationPath.resolved(relativeFBXFilePath);
// enumerate the QJsonRef values for the URL of this model from our multi hash of
// entity objects needing a URL re-write
for (QJsonValueRef entityValue : _entitiesNeedingRewrite.values(baker->getModelURL())) {
for (auto propertyEntityPair : _entitiesNeedingRewrite.values(baker->getModelURL())) {
QString property = propertyEntityPair.first;
// convert the entity QJsonValueRef to a QJsonObject so we can modify its URL
auto entity = entityValue.toObject();
auto entity = propertyEntityPair.second.toObject();
// grab the old URL
QUrl oldModelURL { entity[ENTITY_MODEL_URL_KEY].toString() };
if (!property.contains(".")) {
// grab the old URL
QUrl oldURL = entity[property].toString();
// setup a new URL using the prefix we were passed
auto relativeFBXFilePath = baker->getBakedModelFilePath().remove(_contentOutputPath);
if (relativeFBXFilePath.startsWith("/")) {
relativeFBXFilePath = relativeFBXFilePath.right(relativeFBXFilePath.length() - 1);
// copy the fragment and query, and user info from the old model URL
newURL.setQuery(oldURL.query());
newURL.setFragment(oldURL.fragment());
newURL.setUserInfo(oldURL.userInfo());
// set the new URL as the value in our temp QJsonObject
entity[property] = newURL.toString();
} else {
// Group property
QStringList propertySplit = property.split(".");
assert(propertySplit.length() == 2);
// grab the old URL
auto oldObject = entity[propertySplit[0]].toObject();
QUrl oldURL = oldObject[propertySplit[1]].toString();
// copy the fragment and query, and user info from the old model URL
newURL.setQuery(oldURL.query());
newURL.setFragment(oldURL.fragment());
newURL.setUserInfo(oldURL.userInfo());
// set the new URL as the value in our temp QJsonObject
oldObject[propertySplit[1]] = newURL.toString();
entity[propertySplit[0]] = oldObject;
}
QUrl newModelURL = _destinationPath.resolved(relativeFBXFilePath);
// copy the fragment and query, and user info from the old model URL
newModelURL.setQuery(oldModelURL.query());
newModelURL.setFragment(oldModelURL.fragment());
newModelURL.setUserInfo(oldModelURL.userInfo());
// set the new model URL as the value in our temp QJsonObject
entity[ENTITY_MODEL_URL_KEY] = newModelURL.toString();
// check if the entity also had an animation at the same URL
// in which case it should be replaced with our baked model URL too
const QString ENTITY_ANIMATION_KEY = "animation";
const QString ENTITIY_ANIMATION_URL_KEY = "url";
if (entity.contains(ENTITY_ANIMATION_KEY)) {
auto animationObject = entity[ENTITY_ANIMATION_KEY].toObject();
if (animationObject.contains(ENTITIY_ANIMATION_URL_KEY)) {
// grab the old animation URL
QUrl oldAnimationURL { animationObject[ENTITIY_ANIMATION_URL_KEY].toString() };
// check if its stripped down version matches our stripped down model URL
if (oldAnimationURL.matches(oldModelURL, QUrl::RemoveQuery | QUrl::RemoveFragment)) {
// the animation URL matched the old model URL, so make the animation URL point to the baked FBX
// with its original query and fragment
auto newAnimationURL = _destinationPath.resolved(relativeFBXFilePath);
newAnimationURL.setQuery(oldAnimationURL.query());
newAnimationURL.setFragment(oldAnimationURL.fragment());
newAnimationURL.setUserInfo(oldAnimationURL.userInfo());
animationObject[ENTITIY_ANIMATION_URL_KEY] = newAnimationURL.toString();
// replace the animation object in the entity object
entity[ENTITY_ANIMATION_KEY] = animationObject;
}
}
}
// replace our temp object with the value referenced by our QJsonValueRef
entityValue = entity;
propertyEntityPair.second = entity;
}
} else {
// this model failed to bake - this doesn't fail the entire bake but we need to add
@ -352,7 +426,7 @@ void DomainBaker::handleFinishedModelBaker() {
}
}
void DomainBaker::handleFinishedSkyboxBaker() {
void DomainBaker::handleFinishedTextureBaker() {
auto baker = qobject_cast<TextureBaker*>(sender());
if (baker) {
@ -360,36 +434,46 @@ void DomainBaker::handleFinishedSkyboxBaker() {
// this FBXBaker is done and everything went according to plan
qDebug() << "Re-writing entity references to" << baker->getTextureURL();
// enumerate the QJsonRef values for the URL of this FBX from our multi hash of
auto newURL = _destinationPath.resolved(baker->getMetaTextureFileName());
// enumerate the QJsonRef values for the URL of this texture from our multi hash of
// entity objects needing a URL re-write
for (QJsonValueRef entityValue : _entitiesNeedingRewrite.values(baker->getTextureURL())) {
for (auto propertyEntityPair : _entitiesNeedingRewrite.values(baker->getTextureURL())) {
QString property = propertyEntityPair.first;
// convert the entity QJsonValueRef to a QJsonObject so we can modify its URL
auto entity = entityValue.toObject();
auto entity = propertyEntityPair.second.toObject();
if (entity.contains(ENTITY_SKYBOX_KEY)) {
auto skyboxObject = entity[ENTITY_SKYBOX_KEY].toObject();
if (!property.contains(".")) {
// grab the old URL
QUrl oldURL = entity[property].toString();
if (skyboxObject.contains(ENTITY_SKYBOX_URL_KEY)) {
if (rewriteSkyboxURL(skyboxObject[ENTITY_SKYBOX_URL_KEY], baker)) {
// we re-wrote the URL, replace the skybox object referenced by the entity object
entity[ENTITY_SKYBOX_KEY] = skyboxObject;
}
}
}
// copy the fragment and query, and user info from the old model URL
newURL.setQuery(oldURL.query());
newURL.setFragment(oldURL.fragment());
newURL.setUserInfo(oldURL.userInfo());
if (entity.contains(ENTITY_KEYLIGHT_KEY)) {
auto ambientObject = entity[ENTITY_KEYLIGHT_KEY].toObject();
// set the new URL as the value in our temp QJsonObject
entity[property] = newURL.toString();
} else {
// Group property
QStringList propertySplit = property.split(".");
assert(propertySplit.length() == 2);
// grab the old URL
auto oldObject = entity[propertySplit[0]].toObject();
QUrl oldURL = oldObject[propertySplit[1]].toString();
if (ambientObject.contains(ENTITY_KEYLIGHT_AMBIENT_URL_KEY)) {
if (rewriteSkyboxURL(ambientObject[ENTITY_KEYLIGHT_AMBIENT_URL_KEY], baker)) {
// we re-wrote the URL, replace the ambient object referenced by the entity object
entity[ENTITY_KEYLIGHT_KEY] = ambientObject;
}
}
// copy the fragment and query, and user info from the old model URL
newURL.setQuery(oldURL.query());
newURL.setFragment(oldURL.fragment());
newURL.setUserInfo(oldURL.userInfo());
// set the new URL as the value in our temp QJsonObject
oldObject[propertySplit[1]] = newURL.toString();
entity[propertySplit[0]] = oldObject;
}
// replace our temp object with the value referenced by our QJsonValueRef
entityValue = entity;
propertyEntityPair.second = entity;
}
} else {
// this skybox failed to bake - this doesn't fail the entire bake but we need to add the errors from
@ -401,7 +485,7 @@ void DomainBaker::handleFinishedSkyboxBaker() {
_entitiesNeedingRewrite.remove(baker->getTextureURL());
// drop our shared pointer to this baker so that it gets cleaned up
_skyboxBakers.remove(baker->getTextureURL());
_textureBakers.remove(baker->getTextureURL());
// emit progress to tell listeners how many models we have baked
emit bakeProgress(++_completedSubBakes, _totalNumberOfSubBakes);
@ -411,23 +495,72 @@ void DomainBaker::handleFinishedSkyboxBaker() {
}
}
bool DomainBaker::rewriteSkyboxURL(QJsonValueRef urlValue, TextureBaker* baker) {
// grab the old skybox URL
QUrl oldSkyboxURL { urlValue.toString() };
void DomainBaker::handleFinishedScriptBaker() {
auto baker = qobject_cast<JSBaker*>(sender());
if (oldSkyboxURL.matches(baker->getTextureURL(), QUrl::RemoveQuery | QUrl::RemoveFragment)) {
// change the URL to point to the baked texture with its original query and fragment
if (baker) {
if (!baker->hasErrors()) {
// this FBXBaker is done and everything went according to plan
qDebug() << "Re-writing entity references to" << baker->getJSPath();
auto newSkyboxURL = _destinationPath.resolved(baker->getMetaTextureFileName());
newSkyboxURL.setQuery(oldSkyboxURL.query());
newSkyboxURL.setFragment(oldSkyboxURL.fragment());
newSkyboxURL.setUserInfo(oldSkyboxURL.userInfo());
auto newURL = _destinationPath.resolved(baker->getBakedJSFilePath());
urlValue = newSkyboxURL.toString();
// enumerate the QJsonRef values for the URL of this script from our multi hash of
// entity objects needing a URL re-write
for (auto propertyEntityPair : _entitiesNeedingRewrite.values(baker->getJSPath())) {
QString property = propertyEntityPair.first;
// convert the entity QJsonValueRef to a QJsonObject so we can modify its URL
auto entity = propertyEntityPair.second.toObject();
return true;
} else {
return false;
if (!property.contains(".")) {
// grab the old URL
QUrl oldURL = entity[property].toString();
// copy the fragment and query, and user info from the old model URL
newURL.setQuery(oldURL.query());
newURL.setFragment(oldURL.fragment());
newURL.setUserInfo(oldURL.userInfo());
// set the new URL as the value in our temp QJsonObject
entity[property] = newURL.toString();
} else {
// Group property
QStringList propertySplit = property.split(".");
assert(propertySplit.length() == 2);
// grab the old URL
auto oldObject = entity[propertySplit[0]].toObject();
QUrl oldURL = oldObject[propertySplit[1]].toString();
// copy the fragment and query, and user info from the old model URL
newURL.setQuery(oldURL.query());
newURL.setFragment(oldURL.fragment());
newURL.setUserInfo(oldURL.userInfo());
// set the new URL as the value in our temp QJsonObject
oldObject[propertySplit[1]] = newURL.toString();
entity[propertySplit[0]] = oldObject;
}
// replace our temp object with the value referenced by our QJsonValueRef
propertyEntityPair.second = entity;
}
} else {
// this model failed to bake - this doesn't fail the entire bake but we need to add
// the errors from the model to our warnings
_warningList << baker->getErrors();
}
// remove the baked URL from the multi hash of entities needing a re-write
_entitiesNeedingRewrite.remove(baker->getJSPath());
// drop our shared pointer to this baker so that it gets cleaned up
_scriptBakers.remove(baker->getJSPath());
// emit progress to tell listeners how many models we have baked
emit bakeProgress(++_completedSubBakes, _totalNumberOfSubBakes);
// check if this was the last model we needed to re-write and if we are done now
checkIfRewritingComplete();
}
}
@ -480,4 +613,3 @@ void DomainBaker::writeNewEntitiesFile() {
qDebug() << "Exported entities file with baked model URLs to" << bakedEntitiesFilePath;
}

View file

@ -18,8 +18,9 @@
#include <QtCore/QThread>
#include "Baker.h"
#include "FBXBaker.h"
#include "ModelBaker.h"
#include "TextureBaker.h"
#include "JSBaker.h"
class DomainBaker : public Baker {
Q_OBJECT
@ -38,7 +39,8 @@ signals:
private slots:
virtual void bake() override;
void handleFinishedModelBaker();
void handleFinishedSkyboxBaker();
void handleFinishedTextureBaker();
void handleFinishedScriptBaker();
private:
void setupOutputFolder();
@ -47,9 +49,6 @@ private:
void checkIfRewritingComplete();
void writeNewEntitiesFile();
void bakeSkybox(QUrl skyboxURL, QJsonValueRef entity);
bool rewriteSkyboxURL(QJsonValueRef urlValue, TextureBaker* baker);
QUrl _localEntitiesFileURL;
QString _domainName;
QString _baseOutputPath;
@ -62,14 +61,17 @@ private:
QJsonArray _entities;
QHash<QUrl, QSharedPointer<ModelBaker>> _modelBakers;
QHash<QUrl, QSharedPointer<TextureBaker>> _skyboxBakers;
QHash<QUrl, QSharedPointer<TextureBaker>> _textureBakers;
QHash<QUrl, QSharedPointer<JSBaker>> _scriptBakers;
QMultiHash<QUrl, QJsonValueRef> _entitiesNeedingRewrite;
QMultiHash<QUrl, std::pair<QString, QJsonValueRef>> _entitiesNeedingRewrite;
int _totalNumberOfSubBakes { 0 };
int _completedSubBakes { 0 };
bool _shouldRebakeOriginals { false };
void addModelBaker(const QString& property, const QString& url, QJsonValueRef& jsonRef);
void addTextureBaker(const QString& property, const QString& url, image::TextureUsage::Type type, QJsonValueRef& jsonRef);
void addScriptBaker(const QString& property, const QString& url, QJsonValueRef& jsonRef);
};
#endif // hifi_DomainBaker_h

View file

@ -116,7 +116,7 @@ void ModelBakeWidget::chooseFileButtonClicked() {
startDir = QDir::homePath();
}
auto selectedFiles = QFileDialog::getOpenFileNames(this, "Choose Model", startDir, "Models (*.fbx *.obj)");
auto selectedFiles = QFileDialog::getOpenFileNames(this, "Choose Model", startDir, "Models (*.fbx *.obj *.gltf *.fst)");
if (!selectedFiles.isEmpty()) {
// set the contents of the model file text box to be the path to the selected file
@ -165,21 +165,20 @@ void ModelBakeWidget::bakeButtonClicked() {
return;
}
// make sure we have a valid output directory
QDir outputDirectory(_outputDirLineEdit->text());
if (!outputDirectory.exists()) {
QMessageBox::warning(this, "Unable to create directory", "Unable to create output directory. Please create it manually or choose a different directory.");
return;
}
// split the list from the model line edit to see how many models we need to bake
auto fileURLStrings = _modelLineEdit->text().split(',');
foreach (QString fileURLString, fileURLStrings) {
// construct a URL from the path in the model file text box
QUrl modelToBakeURL(fileURLString);
// make sure we have a valid output directory
QDir outputDirectory(_outputDirLineEdit->text());
if (!outputDirectory.exists()) {
QMessageBox::warning(this, "Unable to create directory", "Unable to create output directory. Please create it manually or choose a different directory.");
return;
}
QUrl bakeableModelURL = getBakeableModelURL(QUrl(modelToBakeURL), false);
QUrl bakeableModelURL = getBakeableModelURL(QUrl(modelToBakeURL));
if (!bakeableModelURL.isEmpty()) {
auto getWorkerThreadCallback = []() -> QThread* {
return Oven::instance().getNextWorkerThread();