mirror of
https://github.com/overte-org/overte.git
synced 2025-04-21 18:44:00 +02:00
Merge pull request #1007 from ey6es/master
Shared geometry/textures between instances and download/apply diffuse texture maps. Also some transform work.
This commit is contained in:
commit
eb5c48c6ac
12 changed files with 484 additions and 217 deletions
Binary file not shown.
Before Width: | Height: | Size: 862 KiB |
|
@ -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<NetworkMesh>& 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<DilatableNetworkTexture*>(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<float>& 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();
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
#include <QUrl>
|
||||
|
||||
#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<GLuint, GLuint> VerticesIndices;
|
||||
QVector<VerticesIndices> _meshIDs;
|
||||
QSharedPointer<NetworkGeometry> _geometry;
|
||||
|
||||
QVector<GLuint> _blendedVertexBufferIDs;
|
||||
QVector<QSharedPointer<Texture> > _dilatedTextures;
|
||||
|
||||
FBXGeometry _geometry;
|
||||
QVector<glm::vec3> _blendedVertices;
|
||||
QVector<glm::vec3> _blendedNormals;
|
||||
|
||||
QSharedPointer<Texture> _eyeTexture;
|
||||
|
||||
static ProgramObject _eyeProgram;
|
||||
static DilatedTextureCache _eyeTextureCache;
|
||||
};
|
||||
|
||||
#endif /* defined(__interface__BlendFace__) */
|
||||
|
|
|
@ -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<DilatableNetworkTexture> 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<DilatableNetworkTexture>();
|
||||
}
|
||||
_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);
|
||||
|
||||
|
|
|
@ -139,10 +139,10 @@ private:
|
|||
PerlinFace _perlinFace;
|
||||
BlendFace _blendFace;
|
||||
|
||||
QSharedPointer<Texture> _irisTexture;
|
||||
QSharedPointer<Texture> _dilatedIrisTexture;
|
||||
|
||||
static ProgramObject _irisProgram;
|
||||
static DilatedTextureCache _irisTextureCache;
|
||||
static QSharedPointer<DilatableNetworkTexture> _irisTexture;
|
||||
static int _eyePositionLocation;
|
||||
|
||||
// private methods
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -205,7 +205,7 @@ QVector<glm::vec2> createVec2Vector(const QVector<double>& 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<QByteArray, int> createBlendshapeMap() {
|
|||
}
|
||||
}
|
||||
|
||||
glm::mat4 getGlobalTransform(
|
||||
const QMultiHash<qint64, qint64>& parentMap, const QHash<qint64, glm::mat4>& localTransforms, qint64 nodeID) {
|
||||
class Transform {
|
||||
public:
|
||||
bool inheritScale;
|
||||
glm::mat4 withScale;
|
||||
glm::mat4 withoutScale;
|
||||
};
|
||||
|
||||
glm::mat4 getGlobalTransform(const QMultiHash<qint64, qint64>& parentMap, const QHash<qint64, Transform>& 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<qint64> parentIDs = parentMap.values(nodeID);
|
||||
nodeID = 0;
|
||||
|
@ -314,8 +324,11 @@ FBXGeometry extractFBXGeometry(const FBXNode& node) {
|
|||
QVector<ExtractedBlendshape> blendshapes;
|
||||
QMultiHash<qint64, qint64> parentMap;
|
||||
QMultiHash<qint64, qint64> childMap;
|
||||
QHash<qint64, glm::mat4> localTransforms;
|
||||
QHash<qint64, Transform> localTransforms;
|
||||
QHash<qint64, glm::mat4> transformLinkMatrices;
|
||||
QHash<qint64, QByteArray> textureFilenames;
|
||||
QHash<qint64, qint64> diffuseTextures;
|
||||
QHash<qint64, qint64> 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<qint64>();
|
||||
|
||||
} 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<qint64>();
|
||||
|
||||
} 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<qint64>();
|
||||
}
|
||||
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<double>(),
|
||||
property.properties.at(5).value<double>(),
|
||||
property.properties.at(6).value<double>());
|
||||
|
||||
} 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<qint64>(),
|
||||
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<qint64>(), transform);
|
||||
|
||||
} else if (object.name == "Texture") {
|
||||
foreach (const FBXNode& subobject, object.children) {
|
||||
if (subobject.name == "RelativeFilename") {
|
||||
textureFilenames.insert(object.properties.at(0).value<qint64>(),
|
||||
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<qint64>(),
|
||||
connection.properties.at(1).value<qint64>());
|
||||
|
||||
} else if (connection.properties.at(3) == "Bump") {
|
||||
bumpTextures.insert(connection.properties.at(2).value<qint64>(),
|
||||
connection.properties.at(1).value<qint64>());
|
||||
}
|
||||
}
|
||||
parentMap.insert(connection.properties.at(1).value<qint64>(), connection.properties.at(2).value<qint64>());
|
||||
childMap.insert(connection.properties.at(2).value<qint64>(), connection.properties.at(1).value<qint64>());
|
||||
}
|
||||
|
@ -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;
|
||||
|
|
|
@ -53,6 +53,9 @@ public:
|
|||
|
||||
bool isEye;
|
||||
|
||||
QByteArray diffuseFilename;
|
||||
QByteArray normalFilename;
|
||||
|
||||
QVector<FBXBlendshape> blendshapes;
|
||||
};
|
||||
|
||||
|
|
|
@ -7,6 +7,9 @@
|
|||
|
||||
#include <cmath>
|
||||
|
||||
#include <QNetworkReply>
|
||||
|
||||
#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);
|
||||
}
|
||||
}
|
||||
|
||||
QSharedPointer<NetworkGeometry> GeometryCache::getGeometry(const QUrl& url) {
|
||||
QSharedPointer<NetworkGeometry> geometry = _networkGeometry.value(url);
|
||||
if (geometry.isNull()) {
|
||||
geometry = QSharedPointer<NetworkGeometry>(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;
|
||||
}
|
||||
|
|
|
@ -10,9 +10,20 @@
|
|||
#define __interface__GeometryCache__
|
||||
|
||||
#include <QHash>
|
||||
#include <QObject>
|
||||
#include <QSharedPointer>
|
||||
#include <QWeakPointer>
|
||||
|
||||
#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<NetworkGeometry> getGeometry(const QUrl& url);
|
||||
|
||||
private:
|
||||
|
||||
typedef QPair<int, int> IntPair;
|
||||
|
@ -30,6 +44,46 @@ private:
|
|||
QHash<IntPair, VerticesIndices> _hemisphereVBOs;
|
||||
QHash<IntPair, VerticesIndices> _squareVBOs;
|
||||
QHash<IntPair, VerticesIndices> _halfCylinderVBOs;
|
||||
|
||||
QHash<QUrl, QWeakPointer<NetworkGeometry> > _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<NetworkMesh>& getMeshes() const { return _meshes; }
|
||||
|
||||
private slots:
|
||||
|
||||
void handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
|
||||
void handleReplyError();
|
||||
|
||||
private:
|
||||
|
||||
QNetworkReply* _reply;
|
||||
|
||||
FBXGeometry _geometry;
|
||||
QVector<NetworkMesh> _meshes;
|
||||
};
|
||||
|
||||
/// The state associated with a single mesh.
|
||||
class NetworkMesh {
|
||||
public:
|
||||
|
||||
GLuint indexBufferID;
|
||||
GLuint vertexBufferID;
|
||||
|
||||
QSharedPointer<NetworkTexture> diffuseTexture;
|
||||
QSharedPointer<NetworkTexture> normalTexture;
|
||||
};
|
||||
|
||||
#endif /* defined(__interface__GeometryCache__) */
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include "InterfaceConfig.h"
|
||||
|
||||
#include <QGLWidget>
|
||||
#include <QNetworkReply>
|
||||
#include <QOpenGLFramebufferObject>
|
||||
|
||||
#include <glm/gtc/random.hpp>
|
||||
|
@ -83,6 +84,15 @@ GLuint TextureCache::getFileTextureID(const QString& filename) {
|
|||
return id;
|
||||
}
|
||||
|
||||
QSharedPointer<NetworkTexture> TextureCache::getTexture(const QUrl& url, bool dilatable) {
|
||||
QSharedPointer<NetworkTexture> texture = _networkTextures.value(url);
|
||||
if (texture.isNull()) {
|
||||
texture = QSharedPointer<NetworkTexture>(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<Texture> DilatedTextureCache::getTexture(float dilation) {
|
||||
QSharedPointer<Texture> 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<Texture> DilatableNetworkTexture::getDilatedTexture(float dilation) {
|
||||
QSharedPointer<Texture> texture = _dilatedTextures.value(dilation);
|
||||
if (texture.isNull()) {
|
||||
texture = QSharedPointer<Texture>(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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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<NetworkTexture> 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<QString, GLuint> _fileTextureIDs;
|
||||
|
||||
QHash<QUrl, QWeakPointer<NetworkTexture> > _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<Texture> 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<Texture> getDilatedTexture(float dilation);
|
||||
|
||||
protected:
|
||||
|
||||
virtual void imageLoaded(const QImage& image);
|
||||
|
||||
private:
|
||||
|
||||
QImage _image;
|
||||
int _innerRadius;
|
||||
int _outerRadius;
|
||||
|
||||
QMap<float, QWeakPointer<Texture> > _textures;
|
||||
QMap<float, QWeakPointer<Texture> > _dilatedTextures;
|
||||
};
|
||||
|
||||
#endif /* defined(__interface__TextureCache__) */
|
||||
|
|
Loading…
Reference in a new issue