mirror of
https://thingvellir.net/git/overte
synced 2025-03-27 23:52:03 +01:00
Merge pull request #15319 from SamGondelman/materialBaker
Case 21764: Oven bakes model materials directly to separate material description
This commit is contained in:
commit
88c278d357
23 changed files with 314 additions and 693 deletions
|
@ -33,9 +33,8 @@
|
|||
#include "ModelBakingLoggingCategory.h"
|
||||
#include "TextureBaker.h"
|
||||
|
||||
FBXBaker::FBXBaker(const QUrl& inputModelURL, TextureBakerThreadGetter inputTextureThreadGetter,
|
||||
const QString& bakedOutputDirectory, const QString& originalOutputDirectory, bool hasBeenBaked) :
|
||||
ModelBaker(inputModelURL, inputTextureThreadGetter, bakedOutputDirectory, originalOutputDirectory, hasBeenBaked) {
|
||||
FBXBaker::FBXBaker(const QUrl& inputModelURL, const QString& bakedOutputDirectory, const QString& originalOutputDirectory, bool hasBeenBaked) :
|
||||
ModelBaker(inputModelURL, bakedOutputDirectory, originalOutputDirectory, hasBeenBaked) {
|
||||
if (hasBeenBaked) {
|
||||
// Look for the original model file one directory higher. Perhaps this is an oven output directory.
|
||||
QUrl originalRelativePath = QUrl("../original/" + inputModelURL.fileName().replace(BAKED_FBX_EXTENSION, FBX_EXTENSION));
|
||||
|
@ -45,15 +44,6 @@ FBXBaker::FBXBaker(const QUrl& inputModelURL, TextureBakerThreadGetter inputText
|
|||
}
|
||||
|
||||
void FBXBaker::bakeProcessedSource(const hfm::Model::Pointer& hfmModel, const std::vector<hifi::ByteArray>& dracoMeshes, const std::vector<std::vector<hifi::ByteArray>>& dracoMaterialLists) {
|
||||
_hfmModel = hfmModel;
|
||||
|
||||
if (shouldStop()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// enumerate the models and textures found in the scene and start a bake for them
|
||||
rewriteAndBakeSceneTextures();
|
||||
|
||||
if (shouldStop()) {
|
||||
return;
|
||||
}
|
||||
|
@ -114,15 +104,15 @@ void FBXBaker::rewriteAndBakeSceneModels(const QVector<hfm::Mesh>& meshes, const
|
|||
int meshIndex = 0;
|
||||
for (FBXNode& rootChild : _rootNode.children) {
|
||||
if (rootChild.name == "Objects") {
|
||||
for (FBXNode& object : rootChild.children) {
|
||||
if (object.name == "Geometry") {
|
||||
if (object.properties.at(2) == "Mesh") {
|
||||
for (auto object = rootChild.children.begin(); object != rootChild.children.end(); object++) {
|
||||
if (object->name == "Geometry") {
|
||||
if (object->properties.at(2) == "Mesh") {
|
||||
int meshNum = meshIndexToRuntimeOrder[meshIndex];
|
||||
replaceMeshNodeWithDraco(object, dracoMeshes[meshNum], dracoMaterialLists[meshNum]);
|
||||
replaceMeshNodeWithDraco(*object, dracoMeshes[meshNum], dracoMaterialLists[meshNum]);
|
||||
meshIndex++;
|
||||
}
|
||||
} else if (object.name == "Model") {
|
||||
for (FBXNode& modelChild : object.children) {
|
||||
} else if (object->name == "Model") {
|
||||
for (FBXNode& modelChild : object->children) {
|
||||
if (modelChild.name == "Properties60" || modelChild.name == "Properties70") {
|
||||
// This is a properties node
|
||||
// Remove the geometric transform because that has been applied directly to the vertices in FBXSerializer
|
||||
|
@ -142,10 +132,13 @@ void FBXBaker::rewriteAndBakeSceneModels(const QVector<hfm::Mesh>& meshes, const
|
|||
} else if (modelChild.name == "Vertices") {
|
||||
// This model is also a mesh
|
||||
int meshNum = meshIndexToRuntimeOrder[meshIndex];
|
||||
replaceMeshNodeWithDraco(object, dracoMeshes[meshNum], dracoMaterialLists[meshNum]);
|
||||
replaceMeshNodeWithDraco(*object, dracoMeshes[meshNum], dracoMaterialLists[meshNum]);
|
||||
meshIndex++;
|
||||
}
|
||||
}
|
||||
} else if (object->name == "Texture" || object->name == "Video") {
|
||||
// this is an embedded texture, we need to remove it from the FBX
|
||||
object = rootChild.children.erase(object);
|
||||
}
|
||||
|
||||
if (hasErrors()) {
|
||||
|
@ -154,82 +147,4 @@ void FBXBaker::rewriteAndBakeSceneModels(const QVector<hfm::Mesh>& meshes, const
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FBXBaker::rewriteAndBakeSceneTextures() {
|
||||
using namespace image::TextureUsage;
|
||||
QHash<QString, image::TextureUsage::Type> textureTypes;
|
||||
|
||||
// enumerate the materials in the extracted geometry so we can determine the texture type for each texture ID
|
||||
for (const auto& material : _hfmModel->materials) {
|
||||
if (material.normalTexture.isBumpmap) {
|
||||
textureTypes[material.normalTexture.id] = BUMP_TEXTURE;
|
||||
} else {
|
||||
textureTypes[material.normalTexture.id] = NORMAL_TEXTURE;
|
||||
}
|
||||
|
||||
textureTypes[material.albedoTexture.id] = ALBEDO_TEXTURE;
|
||||
textureTypes[material.glossTexture.id] = GLOSS_TEXTURE;
|
||||
textureTypes[material.roughnessTexture.id] = ROUGHNESS_TEXTURE;
|
||||
textureTypes[material.specularTexture.id] = SPECULAR_TEXTURE;
|
||||
textureTypes[material.metallicTexture.id] = METALLIC_TEXTURE;
|
||||
textureTypes[material.emissiveTexture.id] = EMISSIVE_TEXTURE;
|
||||
textureTypes[material.occlusionTexture.id] = OCCLUSION_TEXTURE;
|
||||
textureTypes[material.lightmapTexture.id] = LIGHTMAP_TEXTURE;
|
||||
}
|
||||
|
||||
// enumerate the children of the root node
|
||||
for (FBXNode& rootChild : _rootNode.children) {
|
||||
|
||||
if (rootChild.name == "Objects") {
|
||||
|
||||
// enumerate the objects
|
||||
auto object = rootChild.children.begin();
|
||||
while (object != rootChild.children.end()) {
|
||||
if (object->name == "Texture") {
|
||||
|
||||
// double check that we didn't get an abort while baking the last texture
|
||||
if (shouldStop()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// enumerate the texture children
|
||||
for (FBXNode& textureChild : object->children) {
|
||||
|
||||
if (textureChild.name == "RelativeFilename") {
|
||||
QString hfmTextureFileName { textureChild.properties.at(0).toString() };
|
||||
|
||||
// grab the ID for this texture so we can figure out the
|
||||
// texture type from the loaded materials
|
||||
auto textureID { object->properties[0].toString() };
|
||||
auto textureType = textureTypes[textureID];
|
||||
|
||||
// Compress the texture information and return the new filename to be added into the FBX scene
|
||||
auto bakedTextureFile = compressTexture(hfmTextureFileName, textureType);
|
||||
|
||||
// If no errors or warnings have occurred during texture compression add the filename to the FBX scene
|
||||
if (!bakedTextureFile.isNull()) {
|
||||
textureChild.properties[0] = bakedTextureFile;
|
||||
} else {
|
||||
// if bake fails - return, if there were errors and continue, if there were warnings.
|
||||
if (hasErrors()) {
|
||||
return;
|
||||
} else if (hasWarnings()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
++object;
|
||||
|
||||
} else if (object->name == "Video") {
|
||||
// this is an embedded texture, we need to remove it from the FBX
|
||||
object = rootChild.children.erase(object);
|
||||
} else {
|
||||
++object;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -31,20 +31,14 @@ using TextureBakerThreadGetter = std::function<QThread*()>;
|
|||
class FBXBaker : public ModelBaker {
|
||||
Q_OBJECT
|
||||
public:
|
||||
FBXBaker(const QUrl& inputModelURL, TextureBakerThreadGetter inputTextureThreadGetter,
|
||||
const QString& bakedOutputDirectory, const QString& originalOutputDirectory = "", bool hasBeenBaked = false);
|
||||
FBXBaker(const QUrl& inputModelURL, const QString& bakedOutputDirectory, const QString& originalOutputDirectory = "", bool hasBeenBaked = false);
|
||||
|
||||
protected:
|
||||
virtual void bakeProcessedSource(const hfm::Model::Pointer& hfmModel, const std::vector<hifi::ByteArray>& dracoMeshes, const std::vector<std::vector<hifi::ByteArray>>& dracoMaterialLists) override;
|
||||
|
||||
private:
|
||||
void rewriteAndBakeSceneModels(const QVector<hfm::Mesh>& meshes, const std::vector<hifi::ByteArray>& dracoMeshes, const std::vector<std::vector<hifi::ByteArray>>& dracoMaterialLists);
|
||||
void rewriteAndBakeSceneTextures();
|
||||
void replaceMeshNodeWithDraco(FBXNode& meshNode, const QByteArray& dracoMeshBytes, const std::vector<hifi::ByteArray>& dracoMaterialList);
|
||||
|
||||
hfm::Model::Pointer _hfmModel;
|
||||
|
||||
bool _pendingErrorEmission { false };
|
||||
};
|
||||
|
||||
#endif // hifi_FBXBaker_h
|
||||
|
|
|
@ -27,21 +27,11 @@ std::function<QThread*()> MaterialBaker::_getNextOvenWorkerThreadOperator;
|
|||
|
||||
static int materialNum = 0;
|
||||
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<graphics::Material::MapChannel> {
|
||||
size_t operator()(const graphics::Material::MapChannel& a) const {
|
||||
return std::hash<size_t>()((size_t)a);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
MaterialBaker::MaterialBaker(const QString& materialData, bool isURL, const QString& bakedOutputDir, const QUrl& destinationPath) :
|
||||
MaterialBaker::MaterialBaker(const QString& materialData, bool isURL, const QString& bakedOutputDir) :
|
||||
_materialData(materialData),
|
||||
_isURL(isURL),
|
||||
_bakedOutputDir(bakedOutputDir),
|
||||
_textureOutputDir(bakedOutputDir + "/materialTextures/" + QString::number(materialNum++)),
|
||||
_destinationPath(destinationPath)
|
||||
_textureOutputDir(bakedOutputDir + "/materialTextures/" + QString::number(materialNum++))
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -64,6 +54,14 @@ void MaterialBaker::bake() {
|
|||
}
|
||||
}
|
||||
|
||||
void MaterialBaker::abort() {
|
||||
Baker::abort();
|
||||
|
||||
for (auto& textureBaker : _textureBakers) {
|
||||
textureBaker->abort();
|
||||
}
|
||||
}
|
||||
|
||||
void MaterialBaker::loadMaterial() {
|
||||
if (!_isURL) {
|
||||
qCDebug(material_baking) << "Loading local material" << _materialData;
|
||||
|
@ -104,45 +102,42 @@ void MaterialBaker::processMaterial() {
|
|||
|
||||
for (auto networkMaterial : _materialResource->parsedMaterials.networkMaterials) {
|
||||
if (networkMaterial.second) {
|
||||
auto textureMaps = networkMaterial.second->getTextureMaps();
|
||||
for (auto textureMap : textureMaps) {
|
||||
if (textureMap.second && textureMap.second->getTextureSource()) {
|
||||
graphics::Material::MapChannel mapChannel = textureMap.first;
|
||||
auto texture = textureMap.second->getTextureSource();
|
||||
auto textures = networkMaterial.second->getTextures();
|
||||
for (auto texturePair : textures) {
|
||||
auto mapChannel = texturePair.first;
|
||||
auto textureMap = texturePair.second;
|
||||
if (textureMap.texture && textureMap.texture->_textureSource) {
|
||||
auto type = textureMap.texture->getTextureType();
|
||||
|
||||
QUrl url = texture->getUrl();
|
||||
QString cleanURL = url.adjusted(QUrl::RemoveQuery | QUrl::RemoveFragment).toDisplayString();
|
||||
QByteArray content;
|
||||
QUrl textureURL;
|
||||
{
|
||||
bool foundEmbeddedTexture = false;
|
||||
auto textureContentMapIter = _textureContentMap.find(networkMaterial.second->getName());
|
||||
if (textureContentMapIter != _textureContentMap.end()) {
|
||||
auto textureUsageIter = textureContentMapIter->second.find(type);
|
||||
if (textureUsageIter != textureContentMapIter->second.end()) {
|
||||
content = textureUsageIter->second.first;
|
||||
textureURL = textureUsageIter->second.second;
|
||||
foundEmbeddedTexture = true;
|
||||
}
|
||||
}
|
||||
if (!foundEmbeddedTexture && textureMap.texture->_textureSource) {
|
||||
textureURL = textureMap.texture->_textureSource->getUrl().adjusted(QUrl::RemoveQuery | QUrl::RemoveFragment);
|
||||
}
|
||||
}
|
||||
|
||||
QString cleanURL = textureURL.toDisplayString();
|
||||
auto idx = cleanURL.lastIndexOf('.');
|
||||
auto extension = idx >= 0 ? url.toDisplayString().mid(idx + 1).toLower() : "";
|
||||
QString extension = idx >= 0 ? cleanURL.mid(idx + 1).toLower() : "";
|
||||
|
||||
if (QImageReader::supportedImageFormats().contains(extension.toLatin1())) {
|
||||
QUrl textureURL = url.adjusted(QUrl::RemoveQuery | QUrl::RemoveFragment);
|
||||
|
||||
// FIXME: this isn't properly handling bumpMaps or glossMaps
|
||||
static std::unordered_map<graphics::Material::MapChannel, image::TextureUsage::Type> MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP;
|
||||
if (MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP.empty()) {
|
||||
MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP[graphics::Material::MapChannel::EMISSIVE_MAP] = image::TextureUsage::EMISSIVE_TEXTURE;
|
||||
MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP[graphics::Material::MapChannel::ALBEDO_MAP] = image::TextureUsage::ALBEDO_TEXTURE;
|
||||
MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP[graphics::Material::MapChannel::METALLIC_MAP] = image::TextureUsage::METALLIC_TEXTURE;
|
||||
MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP[graphics::Material::MapChannel::ROUGHNESS_MAP] = image::TextureUsage::ROUGHNESS_TEXTURE;
|
||||
MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP[graphics::Material::MapChannel::NORMAL_MAP] = image::TextureUsage::NORMAL_TEXTURE;
|
||||
MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP[graphics::Material::MapChannel::OCCLUSION_MAP] = image::TextureUsage::OCCLUSION_TEXTURE;
|
||||
MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP[graphics::Material::MapChannel::LIGHTMAP_MAP] = image::TextureUsage::LIGHTMAP_TEXTURE;
|
||||
MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP[graphics::Material::MapChannel::SCATTERING_MAP] = image::TextureUsage::SCATTERING_TEXTURE;
|
||||
}
|
||||
|
||||
auto it = MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP.find(mapChannel);
|
||||
if (it == MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP.end()) {
|
||||
handleError("Unknown map channel");
|
||||
return;
|
||||
}
|
||||
|
||||
QPair<QUrl, image::TextureUsage::Type> textureKey(textureURL, it->second);
|
||||
QPair<QUrl, image::TextureUsage::Type> textureKey(textureURL, type);
|
||||
if (!_textureBakers.contains(textureKey)) {
|
||||
auto baseTextureFileName = _textureFileNamer.createBaseTextureFileName(textureURL.fileName(), it->second);
|
||||
auto baseTextureFileName = _textureFileNamer.createBaseTextureFileName(textureURL.fileName(), type);
|
||||
|
||||
QSharedPointer<TextureBaker> textureBaker {
|
||||
new TextureBaker(textureURL, it->second, _textureOutputDir, "", baseTextureFileName),
|
||||
new TextureBaker(textureURL, type, _textureOutputDir, "", baseTextureFileName, content),
|
||||
&TextureBaker::deleteLater
|
||||
};
|
||||
textureBaker->setMapChannel(mapChannel);
|
||||
|
@ -179,7 +174,7 @@ void MaterialBaker::handleFinishedTextureBaker() {
|
|||
|
||||
// Replace the old texture URLs
|
||||
for (auto networkMaterial : _materialsNeedingRewrite.values(textureKey)) {
|
||||
networkMaterial->getTextureMap(baker->getMapChannel())->getTextureSource()->setUrl(_destinationPath.resolved(relativeURL));
|
||||
networkMaterial->getTextureMap(baker->getMapChannel())->getTextureSource()->setUrl(relativeURL);
|
||||
}
|
||||
} else {
|
||||
// this texture failed to bake - this doesn't fail the entire bake but we need to add the errors from
|
||||
|
@ -245,3 +240,30 @@ void MaterialBaker::outputMaterial() {
|
|||
// emit signal to indicate the material baking is finished
|
||||
emit finished();
|
||||
}
|
||||
|
||||
void MaterialBaker::addTexture(const QString& materialName, image::TextureUsage::Type textureUsage, const hfm::Texture& texture) {
|
||||
auto& textureUsageMap = _textureContentMap[materialName.toStdString()];
|
||||
if (textureUsageMap.find(textureUsage) == textureUsageMap.end() && !texture.content.isEmpty()) {
|
||||
textureUsageMap[textureUsage] = { texture.content, texture.filename };
|
||||
}
|
||||
};
|
||||
|
||||
void MaterialBaker::setMaterials(const QHash<QString, hfm::Material>& materials, const QString& baseURL) {
|
||||
_materialResource = NetworkMaterialResourcePointer(new NetworkMaterialResource(), [](NetworkMaterialResource* ptr) { ptr->deleteLater(); });
|
||||
for (auto& material : materials) {
|
||||
_materialResource->parsedMaterials.names.push_back(material.name.toStdString());
|
||||
_materialResource->parsedMaterials.networkMaterials[material.name.toStdString()] = std::make_shared<NetworkMaterial>(material, baseURL);
|
||||
|
||||
// Store any embedded texture content
|
||||
addTexture(material.name, image::TextureUsage::NORMAL_TEXTURE, material.normalTexture);
|
||||
addTexture(material.name, image::TextureUsage::ALBEDO_TEXTURE, material.albedoTexture);
|
||||
addTexture(material.name, image::TextureUsage::GLOSS_TEXTURE, material.glossTexture);
|
||||
addTexture(material.name, image::TextureUsage::ROUGHNESS_TEXTURE, material.roughnessTexture);
|
||||
addTexture(material.name, image::TextureUsage::SPECULAR_TEXTURE, material.specularTexture);
|
||||
addTexture(material.name, image::TextureUsage::METALLIC_TEXTURE, material.metallicTexture);
|
||||
addTexture(material.name, image::TextureUsage::EMISSIVE_TEXTURE, material.emissiveTexture);
|
||||
addTexture(material.name, image::TextureUsage::OCCLUSION_TEXTURE, material.occlusionTexture);
|
||||
addTexture(material.name, image::TextureUsage::SCATTERING_TEXTURE, material.scatteringTexture);
|
||||
addTexture(material.name, image::TextureUsage::LIGHTMAP_TEXTURE, material.lightmapTexture);
|
||||
}
|
||||
}
|
|
@ -24,16 +24,19 @@ static const QString BAKED_MATERIAL_EXTENSION = ".baked.json";
|
|||
class MaterialBaker : public Baker {
|
||||
Q_OBJECT
|
||||
public:
|
||||
MaterialBaker(const QString& materialData, bool isURL, const QString& bakedOutputDir, const QUrl& destinationPath);
|
||||
MaterialBaker(const QString& materialData, bool isURL, const QString& bakedOutputDir);
|
||||
|
||||
QString getMaterialData() const { return _materialData; }
|
||||
bool isURL() const { return _isURL; }
|
||||
QString getBakedMaterialData() const { return _bakedMaterialData; }
|
||||
|
||||
void setMaterials(const QHash<QString, hfm::Material>& materials, const QString& baseURL);
|
||||
|
||||
static void setNextOvenWorkerThreadOperator(std::function<QThread*()> getNextOvenWorkerThreadOperator) { _getNextOvenWorkerThreadOperator = getNextOvenWorkerThreadOperator; }
|
||||
|
||||
public slots:
|
||||
virtual void bake() override;
|
||||
virtual void abort() override;
|
||||
|
||||
signals:
|
||||
void originalMaterialLoaded();
|
||||
|
@ -57,11 +60,18 @@ private:
|
|||
QString _bakedOutputDir;
|
||||
QString _textureOutputDir;
|
||||
QString _bakedMaterialData;
|
||||
QUrl _destinationPath;
|
||||
|
||||
QScriptEngine _scriptEngine;
|
||||
static std::function<QThread*()> _getNextOvenWorkerThreadOperator;
|
||||
TextureFileNamer _textureFileNamer;
|
||||
|
||||
void addTexture(const QString& materialName, image::TextureUsage::Type textureUsage, const hfm::Texture& texture);
|
||||
struct TextureUsageHash {
|
||||
std::size_t operator()(image::TextureUsage::Type textureUsage) const {
|
||||
return static_cast<std::size_t>(textureUsage);
|
||||
}
|
||||
};
|
||||
std::unordered_map<std::string, std::unordered_map<image::TextureUsage::Type, std::pair<QByteArray, QString>, TextureUsageHash>> _textureContentMap;
|
||||
};
|
||||
|
||||
#endif // !hifi_MaterialBaker_h
|
||||
|
|
|
@ -42,12 +42,12 @@
|
|||
|
||||
#include "baking/BakerLibrary.h"
|
||||
|
||||
ModelBaker::ModelBaker(const QUrl& inputModelURL, TextureBakerThreadGetter inputTextureThreadGetter,
|
||||
const QString& bakedOutputDirectory, const QString& originalOutputDirectory, bool hasBeenBaked) :
|
||||
#include <QJsonArray>
|
||||
|
||||
ModelBaker::ModelBaker(const QUrl& inputModelURL, const QString& bakedOutputDirectory, const QString& originalOutputDirectory, bool hasBeenBaked) :
|
||||
_modelURL(inputModelURL),
|
||||
_bakedOutputDir(bakedOutputDirectory),
|
||||
_originalOutputDir(originalOutputDirectory),
|
||||
_textureThreadGetter(inputTextureThreadGetter),
|
||||
_hasBeenBaked(hasBeenBaked)
|
||||
{
|
||||
auto bakedFilename = _modelURL.fileName();
|
||||
|
@ -209,7 +209,6 @@ void ModelBaker::bakeSourceCopy() {
|
|||
}
|
||||
hifi::ByteArray modelData = modelFile.readAll();
|
||||
|
||||
hfm::Model::Pointer bakedModel;
|
||||
std::vector<hifi::ByteArray> dracoMeshes;
|
||||
std::vector<std::vector<hifi::ByteArray>> dracoMaterialLists; // Material order for per-mesh material lookup used by dracoMeshes
|
||||
|
||||
|
@ -245,40 +244,76 @@ void ModelBaker::bakeSourceCopy() {
|
|||
// Begin hfm baking
|
||||
baker.run();
|
||||
|
||||
bakedModel = baker.getHFMModel();
|
||||
_hfmModel = baker.getHFMModel();
|
||||
dracoMeshes = baker.getDracoMeshes();
|
||||
dracoMaterialLists = baker.getDracoMaterialLists();
|
||||
}
|
||||
|
||||
// Populate _textureContentMap with path to content mappings, for quick lookup by URL
|
||||
for (auto materialIt = bakedModel->materials.cbegin(); materialIt != bakedModel->materials.cend(); materialIt++) {
|
||||
static const auto addTexture = [](QHash<hifi::ByteArray, hifi::ByteArray>& textureContentMap, const hfm::Texture& texture) {
|
||||
if (!textureContentMap.contains(texture.filename)) {
|
||||
// Content may be empty, unless the data is inlined
|
||||
textureContentMap[texture.filename] = texture.content;
|
||||
}
|
||||
};
|
||||
const hfm::Material& material = *materialIt;
|
||||
addTexture(_textureContentMap, material.normalTexture);
|
||||
addTexture(_textureContentMap, material.albedoTexture);
|
||||
addTexture(_textureContentMap, material.opacityTexture);
|
||||
addTexture(_textureContentMap, material.glossTexture);
|
||||
addTexture(_textureContentMap, material.roughnessTexture);
|
||||
addTexture(_textureContentMap, material.specularTexture);
|
||||
addTexture(_textureContentMap, material.metallicTexture);
|
||||
addTexture(_textureContentMap, material.emissiveTexture);
|
||||
addTexture(_textureContentMap, material.occlusionTexture);
|
||||
addTexture(_textureContentMap, material.scatteringTexture);
|
||||
addTexture(_textureContentMap, material.lightmapTexture);
|
||||
}
|
||||
|
||||
// Do format-specific baking
|
||||
bakeProcessedSource(bakedModel, dracoMeshes, dracoMaterialLists);
|
||||
bakeProcessedSource(_hfmModel, dracoMeshes, dracoMaterialLists);
|
||||
|
||||
if (shouldStop()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_hfmModel->materials.size() > 0) {
|
||||
_materialBaker = QSharedPointer<MaterialBaker>(
|
||||
new MaterialBaker(_modelURL.fileName(), true, _bakedOutputDir),
|
||||
&MaterialBaker::deleteLater
|
||||
);
|
||||
_materialBaker->setMaterials(_hfmModel->materials, _modelURL.toString());
|
||||
connect(_materialBaker.data(), &MaterialBaker::finished, this, &ModelBaker::handleFinishedMaterialBaker);
|
||||
_materialBaker->bake();
|
||||
} else {
|
||||
outputBakedFST();
|
||||
}
|
||||
}
|
||||
|
||||
void ModelBaker::handleFinishedMaterialBaker() {
|
||||
auto baker = qobject_cast<MaterialBaker*>(sender());
|
||||
|
||||
if (baker) {
|
||||
if (!baker->hasErrors()) {
|
||||
// this MaterialBaker is done and everything went according to plan
|
||||
qCDebug(model_baking) << "Adding baked material to FST mapping " << baker->getBakedMaterialData();
|
||||
|
||||
QString relativeBakedMaterialURL = _modelURL.fileName();
|
||||
auto baseName = relativeBakedMaterialURL.left(relativeBakedMaterialURL.lastIndexOf('.'));
|
||||
relativeBakedMaterialURL = baseName + BAKED_MATERIAL_EXTENSION;
|
||||
|
||||
// First we add the materials in the model
|
||||
QJsonArray materialMapping;
|
||||
for (auto material : _hfmModel->materials) {
|
||||
QJsonObject json;
|
||||
json["mat::" + material.name] = relativeBakedMaterialURL + "?" + material.name;
|
||||
materialMapping.push_back(json);
|
||||
}
|
||||
|
||||
// The we add any existing mappings from the mapping
|
||||
if (_mapping.contains(MATERIAL_MAPPING_FIELD)) {
|
||||
QByteArray materialMapValue = _mapping[MATERIAL_MAPPING_FIELD].toByteArray();
|
||||
QJsonObject oldMaterialMapping = QJsonDocument::fromJson(materialMapValue).object();
|
||||
for (auto key : oldMaterialMapping.keys()) {
|
||||
QJsonObject json;
|
||||
json[key] = oldMaterialMapping[key];
|
||||
materialMapping.push_back(json);
|
||||
}
|
||||
}
|
||||
|
||||
_mapping[MATERIAL_MAPPING_FIELD] = QJsonDocument(materialMapping).toJson(QJsonDocument::Compact);
|
||||
} 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->getWarnings();
|
||||
}
|
||||
} else {
|
||||
handleWarning("Failed to bake the materials for model with URL " + _modelURL.toString());
|
||||
}
|
||||
|
||||
outputBakedFST();
|
||||
}
|
||||
|
||||
void ModelBaker::outputBakedFST() {
|
||||
// Output FST file, copying over input mappings if available
|
||||
QString outputFSTFilename = !_mappingURL.isEmpty() ? _mappingURL.fileName() : _modelURL.fileName();
|
||||
auto extensionStart = outputFSTFilename.indexOf(".");
|
||||
|
@ -291,8 +326,7 @@ void ModelBaker::bakeSourceCopy() {
|
|||
auto outputMapping = _mapping;
|
||||
outputMapping[FST_VERSION_FIELD] = FST_VERSION;
|
||||
outputMapping[FILENAME_FIELD] = _bakedModelURL.fileName();
|
||||
// All textures will be found in the same directory as the model
|
||||
outputMapping[TEXDIR_FIELD] = ".";
|
||||
outputMapping.remove(TEXDIR_FIELD);
|
||||
hifi::ByteArray fstOut = FSTReader::writeMapping(outputMapping);
|
||||
|
||||
QFile fstOutputFile { outputFSTURL };
|
||||
|
@ -307,17 +341,16 @@ void ModelBaker::bakeSourceCopy() {
|
|||
_outputFiles.push_back(outputFSTURL);
|
||||
_outputMappingURL = outputFSTURL;
|
||||
|
||||
// check if we're already done with textures (in case we had none to re-write)
|
||||
checkIfTexturesFinished();
|
||||
exportScene();
|
||||
qCDebug(model_baking) << "Finished baking, emitting finished" << _modelURL;
|
||||
emit finished();
|
||||
}
|
||||
|
||||
void ModelBaker::abort() {
|
||||
Baker::abort();
|
||||
|
||||
// tell our underlying TextureBaker instances to abort
|
||||
// the ModelBaker will wait until all are aborted before emitting its own abort signal
|
||||
for (auto& textureBaker : _bakingTextures) {
|
||||
textureBaker->abort();
|
||||
if (_materialBaker) {
|
||||
_materialBaker->abort();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -354,247 +387,6 @@ bool ModelBaker::buildDracoMeshNode(FBXNode& dracoMeshNode, const QByteArray& dr
|
|||
return true;
|
||||
}
|
||||
|
||||
QString ModelBaker::compressTexture(QString modelTextureFileName, image::TextureUsage::Type textureType) {
|
||||
|
||||
QFileInfo modelTextureFileInfo { modelTextureFileName.replace("\\", "/") };
|
||||
|
||||
if (modelTextureFileInfo.suffix().toLower() == BAKED_TEXTURE_KTX_EXT.mid(1)) {
|
||||
// re-baking a model that already references baked textures
|
||||
// this is an error - return from here
|
||||
handleError("Cannot re-bake a file that already references compressed textures");
|
||||
return QString::null;
|
||||
}
|
||||
|
||||
if (!image::getSupportedFormats().contains(modelTextureFileInfo.suffix())) {
|
||||
// this is a texture format we don't bake, skip it
|
||||
handleWarning(modelTextureFileName + " is not a bakeable texture format");
|
||||
return QString::null;
|
||||
}
|
||||
|
||||
// make sure this texture points to something and isn't one we've already re-mapped
|
||||
QString textureChild { QString::null };
|
||||
if (!modelTextureFileInfo.filePath().isEmpty()) {
|
||||
// check if this was an embedded texture that we already have in-memory content for
|
||||
QByteArray textureContent;
|
||||
|
||||
// figure out the URL to this texture, embedded or external
|
||||
if (!modelTextureFileInfo.filePath().isEmpty()) {
|
||||
textureContent = _textureContentMap.value(modelTextureFileName.toLocal8Bit());
|
||||
}
|
||||
auto urlToTexture = getTextureURL(modelTextureFileInfo, !textureContent.isNull());
|
||||
|
||||
TextureKey textureKey { urlToTexture, textureType };
|
||||
auto bakingTextureIt = _bakingTextures.find(textureKey);
|
||||
if (bakingTextureIt == _bakingTextures.cend()) {
|
||||
// construct the new baked texture file name and file path
|
||||
// ensuring that the baked texture will have a unique name
|
||||
// even if there was another texture with the same name at a different path
|
||||
QString baseTextureFileName = _textureFileNamer.createBaseTextureFileName(modelTextureFileInfo, textureType);
|
||||
|
||||
QString bakedTextureFilePath {
|
||||
_bakedOutputDir + "/" + baseTextureFileName + BAKED_META_TEXTURE_SUFFIX
|
||||
};
|
||||
|
||||
textureChild = baseTextureFileName + BAKED_META_TEXTURE_SUFFIX;
|
||||
|
||||
_outputFiles.push_back(bakedTextureFilePath);
|
||||
|
||||
// bake this texture asynchronously
|
||||
bakeTexture(textureKey, _bakedOutputDir, baseTextureFileName, textureContent);
|
||||
} else {
|
||||
// Fetch existing texture meta name
|
||||
textureChild = (*bakingTextureIt)->getBaseFilename() + BAKED_META_TEXTURE_SUFFIX;
|
||||
}
|
||||
}
|
||||
|
||||
qCDebug(model_baking).noquote() << "Re-mapping" << modelTextureFileName
|
||||
<< "to" << textureChild;
|
||||
|
||||
return textureChild;
|
||||
}
|
||||
|
||||
void ModelBaker::bakeTexture(const TextureKey& textureKey, const QDir& outputDir, const QString& bakedFilename, const QByteArray& textureContent) {
|
||||
// start a bake for this texture and add it to our list to keep track of
|
||||
QSharedPointer<TextureBaker> bakingTexture{
|
||||
new TextureBaker(textureKey.first, textureKey.second, outputDir, "../", bakedFilename, textureContent),
|
||||
&TextureBaker::deleteLater
|
||||
};
|
||||
|
||||
// make sure we hear when the baking texture is done or aborted
|
||||
connect(bakingTexture.data(), &Baker::finished, this, &ModelBaker::handleBakedTexture);
|
||||
connect(bakingTexture.data(), &TextureBaker::aborted, this, &ModelBaker::handleAbortedTexture);
|
||||
|
||||
// keep a shared pointer to the baking texture
|
||||
_bakingTextures.insert(textureKey, bakingTexture);
|
||||
|
||||
// start baking the texture on one of our available worker threads
|
||||
bakingTexture->moveToThread(_textureThreadGetter());
|
||||
QMetaObject::invokeMethod(bakingTexture.data(), "bake");
|
||||
}
|
||||
|
||||
void ModelBaker::handleBakedTexture() {
|
||||
TextureBaker* bakedTexture = qobject_cast<TextureBaker*>(sender());
|
||||
qDebug() << "Handling baked texture" << bakedTexture->getTextureURL();
|
||||
|
||||
// make sure we haven't already run into errors, and that this is a valid texture
|
||||
if (bakedTexture) {
|
||||
if (!shouldStop()) {
|
||||
if (!bakedTexture->hasErrors()) {
|
||||
if (!_originalOutputDir.isEmpty()) {
|
||||
// we've been asked to make copies of the originals, so we need to make copies of this if it is a linked texture
|
||||
|
||||
// use the path to the texture being baked to determine if this was an embedded or a linked texture
|
||||
|
||||
// it is embeddded if the texure being baked was inside a folder with the name of the model
|
||||
// since that is the fake URL we provide when baking external textures
|
||||
|
||||
if (!_modelURL.isParentOf(bakedTexture->getTextureURL())) {
|
||||
// for linked textures we want to save a copy of original texture beside the original model
|
||||
|
||||
qCDebug(model_baking) << "Saving original texture for" << bakedTexture->getTextureURL();
|
||||
|
||||
// check if we have a relative path to use for the texture
|
||||
auto relativeTexturePath = texturePathRelativeToModel(_modelURL, bakedTexture->getTextureURL());
|
||||
|
||||
QFile originalTextureFile{
|
||||
_originalOutputDir + "/" + relativeTexturePath + bakedTexture->getTextureURL().fileName()
|
||||
};
|
||||
|
||||
if (relativeTexturePath.length() > 0) {
|
||||
// make the folders needed by the relative path
|
||||
}
|
||||
|
||||
if (originalTextureFile.open(QIODevice::WriteOnly) && originalTextureFile.write(bakedTexture->getOriginalTexture()) != -1) {
|
||||
qCDebug(model_baking) << "Saved original texture file" << originalTextureFile.fileName()
|
||||
<< "for" << _modelURL;
|
||||
} else {
|
||||
handleError("Could not save original external texture " + originalTextureFile.fileName()
|
||||
+ " for " + _modelURL.toString());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// now that this texture has been baked and handled, we can remove that TextureBaker from our hash
|
||||
_bakingTextures.remove({ bakedTexture->getTextureURL(), bakedTexture->getTextureType() });
|
||||
|
||||
checkIfTexturesFinished();
|
||||
} else {
|
||||
// there was an error baking this texture - add it to our list of errors
|
||||
_errorList.append(bakedTexture->getErrors());
|
||||
|
||||
// we don't emit finished yet so that the other textures can finish baking first
|
||||
_pendingErrorEmission = true;
|
||||
|
||||
// now that this texture has been baked, even though it failed, we can remove that TextureBaker from our list
|
||||
_bakingTextures.remove({ bakedTexture->getTextureURL(), bakedTexture->getTextureType() });
|
||||
|
||||
// abort any other ongoing texture bakes since we know we'll end up failing
|
||||
for (auto& bakingTexture : _bakingTextures) {
|
||||
bakingTexture->abort();
|
||||
}
|
||||
|
||||
checkIfTexturesFinished();
|
||||
}
|
||||
} else {
|
||||
// we have errors to attend to, so we don't do extra processing for this texture
|
||||
// but we do need to remove that TextureBaker from our list
|
||||
// and then check if we're done with all textures
|
||||
_bakingTextures.remove({ bakedTexture->getTextureURL(), bakedTexture->getTextureType() });
|
||||
|
||||
checkIfTexturesFinished();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ModelBaker::handleAbortedTexture() {
|
||||
// grab the texture bake that was aborted and remove it from our hash since we don't need to track it anymore
|
||||
TextureBaker* bakedTexture = qobject_cast<TextureBaker*>(sender());
|
||||
|
||||
qDebug() << "Texture aborted: " << bakedTexture->getTextureURL();
|
||||
|
||||
if (bakedTexture) {
|
||||
_bakingTextures.remove({ bakedTexture->getTextureURL(), bakedTexture->getTextureType() });
|
||||
}
|
||||
|
||||
// since a texture we were baking aborted, our status is also aborted
|
||||
_shouldAbort.store(true);
|
||||
|
||||
// abort any other ongoing texture bakes since we know we'll end up failing
|
||||
for (auto& bakingTexture : _bakingTextures) {
|
||||
bakingTexture->abort();
|
||||
}
|
||||
|
||||
checkIfTexturesFinished();
|
||||
}
|
||||
|
||||
QUrl ModelBaker::getTextureURL(const QFileInfo& textureFileInfo, bool isEmbedded) {
|
||||
QUrl urlToTexture;
|
||||
|
||||
if (isEmbedded) {
|
||||
urlToTexture = _modelURL.toString() + "/" + textureFileInfo.filePath();
|
||||
} else {
|
||||
if (textureFileInfo.exists() && textureFileInfo.isFile()) {
|
||||
// set the texture URL to the local texture that we have confirmed exists
|
||||
urlToTexture = QUrl::fromLocalFile(textureFileInfo.absoluteFilePath());
|
||||
} else {
|
||||
// external texture that we'll need to download or find
|
||||
|
||||
// this is a relative file path which will require different handling
|
||||
// depending on the location of the original model
|
||||
if (_modelURL.isLocalFile() && textureFileInfo.exists() && textureFileInfo.isFile()) {
|
||||
// the absolute path we ran into for the texture in the model exists on this machine
|
||||
// so use that file
|
||||
urlToTexture = QUrl::fromLocalFile(textureFileInfo.absoluteFilePath());
|
||||
} else {
|
||||
// we didn't find the texture on this machine at the absolute path
|
||||
// so assume that it is right beside the model to match the behaviour of interface
|
||||
urlToTexture = _modelURL.resolved(textureFileInfo.fileName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return urlToTexture;
|
||||
}
|
||||
|
||||
QString ModelBaker::texturePathRelativeToModel(QUrl modelURL, QUrl textureURL) {
|
||||
auto modelPath = modelURL.toString(QUrl::RemoveFilename | QUrl::RemoveQuery | QUrl::RemoveFragment);
|
||||
auto texturePath = textureURL.toString(QUrl::RemoveFilename | QUrl::RemoveQuery | QUrl::RemoveFragment);
|
||||
|
||||
if (texturePath.startsWith(modelPath)) {
|
||||
// texture path is a child of the model path, return the texture path without the model path
|
||||
return texturePath.mid(modelPath.length());
|
||||
} else {
|
||||
// the texture path was not a child of the model path, return the empty string
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
void ModelBaker::checkIfTexturesFinished() {
|
||||
// check if we're done everything we need to do for this model
|
||||
// and emit our finished signal if we're done
|
||||
|
||||
if (_bakingTextures.isEmpty()) {
|
||||
if (shouldStop()) {
|
||||
// if we're checking for completion but we have errors
|
||||
// that means one or more of our texture baking operations failed
|
||||
|
||||
if (_pendingErrorEmission) {
|
||||
setIsFinished(true);
|
||||
}
|
||||
|
||||
return;
|
||||
} else {
|
||||
qCDebug(model_baking) << "Finished baking, emitting finished" << _modelURL;
|
||||
|
||||
texturesFinished();
|
||||
|
||||
setIsFinished(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ModelBaker::setWasAborted(bool wasAborted) {
|
||||
if (wasAborted != _wasAborted.load()) {
|
||||
Baker::setWasAborted(wasAborted);
|
||||
|
@ -605,70 +397,6 @@ void ModelBaker::setWasAborted(bool wasAborted) {
|
|||
}
|
||||
}
|
||||
|
||||
void ModelBaker::texturesFinished() {
|
||||
embedTextureMetaData();
|
||||
exportScene();
|
||||
}
|
||||
|
||||
void ModelBaker::embedTextureMetaData() {
|
||||
std::vector<FBXNode> embeddedTextureNodes;
|
||||
|
||||
for (FBXNode& rootChild : _rootNode.children) {
|
||||
if (rootChild.name == "Objects") {
|
||||
qlonglong maxId = 0;
|
||||
for (auto &child : rootChild.children) {
|
||||
if (child.properties.length() == 3) {
|
||||
maxId = std::max(maxId, child.properties[0].toLongLong());
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& object : rootChild.children) {
|
||||
if (object.name == "Texture") {
|
||||
QVariant relativeFilename;
|
||||
for (auto& child : object.children) {
|
||||
if (child.name == "RelativeFilename") {
|
||||
relativeFilename = child.properties[0];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (relativeFilename.isNull()
|
||||
|| !relativeFilename.toString().endsWith(BAKED_META_TEXTURE_SUFFIX)) {
|
||||
continue;
|
||||
}
|
||||
if (object.properties.length() < 2) {
|
||||
qWarning() << "Found texture with unexpected number of properties: " << object.name;
|
||||
continue;
|
||||
}
|
||||
|
||||
FBXNode videoNode;
|
||||
videoNode.name = "Video";
|
||||
videoNode.properties.append(++maxId);
|
||||
videoNode.properties.append(object.properties[1]);
|
||||
videoNode.properties.append("Clip");
|
||||
|
||||
QString bakedTextureFilePath {
|
||||
_bakedOutputDir + "/" + relativeFilename.toString()
|
||||
};
|
||||
|
||||
QFile textureFile { bakedTextureFilePath };
|
||||
if (!textureFile.open(QIODevice::ReadOnly)) {
|
||||
qWarning() << "Failed to open: " << bakedTextureFilePath;
|
||||
continue;
|
||||
}
|
||||
|
||||
videoNode.children.append({ "RelativeFilename", { relativeFilename }, { } });
|
||||
videoNode.children.append({ "Content", { textureFile.readAll() }, { } });
|
||||
|
||||
rootChild.children.append(videoNode);
|
||||
|
||||
textureFile.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ModelBaker::exportScene() {
|
||||
auto fbxData = FBXWriter::encodeFBX(_rootNode);
|
||||
|
||||
|
|
|
@ -18,17 +18,13 @@
|
|||
#include <QtNetwork/QNetworkReply>
|
||||
|
||||
#include "Baker.h"
|
||||
#include "TextureBaker.h"
|
||||
#include "baking/TextureFileNamer.h"
|
||||
#include "MaterialBaker.h"
|
||||
|
||||
#include "ModelBakingLoggingCategory.h"
|
||||
|
||||
#include <gpu/Texture.h>
|
||||
|
||||
#include <FBX.h>
|
||||
#include <hfm/HFM.h>
|
||||
|
||||
using TextureBakerThreadGetter = std::function<QThread*()>;
|
||||
using GetMaterialIDCallback = std::function <int(int)>;
|
||||
|
||||
static const QString FST_EXTENSION { ".fst" };
|
||||
|
@ -42,10 +38,7 @@ class ModelBaker : public Baker {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
using TextureKey = QPair<QUrl, image::TextureUsage::Type>;
|
||||
|
||||
ModelBaker(const QUrl& inputModelURL, TextureBakerThreadGetter inputTextureThreadGetter,
|
||||
const QString& bakedOutputDirectory, const QString& originalOutputDirectory = "", bool hasBeenBaked = false);
|
||||
ModelBaker(const QUrl& inputModelURL, const QString& bakedOutputDirectory, const QString& originalOutputDirectory = "", bool hasBeenBaked = false);
|
||||
|
||||
void setOutputURLSuffix(const QUrl& urlSuffix);
|
||||
void setMappingURL(const QUrl& mappingURL);
|
||||
|
@ -54,7 +47,6 @@ public:
|
|||
void initializeOutputDirs();
|
||||
|
||||
bool buildDracoMeshNode(FBXNode& dracoMeshNode, const QByteArray& dracoMeshBytes, const std::vector<hifi::ByteArray>& dracoMaterialList);
|
||||
QString compressTexture(QString textureFileName, image::TextureUsage::Type = image::TextureUsage::Type::DEFAULT_TEXTURE);
|
||||
virtual void setWasAborted(bool wasAborted) override;
|
||||
|
||||
QUrl getModelURL() const { return _modelURL; }
|
||||
|
@ -71,20 +63,15 @@ public slots:
|
|||
protected:
|
||||
void saveSourceModel();
|
||||
virtual void bakeProcessedSource(const hfm::Model::Pointer& hfmModel, const std::vector<hifi::ByteArray>& dracoMeshes, const std::vector<std::vector<hifi::ByteArray>>& dracoMaterialLists) = 0;
|
||||
void checkIfTexturesFinished();
|
||||
void texturesFinished();
|
||||
void embedTextureMetaData();
|
||||
void exportScene();
|
||||
|
||||
FBXNode _rootNode;
|
||||
QHash<QByteArray, QByteArray> _textureContentMap;
|
||||
QUrl _modelURL;
|
||||
QUrl _outputURLSuffix;
|
||||
QUrl _mappingURL;
|
||||
hifi::VariantHash _mapping;
|
||||
QString _bakedOutputDir;
|
||||
QString _originalOutputDir;
|
||||
TextureBakerThreadGetter _textureThreadGetter;
|
||||
QString _originalOutputModelPath;
|
||||
QString _outputMappingURL;
|
||||
QUrl _bakedModelURL;
|
||||
|
@ -92,23 +79,15 @@ protected:
|
|||
protected slots:
|
||||
void handleModelNetworkReply();
|
||||
virtual void bakeSourceCopy();
|
||||
|
||||
private slots:
|
||||
void handleBakedTexture();
|
||||
void handleAbortedTexture();
|
||||
void handleFinishedMaterialBaker();
|
||||
|
||||
private:
|
||||
QUrl getTextureURL(const QFileInfo& textureFileInfo, bool isEmbedded = false);
|
||||
void bakeTexture(const TextureKey& textureKey, const QDir& outputDir, const QString& bakedFilename, const QByteArray& textureContent);
|
||||
QString texturePathRelativeToModel(QUrl modelURL, QUrl textureURL);
|
||||
|
||||
QMultiHash<TextureKey, QSharedPointer<TextureBaker>> _bakingTextures;
|
||||
QHash<QString, int> _textureNameMatchCount;
|
||||
bool _pendingErrorEmission { false };
|
||||
void outputBakedFST();
|
||||
|
||||
bool _hasBeenBaked { false };
|
||||
|
||||
TextureFileNamer _textureFileNamer;
|
||||
hfm::Model::Pointer _hfmModel;
|
||||
QSharedPointer<MaterialBaker> _materialBaker;
|
||||
};
|
||||
|
||||
#endif // hifi_ModelBaker_h
|
||||
|
|
|
@ -132,55 +132,6 @@ void OBJBaker::createFBXNodeTree(FBXNode& rootNode, const hfm::Model::Pointer& h
|
|||
handleWarning("Baked mesh for OBJ model '" + _modelURL.toString() + "' is empty");
|
||||
}
|
||||
|
||||
// Generating Texture Node
|
||||
// iterate through mesh parts and process the associated textures
|
||||
auto size = meshParts.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
QString material = meshParts[i].materialID;
|
||||
HFMMaterial currentMaterial = hfmModel->materials[material];
|
||||
if (!currentMaterial.albedoTexture.filename.isEmpty() || !currentMaterial.specularTexture.filename.isEmpty()) {
|
||||
auto textureID = nextNodeID();
|
||||
_mapTextureMaterial.emplace_back(textureID, i);
|
||||
|
||||
FBXNode textureNode;
|
||||
{
|
||||
textureNode.name = TEXTURE_NODE_NAME;
|
||||
textureNode.properties = { textureID, "texture" + QString::number(textureID) };
|
||||
}
|
||||
|
||||
// Texture node child - TextureName node
|
||||
FBXNode textureNameNode;
|
||||
{
|
||||
textureNameNode.name = TEXTURENAME_NODE_NAME;
|
||||
QByteArray propertyString = (!currentMaterial.albedoTexture.filename.isEmpty()) ? "Kd" : "Ka";
|
||||
textureNameNode.properties = { propertyString };
|
||||
}
|
||||
|
||||
// Texture node child - Relative Filename node
|
||||
FBXNode relativeFilenameNode;
|
||||
{
|
||||
relativeFilenameNode.name = RELATIVEFILENAME_NODE_NAME;
|
||||
}
|
||||
|
||||
QByteArray textureFileName = (!currentMaterial.albedoTexture.filename.isEmpty()) ? currentMaterial.albedoTexture.filename : currentMaterial.specularTexture.filename;
|
||||
|
||||
auto textureType = (!currentMaterial.albedoTexture.filename.isEmpty()) ? image::TextureUsage::Type::ALBEDO_TEXTURE : image::TextureUsage::Type::SPECULAR_TEXTURE;
|
||||
|
||||
// Compress the texture using ModelBaker::compressTexture() and store compressed file's name in the node
|
||||
auto textureFile = compressTexture(textureFileName, textureType);
|
||||
if (textureFile.isNull()) {
|
||||
// Baking failed return
|
||||
handleError("Failed to compress texture: " + textureFileName);
|
||||
return;
|
||||
}
|
||||
relativeFilenameNode.properties = { textureFile };
|
||||
|
||||
textureNode.children = { textureNameNode, relativeFilenameNode };
|
||||
|
||||
objectNode.children.append(textureNode);
|
||||
}
|
||||
}
|
||||
|
||||
// Generating Connections node
|
||||
connectionsNode.name = CONNECTIONS_NODE_NAME;
|
||||
|
||||
|
@ -199,29 +150,6 @@ void OBJBaker::createFBXNodeTree(FBXNode& rootNode, const hfm::Model::Pointer& h
|
|||
cNode.properties = { CONNECTIONS_NODE_PROPERTY, materialID, modelID };
|
||||
connectionsNode.children.append(cNode);
|
||||
}
|
||||
|
||||
// Connect textures to materials
|
||||
for (const auto& texMat : _mapTextureMaterial) {
|
||||
FBXNode cAmbientNode;
|
||||
cAmbientNode.name = C_NODE_NAME;
|
||||
cAmbientNode.properties = {
|
||||
CONNECTIONS_NODE_PROPERTY_1,
|
||||
texMat.first,
|
||||
_materialIDs[texMat.second],
|
||||
"AmbientFactor"
|
||||
};
|
||||
connectionsNode.children.append(cAmbientNode);
|
||||
|
||||
FBXNode cDiffuseNode;
|
||||
cDiffuseNode.name = C_NODE_NAME;
|
||||
cDiffuseNode.properties = {
|
||||
CONNECTIONS_NODE_PROPERTY_1,
|
||||
texMat.first,
|
||||
_materialIDs[texMat.second],
|
||||
"DiffuseColor"
|
||||
};
|
||||
connectionsNode.children.append(cDiffuseNode);
|
||||
}
|
||||
}
|
||||
|
||||
// Set properties for material nodes
|
||||
|
|
|
@ -35,9 +35,7 @@ private:
|
|||
void setMaterialNodeProperties(FBXNode& materialNode, QString material, const hfm::Model::Pointer& hfmModel);
|
||||
NodeID nextNodeID() { return _nodeID++; }
|
||||
|
||||
|
||||
NodeID _nodeID { 0 };
|
||||
std::vector<NodeID> _materialIDs;
|
||||
std::vector<std::pair<NodeID, int>> _mapTextureMaterial;
|
||||
};
|
||||
#endif // hifi_OBJBaker_h
|
||||
|
|
|
@ -44,7 +44,7 @@ bool isModelBaked(const QUrl& bakeableModelURL) {
|
|||
return beforeModelExtension.endsWith(".baked");
|
||||
}
|
||||
|
||||
std::unique_ptr<ModelBaker> getModelBaker(const QUrl& bakeableModelURL, TextureBakerThreadGetter inputTextureThreadGetter, const QString& contentOutputPath) {
|
||||
std::unique_ptr<ModelBaker> getModelBaker(const QUrl& bakeableModelURL, const QString& contentOutputPath) {
|
||||
auto filename = bakeableModelURL.fileName();
|
||||
|
||||
// Output in a sub-folder with the name of the model, potentially suffixed by a number to make it unique
|
||||
|
@ -58,20 +58,20 @@ std::unique_ptr<ModelBaker> getModelBaker(const QUrl& bakeableModelURL, TextureB
|
|||
QString bakedOutputDirectory = contentOutputPath + subDirName + "/baked";
|
||||
QString originalOutputDirectory = contentOutputPath + subDirName + "/original";
|
||||
|
||||
return getModelBakerWithOutputDirectories(bakeableModelURL, inputTextureThreadGetter, bakedOutputDirectory, originalOutputDirectory);
|
||||
return getModelBakerWithOutputDirectories(bakeableModelURL, bakedOutputDirectory, originalOutputDirectory);
|
||||
}
|
||||
|
||||
std::unique_ptr<ModelBaker> getModelBakerWithOutputDirectories(const QUrl& bakeableModelURL, TextureBakerThreadGetter inputTextureThreadGetter, const QString& bakedOutputDirectory, const QString& originalOutputDirectory) {
|
||||
std::unique_ptr<ModelBaker> getModelBakerWithOutputDirectories(const QUrl& bakeableModelURL, const QString& bakedOutputDirectory, const QString& originalOutputDirectory) {
|
||||
auto filename = bakeableModelURL.fileName();
|
||||
|
||||
std::unique_ptr<ModelBaker> baker;
|
||||
|
||||
if (filename.endsWith(FST_EXTENSION, Qt::CaseInsensitive)) {
|
||||
baker = std::make_unique<FSTBaker>(bakeableModelURL, inputTextureThreadGetter, bakedOutputDirectory, originalOutputDirectory, filename.endsWith(BAKED_FST_EXTENSION, Qt::CaseInsensitive));
|
||||
baker = std::make_unique<FSTBaker>(bakeableModelURL, 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));
|
||||
baker = std::make_unique<FBXBaker>(bakeableModelURL, 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);
|
||||
baker = std::make_unique<OBJBaker>(bakeableModelURL, bakedOutputDirectory, originalOutputDirectory);
|
||||
//} else if (filename.endsWith(GLTF_EXTENSION, Qt::CaseInsensitive)) {
|
||||
//baker = std::make_unique<GLTFBaker>(bakeableModelURL, inputTextureThreadGetter, bakedOutputDirectory, originalOutputDirectory);
|
||||
} else {
|
||||
|
|
|
@ -23,9 +23,9 @@ bool isModelBaked(const QUrl& bakeableModelURL);
|
|||
|
||||
// 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
|
||||
std::unique_ptr<ModelBaker> getModelBaker(const QUrl& bakeableModelURL, TextureBakerThreadGetter inputTextureThreadGetter, const QString& contentOutputPath);
|
||||
std::unique_ptr<ModelBaker> getModelBaker(const QUrl& bakeableModelURL, const QString& contentOutputPath);
|
||||
|
||||
// Similar to getModelBaker, but gives control over where the output folders will be
|
||||
std::unique_ptr<ModelBaker> getModelBakerWithOutputDirectories(const QUrl& bakeableModelURL, TextureBakerThreadGetter inputTextureThreadGetter, const QString& bakedOutputDirectory, const QString& originalOutputDirectory);
|
||||
std::unique_ptr<ModelBaker> getModelBakerWithOutputDirectories(const QUrl& bakeableModelURL, const QString& bakedOutputDirectory, const QString& originalOutputDirectory);
|
||||
|
||||
#endif // hifi_BakerLibrary_h
|
||||
|
|
|
@ -18,9 +18,8 @@
|
|||
|
||||
#include <FSTReader.h>
|
||||
|
||||
FSTBaker::FSTBaker(const QUrl& inputMappingURL, TextureBakerThreadGetter inputTextureThreadGetter,
|
||||
const QString& bakedOutputDirectory, const QString& originalOutputDirectory, bool hasBeenBaked) :
|
||||
ModelBaker(inputMappingURL, inputTextureThreadGetter, bakedOutputDirectory, originalOutputDirectory, hasBeenBaked) {
|
||||
FSTBaker::FSTBaker(const QUrl& inputMappingURL, const QString& bakedOutputDirectory, const QString& originalOutputDirectory, bool hasBeenBaked) :
|
||||
ModelBaker(inputMappingURL, bakedOutputDirectory, originalOutputDirectory, hasBeenBaked) {
|
||||
if (hasBeenBaked) {
|
||||
// Look for the original model file one directory higher. Perhaps this is an oven output directory.
|
||||
QUrl originalRelativePath = QUrl("../original/" + inputMappingURL.fileName().replace(BAKED_FST_EXTENSION, FST_EXTENSION));
|
||||
|
@ -70,7 +69,7 @@ void FSTBaker::bakeSourceCopy() {
|
|||
return;
|
||||
}
|
||||
|
||||
auto baker = getModelBakerWithOutputDirectories(bakeableModelURL, _textureThreadGetter, _bakedOutputDir, _originalOutputDir);
|
||||
auto baker = getModelBakerWithOutputDirectories(bakeableModelURL, _bakedOutputDir, _originalOutputDir);
|
||||
_modelBaker = std::unique_ptr<ModelBaker>(dynamic_cast<ModelBaker*>(baker.release()));
|
||||
if (!_modelBaker) {
|
||||
handleError("The model url '" + bakeableModelURL.toString() + "' from the FST file '" + _originalOutputModelPath + "' (property: '" + FILENAME_FIELD + "') could not be used to initialize a valid model baker");
|
||||
|
|
|
@ -18,8 +18,7 @@ class FSTBaker : public ModelBaker {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
FSTBaker(const QUrl& inputMappingURL, TextureBakerThreadGetter inputTextureThreadGetter,
|
||||
const QString& bakedOutputDirectory, const QString& originalOutputDirectory = "", bool hasBeenBaked = false);
|
||||
FSTBaker(const QUrl& inputMappingURL, const QString& bakedOutputDirectory, const QString& originalOutputDirectory = "", bool hasBeenBaked = false);
|
||||
|
||||
virtual QUrl getFullOutputMappingURL() const override;
|
||||
|
||||
|
|
|
@ -97,7 +97,7 @@ QString processID(const QString& id) {
|
|||
return id.mid(id.lastIndexOf(':') + 1);
|
||||
}
|
||||
|
||||
QString getName(const QVariantList& properties) {
|
||||
QString getModelName(const QVariantList& properties) {
|
||||
QString name;
|
||||
if (properties.size() == 3) {
|
||||
name = properties.at(1).toString();
|
||||
|
@ -108,6 +108,17 @@ QString getName(const QVariantList& properties) {
|
|||
return name;
|
||||
}
|
||||
|
||||
QString getMaterialName(const QVariantList& properties) {
|
||||
QString name;
|
||||
if (properties.size() == 1 || properties.at(1).toString().isEmpty()) {
|
||||
name = properties.at(0).toString();
|
||||
name = processID(name.left(name.indexOf(QChar('\0'))));
|
||||
} else {
|
||||
name = processID(properties.at(1).toString());
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
QString getID(const QVariantList& properties, int index = 0) {
|
||||
return processID(properties.at(index).toString());
|
||||
}
|
||||
|
@ -508,7 +519,7 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const
|
|||
blendshapes.append(extracted);
|
||||
}
|
||||
} else if (object.name == "Model") {
|
||||
QString name = getName(object.properties);
|
||||
QString name = getModelName(object.properties);
|
||||
QString id = getID(object.properties);
|
||||
modelIDsToNames.insert(id, name);
|
||||
|
||||
|
@ -827,7 +838,7 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const
|
|||
} else if (object.name == "Material") {
|
||||
HFMMaterial material;
|
||||
MaterialParam materialParam;
|
||||
material.name = (object.properties.at(1).toString());
|
||||
material.name = getMaterialName(object.properties);
|
||||
foreach (const FBXNode& subobject, object.children) {
|
||||
bool properties = false;
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ static const QString JOINT_FIELD = "joint";
|
|||
static const QString BLENDSHAPE_FIELD = "bs";
|
||||
static const QString SCRIPT_FIELD = "script";
|
||||
static const QString JOINT_NAME_MAPPING_FIELD = "jointMap";
|
||||
static const QString MATERIAL_MAPPING_FIELD = "materialMap";
|
||||
|
||||
class FSTReader {
|
||||
public:
|
||||
|
|
|
@ -891,12 +891,14 @@ HFMModel::Pointer OBJSerializer::read(const hifi::ByteArray& data, const hifi::V
|
|||
if (!objMaterial.used) {
|
||||
continue;
|
||||
}
|
||||
hfmModel.materials[materialID] = HFMMaterial(objMaterial.diffuseColor,
|
||||
objMaterial.specularColor,
|
||||
objMaterial.emissiveColor,
|
||||
objMaterial.shininess,
|
||||
objMaterial.opacity);
|
||||
HFMMaterial& hfmMaterial = hfmModel.materials[materialID];
|
||||
|
||||
HFMMaterial& hfmMaterial = hfmModel.materials[materialID] = HFMMaterial(objMaterial.diffuseColor,
|
||||
objMaterial.specularColor,
|
||||
objMaterial.emissiveColor,
|
||||
objMaterial.shininess,
|
||||
objMaterial.opacity);
|
||||
|
||||
hfmMaterial.name = materialID;
|
||||
hfmMaterial.materialID = materialID;
|
||||
hfmMaterial._material = std::make_shared<graphics::Material>();
|
||||
graphics::MaterialPointer modelMaterial = hfmMaterial._material;
|
||||
|
|
|
@ -559,8 +559,7 @@ void NetworkMaterial::setLightmapMap(const QUrl& url) {
|
|||
}
|
||||
|
||||
NetworkMaterial::NetworkMaterial(const HFMMaterial& material, const QUrl& textureBaseUrl) :
|
||||
graphics::Material(*material._material),
|
||||
_textures(MapChannel::NUM_MAP_CHANNELS)
|
||||
graphics::Material(*material._material)
|
||||
{
|
||||
_name = material.name.toStdString();
|
||||
if (!material.albedoTexture.filename.isEmpty()) {
|
||||
|
@ -709,7 +708,7 @@ void NetworkMaterial::setTextures(const QVariantMap& textureMap) {
|
|||
|
||||
bool NetworkMaterial::isMissingTexture() {
|
||||
for (auto& networkTexture : _textures) {
|
||||
auto& texture = networkTexture.texture;
|
||||
auto& texture = networkTexture.second.texture;
|
||||
if (!texture) {
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -36,15 +36,21 @@ public:
|
|||
bool isMissingTexture();
|
||||
void checkResetOpacityMap();
|
||||
|
||||
protected:
|
||||
friend class Geometry;
|
||||
|
||||
class Texture {
|
||||
public:
|
||||
QString name;
|
||||
NetworkTexturePointer texture;
|
||||
};
|
||||
using Textures = std::vector<Texture>;
|
||||
struct MapChannelHash {
|
||||
std::size_t operator()(MapChannel mapChannel) const {
|
||||
return static_cast<std::size_t>(mapChannel);
|
||||
}
|
||||
};
|
||||
using Textures = std::unordered_map<MapChannel, Texture, MapChannelHash>;
|
||||
Textures getTextures() { return _textures; }
|
||||
|
||||
protected:
|
||||
friend class Geometry;
|
||||
|
||||
Textures _textures;
|
||||
|
||||
|
|
|
@ -10,6 +10,62 @@
|
|||
|
||||
#include "ModelBakerLogging.h"
|
||||
|
||||
#include <QJsonArray>
|
||||
|
||||
void processMaterialMapping(MaterialMapping& materialMapping, const QJsonObject& materialMap, const hifi::URL& url) {
|
||||
auto mappingKeys = materialMap.keys();
|
||||
for (auto mapping : mappingKeys) {
|
||||
auto mappingJSON = materialMap[mapping];
|
||||
if (mappingJSON.isObject()) {
|
||||
auto mappingValue = mappingJSON.toObject();
|
||||
|
||||
// Old subsurface scattering mapping
|
||||
{
|
||||
auto scatteringIter = mappingValue.find("scattering");
|
||||
auto scatteringMapIter = mappingValue.find("scatteringMap");
|
||||
if (scatteringIter != mappingValue.end() || scatteringMapIter != mappingValue.end()) {
|
||||
std::shared_ptr<NetworkMaterial> material = std::make_shared<NetworkMaterial>();
|
||||
|
||||
if (scatteringIter != mappingValue.end()) {
|
||||
float scattering = (float)scatteringIter.value().toDouble();
|
||||
material->setScattering(scattering);
|
||||
}
|
||||
|
||||
if (scatteringMapIter != mappingValue.end()) {
|
||||
QString scatteringMap = scatteringMapIter.value().toString();
|
||||
material->setScatteringMap(scatteringMap);
|
||||
}
|
||||
|
||||
material->setDefaultFallthrough(true);
|
||||
|
||||
NetworkMaterialResourcePointer materialResource = NetworkMaterialResourcePointer(new NetworkMaterialResource(),
|
||||
[](NetworkMaterialResource* ptr) { ptr->deleteLater(); });
|
||||
materialResource->moveToThread(qApp->thread());
|
||||
materialResource->parsedMaterials.names.push_back("scattering");
|
||||
materialResource->parsedMaterials.networkMaterials["scattering"] = material;
|
||||
|
||||
materialMapping.push_back(std::pair<std::string, NetworkMaterialResourcePointer>("mat::" + mapping.toStdString(), materialResource));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Material JSON description
|
||||
{
|
||||
NetworkMaterialResourcePointer materialResource = NetworkMaterialResourcePointer(new NetworkMaterialResource(),
|
||||
[](NetworkMaterialResource* ptr) { ptr->deleteLater(); });
|
||||
materialResource->moveToThread(qApp->thread());
|
||||
materialResource->parsedMaterials = NetworkMaterialResource::parseJSONMaterials(QJsonDocument(mappingValue), url);
|
||||
materialMapping.push_back(std::pair<std::string, NetworkMaterialResourcePointer>(mapping.toStdString(), materialResource));
|
||||
}
|
||||
|
||||
} else if (mappingJSON.isString()) {
|
||||
auto mappingValue = mappingJSON.toString();
|
||||
materialMapping.push_back(std::pair<std::string, NetworkMaterialResourcePointer>(mapping.toStdString(),
|
||||
MaterialCache::instance().getMaterial(url.resolved(mappingValue))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ParseMaterialMappingTask::run(const baker::BakeContextPointer& context, const Input& input, Output& output) {
|
||||
const auto& mapping = input.get0();
|
||||
const auto& url = input.get1();
|
||||
|
@ -18,56 +74,18 @@ void ParseMaterialMappingTask::run(const baker::BakeContextPointer& context, con
|
|||
auto mappingIter = mapping.find("materialMap");
|
||||
if (mappingIter != mapping.end()) {
|
||||
QByteArray materialMapValue = mappingIter.value().toByteArray();
|
||||
QJsonObject materialMap = QJsonDocument::fromJson(materialMapValue).object();
|
||||
if (materialMap.isEmpty()) {
|
||||
QJsonDocument materialMapJSON = QJsonDocument::fromJson(materialMapValue);
|
||||
if (materialMapJSON.isEmpty()) {
|
||||
qCDebug(model_baker) << "Material Map found but did not produce valid JSON:" << materialMapValue;
|
||||
} else if (materialMapJSON.isObject()) {
|
||||
QJsonObject materialMap = materialMapJSON.object();
|
||||
processMaterialMapping(materialMapping, materialMap, url);
|
||||
} else {
|
||||
auto mappingKeys = materialMap.keys();
|
||||
for (auto mapping : mappingKeys) {
|
||||
auto mappingJSON = materialMap[mapping];
|
||||
if (mappingJSON.isObject()) {
|
||||
auto mappingValue = mappingJSON.toObject();
|
||||
|
||||
// Old subsurface scattering mapping
|
||||
{
|
||||
auto scatteringIter = mappingValue.find("scattering");
|
||||
auto scatteringMapIter = mappingValue.find("scatteringMap");
|
||||
if (scatteringIter != mappingValue.end() || scatteringMapIter != mappingValue.end()) {
|
||||
std::shared_ptr<NetworkMaterial> material = std::make_shared<NetworkMaterial>();
|
||||
|
||||
if (scatteringIter != mappingValue.end()) {
|
||||
float scattering = (float)scatteringIter.value().toDouble();
|
||||
material->setScattering(scattering);
|
||||
}
|
||||
|
||||
if (scatteringMapIter != mappingValue.end()) {
|
||||
QString scatteringMap = scatteringMapIter.value().toString();
|
||||
material->setScatteringMap(scatteringMap);
|
||||
}
|
||||
|
||||
material->setDefaultFallthrough(true);
|
||||
|
||||
NetworkMaterialResourcePointer materialResource = NetworkMaterialResourcePointer(new NetworkMaterialResource(), [](NetworkMaterialResource* ptr) { ptr->deleteLater(); });
|
||||
materialResource->moveToThread(qApp->thread());
|
||||
materialResource->parsedMaterials.names.push_back("scattering");
|
||||
materialResource->parsedMaterials.networkMaterials["scattering"] = material;
|
||||
|
||||
materialMapping.push_back(std::pair<std::string, NetworkMaterialResourcePointer>("mat::" + mapping.toStdString(), materialResource));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Material JSON description
|
||||
{
|
||||
NetworkMaterialResourcePointer materialResource = NetworkMaterialResourcePointer(new NetworkMaterialResource(), [](NetworkMaterialResource* ptr) { ptr->deleteLater(); });
|
||||
materialResource->moveToThread(qApp->thread());
|
||||
materialResource->parsedMaterials = NetworkMaterialResource::parseJSONMaterials(QJsonDocument(mappingValue), url);
|
||||
materialMapping.push_back(std::pair<std::string, NetworkMaterialResourcePointer>(mapping.toStdString(), materialResource));
|
||||
}
|
||||
|
||||
} else if (mappingJSON.isString()) {
|
||||
auto mappingValue = mappingJSON.toString();
|
||||
materialMapping.push_back(std::pair<std::string, NetworkMaterialResourcePointer>(mapping.toStdString(), MaterialCache::instance().getMaterial(url.resolved(mappingValue))));
|
||||
QJsonArray materialMapArray = materialMapJSON.array();
|
||||
for (auto materialMapIter : materialMapArray) {
|
||||
if (materialMapIter.isObject()) {
|
||||
QJsonObject materialMap = materialMapIter.toObject();
|
||||
processMaterialMapping(materialMapping, materialMap, url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -437,8 +437,8 @@ const QVariantMap Geometry::getTextures() const {
|
|||
QVariantMap textures;
|
||||
for (const auto& material : _materials) {
|
||||
for (const auto& texture : material->_textures) {
|
||||
if (texture.texture) {
|
||||
textures[texture.name] = texture.texture->getURL();
|
||||
if (texture.second.texture) {
|
||||
textures[texture.second.name] = texture.second.texture->getURL();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -467,7 +467,7 @@ void Geometry::setTextures(const QVariantMap& textureMap) {
|
|||
for (auto& material : _materials) {
|
||||
// Check if any material textures actually changed
|
||||
if (std::any_of(material->_textures.cbegin(), material->_textures.cend(),
|
||||
[&textureMap](const NetworkMaterial::Textures::value_type& it) { return it.texture && textureMap.contains(it.name); })) {
|
||||
[&textureMap](const NetworkMaterial::Textures::value_type& it) { return it.second.texture && textureMap.contains(it.second.name); })) {
|
||||
|
||||
// FIXME: The Model currently caches the materials (waste of space!)
|
||||
// so they must be copied in the Geometry copy-ctor
|
||||
|
|
|
@ -1546,18 +1546,40 @@ void Model::applyMaterialMapping() {
|
|||
continue;
|
||||
}
|
||||
|
||||
auto materialLoaded = [this, networkMaterialResource, shapeIDs, renderItemsKey, primitiveMode, useDualQuaternionSkinning]() {
|
||||
// This needs to be precomputed before the lambda, since the lambdas could be called out of order
|
||||
std::unordered_map<unsigned int, quint16> priorityMapPerResource;
|
||||
for (auto shapeID : shapeIDs) {
|
||||
priorityMapPerResource[shapeID] = ++_priorityMap[shapeID];
|
||||
}
|
||||
|
||||
auto materialLoaded = [this, networkMaterialResource, shapeIDs, priorityMapPerResource, renderItemsKey, primitiveMode, useDualQuaternionSkinning]() {
|
||||
if (networkMaterialResource->isFailed() || networkMaterialResource->parsedMaterials.names.size() == 0) {
|
||||
return;
|
||||
}
|
||||
render::Transaction transaction;
|
||||
auto networkMaterial = networkMaterialResource->parsedMaterials.networkMaterials[networkMaterialResource->parsedMaterials.names[0]];
|
||||
std::shared_ptr<NetworkMaterial> networkMaterial;
|
||||
{
|
||||
QString url = networkMaterialResource->getURL().toString();
|
||||
bool foundMaterialName = false;
|
||||
if (url.contains("?")) {
|
||||
auto split = url.split("?");
|
||||
std::string materialName = split.last().toStdString();
|
||||
auto networkMaterialIter = networkMaterialResource->parsedMaterials.networkMaterials.find(materialName);
|
||||
if (networkMaterialIter != networkMaterialResource->parsedMaterials.networkMaterials.end()) {
|
||||
networkMaterial = networkMaterialIter->second;
|
||||
foundMaterialName = true;
|
||||
}
|
||||
}
|
||||
if (!foundMaterialName) {
|
||||
networkMaterial = networkMaterialResource->parsedMaterials.networkMaterials[networkMaterialResource->parsedMaterials.names[0]];
|
||||
}
|
||||
}
|
||||
for (auto shapeID : shapeIDs) {
|
||||
if (shapeID < _modelMeshRenderItemIDs.size()) {
|
||||
auto itemID = _modelMeshRenderItemIDs[shapeID];
|
||||
auto meshIndex = _modelMeshRenderItemShapes[shapeID].meshIndex;
|
||||
bool invalidatePayloadShapeKey = shouldInvalidatePayloadShapeKey(meshIndex);
|
||||
graphics::MaterialLayer material = graphics::MaterialLayer(networkMaterial, ++_priorityMap[shapeID]);
|
||||
graphics::MaterialLayer material = graphics::MaterialLayer(networkMaterial, priorityMapPerResource.at(shapeID));
|
||||
_materialMapping[shapeID].push_back(material);
|
||||
transaction.updateItem<ModelMeshPartPayload>(itemID, [material, renderItemsKey,
|
||||
invalidatePayloadShapeKey, primitiveMode, useDualQuaternionSkinning](ModelMeshPartPayload& data) {
|
||||
|
|
|
@ -49,10 +49,7 @@ void BakerCLI::bakeFile(QUrl inputUrl, const QString& outputPath, const QString&
|
|||
if (type == MODEL_EXTENSION || type == FBX_EXTENSION) {
|
||||
QUrl bakeableModelURL = getBakeableModelURL(inputUrl);
|
||||
if (!bakeableModelURL.isEmpty()) {
|
||||
auto getWorkerThreadCallback = []() -> QThread* {
|
||||
return Oven::instance().getNextWorkerThread();
|
||||
};
|
||||
_baker = getModelBaker(bakeableModelURL, getWorkerThreadCallback, outputPath);
|
||||
_baker = getModelBaker(bakeableModelURL, outputPath);
|
||||
if (_baker) {
|
||||
_baker->moveToThread(Oven::instance().getNextWorkerThread());
|
||||
}
|
||||
|
@ -61,7 +58,7 @@ void BakerCLI::bakeFile(QUrl inputUrl, const QString& outputPath, const QString&
|
|||
_baker = std::unique_ptr<Baker> { new JSBaker(inputUrl, outputPath) };
|
||||
_baker->moveToThread(Oven::instance().getNextWorkerThread());
|
||||
} else if (type == MATERIAL_EXTENSION) {
|
||||
_baker = std::unique_ptr<Baker> { new MaterialBaker(inputUrl.toDisplayString(), true, outputPath, QUrl(outputPath)) };
|
||||
_baker = std::unique_ptr<Baker> { new MaterialBaker(inputUrl.toDisplayString(), true, 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
|
||||
|
|
|
@ -152,10 +152,7 @@ void DomainBaker::addModelBaker(const QString& property, const QString& url, con
|
|||
// setup a ModelBaker for this URL, as long as we don't already have one
|
||||
bool haveBaker = _modelBakers.contains(bakeableModelURL);
|
||||
if (!haveBaker) {
|
||||
auto getWorkerThreadCallback = []() -> QThread* {
|
||||
return Oven::instance().getNextWorkerThread();
|
||||
};
|
||||
QSharedPointer<ModelBaker> baker = QSharedPointer<ModelBaker>(getModelBaker(bakeableModelURL, getWorkerThreadCallback, _contentOutputPath).release(), &Baker::deleteLater);
|
||||
QSharedPointer<ModelBaker> baker = QSharedPointer<ModelBaker>(getModelBaker(bakeableModelURL, _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.
|
||||
|
@ -275,7 +272,7 @@ void DomainBaker::addMaterialBaker(const QString& property, const QString& data,
|
|||
|
||||
// setup a baker for this material
|
||||
QSharedPointer<MaterialBaker> materialBaker {
|
||||
new MaterialBaker(data, isURL, _contentOutputPath, _destinationPath),
|
||||
new MaterialBaker(data, isURL, _contentOutputPath),
|
||||
&MaterialBaker::deleteLater
|
||||
};
|
||||
|
||||
|
|
|
@ -180,11 +180,7 @@ void ModelBakeWidget::bakeButtonClicked() {
|
|||
|
||||
QUrl bakeableModelURL = getBakeableModelURL(modelToBakeURL);
|
||||
if (!bakeableModelURL.isEmpty()) {
|
||||
auto getWorkerThreadCallback = []() -> QThread* {
|
||||
return Oven::instance().getNextWorkerThread();
|
||||
};
|
||||
|
||||
std::unique_ptr<Baker> baker = getModelBaker(bakeableModelURL, getWorkerThreadCallback, outputDirectory.path());
|
||||
std::unique_ptr<Baker> baker = getModelBaker(bakeableModelURL, outputDirectory.path());
|
||||
if (baker) {
|
||||
// everything seems to be in place, kick off a bake for this model now
|
||||
|
||||
|
|
Loading…
Reference in a new issue