Added ability to read FBX textures embedded in FBX files.

This commit is contained in:
Andrzej Kapolka 2014-04-17 15:42:23 -07:00
parent 6251b6b819
commit 197127fbde
6 changed files with 112 additions and 47 deletions

View file

@ -306,14 +306,14 @@ bool ModelUploader::addTextures(const QString& texdir, const QString fbxFile) {
foreach (FBXMesh mesh, geometry.meshes) {
foreach (FBXMeshPart part, mesh.parts) {
if (!part.diffuseFilename.isEmpty()) {
if (!addPart(texdir + "/" + part.diffuseFilename,
if (!part.diffuseTexture.filename.isEmpty()) {
if (!addPart(texdir + "/" + part.diffuseTexture.filename,
QString("texture%1").arg(++_texturesCount))) {
return false;
}
}
if (!part.normalFilename.isEmpty()) {
if (!addPart(texdir + "/" + part.normalFilename,
if (!part.normalTexture.filename.isEmpty()) {
if (!addPart(texdir + "/" + part.normalTexture.filename,
QString("texture%1").arg(++_texturesCount))) {
return false;
}

View file

@ -543,14 +543,14 @@ void NetworkGeometry::setGeometry(const FBXGeometry& geometry) {
int totalIndices = 0;
foreach (const FBXMeshPart& part, mesh.parts) {
NetworkMeshPart networkPart;
if (!part.diffuseFilename.isEmpty()) {
if (!part.diffuseTexture.filename.isEmpty()) {
networkPart.diffuseTexture = Application::getInstance()->getTextureCache()->getTexture(
_textureBase.resolved(QUrl(part.diffuseFilename)), false, mesh.isEye);
_textureBase.resolved(QUrl(part.diffuseTexture.filename)), false, mesh.isEye, part.diffuseTexture.content);
networkPart.diffuseTexture->setLoadPriorities(_loadPriorities);
}
if (!part.normalFilename.isEmpty()) {
if (!part.normalTexture.filename.isEmpty()) {
networkPart.normalTexture = Application::getInstance()->getTextureCache()->getTexture(
_textureBase.resolved(QUrl(part.normalFilename)), true);
_textureBase.resolved(QUrl(part.normalTexture.filename)), true, false, part.normalTexture.content);
networkPart.normalTexture->setLoadPriorities(_loadPriorities);
}
networkMesh.parts.append(networkPart);

View file

@ -105,13 +105,22 @@ GLuint TextureCache::getBlueTextureID() {
return _blueTextureID;
}
QSharedPointer<NetworkTexture> TextureCache::getTexture(const QUrl& url, bool normalMap, bool dilatable) {
/// Extra data for creating textures.
class TextureExtra {
public:
bool normalMap;
const QByteArray& content;
};
QSharedPointer<NetworkTexture> TextureCache::getTexture(const QUrl& url, bool normalMap,
bool dilatable, const QByteArray& content) {
if (!dilatable) {
return ResourceCache::getResource(url, QUrl(), false, &normalMap).staticCast<NetworkTexture>();
TextureExtra extra = { normalMap, content };
return ResourceCache::getResource(url, QUrl(), false, &extra).staticCast<NetworkTexture>();
}
QSharedPointer<NetworkTexture> texture = _dilatableNetworkTextures.value(url);
if (texture.isNull()) {
texture = QSharedPointer<NetworkTexture>(new DilatableNetworkTexture(url), &Resource::allReferencesCleared);
texture = QSharedPointer<NetworkTexture>(new DilatableNetworkTexture(url, content), &Resource::allReferencesCleared);
texture->setSelf(texture);
texture->setCache(this);
_dilatableNetworkTextures.insert(url, texture);
@ -215,7 +224,9 @@ bool TextureCache::eventFilter(QObject* watched, QEvent* event) {
QSharedPointer<Resource> TextureCache::createResource(const QUrl& url,
const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra) {
return QSharedPointer<Resource>(new NetworkTexture(url, *(const bool*)extra), &Resource::allReferencesCleared);
const TextureExtra* textureExtra = static_cast<const TextureExtra*>(extra);
return QSharedPointer<Resource>(new NetworkTexture(url, textureExtra->normalMap, textureExtra->content),
&Resource::allReferencesCleared);
}
QOpenGLFramebufferObject* TextureCache::createFramebufferObject() {
@ -238,8 +249,8 @@ Texture::~Texture() {
glDeleteTextures(1, &_id);
}
NetworkTexture::NetworkTexture(const QUrl& url, bool normalMap) :
Resource(url),
NetworkTexture::NetworkTexture(const QUrl& url, bool normalMap, const QByteArray& content) :
Resource(url, !content.isEmpty()),
_translucent(false) {
if (!url.isValid()) {
@ -250,12 +261,19 @@ NetworkTexture::NetworkTexture(const QUrl& url, bool normalMap) :
glBindTexture(GL_TEXTURE_2D, getID());
loadSingleColorTexture(normalMap ? OPAQUE_BLUE : OPAQUE_WHITE);
glBindTexture(GL_TEXTURE_2D, 0);
// if we have content, load it after we have our self pointer
if (!content.isEmpty()) {
_startedLoading = true;
QMetaObject::invokeMethod(this, "loadContent", Qt::QueuedConnection, Q_ARG(const QByteArray&, content));
}
}
class ImageReader : public QRunnable {
public:
ImageReader(const QWeakPointer<Resource>& texture, QNetworkReply* reply);
ImageReader(const QWeakPointer<Resource>& texture, QNetworkReply* reply, const QUrl& url = QUrl(),
const QByteArray& content = QByteArray());
virtual void run();
@ -263,27 +281,37 @@ private:
QWeakPointer<Resource> _texture;
QNetworkReply* _reply;
QUrl _url;
QByteArray _content;
};
ImageReader::ImageReader(const QWeakPointer<Resource>& texture, QNetworkReply* reply) :
ImageReader::ImageReader(const QWeakPointer<Resource>& texture, QNetworkReply* reply,
const QUrl& url, const QByteArray& content) :
_texture(texture),
_reply(reply) {
_reply(reply),
_url(url),
_content(content) {
}
void ImageReader::run() {
QSharedPointer<Resource> texture = _texture.toStrongRef();
if (texture.isNull()) {
_reply->deleteLater();
if (_reply) {
_reply->deleteLater();
}
return;
}
QUrl url = _reply->url();
QImage image = QImage::fromData(_reply->readAll());
_reply->deleteLater();
if (_reply) {
_url = _reply->url();
_content = _reply->readAll();
_reply->deleteLater();
}
QImage image = QImage::fromData(_content);
// enforce a fixed maximum
const int MAXIMUM_SIZE = 1024;
if (image.width() > MAXIMUM_SIZE || image.height() > MAXIMUM_SIZE) {
qDebug() << "Image greater than maximum size:" << url << image.width() << image.height();
qDebug() << "Image greater than maximum size:" << _url << image.width() << image.height();
image = image.scaled(MAXIMUM_SIZE, MAXIMUM_SIZE, Qt::KeepAspectRatio);
}
@ -315,7 +343,7 @@ void ImageReader::run() {
}
int imageArea = image.width() * image.height();
if (opaquePixels == imageArea) {
qDebug() << "Image with alpha channel is completely opaque:" << url;
qDebug() << "Image with alpha channel is completely opaque:" << _url;
image = image.convertToFormat(QImage::Format_RGB888);
}
QMetaObject::invokeMethod(texture.data(), "setImage", Q_ARG(const QImage&, image),
@ -327,6 +355,10 @@ void NetworkTexture::downloadFinished(QNetworkReply* reply) {
QThreadPool::globalInstance()->start(new ImageReader(_self, reply));
}
void NetworkTexture::loadContent(const QByteArray& content) {
QThreadPool::globalInstance()->start(new ImageReader(_self, NULL, _url, content));
}
void NetworkTexture::setImage(const QImage& image, bool translucent) {
_translucent = translucent;
@ -348,8 +380,8 @@ void NetworkTexture::imageLoaded(const QImage& image) {
// nothing by default
}
DilatableNetworkTexture::DilatableNetworkTexture(const QUrl& url) :
NetworkTexture(url, false),
DilatableNetworkTexture::DilatableNetworkTexture(const QUrl& url, const QByteArray& content) :
NetworkTexture(url, false, content),
_innerRadius(0),
_outerRadius(0)
{

View file

@ -44,7 +44,8 @@ public:
GLuint getBlueTextureID();
/// Loads a texture from the specified URL.
QSharedPointer<NetworkTexture> getTexture(const QUrl& url, bool normalMap = false, bool dilatable = false);
QSharedPointer<NetworkTexture> getTexture(const QUrl& url, bool normalMap = false, bool dilatable = false,
const QByteArray& content = QByteArray());
/// Returns a pointer to the primary framebuffer object. This render target includes a depth component, and is
/// used for scene rendering.
@ -115,7 +116,7 @@ class NetworkTexture : public Resource, public Texture {
public:
NetworkTexture(const QUrl& url, bool normalMap);
NetworkTexture(const QUrl& url, bool normalMap, const QByteArray& content);
/// Checks whether it "looks like" this texture is translucent
/// (majority of pixels neither fully opaque or fully transparent).
@ -124,10 +125,12 @@ public:
protected:
virtual void downloadFinished(QNetworkReply* reply);
virtual void imageLoaded(const QImage& image);
Q_INVOKABLE void loadContent(const QByteArray& content);
Q_INVOKABLE void setImage(const QImage& image, bool translucent);
virtual void imageLoaded(const QImage& image);
private:
bool _translucent;
@ -139,7 +142,7 @@ class DilatableNetworkTexture : public NetworkTexture {
public:
DilatableNetworkTexture(const QUrl& url);
DilatableNetworkTexture(const QUrl& url, const QByteArray& content);
/// Returns a pointer to a texture with the requested amount of dilation.
QSharedPointer<Texture> getDilatedTexture(float dilation);

View file

@ -935,6 +935,14 @@ public:
QVector<float> values;
};
FBXTexture getTexture(const QString& textureID, const QHash<QString, QByteArray>& textureFilenames,
const QHash<QByteArray, QByteArray>& textureContent) {
FBXTexture texture;
texture.filename = textureFilenames.value(textureID);
texture.content = textureContent.value(texture.filename);
return texture;
}
FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) {
QHash<QString, ExtractedMesh> meshes;
QVector<ExtractedBlendshape> blendshapes;
@ -944,6 +952,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
QHash<QString, Cluster> clusters;
QHash<QString, AnimationCurve> animationCurves;
QHash<QString, QByteArray> textureFilenames;
QHash<QByteArray, QByteArray> textureContent;
QHash<QString, Material> materials;
QHash<QString, QString> diffuseTextures;
QHash<QString, QString> bumpTextures;
@ -952,8 +961,6 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
QHash<QString, QString> yComponents;
QHash<QString, QString> zComponents;
printNode(node, 0);
QVariantHash joints = mapping.value("joint").toHash();
QString jointEyeLeftName = processID(getString(joints.value("jointEyeLeft", "jointEyeLeft")));
QString jointEyeRightName = processID(getString(joints.value("jointEyeRight", "jointEyeRight")));
@ -1182,6 +1189,21 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
textureFilenames.insert(getID(object.properties), filename);
}
}
} else if (object.name == "Video") {
QByteArray filename;
QByteArray content;
foreach (const FBXNode& subobject, object.children) {
if (subobject.name == "RelativeFilename") {
filename = subobject.properties.at(0).toByteArray();
filename = filename.mid(qMax(filename.lastIndexOf('\\'), filename.lastIndexOf('/')) + 1);
} else if (subobject.name == "Content" && !subobject.properties.isEmpty()) {
content = subobject.properties.at(0).toByteArray();
}
}
if (!content.isEmpty()) {
textureContent.insert(filename, content);
}
} else if (object.name == "Material") {
Material material = { glm::vec3(1.0f, 1.0f, 1.0f), glm::vec3(1.0f, 1.0f, 1.0f), 96.0f };
foreach (const FBXNode& subobject, object.children) {
@ -1263,7 +1285,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
if (type.contains("diffuse")) {
diffuseTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1));
} else if (type.contains("bump")) {
} else if (type.contains("bump") || type.contains("normal")) {
bumpTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1));
} else if (type == "lcl rotation") {
@ -1463,23 +1485,23 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
if (materials.contains(childID)) {
Material material = materials.value(childID);
QByteArray diffuseFilename;
FBXTexture diffuseTexture;
QString diffuseTextureID = diffuseTextures.value(childID);
if (!diffuseTextureID.isNull()) {
diffuseFilename = textureFilenames.value(diffuseTextureID);
diffuseTexture = getTexture(diffuseTextureID, textureFilenames, textureContent);
// FBX files generated by 3DSMax have an intermediate texture parent, apparently
foreach (const QString& childTextureID, childMap.values(diffuseTextureID)) {
if (textureFilenames.contains(childTextureID)) {
diffuseFilename = textureFilenames.value(childTextureID);
diffuseTexture = getTexture(diffuseTextureID, textureFilenames, textureContent);
}
}
}
QByteArray normalFilename;
FBXTexture normalTexture;
QString bumpTextureID = bumpTextures.value(childID);
if (!bumpTextureID.isNull()) {
normalFilename = textureFilenames.value(bumpTextureID);
normalTexture = getTexture(bumpTextureID, textureFilenames, textureContent);
generateTangents = true;
}
@ -1489,21 +1511,21 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
part.diffuseColor = material.diffuse;
part.specularColor = material.specular;
part.shininess = material.shininess;
if (!diffuseFilename.isNull()) {
part.diffuseFilename = diffuseFilename;
if (!diffuseTexture.filename.isNull()) {
part.diffuseTexture = diffuseTexture;
}
if (!normalFilename.isNull()) {
part.normalFilename = normalFilename;
if (!normalTexture.filename.isNull()) {
part.normalTexture = normalTexture;
}
}
}
materialIndex++;
} else if (textureFilenames.contains(childID)) {
QByteArray filename = textureFilenames.value(childID);
FBXTexture texture = getTexture(childID, textureFilenames, textureContent);
for (int j = 0; j < extracted.partMaterialTextures.size(); j++) {
if (extracted.partMaterialTextures.at(j).second == textureIndex) {
extracted.mesh.parts[j].diffuseFilename = filename;
extracted.mesh.parts[j].diffuseTexture = texture;
}
}
textureIndex++;

View file

@ -103,6 +103,14 @@ public:
glm::mat4 inverseBindMatrix;
};
/// A texture map in an FBX document.
class FBXTexture {
public:
QByteArray filename;
QByteArray content;
};
/// A single part of a mesh (with the same material).
class FBXMeshPart {
public:
@ -114,8 +122,8 @@ public:
glm::vec3 specularColor;
float shininess;
QByteArray diffuseFilename;
QByteArray normalFilename;
FBXTexture diffuseTexture;
FBXTexture normalTexture;
};
/// A single mesh (with optional blendshapes) extracted from an FBX document.