Merge branch 'master' of https://github.com/worklist/hifi into voxeleditsender_improvements

This commit is contained in:
ZappoMan 2013-10-02 15:02:33 -07:00
commit 51fa5f4f47
16 changed files with 495 additions and 224 deletions

View file

@ -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();
}

View file

@ -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);
}

View file

@ -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");

View file

@ -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);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 KiB

View file

@ -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();
}

View file

@ -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__) */

View file

@ -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);

View file

@ -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

View file

@ -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);

View file

@ -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;

View file

@ -53,6 +53,9 @@ public:
bool isEye;
QByteArray diffuseFilename;
QByteArray normalFilename;
QVector<FBXBlendshape> blendshapes;
};

View file

@ -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;
}

View file

@ -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__) */

View file

@ -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;
}

View file

@ -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__) */