mirror of
https://github.com/overte-org/overte.git
synced 2025-08-07 10:36:04 +02:00
760 lines
33 KiB
C++
760 lines
33 KiB
C++
//
|
|
// DomainBaker.cpp
|
|
// tools/oven/src
|
|
//
|
|
// Created by Stephen Birarda on 4/12/17.
|
|
// Copyright 2017 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 "DomainBaker.h"
|
|
|
|
#include <QtConcurrent>
|
|
#include <QtCore/QEventLoop>
|
|
#include <QtCore/QFile>
|
|
#include <QtCore/QFileInfo>
|
|
#include <QtCore/QJsonDocument>
|
|
#include <QtCore/QJsonObject>
|
|
|
|
#include "Gzip.h"
|
|
#include "Oven.h"
|
|
#include "baking/BakerLibrary.h"
|
|
|
|
DomainBaker::DomainBaker(const QUrl& localModelFileURL, const QString& domainName,
|
|
const QString& baseOutputPath, const QUrl& destinationPath) :
|
|
_localEntitiesFileURL(localModelFileURL),
|
|
_domainName(domainName),
|
|
_baseOutputPath(baseOutputPath)
|
|
{
|
|
// make sure the destination path has a trailing slash
|
|
if (!destinationPath.toString().endsWith('/')) {
|
|
_destinationPath = destinationPath.toString() + '/';
|
|
} else {
|
|
_destinationPath = destinationPath;
|
|
}
|
|
}
|
|
|
|
void DomainBaker::bake() {
|
|
setupOutputFolder();
|
|
|
|
if (hasErrors()) {
|
|
return;
|
|
}
|
|
|
|
loadLocalFile();
|
|
|
|
if (hasErrors()) {
|
|
return;
|
|
}
|
|
|
|
enumerateEntities();
|
|
|
|
if (hasErrors()) {
|
|
return;
|
|
}
|
|
|
|
// in case we've baked and re-written all of our entities already, check if we're done
|
|
checkIfRewritingComplete();
|
|
}
|
|
|
|
void DomainBaker::setupOutputFolder() {
|
|
// in order to avoid overwriting previous bakes, we create a special output folder with the domain name and timestamp
|
|
|
|
// first, construct the directory name
|
|
auto domainPrefix = !_domainName.isEmpty() ? _domainName + "-" : "";
|
|
auto timeNow = QDateTime::currentDateTime();
|
|
|
|
static const QString FOLDER_TIMESTAMP_FORMAT = "yyyyMMdd-hhmmss";
|
|
QString outputDirectoryName = domainPrefix + timeNow.toString(FOLDER_TIMESTAMP_FORMAT);
|
|
|
|
// make sure we can create that directory
|
|
QDir outputDir { _baseOutputPath };
|
|
|
|
if (!outputDir.mkpath(outputDirectoryName)) {
|
|
// add an error to specify that the output directory could not be created
|
|
handleError("Could not create output folder");
|
|
|
|
return;
|
|
}
|
|
|
|
// store the unique output path so we can re-use it when saving baked models
|
|
outputDir.cd(outputDirectoryName);
|
|
_uniqueOutputPath = outputDir.absolutePath();
|
|
|
|
// add a content folder inside the unique output folder
|
|
static const QString CONTENT_OUTPUT_FOLDER_NAME = "content";
|
|
if (!outputDir.mkpath(CONTENT_OUTPUT_FOLDER_NAME)) {
|
|
// add an error to specify that the content output directory could not be created
|
|
handleError("Could not create content folder");
|
|
return;
|
|
}
|
|
|
|
_contentOutputPath = outputDir.absoluteFilePath(CONTENT_OUTPUT_FOLDER_NAME);
|
|
}
|
|
|
|
const QString ENTITIES_OBJECT_KEY = "Entities";
|
|
|
|
void DomainBaker::loadLocalFile() {
|
|
// load up the local entities file
|
|
QFile entitiesFile { _localEntitiesFileURL.toLocalFile() };
|
|
|
|
// first make a copy of the local entities file in our output folder
|
|
if (!entitiesFile.copy(_uniqueOutputPath + "/" + "original-" + _localEntitiesFileURL.fileName())) {
|
|
// add an error to our list to specify that the file could not be copied
|
|
handleError("Could not make a copy of entities file");
|
|
|
|
// return to stop processing
|
|
return;
|
|
}
|
|
|
|
if (!entitiesFile.open(QIODevice::ReadOnly)) {
|
|
// add an error to our list to specify that the file could not be read
|
|
handleError("Could not open entities file");
|
|
|
|
// return to stop processing
|
|
return;
|
|
}
|
|
|
|
// grab a byte array from the file
|
|
auto fileContents = entitiesFile.readAll();
|
|
|
|
// check if we need to inflate a gzipped models file or if this was already decompressed
|
|
static const QString GZIPPED_ENTITIES_FILE_SUFFIX = "gz";
|
|
if (QFileInfo(_localEntitiesFileURL.toLocalFile()).suffix() == "gz") {
|
|
// this was a gzipped models file that we need to decompress
|
|
QByteArray uncompressedContents;
|
|
gunzip(fileContents, uncompressedContents);
|
|
fileContents = uncompressedContents;
|
|
}
|
|
|
|
// read the file contents to a JSON document
|
|
auto jsonDocument = QJsonDocument::fromJson(fileContents);
|
|
|
|
// grab the entities object from the root JSON object
|
|
_entities = jsonDocument.object()[ENTITIES_OBJECT_KEY].toArray();
|
|
|
|
if (_entities.isEmpty()) {
|
|
// add an error to our list stating that the models file was empty
|
|
|
|
// return to stop processing
|
|
return;
|
|
}
|
|
}
|
|
|
|
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(), &Baker::deleteLater);
|
|
if (baker) {
|
|
// Hold on to the old url userinfo/query/fragment data so ModelBaker::getFullOutputMappingURL retains that data from the original model URL
|
|
// Note: The ModelBaker currently doesn't store this in the FST because the equal signs mess up FST parsing.
|
|
// There is a small chance this could break a server workflow relying on the old behavior.
|
|
// Url suffix is still propagated to the baked URL if the input URL is an FST.
|
|
// Url suffix has always been stripped from the URL when loading the original model file to be baked.
|
|
baker->setOutputURLSuffix(url);
|
|
|
|
// 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) {
|
|
QString cleanURL = QUrl(url).adjusted(QUrl::RemoveQuery | QUrl::RemoveFragment).toDisplayString();
|
|
auto idx = cleanURL.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 });
|
|
} else {
|
|
qDebug() << "Texture extension not supported: " << extension;
|
|
}
|
|
}
|
|
|
|
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 script baker for this URL, as long as we aren't baking a script already
|
|
if (!_scriptBakers.contains(scriptURL)) {
|
|
|
|
// setup a baker for this script
|
|
QSharedPointer<JSBaker> scriptBaker {
|
|
new JSBaker(scriptURL, _contentOutputPath),
|
|
&JSBaker::deleteLater
|
|
};
|
|
|
|
// make sure our handler is called when the script 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 script URL
|
|
// to the baked version once the baker is complete
|
|
_entitiesNeedingRewrite.insert(scriptURL, { property, jsonRef });
|
|
}
|
|
|
|
void DomainBaker::addMaterialBaker(const QString& property, const QString& data, bool isURL, QJsonValueRef& jsonRef) {
|
|
// grab a clean version of the URL without a query or fragment
|
|
QString materialData;
|
|
if (isURL) {
|
|
materialData = QUrl(data).adjusted(QUrl::RemoveQuery | QUrl::RemoveFragment).toDisplayString();
|
|
} else {
|
|
materialData = data;
|
|
}
|
|
|
|
// setup a material baker for this URL, as long as we aren't baking a material already
|
|
if (!_materialBakers.contains(materialData)) {
|
|
|
|
// setup a baker for this material
|
|
QSharedPointer<MaterialBaker> materialBaker {
|
|
new MaterialBaker(data, isURL, _contentOutputPath),
|
|
&MaterialBaker::deleteLater
|
|
};
|
|
|
|
// make sure our handler is called when the material baker is done
|
|
connect(materialBaker.data(), &MaterialBaker::finished, this, &DomainBaker::handleFinishedMaterialBaker);
|
|
|
|
// insert it into our bakers hash so we hold a strong pointer to it
|
|
_materialBakers.insert(materialData, materialBaker);
|
|
|
|
// move the baker to a worker thread and kickoff the bake
|
|
materialBaker->moveToThread(Oven::instance().getNextWorkerThread());
|
|
QMetaObject::invokeMethod(materialBaker.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 material URL
|
|
// to the baked version once the baker is complete
|
|
_entitiesNeedingRewrite.insert(materialData, { property, jsonRef });
|
|
}
|
|
|
|
// All the Entity Properties that can be baked
|
|
// ***************************************************************************************
|
|
|
|
const QString TYPE_KEY = "type";
|
|
|
|
// 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";
|
|
|
|
for (auto it = _entities.begin(); it != _entities.end(); ++it) {
|
|
// make sure this is a JSON object
|
|
if (it->isObject()) {
|
|
auto entity = it->toObject();
|
|
|
|
// 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: Support collision model baking
|
|
// Do not combine mesh parts, otherwise the collision behavior will be different
|
|
// combineParts is currently only used by OBJBaker (mesh-combining functionality ought to be moved to the asset engine at some point), and is also used by OBJBaker to determine if the material library should be loaded (should be separate flag)
|
|
// TODO: this could be optimized so that we don't do the full baking pass for collision shapes,
|
|
// but we have to handle the case where it's also used as a modelURL somewhere
|
|
//addModelBaker(COMPOUND_SHAPE_URL_KEY, entity[COMPOUND_SHAPE_URL_KEY].toString(), *it);
|
|
}
|
|
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)) {
|
|
if (entity.contains(TYPE_KEY)) {
|
|
QString type = entity[TYPE_KEY].toString();
|
|
// TODO: handle textures for model entities
|
|
if (type == "ParticleEffect" || type == "PolyLine") {
|
|
addTextureBaker(TEXTURES_KEY, entity[TEXTURES_KEY].toString(), image::TextureUsage::DEFAULT_TEXTURE, *it);
|
|
}
|
|
}
|
|
}
|
|
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
|
|
if (entity.contains(MATERIAL_URL_KEY)) {
|
|
addMaterialBaker(MATERIAL_URL_KEY, entity[MATERIAL_URL_KEY].toString(), true, *it);
|
|
}
|
|
if (entity.contains(MATERIAL_DATA_KEY)) {
|
|
addMaterialBaker(MATERIAL_DATA_KEY, entity[MATERIAL_DATA_KEY].toString(), false, *it);
|
|
}
|
|
}
|
|
}
|
|
|
|
// emit progress now to say we're just starting
|
|
emit bakeProgress(0, _totalNumberOfSubBakes);
|
|
}
|
|
|
|
void DomainBaker::handleFinishedModelBaker() {
|
|
auto baker = qobject_cast<ModelBaker*>(sender());
|
|
|
|
if (baker) {
|
|
if (!baker->hasErrors()) {
|
|
// this ModelBaker is done and everything went according to plan
|
|
qDebug() << "Re-writing entity references to" << baker->getModelURL();
|
|
|
|
// setup a new URL using the prefix we were passed
|
|
auto relativeMappingFilePath = baker->getFullOutputMappingURL().toString().remove(_contentOutputPath);
|
|
if (relativeMappingFilePath.startsWith("/")) {
|
|
relativeMappingFilePath = relativeMappingFilePath.right(relativeMappingFilePath.length() - 1);
|
|
}
|
|
QUrl newURL = _destinationPath.resolved(relativeMappingFilePath);
|
|
|
|
// enumerate the QJsonRef values for the URL of this model from our multi hash of
|
|
// entity objects needing a URL re-write
|
|
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 = propertyEntityPair.second.toObject();
|
|
|
|
if (!property.contains(".")) {
|
|
// grab the old URL
|
|
QUrl oldURL = entity[property].toString();
|
|
|
|
// set the new URL as the value in our temp QJsonObject
|
|
// The fragment, query, and user info from the original model URL should now be present on the filename in the FST file
|
|
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->getModelURL());
|
|
|
|
// drop our shared pointer to this baker so that it gets cleaned up
|
|
_modelBakers.remove(baker->getModelURL());
|
|
|
|
// 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();
|
|
}
|
|
}
|
|
|
|
void DomainBaker::handleFinishedTextureBaker() {
|
|
auto baker = qobject_cast<TextureBaker*>(sender());
|
|
|
|
if (baker) {
|
|
if (!baker->hasErrors()) {
|
|
// this TextureBaker is done and everything went according to plan
|
|
qDebug() << "Re-writing entity references to" << baker->getTextureURL();
|
|
|
|
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 (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 = propertyEntityPair.second.toObject();
|
|
|
|
if (!property.contains(".")) {
|
|
// grab the old URL
|
|
QUrl oldURL = entity[property].toString();
|
|
|
|
// copy the fragment and query, and user info from the old texture 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 texture 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 texture failed to bake - this doesn't fail the entire bake but we need to add the errors from
|
|
// the texture to our warnings
|
|
_warningList << baker->getWarnings();
|
|
}
|
|
|
|
// remove the baked URL from the multi hash of entities needing a re-write
|
|
_entitiesNeedingRewrite.remove(baker->getTextureURL());
|
|
|
|
// drop our shared pointer to this baker so that it gets cleaned up
|
|
_textureBakers.remove(baker->getTextureURL());
|
|
|
|
// emit progress to tell listeners how many textures we have baked
|
|
emit bakeProgress(++_completedSubBakes, _totalNumberOfSubBakes);
|
|
|
|
// check if this was the last texture we needed to re-write and if we are done now
|
|
checkIfRewritingComplete();
|
|
}
|
|
}
|
|
|
|
void DomainBaker::handleFinishedScriptBaker() {
|
|
auto baker = qobject_cast<JSBaker*>(sender());
|
|
|
|
if (baker) {
|
|
if (!baker->hasErrors()) {
|
|
// this JSBaker is done and everything went according to plan
|
|
qDebug() << "Re-writing entity references to" << baker->getJSPath();
|
|
|
|
auto newURL = _destinationPath.resolved(baker->getBakedJSFilePath());
|
|
|
|
// 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();
|
|
|
|
if (!property.contains(".")) {
|
|
// grab the old URL
|
|
QUrl oldURL = entity[property].toString();
|
|
|
|
// copy the fragment and query, and user info from the old script 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 script 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 script failed to bake - this doesn't fail the entire bake but we need to add
|
|
// the errors from the script 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 scripts we have baked
|
|
emit bakeProgress(++_completedSubBakes, _totalNumberOfSubBakes);
|
|
|
|
// check if this was the last script we needed to re-write and if we are done now
|
|
checkIfRewritingComplete();
|
|
}
|
|
}
|
|
|
|
void DomainBaker::handleFinishedMaterialBaker() {
|
|
auto baker = qobject_cast<MaterialBaker*>(sender());
|
|
|
|
if (baker) {
|
|
if (!baker->hasErrors()) {
|
|
// this MaterialBaker is done and everything went according to plan
|
|
qDebug() << "Re-writing entity references to" << baker->getMaterialData();
|
|
|
|
QString newDataOrURL;
|
|
if (baker->isURL()) {
|
|
newDataOrURL = _destinationPath.resolved(baker->getBakedMaterialData()).toDisplayString();
|
|
} else {
|
|
newDataOrURL = baker->getBakedMaterialData();
|
|
}
|
|
|
|
// enumerate the QJsonRef values for the URL of this material from our multi hash of
|
|
// entity objects needing a URL re-write
|
|
for (auto propertyEntityPair : _entitiesNeedingRewrite.values(baker->getMaterialData())) {
|
|
QString property = propertyEntityPair.first;
|
|
// convert the entity QJsonValueRef to a QJsonObject so we can modify its URL
|
|
auto entity = propertyEntityPair.second.toObject();
|
|
|
|
if (!property.contains(".")) {
|
|
// grab the old URL
|
|
QUrl oldURL = entity[property].toString();
|
|
|
|
// copy the fragment and query, and user info from the old material data
|
|
if (baker->isURL()) {
|
|
QUrl newURL = newDataOrURL;
|
|
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 {
|
|
entity[property] = newDataOrURL;
|
|
}
|
|
} 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 material data
|
|
if (baker->isURL()) {
|
|
QUrl newURL = newDataOrURL;
|
|
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;
|
|
} else {
|
|
oldObject[propertySplit[1]] = newDataOrURL;
|
|
entity[propertySplit[0]] = oldObject;
|
|
}
|
|
}
|
|
|
|
// replace our temp object with the value referenced by our QJsonValueRef
|
|
propertyEntityPair.second = entity;
|
|
}
|
|
} else {
|
|
// this material failed to bake - this doesn't fail the entire bake but we need to add
|
|
// the errors from the material to our warnings
|
|
_warningList << baker->getErrors();
|
|
}
|
|
|
|
// remove the baked URL from the multi hash of entities needing a re-write
|
|
_entitiesNeedingRewrite.remove(baker->getMaterialData());
|
|
|
|
// drop our shared pointer to this baker so that it gets cleaned up
|
|
_materialBakers.remove(baker->getMaterialData());
|
|
|
|
// emit progress to tell listeners how many materials we have baked
|
|
emit bakeProgress(++_completedSubBakes, _totalNumberOfSubBakes);
|
|
|
|
// check if this was the last material we needed to re-write and if we are done now
|
|
checkIfRewritingComplete();
|
|
}
|
|
}
|
|
|
|
void DomainBaker::checkIfRewritingComplete() {
|
|
if (_entitiesNeedingRewrite.isEmpty()) {
|
|
writeNewEntitiesFile();
|
|
|
|
if (hasErrors()) {
|
|
return;
|
|
}
|
|
|
|
// we've now written out our new models file - time to say that we are finished up
|
|
emit finished();
|
|
}
|
|
}
|
|
|
|
void DomainBaker::writeNewEntitiesFile() {
|
|
// we've enumerated all of our entities and re-written all the URLs we'll be able to re-write
|
|
// time to write out a main models.json.gz file
|
|
|
|
// first setup a document with the entities array below the entities key
|
|
QJsonDocument entitiesDocument;
|
|
|
|
QJsonObject rootObject;
|
|
rootObject[ENTITIES_OBJECT_KEY] = _entities;
|
|
|
|
entitiesDocument.setObject(rootObject);
|
|
|
|
// turn that QJsonDocument into a byte array ready for compression
|
|
QByteArray jsonByteArray = entitiesDocument.toJson();
|
|
|
|
// compress the json byte array using gzip
|
|
QByteArray compressedJson;
|
|
gzip(jsonByteArray, compressedJson);
|
|
|
|
// write the gzipped json to a new models file
|
|
static const QString MODELS_FILE_NAME = "models.json.gz";
|
|
|
|
auto bakedEntitiesFilePath = QDir(_uniqueOutputPath).filePath(MODELS_FILE_NAME);
|
|
QFile compressedEntitiesFile { bakedEntitiesFilePath };
|
|
|
|
if (!compressedEntitiesFile.open(QIODevice::WriteOnly)
|
|
|| (compressedEntitiesFile.write(compressedJson) == -1)) {
|
|
|
|
// add an error to our list to state that the output models file could not be created or could not be written to
|
|
handleError("Failed to export baked entities file");
|
|
|
|
return;
|
|
}
|
|
|
|
qDebug() << "Exported baked entities file to" << bakedEntitiesFilePath;
|
|
}
|