From 23806ed67d73bf870b0a98a336581679bd94e165 Mon Sep 17 00:00:00 2001
From: sabrina-shanman <sabrina@highfidelity.io>
Date: Fri, 8 Feb 2019 14:33:55 -0800
Subject: [PATCH] Add support for GLTF roughness/metallic

---
 libraries/fbx/src/GLTFSerializer.cpp          |  2 +
 libraries/hfm/src/hfm/HFM.h                   |  2 +
 libraries/image/src/image/Image.cpp           | 46 ++++++++++++++++++-
 libraries/image/src/image/Image.h             |  3 +-
 .../src/model-networking/ModelCache.cpp       |  2 +-
 .../src/model-networking/TextureCache.cpp     | 36 ++++++++++-----
 .../src/model-networking/TextureCache.h       |  6 ++-
 .../shared/src/shared/ColorChannelMapping.h   | 37 +++++++++++++++
 8 files changed, 118 insertions(+), 16 deletions(-)
 create mode 100644 libraries/shared/src/shared/ColorChannelMapping.h

diff --git a/libraries/fbx/src/GLTFSerializer.cpp b/libraries/fbx/src/GLTFSerializer.cpp
index 96c236f703..f7493ad88b 100644
--- a/libraries/fbx/src/GLTFSerializer.cpp
+++ b/libraries/fbx/src/GLTFSerializer.cpp
@@ -1057,8 +1057,10 @@ void GLTFSerializer::setHFMMaterial(HFMMaterial& fbxmat, const GLTFMaterial& mat
         }
         if (material.pbrMetallicRoughness.defined["metallicRoughnessTexture"]) {
             fbxmat.roughnessTexture = getHFMTexture(_file.textures[material.pbrMetallicRoughness.metallicRoughnessTexture]);
+            fbxmat.roughnessTexture.channelMapping = ColorChannelMapping::GREEN;
             fbxmat.useRoughnessMap = true;
             fbxmat.metallicTexture = getHFMTexture(_file.textures[material.pbrMetallicRoughness.metallicRoughnessTexture]);
+            fbxmat.metallicTexture.channelMapping = ColorChannelMapping::BLUE;
             fbxmat.useMetallicMap = true;
         }
         if (material.pbrMetallicRoughness.defined["roughnessFactor"]) {
diff --git a/libraries/hfm/src/hfm/HFM.h b/libraries/hfm/src/hfm/HFM.h
index 1bd87332a1..f733e4ef69 100644
--- a/libraries/hfm/src/hfm/HFM.h
+++ b/libraries/hfm/src/hfm/HFM.h
@@ -24,6 +24,7 @@
 
 #include <graphics/Geometry.h>
 #include <graphics/Material.h>
+#include <shared/ColorChannelMapping.h>
 
 #if defined(Q_OS_ANDROID)
 #define HFM_PACK_NORMALS 0
@@ -125,6 +126,7 @@ public:
     QString name;
     QByteArray filename;
     QByteArray content;
+    ColorChannelMapping channelMapping { ColorChannelMapping::NONE };
 
     Transform transform;
     int maxNumPixels { MAX_NUM_PIXELS_FOR_FBX_TEXTURE };
diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp
index ac2813667f..0f7acabf85 100644
--- a/libraries/image/src/image/Image.cpp
+++ b/libraries/image/src/image/Image.cpp
@@ -16,6 +16,7 @@
 #include <QtCore/QtGlobal>
 #include <QUrl>
 #include <QImage>
+#include <QRgb>
 #include <QBuffer>
 #include <QImageReader>
 
@@ -221,7 +222,45 @@ QImage processRawImageData(QIODevice& content, const std::string& filename) {
     return QImage();
 }
 
-gpu::TexturePointer processImage(std::shared_ptr<QIODevice> content, const std::string& filename,
+void mapToRedChannel(QImage& image, ColorChannelMapping sourceChannel) {
+    // Change format of image so we know exactly how to process it
+    if (image.format() != QImage::Format_ARGB32) {
+        image = image.convertToFormat(QImage::Format_ARGB32);
+    }
+
+    for (int i = 0; i < image.height(); i++) {
+        QRgb* pixel = reinterpret_cast<QRgb*>(image.scanLine(i));
+        // Past end pointer
+        QRgb* lineEnd = pixel + image.width();
+
+        // Transfer channel data from source to target
+        for (; pixel < lineEnd; pixel++) {
+            int colorValue;
+            switch (sourceChannel) {
+            case ColorChannelMapping::RED:
+                colorValue = qRed(*pixel);
+                break;
+            case ColorChannelMapping::GREEN:
+                colorValue = qGreen(*pixel);
+                break;
+            case ColorChannelMapping::BLUE:
+                colorValue = qBlue(*pixel);
+                break;
+            case ColorChannelMapping::ALPHA:
+                colorValue = qAlpha(*pixel);
+                break;
+            default:
+                colorValue = qRed(*pixel);
+                break;
+            }
+
+            // Dump the color in the red channel, ignore the rest
+            *pixel = qRgba(colorValue, 0, 0, 0);
+        }
+    }
+}
+
+gpu::TexturePointer processImage(std::shared_ptr<QIODevice> content, const std::string& filename, ColorChannelMapping channelMapping,
                                  int maxNumPixels, TextureUsage::Type textureType,
                                  bool compress, BackendTarget target, const std::atomic<bool>& abortProcessing) {
 
@@ -252,6 +291,11 @@ gpu::TexturePointer processImage(std::shared_ptr<QIODevice> content, const std::
             QSize(originalWidth, originalHeight) << " to " <<
             QSize(imageWidth, imageHeight) << ")";
     }
+
+    // Re-map to image with single red channel texture if requested
+    if (channelMapping != ColorChannelMapping::NONE) {
+        mapToRedChannel(image, channelMapping);
+    }
     
     auto loader = TextureUsage::getTextureLoaderForType(textureType);
     auto texture = loader(std::move(image), filename, compress, target, abortProcessing);
diff --git a/libraries/image/src/image/Image.h b/libraries/image/src/image/Image.h
index ae72a183b3..cc68ef6718 100644
--- a/libraries/image/src/image/Image.h
+++ b/libraries/image/src/image/Image.h
@@ -15,6 +15,7 @@
 #include <QVariant>
 
 #include <gpu/Texture.h>
+#include <shared/ColorChannelMapping.h>
 
 class QByteArray;
 class QImage;
@@ -81,7 +82,7 @@ gpu::TexturePointer processCubeTextureColorFromImage(QImage&& srcImage, const st
 
 const QStringList getSupportedFormats();
 
-gpu::TexturePointer processImage(std::shared_ptr<QIODevice> content, const std::string& url,
+gpu::TexturePointer processImage(std::shared_ptr<QIODevice> content, const std::string& url, ColorChannelMapping channelMapping,
                                  int maxNumPixels, TextureUsage::Type textureType,
                                  bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing = false);
 
diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp
index 1535f5cfad..0da683789e 100644
--- a/libraries/model-networking/src/model-networking/ModelCache.cpp
+++ b/libraries/model-networking/src/model-networking/ModelCache.cpp
@@ -599,7 +599,7 @@ graphics::TextureMapPointer NetworkMaterial::fetchTextureMap(const QUrl& baseUrl
     }
 
     const auto url = getTextureUrl(baseUrl, hfmTexture);
-    const auto texture = DependencyManager::get<TextureCache>()->getTexture(url, type, hfmTexture.content, hfmTexture.maxNumPixels);
+    const auto texture = DependencyManager::get<TextureCache>()->getTexture(url, type, hfmTexture.content, hfmTexture.maxNumPixels, hfmTexture.channelMapping);
     _textures[channel] = Texture { hfmTexture.name, texture };
 
     auto map = std::make_shared<graphics::TextureMap>();
diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp
index 910de258f9..fe68c4ca6e 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.cpp
+++ b/libraries/model-networking/src/model-networking/TextureCache.cpp
@@ -192,6 +192,7 @@ public:
     image::TextureUsage::Type type;
     const QByteArray& content;
     int maxNumPixels;
+    ColorChannelMapping channelMapping;
 };
 
 namespace std {
@@ -206,19 +207,19 @@ namespace std {
     struct hash<TextureExtra> {
         size_t operator()(const TextureExtra& a) const {
             size_t result = 0;
-            hash_combine(result, (int)a.type, a.content, a.maxNumPixels);
+            hash_combine(result, (int)a.type, a.content, a.maxNumPixels, a.channelMapping);
             return result;
         }
     };
 }
 
-ScriptableResource* TextureCache::prefetch(const QUrl& url, int type, int maxNumPixels) {
+ScriptableResource* TextureCache::prefetch(const QUrl& url, int type, int maxNumPixels, ColorChannelMapping channelMapping) {
     auto byteArray = QByteArray();
-    TextureExtra extra = { (image::TextureUsage::Type)type, byteArray, maxNumPixels };
+    TextureExtra extra = { (image::TextureUsage::Type)type, byteArray, maxNumPixels, channelMapping };
     return ResourceCache::prefetch(url, &extra, std::hash<TextureExtra>()(extra));
 }
 
-NetworkTexturePointer TextureCache::getTexture(const QUrl& url, image::TextureUsage::Type type, const QByteArray& content, int maxNumPixels) {
+NetworkTexturePointer TextureCache::getTexture(const QUrl& url, image::TextureUsage::Type type, const QByteArray& content, int maxNumPixels, ColorChannelMapping channelMapping) {
     if (url.scheme() == RESOURCE_SCHEME) {
         return getResourceTexture(url);
     }
@@ -228,7 +229,7 @@ NetworkTexturePointer TextureCache::getTexture(const QUrl& url, image::TextureUs
         query.addQueryItem("skybox", "");
         modifiedUrl.setQuery(query.toString());
     }
-    TextureExtra extra = { type, content, maxNumPixels };
+    TextureExtra extra = { type, content, maxNumPixels, channelMapping };
     return ResourceCache::getResource(modifiedUrl, QUrl(), &extra, std::hash<TextureExtra>()(extra)).staticCast<NetworkTexture>();
 }
 
@@ -346,6 +347,7 @@ NetworkTexture::NetworkTexture(const QUrl& url) :
 NetworkTexture::NetworkTexture(const NetworkTexture& other) :
     Resource(other),
     _type(other._type),
+    _channelMapping(other._channelMapping),
     _currentlyLoadingResourceType(other._currentlyLoadingResourceType),
     _originalWidth(other._originalWidth),
     _originalHeight(other._originalHeight),
@@ -353,6 +355,11 @@ NetworkTexture::NetworkTexture(const NetworkTexture& other) :
     _height(other._height),
     _maxNumPixels(other._maxNumPixels)
 {
+    if (_width == 0 || _height == 0 ||
+        other._currentlyLoadingResourceType == ResourceType::META ||
+        (other._currentlyLoadingResourceType == ResourceType::KTX && other._ktxResourceState != KTXResourceState::WAITING_FOR_MIP_REQUEST)) {
+        _startedLoading = false;
+    }
 }
 
 static bool isLocalUrl(const QUrl& url) {
@@ -364,6 +371,7 @@ void NetworkTexture::setExtra(void* extra) {
     const TextureExtra* textureExtra = static_cast<const TextureExtra*>(extra);
     _type = textureExtra ? textureExtra->type : image::TextureUsage::DEFAULT_TEXTURE;
     _maxNumPixels = textureExtra ? textureExtra->maxNumPixels : ABSOLUTE_MAX_TEXTURE_NUM_PIXELS;
+    _channelMapping = textureExtra ? textureExtra->channelMapping : ColorChannelMapping::NONE;
 
     _textureSource = std::make_shared<gpu::TextureSource>(_url, (int)_type);
     _lowestRequestedMipLevel = 0;
@@ -425,7 +433,8 @@ gpu::TexturePointer NetworkTexture::getFallbackTexture() const {
 class ImageReader : public QRunnable {
 public:
     ImageReader(const QWeakPointer<Resource>& resource, const QUrl& url,
-                const QByteArray& data, int maxNumPixels);
+                const QByteArray& data, size_t extraHash, int maxNumPixels,
+                ColorChannelMapping channelMapping);
     void run() override final;
     void read();
 
@@ -435,7 +444,9 @@ private:
     QWeakPointer<Resource> _resource;
     QUrl _url;
     QByteArray _content;
+    size_t _extraHash;
     int _maxNumPixels;
+    ColorChannelMapping _channelMapping;
 };
 
 NetworkTexture::~NetworkTexture() {
@@ -1068,7 +1079,7 @@ void NetworkTexture::loadTextureContent(const QByteArray& content) {
         return;
     }
 
-    QThreadPool::globalInstance()->start(new ImageReader(_self, _url, content, _maxNumPixels));
+    QThreadPool::globalInstance()->start(new ImageReader(_self, _url, content, _extraHash, _maxNumPixels, _channelMapping));
 }
 
 void NetworkTexture::refresh() {
@@ -1093,11 +1104,13 @@ void NetworkTexture::refresh() {
     Resource::refresh();
 }
 
-ImageReader::ImageReader(const QWeakPointer<Resource>& resource, const QUrl& url, const QByteArray& data, int maxNumPixels) :
+ImageReader::ImageReader(const QWeakPointer<Resource>& resource, const QUrl& url, const QByteArray& data, size_t extraHash, int maxNumPixels, const ColorChannelMapping channelMapping) :
     _resource(resource),
     _url(url),
     _content(data),
-    _maxNumPixels(maxNumPixels)
+    _extraHash(extraHash),
+    _maxNumPixels(maxNumPixels),
+    _channelMapping(channelMapping)
 {
     DependencyManager::get<StatTracker>()->incrementStat("PendingProcessing");
     listSupportedImageFormats();
@@ -1152,11 +1165,12 @@ void ImageReader::read() {
     }
     auto networkTexture = resource.staticCast<NetworkTexture>();
 
-    // Hash the source image to for KTX caching
+    // Hash the source image and extra to for KTX caching
     std::string hash;
     {
         QCryptographicHash hasher(QCryptographicHash::Md5);
         hasher.addData(_content);
+        hasher.addData(std::to_string(_extraHash).c_str());
         hash = hasher.result().toHex().toStdString();
     }
 
@@ -1204,7 +1218,7 @@ void ImageReader::read() {
         constexpr bool shouldCompress = false;
 #endif
         auto target = getBackendTarget();
-        texture = image::processImage(std::move(buffer), _url.toString().toStdString(), _maxNumPixels, networkTexture->getTextureType(), shouldCompress, target);
+        texture = image::processImage(std::move(buffer), _url.toString().toStdString(), _channelMapping, _maxNumPixels, networkTexture->getTextureType(), shouldCompress, target);
 
         if (!texture) {
             QMetaObject::invokeMethod(resource.data(), "setImage",
diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h
index d744d060b6..44f7a0034c 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.h
+++ b/libraries/model-networking/src/model-networking/TextureCache.h
@@ -96,6 +96,7 @@ private:
     friend class ImageReader;
 
     image::TextureUsage::Type _type;
+    ColorChannelMapping _channelMapping;
 
     enum class ResourceType {
         META,
@@ -178,7 +179,8 @@ public:
 
     /// Loads a texture from the specified URL.
     NetworkTexturePointer getTexture(const QUrl& url, image::TextureUsage::Type type = image::TextureUsage::DEFAULT_TEXTURE,
-        const QByteArray& content = QByteArray(), int maxNumPixels = ABSOLUTE_MAX_TEXTURE_NUM_PIXELS);
+        const QByteArray& content = QByteArray(), int maxNumPixels = ABSOLUTE_MAX_TEXTURE_NUM_PIXELS,
+        ColorChannelMapping channelMapping = ColorChannelMapping::NONE);
 
     gpu::TexturePointer getTextureByHash(const std::string& hash);
     gpu::TexturePointer cacheTextureByHash(const std::string& hash, const gpu::TexturePointer& texture);
@@ -201,7 +203,7 @@ signals:
 protected:
     
     // Overload ResourceCache::prefetch to allow specifying texture type for loads
-    Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url, int type, int maxNumPixels = ABSOLUTE_MAX_TEXTURE_NUM_PIXELS);
+    Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url, int type, int maxNumPixels = ABSOLUTE_MAX_TEXTURE_NUM_PIXELS, ColorChannelMapping channelMapping = ColorChannelMapping::NONE);
 
     virtual QSharedPointer<Resource> createResource(const QUrl& url) override;
     QSharedPointer<Resource> createResourceCopy(const QSharedPointer<Resource>& resource) override;
diff --git a/libraries/shared/src/shared/ColorChannelMapping.h b/libraries/shared/src/shared/ColorChannelMapping.h
new file mode 100644
index 0000000000..c400ec1414
--- /dev/null
+++ b/libraries/shared/src/shared/ColorChannelMapping.h
@@ -0,0 +1,37 @@
+//
+//  ColorChannelMapping.h
+//  libraries/shared/src
+//
+//  Created by Sabrina Shanman on 2019/02/05.
+//  Copyright 2019 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
+//
+
+#ifndef hifi_ColorChannelMapping_h
+#define hifi_ColorChannelMapping_h
+
+#include "../RegisteredMetaTypes.h"
+
+enum class ColorChannelMapping {
+    NONE,
+    RED,
+    GREEN,
+    BLUE,
+    ALPHA,
+    COUNT
+};
+
+namespace std {
+    template <>
+    struct hash<ColorChannelMapping> {
+        size_t operator()(const ColorChannelMapping& a) const {
+            size_t result = 0;
+            hash_combine(result, (int)a);
+            return result;
+        }
+    };
+};
+
+#endif // hifi_ColorChannelMapping_h