diff --git a/interface/resources/images/eye.png b/interface/resources/images/eye.png deleted file mode 100644 index 57896924b1..0000000000 Binary files a/interface/resources/images/eye.png and /dev/null differ diff --git a/interface/src/avatar/BlendFace.cpp b/interface/src/avatar/BlendFace.cpp index 913729b605..4f9adf5f3f 100644 --- a/interface/src/avatar/BlendFace.cpp +++ b/interface/src/avatar/BlendFace.cpp @@ -27,7 +27,6 @@ BlendFace::~BlendFace() { } ProgramObject BlendFace::_eyeProgram; -DilatedTextureCache BlendFace::_eyeTextureCache("resources/images/eye.png", 50, 210); void BlendFace::init() { if (!_eyeProgram.isLinked()) { @@ -65,6 +64,9 @@ bool BlendFace::render(float alpha) { } _blendedVertexBufferIDs.append(id); } + + // make sure we have the right number of dilated texture pointers + _dilatedTextures.resize(geometry.meshes.size()); } glPushMatrix(); @@ -99,6 +101,7 @@ bool BlendFace::render(float alpha) { glPushMatrix(); // apply eye rotation if appropriate + Texture* texture = networkMesh.diffuseTexture.data(); if (mesh.isEye) { glTranslatef(mesh.pivot.x, mesh.pivot.y, mesh.pivot.z); glm::quat rotation = glm::inverse(orientation) * _owningHead->getEyeRotation(orientation * @@ -108,6 +111,11 @@ bool BlendFace::render(float alpha) { glTranslatef(-mesh.pivot.x, -mesh.pivot.y, -mesh.pivot.z); _eyeProgram.bind(); + + if (texture != NULL) { + texture = (_dilatedTextures[i] = static_cast(texture)->getDilatedTexture( + _owningHead->getPupilDilation())).data(); + } } glMultMatrixf((const GLfloat*)&mesh.transform); @@ -117,7 +125,7 @@ bool BlendFace::render(float alpha) { glColor4f(1.0f, 1.0f, 1.0f, alpha); } - glBindTexture(GL_TEXTURE_2D, networkMesh.diffuseTexture ? networkMesh.diffuseTexture->getID() : 0); + glBindTexture(GL_TEXTURE_2D, texture == NULL ? 0 : texture->getID()); glBindBuffer(GL_ARRAY_BUFFER, networkMesh.vertexBufferID); if (mesh.blendshapes.isEmpty()) { @@ -218,8 +226,9 @@ void BlendFace::setModelURL(const QUrl& url) { } _modelURL = url; - // delete our local geometry + // delete our local geometry and custom textures deleteGeometry(); + _dilatedTextures.clear(); _geometry = Application::getInstance()->getGeometryCache()->getGeometry(url); } diff --git a/interface/src/avatar/BlendFace.h b/interface/src/avatar/BlendFace.h index 430442261a..9307dc5c89 100644 --- a/interface/src/avatar/BlendFace.h +++ b/interface/src/avatar/BlendFace.h @@ -51,14 +51,12 @@ private: QSharedPointer _geometry; QVector _blendedVertexBufferIDs; + QVector > _dilatedTextures; QVector _blendedVertices; QVector _blendedNormals; - QSharedPointer _eyeTexture; - static ProgramObject _eyeProgram; - static DilatedTextureCache _eyeTextureCache; }; #endif /* defined(__interface__BlendFace__) */ diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index 47677ec0e5..ae02790b94 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -46,7 +46,7 @@ const float IRIS_PROTRUSION = 0.0145f; const char IRIS_TEXTURE_FILENAME[] = "resources/images/iris.png"; ProgramObject Head::_irisProgram; -DilatedTextureCache Head::_irisTextureCache(IRIS_TEXTURE_FILENAME, 53, 127); +QSharedPointer Head::_irisTexture; int Head::_eyePositionLocation; Head::Head(Avatar* owningAvatar) : @@ -103,6 +103,9 @@ void Head::init() { _irisProgram.setUniformValue("texture", 0); _eyePositionLocation = _irisProgram.uniformLocation("eyePosition"); + + _irisTexture = Application::getInstance()->getTextureCache()->getTexture(QUrl::fromLocalFile(IRIS_TEXTURE_FILENAME), + true).staticCast(); } _blendFace.init(); } @@ -624,8 +627,8 @@ void Head::renderEyeBalls() { _irisProgram.bind(); - _irisTexture = _irisTextureCache.getTexture(_pupilDilation); - glBindTexture(GL_TEXTURE_2D, _irisTexture->getID()); + _dilatedIrisTexture = _irisTexture->getDilatedTexture(_pupilDilation); + glBindTexture(GL_TEXTURE_2D, _dilatedIrisTexture->getID()); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); diff --git a/interface/src/avatar/Head.h b/interface/src/avatar/Head.h index 0af640af04..38574fa430 100644 --- a/interface/src/avatar/Head.h +++ b/interface/src/avatar/Head.h @@ -139,10 +139,10 @@ private: PerlinFace _perlinFace; BlendFace _blendFace; - QSharedPointer _irisTexture; + QSharedPointer _dilatedIrisTexture; static ProgramObject _irisProgram; - static DilatedTextureCache _irisTextureCache; + static QSharedPointer _irisTexture; static int _eyePositionLocation; // private methods diff --git a/interface/src/avatar/PerlinFace.cpp b/interface/src/avatar/PerlinFace.cpp index 5784c51e67..fe041920bf 100644 --- a/interface/src/avatar/PerlinFace.cpp +++ b/interface/src/avatar/PerlinFace.cpp @@ -248,8 +248,8 @@ void PerlinFace::render() { Head::_irisProgram.bind(); - _owningHead->_irisTexture = Head::_irisTextureCache.getTexture(_owningHead->_pupilDilation); - glBindTexture(GL_TEXTURE_2D, _owningHead->_irisTexture->getID()); + _owningHead->_dilatedIrisTexture = Head::_irisTexture->getDilatedTexture(_owningHead->_pupilDilation); + glBindTexture(GL_TEXTURE_2D, _owningHead->_dilatedIrisTexture->getID()); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); diff --git a/interface/src/renderer/GeometryCache.cpp b/interface/src/renderer/GeometryCache.cpp index fcaa2960c8..5045b8b447 100644 --- a/interface/src/renderer/GeometryCache.cpp +++ b/interface/src/renderer/GeometryCache.cpp @@ -329,7 +329,7 @@ void NetworkGeometry::handleDownloadProgress(qint64 bytesReceived, qint64 bytesT } if (!mesh.diffuseFilename.isEmpty()) { url.setPath(basePath + mesh.diffuseFilename); - networkMesh.diffuseTexture = Application::getInstance()->getTextureCache()->getTexture(url); + networkMesh.diffuseTexture = Application::getInstance()->getTextureCache()->getTexture(url, mesh.isEye); } if (!mesh.normalFilename.isEmpty()) { url.setPath(basePath + mesh.normalFilename); diff --git a/interface/src/renderer/TextureCache.cpp b/interface/src/renderer/TextureCache.cpp index ff7008b066..479459f30c 100644 --- a/interface/src/renderer/TextureCache.cpp +++ b/interface/src/renderer/TextureCache.cpp @@ -84,10 +84,10 @@ GLuint TextureCache::getFileTextureID(const QString& filename) { return id; } -QSharedPointer TextureCache::getTexture(const QUrl& url) { +QSharedPointer TextureCache::getTexture(const QUrl& url, bool dilatable) { QSharedPointer texture = _networkTextures.value(url); if (texture.isNull()) { - texture = QSharedPointer(new NetworkTexture(url)); + texture = QSharedPointer(dilatable ? new DilatableNetworkTexture(url) : new NetworkTexture(url)); _networkTextures.insert(url, texture); } return texture; @@ -191,6 +191,10 @@ NetworkTexture::~NetworkTexture() { } } +void NetworkTexture::imageLoaded(const QImage& image) { + // nothing by default +} + void NetworkTexture::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) { if (bytesReceived < bytesTotal && !_reply->isFinished()) { return; @@ -202,6 +206,7 @@ void NetworkTexture::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTo _reply = NULL; QImage image = QImage::fromData(entirety).convertToFormat(QImage::Format_ARGB32); + imageLoaded(image); glBindTexture(GL_TEXTURE_2D, getID()); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width(), image.height(), 1, GL_BGRA, GL_UNSIGNED_BYTE, image.constBits()); @@ -217,35 +222,57 @@ void NetworkTexture::handleReplyError() { _reply = NULL; } -DilatedTextureCache::DilatedTextureCache(const QString& filename, int innerRadius, int outerRadius) : - _innerRadius(innerRadius), - _outerRadius(outerRadius) +DilatableNetworkTexture::DilatableNetworkTexture(const QUrl& url) : + NetworkTexture(url), + _innerRadius(0), + _outerRadius(0) { - switchToResourcesParentIfRequired(); - _image = QImage(filename).convertToFormat(QImage::Format_ARGB32); } -QSharedPointer DilatedTextureCache::getTexture(float dilation) { - QSharedPointer texture = _textures.value(dilation); +void DilatableNetworkTexture::imageLoaded(const QImage& image) { + _image = image; + + // scan out from the center to find inner and outer radii + int halfWidth = image.width() / 2; + int halfHeight = image.height() / 2; + const int BLACK_THRESHOLD = 32; + while (_innerRadius < halfWidth && qGray(image.pixel(halfWidth + _innerRadius, halfHeight)) < BLACK_THRESHOLD) { + _innerRadius++; + } + _outerRadius = _innerRadius; + const int TRANSPARENT_THRESHOLD = 32; + while (_outerRadius < halfWidth && qAlpha(image.pixel(halfWidth + _outerRadius, halfHeight)) > TRANSPARENT_THRESHOLD) { + _outerRadius++; + } + + // clear out any textures we generated before loading + _dilatedTextures.clear(); +} + +QSharedPointer DilatableNetworkTexture::getDilatedTexture(float dilation) { + QSharedPointer texture = _dilatedTextures.value(dilation); if (texture.isNull()) { texture = QSharedPointer(new Texture()); - QImage dilatedImage = _image; - QPainter painter; - painter.begin(&dilatedImage); - QPainterPath path; - qreal radius = glm::mix(_innerRadius, _outerRadius, dilation); - path.addEllipse(QPointF(_image.width() / 2.0, _image.height() / 2.0), radius, radius); - painter.fillPath(path, Qt::black); - painter.end(); + if (!_image.isNull()) { + QImage dilatedImage = _image; + QPainter painter; + painter.begin(&dilatedImage); + QPainterPath path; + qreal radius = glm::mix(_innerRadius, _outerRadius, dilation); + path.addEllipse(QPointF(_image.width() / 2.0, _image.height() / 2.0), radius, radius); + painter.fillPath(path, Qt::black); + painter.end(); + + glBindTexture(GL_TEXTURE_2D, texture->getID()); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dilatedImage.width(), dilatedImage.height(), 1, + GL_BGRA, GL_UNSIGNED_BYTE, dilatedImage.constBits()); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glBindTexture(GL_TEXTURE_2D, 0); + } - glBindTexture(GL_TEXTURE_2D, texture->getID()); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dilatedImage.width(), dilatedImage.height(), 1, - GL_BGRA, GL_UNSIGNED_BYTE, dilatedImage.constBits()); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glBindTexture(GL_TEXTURE_2D, 0); - - _textures.insert(dilation, texture); + _dilatedTextures.insert(dilation, texture); } return texture; } + diff --git a/interface/src/renderer/TextureCache.h b/interface/src/renderer/TextureCache.h index 8bb019a175..a562d2c046 100644 --- a/interface/src/renderer/TextureCache.h +++ b/interface/src/renderer/TextureCache.h @@ -41,7 +41,7 @@ public: GLuint getFileTextureID(const QString& filename); /// Loads a texture from the specified URL. - QSharedPointer getTexture(const QUrl& url); + QSharedPointer getTexture(const QUrl& url, bool dilatable = false); /// Returns a pointer to the primary framebuffer object. This render target includes a depth component, and is /// used for scene rendering. @@ -99,6 +99,10 @@ public: NetworkTexture(const QUrl& url); ~NetworkTexture(); +protected: + + virtual void imageLoaded(const QImage& image); + private slots: void handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal); @@ -109,22 +113,28 @@ private: QNetworkReply* _reply; }; -/// Caches textures according to pupillary dilation. -class DilatedTextureCache { +/// Caches derived, dilated textures. +class DilatableNetworkTexture : public NetworkTexture { + Q_OBJECT + public: - - DilatedTextureCache(const QString& filename, int innerRadius, int outerRadius); - + + DilatableNetworkTexture(const QUrl& url); + /// Returns a pointer to a texture with the requested amount of dilation. - QSharedPointer getTexture(float dilation); + QSharedPointer getDilatedTexture(float dilation); + +protected: + + virtual void imageLoaded(const QImage& image); private: - + QImage _image; int _innerRadius; int _outerRadius; - QMap > _textures; + QMap > _dilatedTextures; }; #endif /* defined(__interface__TextureCache__) */