diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 1a0a17e0c8..c9d5dd3d09 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -138,7 +138,9 @@ void Agent::run() { hasVoxelServer = true; } } - } else { + } + + if (hasVoxelServer) { // allow the scripter's call back to setup visual data emit willSendVisualDataCallback(); @@ -147,6 +149,8 @@ void Agent::run() { qDebug() << "Uncaught exception at line" << line << ":" << engine.uncaughtException().toString() << "\n"; } + // flush the queue of packets and then process them so they are all sent off + voxelScripter.getVoxelPacketSender()->flushQueue(); voxelScripter.getVoxelPacketSender()->processWithoutSleep(); } diff --git a/assignment-client/src/voxels/VoxelScriptingInterface.cpp b/assignment-client/src/voxels/VoxelScriptingInterface.cpp index 3f4df78baf..e94f87264a 100644 --- a/assignment-client/src/voxels/VoxelScriptingInterface.cpp +++ b/assignment-client/src/voxels/VoxelScriptingInterface.cpp @@ -9,7 +9,7 @@ #include "VoxelScriptingInterface.h" void VoxelScriptingInterface::queueVoxelAdd(PACKET_TYPE addPacketType, VoxelDetail& addVoxelDetails) { - _voxelPacketSender.sendVoxelEditMessage(addPacketType, addVoxelDetails); + _voxelPacketSender.queueVoxelEditMessages(addPacketType, 1, &addVoxelDetails); } void VoxelScriptingInterface::queueVoxelAdd(float x, float y, float z, float scale, uchar red, uchar green, uchar blue) { @@ -34,5 +34,5 @@ void VoxelScriptingInterface::queueVoxelDelete(float x, float y, float z, float // setup a VoxelDetail struct with data VoxelDetail deleteVoxelDetail = {x, y, z, scale, 0, 0, 0}; - _voxelPacketSender.sendVoxelEditMessage(PACKET_TYPE_ERASE_VOXEL, deleteVoxelDetail); + _voxelPacketSender.queueVoxelEditMessages(PACKET_TYPE_ERASE_VOXEL, 1, &deleteVoxelDetail); } diff --git a/domain-server/resources/web/assignment/placeholder.js b/domain-server/resources/web/assignment/placeholder.js index e351f5e2b7..74891d12e8 100644 --- a/domain-server/resources/web/assignment/placeholder.js +++ b/domain-server/resources/web/assignment/placeholder.js @@ -2,14 +2,14 @@ // The following is an example of Conway's Game of Life (http://en.wikipedia.org/wiki/Conway's_Game_of_Life) -var NUMBER_OF_CELLS_EACH_DIMENSION = 32; +var NUMBER_OF_CELLS_EACH_DIMENSION = 64; var NUMBER_OF_CELLS = NUMBER_OF_CELLS_EACH_DIMENSION * NUMBER_OF_CELLS_EACH_DIMENSION; var currentCells = []; var nextCells = []; var METER_LENGTH = 1 / TREE_SCALE; -var cellScale = (8 * METER_LENGTH) / NUMBER_OF_CELLS_EACH_DIMENSION; +var cellScale = (NUMBER_OF_CELLS_EACH_DIMENSION * METER_LENGTH) / NUMBER_OF_CELLS_EACH_DIMENSION; print("TREE_SCALE = " + TREE_SCALE + "\n"); diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 11408e8a6d..a062c22ad3 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -345,7 +345,7 @@ void DomainServer::possiblyAddStaticAssignmentsBackToQueueAfterRestart(timeval* // throw into the assignment queue const uint64_t RESTART_HOLD_TIME_USECS = 5 * 1000 * 1000; - if (usecTimestampNow() - usecTimestamp(startTime) > RESTART_HOLD_TIME_USECS) { + if (!_hasCompletedRestartHold && usecTimestampNow() - usecTimestamp(startTime) > RESTART_HOLD_TIME_USECS) { _hasCompletedRestartHold = true; // pull anything in the static assignment file that isn't spoken for and add to the assignment queue @@ -530,7 +530,7 @@ int DomainServer::run() { qDebug("Received a request for assignment.\n"); - if (_hasCompletedRestartHold) { + if (!_hasCompletedRestartHold) { possiblyAddStaticAssignmentsBackToQueueAfterRestart(&startTime); } 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 0bab8c66b3..4f9adf5f3f 100644 --- a/interface/src/avatar/BlendFace.cpp +++ b/interface/src/avatar/BlendFace.cpp @@ -16,8 +16,7 @@ using namespace fs; using namespace std; BlendFace::BlendFace(Head* owningHead) : - _owningHead(owningHead), - _modelReply(NULL) + _owningHead(owningHead) { // we may have been created in the network thread, but we live in the main thread moveToThread(Application::getInstance()->thread()); @@ -28,7 +27,6 @@ BlendFace::~BlendFace() { } ProgramObject BlendFace::_eyeProgram; -DilatedTextureCache BlendFace::_eyeTextureCache("resources/images/eye.png", 50, 210); void BlendFace::init() { if (!_eyeProgram.isLinked()) { @@ -47,10 +45,30 @@ const glm::vec3 MODEL_TRANSLATION(0.0f, -0.07f, -0.025f); // temporary fudge fac const float MODEL_SCALE = 0.0006f; bool BlendFace::render(float alpha) { - if (_meshIDs.isEmpty()) { + if (!isActive()) { return false; } + // set up blended buffer ids on first render after load + const FBXGeometry& geometry = _geometry->getFBXGeometry(); + const QVector& networkMeshes = _geometry->getMeshes(); + if (_blendedVertexBufferIDs.isEmpty()) { + foreach (const FBXMesh& mesh, geometry.meshes) { + GLuint id = 0; + if (!mesh.blendshapes.isEmpty()) { + glGenBuffers(1, &id); + glBindBuffer(GL_ARRAY_BUFFER, id); + glBufferData(GL_ARRAY_BUFFER, (mesh.vertices.size() + mesh.normals.size()) * sizeof(glm::vec3), + NULL, GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); + } + _blendedVertexBufferIDs.append(id); + } + + // make sure we have the right number of dilated texture pointers + _dilatedTextures.resize(geometry.meshes.size()); + } + glPushMatrix(); glTranslatef(_owningHead->getPosition().x, _owningHead->getPosition().y, _owningHead->getPosition().z); glm::quat orientation = _owningHead->getOrientation(); @@ -61,27 +79,29 @@ bool BlendFace::render(float alpha) { -_owningHead->getScale() * MODEL_SCALE); glScalef(scale.x, scale.y, scale.z); - glTranslatef(-_geometry.neckPivot.x, -_geometry.neckPivot.y, -_geometry.neckPivot.z); + glTranslatef(-geometry.neckPivot.x, -geometry.neckPivot.y, -geometry.neckPivot.z); glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); // enable normalization under the expectation that the GPU can do it faster glEnable(GL_NORMALIZE); + glEnable(GL_TEXTURE_2D); glColor4f(_owningHead->getSkinColor().r, _owningHead->getSkinColor().g, _owningHead->getSkinColor().b, alpha); - for (int i = 0; i < _meshIDs.size(); i++) { - const VerticesIndices& ids = _meshIDs.at(i); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ids.first); - glBindBuffer(GL_ARRAY_BUFFER, ids.second); + for (int i = 0; i < networkMeshes.size(); i++) { + const NetworkMesh& networkMesh = networkMeshes.at(i); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, networkMesh.indexBufferID); - const FBXMesh& mesh = _geometry.meshes.at(i); + const FBXMesh& mesh = geometry.meshes.at(i); int vertexCount = mesh.vertices.size(); 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 * @@ -90,15 +110,12 @@ bool BlendFace::render(float alpha) { glRotatef(glm::angle(rotation), -rotationAxis.x, rotationAxis.y, -rotationAxis.z); glTranslatef(-mesh.pivot.x, -mesh.pivot.y, -mesh.pivot.z); - // use texture coordinates only for the eye, for now - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - - _eyeTexture = _eyeTextureCache.getTexture(_owningHead->getPupilDilation()); - glBindTexture(GL_TEXTURE_2D, _eyeTexture->getID()); - - glEnable(GL_TEXTURE_2D); - _eyeProgram.bind(); + + if (texture != NULL) { + texture = (_dilatedTextures[i] = static_cast(texture)->getDilatedTexture( + _owningHead->getPupilDilation())).data(); + } } glMultMatrixf((const GLfloat*)&mesh.transform); @@ -108,7 +125,15 @@ bool BlendFace::render(float alpha) { glColor4f(1.0f, 1.0f, 1.0f, alpha); } - if (!mesh.blendshapes.isEmpty()) { + glBindTexture(GL_TEXTURE_2D, texture == NULL ? 0 : texture->getID()); + + glBindBuffer(GL_ARRAY_BUFFER, networkMesh.vertexBufferID); + if (mesh.blendshapes.isEmpty()) { + glTexCoordPointer(2, GL_FLOAT, 0, (void*)(vertexCount * 2 * sizeof(glm::vec3))); + + } else { + glTexCoordPointer(2, GL_FLOAT, 0, 0); + _blendedVertices.resize(max(_blendedVertices.size(), vertexCount)); _blendedNormals.resize(_blendedVertices.size()); memcpy(_blendedVertices.data(), mesh.vertices.constData(), vertexCount * sizeof(glm::vec3)); @@ -116,68 +141,73 @@ bool BlendFace::render(float alpha) { // blend in each coefficient const vector& coefficients = _owningHead->getBlendshapeCoefficients(); - for (int i = 0; i < coefficients.size(); i++) { - float coefficient = coefficients[i]; - if (coefficient == 0.0f || i >= mesh.blendshapes.size() || mesh.blendshapes[i].vertices.isEmpty()) { + for (int j = 0; j < coefficients.size(); j++) { + float coefficient = coefficients[j]; + if (coefficient == 0.0f || j >= mesh.blendshapes.size() || mesh.blendshapes[j].vertices.isEmpty()) { continue; } const float NORMAL_COEFFICIENT_SCALE = 0.01f; float normalCoefficient = coefficient * NORMAL_COEFFICIENT_SCALE; - const glm::vec3* vertex = mesh.blendshapes[i].vertices.constData(); - const glm::vec3* normal = mesh.blendshapes[i].normals.constData(); - for (const int* index = mesh.blendshapes[i].indices.constData(), - *end = index + mesh.blendshapes[i].indices.size(); index != end; index++, vertex++, normal++) { + const glm::vec3* vertex = mesh.blendshapes[j].vertices.constData(); + const glm::vec3* normal = mesh.blendshapes[j].normals.constData(); + for (const int* index = mesh.blendshapes[j].indices.constData(), + *end = index + mesh.blendshapes[j].indices.size(); index != end; index++, vertex++, normal++) { _blendedVertices[*index] += *vertex * coefficient; _blendedNormals[*index] += *normal * normalCoefficient; } } + glBindBuffer(GL_ARRAY_BUFFER, _blendedVertexBufferIDs.at(i)); glBufferSubData(GL_ARRAY_BUFFER, 0, vertexCount * sizeof(glm::vec3), _blendedVertices.constData()); glBufferSubData(GL_ARRAY_BUFFER, vertexCount * sizeof(glm::vec3), vertexCount * sizeof(glm::vec3), _blendedNormals.constData()); } - glVertexPointer(3, GL_FLOAT, 0, 0); glNormalPointer(GL_FLOAT, 0, (void*)(vertexCount * sizeof(glm::vec3))); - glTexCoordPointer(2, GL_FLOAT, 0, (void*)(vertexCount * 2 * sizeof(glm::vec3))); + glDrawRangeElementsEXT(GL_QUADS, 0, vertexCount - 1, mesh.quadIndices.size(), GL_UNSIGNED_INT, 0); glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertexCount - 1, mesh.triangleIndices.size(), GL_UNSIGNED_INT, (void*)(mesh.quadIndices.size() * sizeof(int))); if (mesh.isEye) { _eyeProgram.release(); - glBindTexture(GL_TEXTURE_2D, 0); - glDisable(GL_TEXTURE_2D); - glDisableClientState(GL_TEXTURE_COORD_ARRAY); } glPopMatrix(); } glDisable(GL_NORMALIZE); - + glDisable(GL_TEXTURE_2D); + // deactivate vertex arrays after drawing glDisableClientState(GL_NORMAL_ARRAY); glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); // bind with 0 to switch back to normal operation glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - + glBindTexture(GL_TEXTURE_2D, 0); + glPopMatrix(); return true; } void BlendFace::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const { + if (!isActive()) { + return; + } + glm::quat orientation = _owningHead->getOrientation(); glm::vec3 scale(-_owningHead->getScale() * MODEL_SCALE, _owningHead->getScale() * MODEL_SCALE, -_owningHead->getScale() * MODEL_SCALE); bool foundFirst = false; - foreach (const FBXMesh& mesh, _geometry.meshes) { + const FBXGeometry& geometry = _geometry->getFBXGeometry(); + foreach (const FBXMesh& mesh, geometry.meshes) { if (mesh.isEye) { - glm::vec3 position = orientation * ((mesh.pivot - _geometry.neckPivot) * scale + MODEL_TRANSLATION) + + glm::vec3 position = orientation * ((mesh.pivot - geometry.neckPivot) * scale + MODEL_TRANSLATION) + _owningHead->getPosition(); if (foundFirst) { secondEyePosition = position; @@ -190,112 +220,22 @@ void BlendFace::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEy } void BlendFace::setModelURL(const QUrl& url) { - // don't restart the download if it's the same URL + // don't recreate the geometry if it's the same URL if (_modelURL == url) { return; } - - // cancel any current download - if (_modelReply != 0) { - delete _modelReply; - _modelReply = 0; - } - - // clear the current geometry, if any - setGeometry(FBXGeometry()); - - // remember the URL _modelURL = url; - - // load the URL data asynchronously - if (!url.isValid()) { - return; - } - QNetworkRequest request(url); - request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache); - _modelReply = Application::getInstance()->getNetworkAccessManager()->get(request); - connect(_modelReply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(handleModelDownloadProgress(qint64,qint64))); - connect(_modelReply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleModelReplyError())); -} -glm::vec3 createVec3(const fsVector3f& vector) { - return glm::vec3(vector.x, vector.y, vector.z); -} - -void BlendFace::handleModelDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) { - if (bytesReceived < bytesTotal && !_modelReply->isFinished()) { - return; - } - - QByteArray entirety = _modelReply->readAll(); - _modelReply->disconnect(this); - _modelReply->deleteLater(); - _modelReply = 0; - - try { - setGeometry(extractFBXGeometry(parseFBX(entirety))); - - } catch (const QString& error) { - qDebug() << error << "\n"; - return; - } -} - -void BlendFace::handleModelReplyError() { - qDebug("%s\n", _modelReply->errorString().toLocal8Bit().constData()); - - _modelReply->disconnect(this); - _modelReply->deleteLater(); - _modelReply = 0; -} - -void BlendFace::setGeometry(const FBXGeometry& geometry) { - // clear any existing geometry + // delete our local geometry and custom textures deleteGeometry(); + _dilatedTextures.clear(); - if (geometry.meshes.isEmpty()) { - return; - } - foreach (const FBXMesh& mesh, geometry.meshes) { - VerticesIndices ids; - glGenBuffers(1, &ids.first); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ids.first); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, (mesh.quadIndices.size() + mesh.triangleIndices.size()) * sizeof(int), - NULL, GL_STATIC_DRAW); - glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, mesh.quadIndices.size() * sizeof(int), mesh.quadIndices.constData()); - glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, mesh.quadIndices.size() * sizeof(int), - mesh.triangleIndices.size() * sizeof(int), mesh.triangleIndices.constData()); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - - glGenBuffers(1, &ids.second); - glBindBuffer(GL_ARRAY_BUFFER, ids.second); - - if (mesh.blendshapes.isEmpty()) { - glBufferData(GL_ARRAY_BUFFER, (mesh.vertices.size() + mesh.normals.size()) * sizeof(glm::vec3) + - mesh.texCoords.size() * sizeof(glm::vec2), NULL, GL_STATIC_DRAW); - glBufferSubData(GL_ARRAY_BUFFER, 0, mesh.vertices.size() * sizeof(glm::vec3), mesh.vertices.constData()); - glBufferSubData(GL_ARRAY_BUFFER, mesh.vertices.size() * sizeof(glm::vec3), - mesh.normals.size() * sizeof(glm::vec3), mesh.normals.constData()); - glBufferSubData(GL_ARRAY_BUFFER, (mesh.vertices.size() + mesh.normals.size()) * sizeof(glm::vec3), - mesh.texCoords.size() * sizeof(glm::vec2), mesh.texCoords.constData()); - - } else { - glBufferData(GL_ARRAY_BUFFER, (mesh.vertices.size() + mesh.normals.size()) * sizeof(glm::vec3), - NULL, GL_DYNAMIC_DRAW); - } - - glBindBuffer(GL_ARRAY_BUFFER, 0); - - _meshIDs.append(ids); - } - - _geometry = geometry; + _geometry = Application::getInstance()->getGeometryCache()->getGeometry(url); } void BlendFace::deleteGeometry() { - foreach (const VerticesIndices& meshIDs, _meshIDs) { - glDeleteBuffers(1, &meshIDs.first); - glDeleteBuffers(1, &meshIDs.second); + foreach (GLuint id, _blendedVertexBufferIDs) { + glDeleteBuffers(1, &id); } - _meshIDs.clear(); + _blendedVertexBufferIDs.clear(); } diff --git a/interface/src/avatar/BlendFace.h b/interface/src/avatar/BlendFace.h index 47b3caf758..9307dc5c89 100644 --- a/interface/src/avatar/BlendFace.h +++ b/interface/src/avatar/BlendFace.h @@ -13,7 +13,7 @@ #include #include "InterfaceConfig.h" -#include "renderer/FBXReader.h" +#include "renderer/GeometryCache.h" #include "renderer/ProgramObject.h" #include "renderer/TextureCache.h" @@ -30,7 +30,7 @@ public: BlendFace(Head* owningHead); ~BlendFace(); - bool isActive() const { return !_meshIDs.isEmpty(); } + bool isActive() const { return _geometry && _geometry->isLoaded(); } void init(); bool render(float alpha); @@ -39,34 +39,24 @@ public: const QUrl& getModelURL() const { return _modelURL; } void getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const; - -private slots: - - void handleModelDownloadProgress(qint64 bytesReceived, qint64 bytesTotal); - void handleModelReplyError(); private: - void setGeometry(const FBXGeometry& geometry); void deleteGeometry(); Head* _owningHead; QUrl _modelURL; - QNetworkReply* _modelReply; - - typedef QPair VerticesIndices; - QVector _meshIDs; + QSharedPointer _geometry; + + QVector _blendedVertexBufferIDs; + QVector > _dilatedTextures; - FBXGeometry _geometry; 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/FBXReader.cpp b/interface/src/renderer/FBXReader.cpp index bc77a1c8b4..fdce876d09 100644 --- a/interface/src/renderer/FBXReader.cpp +++ b/interface/src/renderer/FBXReader.cpp @@ -205,7 +205,7 @@ QVector createVec2Vector(const QVector& doubleVector) { for (const double* it = doubleVector.constData(), *end = it + doubleVector.size(); it != end; ) { float s = *it++; float t = *it++; - values.append(glm::vec2(s, t)); + values.append(glm::vec2(s, -t)); } return values; } @@ -282,12 +282,22 @@ QHash createBlendshapeMap() { } } -glm::mat4 getGlobalTransform( - const QMultiHash& parentMap, const QHash& localTransforms, qint64 nodeID) { +class Transform { +public: + bool inheritScale; + glm::mat4 withScale; + glm::mat4 withoutScale; +}; + +glm::mat4 getGlobalTransform(const QMultiHash& parentMap, const QHash& localTransforms, + qint64 nodeID, bool forceScale = true) { glm::mat4 globalTransform; + bool useScale = true; while (nodeID != 0) { - globalTransform = localTransforms.value(nodeID) * globalTransform; + const Transform& localTransform = localTransforms.value(nodeID); + globalTransform = (useScale ? localTransform.withScale : localTransform.withoutScale) * globalTransform; + useScale = (useScale && localTransform.inheritScale) || forceScale; QList parentIDs = parentMap.values(nodeID); nodeID = 0; @@ -314,8 +324,11 @@ FBXGeometry extractFBXGeometry(const FBXNode& node) { QVector blendshapes; QMultiHash parentMap; QMultiHash childMap; - QHash localTransforms; + QHash localTransforms; QHash transformLinkMatrices; + QHash textureFilenames; + QHash diffuseTextures; + QHash bumpTextures; qint64 jointEyeLeftID = 0; qint64 jointEyeRightID = 0; qint64 jointNeckID = 0; @@ -437,19 +450,20 @@ FBXGeometry extractFBXGeometry(const FBXNode& node) { } } else if (object.name == "Model") { QByteArray name = object.properties.at(1).toByteArray(); - if (name.startsWith("jointEyeLeft") || name.startsWith("EyeL")) { + if (name.startsWith("jointEyeLeft") || name.startsWith("EyeL") || name.startsWith("joint_Leye")) { jointEyeLeftID = object.properties.at(0).value(); - } else if (name.startsWith("jointEyeRight") || name.startsWith("EyeR")) { + } else if (name.startsWith("jointEyeRight") || name.startsWith("EyeR") || name.startsWith("joint_Reye")) { jointEyeRightID = object.properties.at(0).value(); - } else if (name.startsWith("jointNeck") || name.startsWith("NeckRot")) { + } else if (name.startsWith("jointNeck") || name.startsWith("NeckRot") || name.startsWith("joint_neck")) { jointNeckID = object.properties.at(0).value(); } glm::vec3 translation; glm::vec3 preRotation, rotation, postRotation; glm::vec3 scale = glm::vec3(1.0f, 1.0f, 1.0f); glm::vec3 scalePivot, rotationPivot; + Transform transform = { true }; foreach (const FBXNode& subobject, object.children) { if (subobject.name == "Properties70") { foreach (const FBXNode& property, subobject.children) { @@ -488,19 +502,30 @@ FBXGeometry extractFBXGeometry(const FBXNode& node) { scale = glm::vec3(property.properties.at(4).value(), property.properties.at(5).value(), property.properties.at(6).value()); + + } else if (property.properties.at(0) == "InheritType") { + transform.inheritScale = property.properties.at(4) != 2; } } } } } // see FBX documentation, http://download.autodesk.com/us/fbx/20112/FBX_SDK_HELP/index.html - localTransforms.insert(object.properties.at(0).value(), - glm::translate(translation) * glm::translate(rotationPivot) * + transform.withoutScale = glm::translate(translation) * glm::translate(rotationPivot) * glm::mat4_cast(glm::quat(glm::radians(preRotation))) * glm::mat4_cast(glm::quat(glm::radians(rotation))) * - glm::mat4_cast(glm::quat(glm::radians(postRotation))) * glm::translate(-rotationPivot) * - glm::translate(scalePivot) * glm::scale(scale) * glm::translate(-scalePivot)); - + glm::mat4_cast(glm::quat(glm::radians(postRotation))) * glm::translate(-rotationPivot); + transform.withScale = transform.withoutScale * glm::translate(scalePivot) * glm::scale(scale) * + glm::translate(-scalePivot); + localTransforms.insert(object.properties.at(0).value(), transform); + + } else if (object.name == "Texture") { + foreach (const FBXNode& subobject, object.children) { + if (subobject.name == "RelativeFilename") { + textureFilenames.insert(object.properties.at(0).value(), + subobject.properties.at(0).toByteArray()); + } + } } else if (object.name == "Deformer" && object.properties.at(2) == "Cluster") { foreach (const FBXNode& subobject, object.children) { if (subobject.name == "TransformLink") { @@ -513,6 +538,16 @@ FBXGeometry extractFBXGeometry(const FBXNode& node) { } else if (child.name == "Connections") { foreach (const FBXNode& connection, child.children) { if (connection.name == "C") { + if (connection.properties.at(0) == "OP") { + if (connection.properties.at(3) == "DiffuseColor") { + diffuseTextures.insert(connection.properties.at(2).value(), + connection.properties.at(1).value()); + + } else if (connection.properties.at(3) == "Bump") { + bumpTextures.insert(connection.properties.at(2).value(), + connection.properties.at(1).value()); + } + } parentMap.insert(connection.properties.at(1).value(), connection.properties.at(2).value()); childMap.insert(connection.properties.at(2).value(), connection.properties.at(1).value()); } @@ -537,29 +572,42 @@ FBXGeometry extractFBXGeometry(const FBXNode& node) { FBXMesh& mesh = it.value(); // accumulate local transforms - for (qint64 parentID = parentMap.value(it.key()); parentID != 0; parentID = parentMap.value(parentID)) { - mesh.transform = localTransforms.value(parentID) * mesh.transform; + qint64 modelID = parentMap.value(it.key()); + glm::mat4 modelTransform = getGlobalTransform(parentMap, localTransforms, modelID); + + // look for textures + foreach (qint64 childID, childMap.values(modelID)) { + qint64 diffuseTextureID = diffuseTextures.value(childID); + if (diffuseTextureID != 0) { + mesh.diffuseFilename = textureFilenames.value(diffuseTextureID); + } + qint64 bumpTextureID = bumpTextures.value(childID); + if (bumpTextureID != 0) { + mesh.normalFilename = textureFilenames.value(bumpTextureID); + } } // look for a limb pivot mesh.isEye = false; foreach (qint64 childID, childMap.values(it.key())) { - qint64 clusterID = childMap.value(childID); - if (!transformLinkMatrices.contains(clusterID)) { - continue; + foreach (qint64 clusterID, childMap.values(childID)) { + if (!transformLinkMatrices.contains(clusterID)) { + continue; + } + qint64 jointID = childMap.value(clusterID); + if (jointID == jointEyeLeftID || jointID == jointEyeRightID) { + mesh.isEye = true; + } + + // see http://stackoverflow.com/questions/13566608/loading-skinning-information-from-fbx for a discussion + // of skinning information in FBX + glm::mat4 jointTransform = getGlobalTransform(parentMap, localTransforms, jointID); + mesh.transform = jointTransform * glm::inverse(transformLinkMatrices.value(clusterID)) * modelTransform; + + // extract translation component for pivot + glm::mat4 jointTransformScaled = getGlobalTransform(parentMap, localTransforms, jointID, true); + mesh.pivot = glm::vec3(jointTransformScaled[3][0], jointTransformScaled[3][1], jointTransformScaled[3][2]); } - qint64 jointID = childMap.value(clusterID); - if (jointID == jointEyeLeftID || jointID == jointEyeRightID) { - mesh.isEye = true; - } - - // see http://stackoverflow.com/questions/13566608/loading-skinning-information-from-fbx for a discussion - // of skinning information in FBX - glm::mat4 jointTransform = getGlobalTransform(parentMap, localTransforms, jointID); - mesh.transform = jointTransform * glm::inverse(transformLinkMatrices.value(clusterID)) * mesh.transform; - - // extract translation component for pivot - mesh.pivot = glm::vec3(jointTransform[3][0], jointTransform[3][1], jointTransform[3][2]); } if (mesh.blendshapes.size() > mostBlendshapes) { @@ -572,7 +620,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node) { } // extract translation component for neck pivot - glm::mat4 neckTransform = getGlobalTransform(parentMap, localTransforms, jointNeckID); + glm::mat4 neckTransform = getGlobalTransform(parentMap, localTransforms, jointNeckID, true); geometry.neckPivot = glm::vec3(neckTransform[3][0], neckTransform[3][1], neckTransform[3][2]); return geometry; diff --git a/interface/src/renderer/FBXReader.h b/interface/src/renderer/FBXReader.h index 38148ced0b..bbdbe4fdaa 100644 --- a/interface/src/renderer/FBXReader.h +++ b/interface/src/renderer/FBXReader.h @@ -53,6 +53,9 @@ public: bool isEye; + QByteArray diffuseFilename; + QByteArray normalFilename; + QVector blendshapes; }; diff --git a/interface/src/renderer/GeometryCache.cpp b/interface/src/renderer/GeometryCache.cpp index adc4d98361..5045b8b447 100644 --- a/interface/src/renderer/GeometryCache.cpp +++ b/interface/src/renderer/GeometryCache.cpp @@ -7,6 +7,9 @@ #include +#include + +#include "Application.h" #include "GeometryCache.h" #include "world.h" @@ -236,4 +239,110 @@ void GeometryCache::renderHalfCylinder(int slices, int stacks) { glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); -} \ No newline at end of file +} + +QSharedPointer GeometryCache::getGeometry(const QUrl& url) { + QSharedPointer geometry = _networkGeometry.value(url); + if (geometry.isNull()) { + geometry = QSharedPointer(new NetworkGeometry(url)); + _networkGeometry.insert(url, geometry); + } + return geometry; +} + +NetworkGeometry::NetworkGeometry(const QUrl& url) : _reply(NULL) { + if (!url.isValid()) { + return; + } + QNetworkRequest request(url); + request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache); + _reply = Application::getInstance()->getNetworkAccessManager()->get(request); + + connect(_reply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(handleDownloadProgress(qint64,qint64))); + connect(_reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleReplyError())); +} + +NetworkGeometry::~NetworkGeometry() { + if (_reply != NULL) { + delete _reply; + } + foreach (const NetworkMesh& mesh, _meshes) { + glDeleteBuffers(1, &mesh.indexBufferID); + glDeleteBuffers(1, &mesh.vertexBufferID); + } +} + +void NetworkGeometry::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) { + if (bytesReceived < bytesTotal && !_reply->isFinished()) { + return; + } + + QUrl url = _reply->url(); + QByteArray entirety = _reply->readAll(); + _reply->disconnect(this); + _reply->deleteLater(); + _reply = NULL; + + try { + _geometry = extractFBXGeometry(parseFBX(entirety)); + + } catch (const QString& error) { + qDebug() << "Error reading " << url << ": " << error << "\n"; + return; + } + + foreach (const FBXMesh& mesh, _geometry.meshes) { + NetworkMesh networkMesh; + + glGenBuffers(1, &networkMesh.indexBufferID); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, networkMesh.indexBufferID); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, (mesh.quadIndices.size() + mesh.triangleIndices.size()) * sizeof(int), + NULL, GL_STATIC_DRAW); + glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, mesh.quadIndices.size() * sizeof(int), mesh.quadIndices.constData()); + glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, mesh.quadIndices.size() * sizeof(int), + mesh.triangleIndices.size() * sizeof(int), mesh.triangleIndices.constData()); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + + glGenBuffers(1, &networkMesh.vertexBufferID); + glBindBuffer(GL_ARRAY_BUFFER, networkMesh.vertexBufferID); + + if (mesh.blendshapes.isEmpty()) { + glBufferData(GL_ARRAY_BUFFER, (mesh.vertices.size() + mesh.normals.size()) * sizeof(glm::vec3) + + mesh.texCoords.size() * sizeof(glm::vec2), NULL, GL_STATIC_DRAW); + glBufferSubData(GL_ARRAY_BUFFER, 0, mesh.vertices.size() * sizeof(glm::vec3), mesh.vertices.constData()); + glBufferSubData(GL_ARRAY_BUFFER, mesh.vertices.size() * sizeof(glm::vec3), + mesh.normals.size() * sizeof(glm::vec3), mesh.normals.constData()); + glBufferSubData(GL_ARRAY_BUFFER, (mesh.vertices.size() + mesh.normals.size()) * sizeof(glm::vec3), + mesh.texCoords.size() * sizeof(glm::vec2), mesh.texCoords.constData()); + + } else { + glBufferData(GL_ARRAY_BUFFER, mesh.texCoords.size() * sizeof(glm::vec2), + mesh.texCoords.constData(), GL_STATIC_DRAW); + } + + glBindBuffer(GL_ARRAY_BUFFER, 0); + + QString basePath = url.path(); + int idx = basePath.lastIndexOf('/'); + if (idx != -1) { + basePath = basePath.left(idx); + } + if (!mesh.diffuseFilename.isEmpty()) { + url.setPath(basePath + mesh.diffuseFilename); + networkMesh.diffuseTexture = Application::getInstance()->getTextureCache()->getTexture(url, mesh.isEye); + } + if (!mesh.normalFilename.isEmpty()) { + url.setPath(basePath + mesh.normalFilename); + networkMesh.normalTexture = Application::getInstance()->getTextureCache()->getTexture(url); + } + _meshes.append(networkMesh); + } +} + +void NetworkGeometry::handleReplyError() { + qDebug() << _reply->errorString() << "\n"; + + _reply->disconnect(this); + _reply->deleteLater(); + _reply = NULL; +} diff --git a/interface/src/renderer/GeometryCache.h b/interface/src/renderer/GeometryCache.h index f1e1ec4e34..f885f005b6 100644 --- a/interface/src/renderer/GeometryCache.h +++ b/interface/src/renderer/GeometryCache.h @@ -10,9 +10,20 @@ #define __interface__GeometryCache__ #include +#include +#include +#include +#include "FBXReader.h" #include "InterfaceConfig.h" +class QNetworkReply; + +class NetworkGeometry; +class NetworkMesh; +class NetworkTexture; + +/// Stores cached geometry. class GeometryCache { public: @@ -22,6 +33,9 @@ public: void renderSquare(int xDivisions, int yDivisions); void renderHalfCylinder(int slices, int stacks); + /// Loads geometry from the specified URL. + QSharedPointer getGeometry(const QUrl& url); + private: typedef QPair IntPair; @@ -30,6 +44,46 @@ private: QHash _hemisphereVBOs; QHash _squareVBOs; QHash _halfCylinderVBOs; + + QHash > _networkGeometry; +}; + +/// Geometry loaded from the network. +class NetworkGeometry : public QObject { + Q_OBJECT + +public: + + NetworkGeometry(const QUrl& url); + ~NetworkGeometry(); + + bool isLoaded() const { return !_meshes.isEmpty(); } + + const FBXGeometry& getFBXGeometry() const { return _geometry; } + const QVector& getMeshes() const { return _meshes; } + +private slots: + + void handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal); + void handleReplyError(); + +private: + + QNetworkReply* _reply; + + FBXGeometry _geometry; + QVector _meshes; +}; + +/// The state associated with a single mesh. +class NetworkMesh { +public: + + GLuint indexBufferID; + GLuint vertexBufferID; + + QSharedPointer diffuseTexture; + QSharedPointer normalTexture; }; #endif /* defined(__interface__GeometryCache__) */ diff --git a/interface/src/renderer/TextureCache.cpp b/interface/src/renderer/TextureCache.cpp index ebb9c77890..479459f30c 100644 --- a/interface/src/renderer/TextureCache.cpp +++ b/interface/src/renderer/TextureCache.cpp @@ -9,6 +9,7 @@ #include "InterfaceConfig.h" #include +#include #include #include @@ -83,6 +84,15 @@ GLuint TextureCache::getFileTextureID(const QString& filename) { return id; } +QSharedPointer TextureCache::getTexture(const QUrl& url, bool dilatable) { + QSharedPointer texture = _networkTextures.value(url); + if (texture.isNull()) { + texture = QSharedPointer(dilatable ? new DilatableNetworkTexture(url) : new NetworkTexture(url)); + _networkTextures.insert(url, texture); + } + return texture; +} + QOpenGLFramebufferObject* TextureCache::getPrimaryFramebufferObject() { if (_primaryFramebufferObject == NULL) { _primaryFramebufferObject = createFramebufferObject(); @@ -163,35 +173,106 @@ Texture::~Texture() { glDeleteTextures(1, &_id); } -DilatedTextureCache::DilatedTextureCache(const QString& filename, int innerRadius, int outerRadius) : - _innerRadius(innerRadius), - _outerRadius(outerRadius) -{ - switchToResourcesParentIfRequired(); - _image = QImage(filename).convertToFormat(QImage::Format_ARGB32); +NetworkTexture::NetworkTexture(const QUrl& url) : _reply(NULL) { + if (!url.isValid()) { + return; + } + QNetworkRequest request(url); + request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache); + _reply = Application::getInstance()->getNetworkAccessManager()->get(request); + + connect(_reply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(handleDownloadProgress(qint64,qint64))); + connect(_reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleReplyError())); } -QSharedPointer DilatedTextureCache::getTexture(float dilation) { - QSharedPointer texture = _textures.value(dilation); +NetworkTexture::~NetworkTexture() { + if (_reply != NULL) { + delete _reply; + } +} + +void NetworkTexture::imageLoaded(const QImage& image) { + // nothing by default +} + +void NetworkTexture::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) { + if (bytesReceived < bytesTotal && !_reply->isFinished()) { + return; + } + + QByteArray entirety = _reply->readAll(); + _reply->disconnect(this); + _reply->deleteLater(); + _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()); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glBindTexture(GL_TEXTURE_2D, 0); +} + +void NetworkTexture::handleReplyError() { + qDebug() << _reply->errorString() << "\n"; + + _reply->disconnect(this); + _reply->deleteLater(); + _reply = NULL; +} + +DilatableNetworkTexture::DilatableNetworkTexture(const QUrl& url) : + NetworkTexture(url), + _innerRadius(0), + _outerRadius(0) +{ +} + +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 11a847d838..a562d2c046 100644 --- a/interface/src/renderer/TextureCache.h +++ b/interface/src/renderer/TextureCache.h @@ -18,10 +18,15 @@ #include "InterfaceConfig.h" +class QNetworkReply; class QOpenGLFramebufferObject; -/// Stored cached textures, including render-to-texture targets. +class NetworkTexture; + +/// Stores cached textures, including render-to-texture targets. class TextureCache : public QObject { + Q_OBJECT + public: TextureCache(); @@ -35,6 +40,9 @@ public: /// Returns the ID of a texture containing the contents of the specified file, loading it if necessary. GLuint getFileTextureID(const QString& filename); + /// Loads a texture from the specified 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. QOpenGLFramebufferObject* getPrimaryFramebufferObject(); @@ -60,6 +68,8 @@ private: QHash _fileTextureIDs; + QHash > _networkTextures; + GLuint _primaryDepthTextureID; QOpenGLFramebufferObject* _primaryFramebufferObject; QOpenGLFramebufferObject* _secondaryFramebufferObject; @@ -80,22 +90,51 @@ private: GLuint _id; }; -/// Caches textures according to pupillary dilation. -class DilatedTextureCache { +/// A texture loaded from the network. +class NetworkTexture : public QObject, public Texture { + Q_OBJECT + public: + + NetworkTexture(const QUrl& url); + ~NetworkTexture(); - DilatedTextureCache(const QString& filename, int innerRadius, int outerRadius); +protected: - /// Returns a pointer to a texture with the requested amount of dilation. - QSharedPointer getTexture(float dilation); + virtual void imageLoaded(const QImage& image); + +private slots: + + void handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal); + void handleReplyError(); private: + + QNetworkReply* _reply; +}; +/// Caches derived, dilated textures. +class DilatableNetworkTexture : public NetworkTexture { + Q_OBJECT + +public: + + DilatableNetworkTexture(const QUrl& url); + + /// Returns a pointer to a texture with the requested amount of 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__) */