Merge pull request #976 from ey6es/blendface

Include other meshes (eyes, teeth) from the FBX files.
This commit is contained in:
Philip Rosedale 2013-09-25 10:59:27 -07:00
commit 2db8985c63
6 changed files with 221 additions and 109 deletions

View file

@ -17,22 +17,18 @@ using namespace std;
BlendFace::BlendFace(Head* owningHead) :
_owningHead(owningHead),
_modelReply(NULL),
_iboID(0)
_modelReply(NULL)
{
// we may have been created in the network thread, but we live in the main thread
moveToThread(Application::getInstance()->thread());
}
BlendFace::~BlendFace() {
if (_iboID != 0) {
glDeleteBuffers(1, &_iboID);
glDeleteBuffers(1, &_vboID);
}
deleteGeometry();
}
bool BlendFace::render(float alpha) {
if (_iboID == 0) {
if (_meshIDs.isEmpty()) {
return false;
}
@ -41,62 +37,87 @@ bool BlendFace::render(float alpha) {
glm::quat orientation = _owningHead->getOrientation();
glm::vec3 axis = glm::axis(orientation);
glRotatef(glm::angle(orientation), axis.x, axis.y, axis.z);
glTranslatef(0.0f, -0.025f, -0.025f); // temporary fudge factor until we have a better method of per-model positioning
const glm::vec3 MODEL_TRANSLATION(0.0f, -0.025f, -0.025f); // temporary fudge factor
glTranslatef(MODEL_TRANSLATION.x, MODEL_TRANSLATION.y, MODEL_TRANSLATION.z);
const float MODEL_SCALE = 0.0006f;
glScalef(_owningHead->getScale() * MODEL_SCALE, _owningHead->getScale() * MODEL_SCALE,
glm::vec3 scale(_owningHead->getScale() * MODEL_SCALE, _owningHead->getScale() * MODEL_SCALE,
-_owningHead->getScale() * MODEL_SCALE);
glScalef(scale.x, scale.y, scale.z);
// start with the base
int vertexCount = _geometry.vertices.size();
int normalCount = _geometry.normals.size();
_blendedVertices.resize(vertexCount);
_blendedNormals.resize(normalCount);
memcpy(_blendedVertices.data(), _geometry.vertices.constData(), vertexCount * sizeof(glm::vec3));
memcpy(_blendedNormals.data(), _geometry.normals.constData(), normalCount * sizeof(glm::vec3));
// 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 >= _geometry.blendshapes.size() || _geometry.blendshapes[i].vertices.isEmpty()) {
continue;
}
const float NORMAL_COEFFICIENT_SCALE = 0.01f;
float normalCoefficient = coefficient * NORMAL_COEFFICIENT_SCALE;
const glm::vec3* vertex = _geometry.blendshapes[i].vertices.constData();
const glm::vec3* normal = _geometry.blendshapes[i].normals.constData();
for (const int* index = _geometry.blendshapes[i].indices.constData(),
*end = index + _geometry.blendshapes[i].indices.size(); index != end; index++, vertex++, normal++) {
_blendedVertices[*index] += *vertex * coefficient;
_blendedNormals[*index] += *normal * normalCoefficient;
}
}
// use the head skin color
glColor4f(_owningHead->getSkinColor().r, _owningHead->getSkinColor().g, _owningHead->getSkinColor().b, alpha);
// update the blended vertices
glBindBuffer(GL_ARRAY_BUFFER, _vboID);
glBufferSubData(GL_ARRAY_BUFFER, 0, vertexCount * sizeof(glm::vec3), _blendedVertices.constData());
glBufferSubData(GL_ARRAY_BUFFER, vertexCount * sizeof(glm::vec3),
normalCount * sizeof(glm::vec3), _blendedNormals.constData());
// tell OpenGL where to find vertex information
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_FLOAT, 0, 0);
glEnableClientState(GL_NORMAL_ARRAY);
glNormalPointer(GL_FLOAT, 0, (void*)(vertexCount * sizeof(glm::vec3)));
// enable normalization under the expectation that the GPU can do it faster
glEnable(GL_NORMALIZE);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _iboID);
glDrawRangeElementsEXT(GL_QUADS, 0, vertexCount - 1, _geometry.quadIndices.size(), GL_UNSIGNED_INT, 0);
glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertexCount - 1, _geometry.triangleIndices.size(), GL_UNSIGNED_INT,
(void*)(_geometry.quadIndices.size() * sizeof(int)));
glDisable(GL_NORMALIZE);
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);
const FBXMesh& mesh = _geometry.meshes.at(i);
int vertexCount = mesh.vertices.size();
// apply eye rotation if appropriate
if (mesh.isEye) {
glPushMatrix();
glTranslatef(mesh.pivot.x, mesh.pivot.y, mesh.pivot.z);
glm::quat rotation = glm::inverse(orientation) * _owningHead->getEyeRotation(orientation *
(mesh.pivot * scale + MODEL_TRANSLATION) + _owningHead->getPosition());
glm::vec3 rotationAxis = glm::axis(rotation);
glRotatef(glm::angle(rotation), rotationAxis.x, rotationAxis.y, rotationAxis.z);
glTranslatef(-mesh.pivot.x, -mesh.pivot.y, -mesh.pivot.z);
}
// all meshes after the first are white
if (i == 1) {
glColor4f(1.0f, 1.0f, 1.0f, alpha);
}
if (!mesh.blendshapes.isEmpty()) {
_blendedVertices.resize(max(_blendedVertices.size(), vertexCount));
_blendedNormals.resize(_blendedVertices.size());
memcpy(_blendedVertices.data(), mesh.vertices.constData(), vertexCount * sizeof(glm::vec3));
memcpy(_blendedNormals.data(), mesh.normals.constData(), vertexCount * sizeof(glm::vec3));
// 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()) {
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++) {
_blendedVertices[*index] += *vertex * coefficient;
_blendedNormals[*index] += *normal * normalCoefficient;
}
}
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)));
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) {
glPopMatrix();
}
}
glDisable(GL_NORMALIZE);
// deactivate vertex arrays after drawing
glDisableClientState(GL_NORMAL_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
@ -171,31 +192,50 @@ void BlendFace::handleModelReplyError() {
}
void BlendFace::setGeometry(const FBXGeometry& geometry) {
if (geometry.vertices.isEmpty()) {
// clear any existing geometry
if (_iboID != 0) {
glDeleteBuffers(1, &_iboID);
glDeleteBuffers(1, &_vboID);
_iboID = 0;
}
// clear any existing geometry
deleteGeometry();
if (geometry.meshes.isEmpty()) {
return;
}
if (_iboID == 0) {
glGenBuffers(1, &_iboID);
glGenBuffers(1, &_vboID);
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),
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());
} 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);
}
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _iboID);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, (geometry.quadIndices.size() + geometry.triangleIndices.size()) * sizeof(int),
NULL, GL_STATIC_DRAW);
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, geometry.quadIndices.size() * sizeof(int), geometry.quadIndices.constData());
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, geometry.quadIndices.size() * sizeof(int),
geometry.triangleIndices.size() * sizeof(int), geometry.triangleIndices.constData());
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindBuffer(GL_ARRAY_BUFFER, _vboID);
glBufferData(GL_ARRAY_BUFFER, (geometry.vertices.size() + geometry.normals.size()) * sizeof(glm::vec3),
NULL, GL_DYNAMIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
_geometry = geometry;
}
void BlendFace::deleteGeometry() {
foreach (const VerticesIndices& meshIDs, _meshIDs) {
glDeleteBuffers(1, &meshIDs.first);
glDeleteBuffers(1, &meshIDs.second);
}
_meshIDs.clear();
}

View file

@ -28,7 +28,7 @@ public:
BlendFace(Head* owningHead);
~BlendFace();
bool isActive() const { return _iboID != 0; }
bool isActive() const { return !_meshIDs.isEmpty(); }
bool render(float alpha);
@ -43,6 +43,7 @@ private slots:
private:
void setGeometry(const FBXGeometry& geometry);
void deleteGeometry();
Head* _owningHead;
@ -50,8 +51,8 @@ private:
QNetworkReply* _modelReply;
GLuint _iboID;
GLuint _vboID;
typedef QPair<GLuint, GLuint> VerticesIndices;
QVector<VerticesIndices> _meshIDs;
FBXGeometry _geometry;
QVector<glm::vec3> _blendedVertices;

View file

@ -459,6 +459,11 @@ glm::quat Head::getCameraOrientation () const {
* glm::quat(glm::radians(glm::vec3(_cameraPitch + _mousePitch, _cameraYaw, 0.0f)));
}
glm::quat Head::getEyeRotation(const glm::vec3& eyePosition) const {
glm::quat orientation = getOrientation();
return rotationBetween(orientation * IDENTITY_FRONT, _lookAtPosition + _saccade - eyePosition) * orientation;
}
void Head::renderHeadSphere() {
glPushMatrix();
glTranslatef(_position.x, _position.y, _position.z); //translate to head position
@ -663,17 +668,13 @@ void Head::renderEyeBalls() {
glBindTexture(GL_TEXTURE_2D, _irisTextureID);
glEnable(GL_TEXTURE_2D);
glm::quat orientation = getOrientation();
glm::vec3 front = orientation * IDENTITY_FRONT;
// render left iris
glm::quat leftIrisRotation;
glPushMatrix(); {
glTranslatef(_leftEyePosition.x, _leftEyePosition.y, _leftEyePosition.z); //translate to eyeball position
//rotate the eyeball to aim towards the lookat position
glm::vec3 targetLookatVector = _lookAtPosition + _saccade - _leftEyePosition;
leftIrisRotation = rotationBetween(front, targetLookatVector) * orientation;
leftIrisRotation = getEyeRotation(_leftEyePosition);
glm::vec3 rotationAxis = glm::axis(leftIrisRotation);
glRotatef(glm::angle(leftIrisRotation), rotationAxis.x, rotationAxis.y, rotationAxis.z);
glTranslatef(0.0f, 0.0f, -_scale * IRIS_PROTRUSION);
@ -697,8 +698,7 @@ void Head::renderEyeBalls() {
glTranslatef(_rightEyePosition.x, _rightEyePosition.y, _rightEyePosition.z); //translate to eyeball position
//rotate the eyeball to aim towards the lookat position
glm::vec3 targetLookatVector = _lookAtPosition + _saccade - _rightEyePosition;
rightIrisRotation = rotationBetween(front, targetLookatVector) * orientation;
rightIrisRotation = getEyeRotation(_rightEyePosition);
glm::vec3 rotationAxis = glm::axis(rightIrisRotation);
glRotatef(glm::angle(rightIrisRotation), rotationAxis.x, rotationAxis.y, rotationAxis.z);
glTranslatef(0.0f, 0.0f, -_scale * IRIS_PROTRUSION);

View file

@ -72,6 +72,8 @@ public:
glm::vec3 getUpDirection() const { return getOrientation() * IDENTITY_UP; }
glm::vec3 getFrontDirection() const { return getOrientation() * IDENTITY_FRONT; }
glm::quat getEyeRotation(const glm::vec3& eyePosition) const;
Face& getFace() { return _face; }
BlendFace& getBlendFace() { return _blendFace; }

View file

@ -189,7 +189,10 @@ FBXNode parseFBX(QIODevice* device) {
QVector<glm::vec3> createVec3Vector(const QVector<double>& doubleVector) {
QVector<glm::vec3> values;
for (const double* it = doubleVector.constData(), *end = it + doubleVector.size(); it != end; ) {
values.append(glm::vec3(*it++, *it++, *it++));
float x = *it++;
float y = *it++;
float z = *it++;
values.append(glm::vec3(x, y, z));
}
return values;
}
@ -259,18 +262,28 @@ QHash<QByteArray, int> createBlendshapeMap() {
}
}
class ExtractedBlendshape {
public:
qint64 id;
int index;
FBXBlendshape blendshape;
};
FBXGeometry extractFBXGeometry(const FBXNode& node) {
QVector<FBXBlendshape> blendshapes;
QHash<qint64, FBXGeometry> meshMap;
qint64 blendshapeId = 0;
QHash<qint64, FBXMesh> meshes;
QVector<ExtractedBlendshape> blendshapes;
QHash<qint64, qint64> parentMap;
QMultiHash<qint64, qint64> childMap;
QHash<qint64, glm::vec3> pivots;
qint64 jointEyeLeftID = 0;
qint64 jointEyeRightID = 0;
foreach (const FBXNode& child, node.children) {
if (child.name == "Objects") {
foreach (const FBXNode& object, child.children) {
if (object.name == "Geometry") {
if (object.properties.at(2) == "Mesh") {
FBXGeometry mesh;
FBXMesh mesh;
QVector<glm::vec3> normals;
QVector<int> polygonIndices;
@ -323,50 +336,95 @@ FBXGeometry extractFBXGeometry(const FBXNode& node) {
beginIndex = endIndex;
}
}
meshMap.insert(object.properties.at(0).value<qint64>(), mesh);
meshes.insert(object.properties.at(0).value<qint64>(), mesh);
} else { // object.properties.at(2) == "Shape"
FBXBlendshape blendshape;
ExtractedBlendshape extracted = { object.properties.at(0).value<qint64>() };
foreach (const FBXNode& data, object.children) {
if (data.name == "Indexes") {
blendshape.indices = data.properties.at(0).value<QVector<int> >();
extracted.blendshape.indices = data.properties.at(0).value<QVector<int> >();
} else if (data.name == "Vertices") {
blendshape.vertices = createVec3Vector(data.properties.at(0).value<QVector<double> >());
extracted.blendshape.vertices = createVec3Vector(
data.properties.at(0).value<QVector<double> >());
} else if (data.name == "Normals") {
blendshape.normals = createVec3Vector(data.properties.at(0).value<QVector<double> >());
extracted.blendshape.normals = createVec3Vector(
data.properties.at(0).value<QVector<double> >());
}
}
// the name is followed by a null and some type info
QByteArray name = object.properties.at(1).toByteArray();
static QHash<QByteArray, int> blendshapeMap = createBlendshapeMap();
int index = blendshapeMap.value(name.left(name.indexOf('\0')));
blendshapes.resize(qMax(blendshapes.size(), index + 1));
blendshapes[index] = blendshape;
extracted.index = blendshapeMap.value(name.left(name.indexOf('\0')));
blendshapes.append(extracted);
}
} else if (object.name == "Model" && object.properties.at(2) == "LimbNode") {
if (object.properties.at(1).toByteArray().startsWith("jointEyeLeft")) {
jointEyeLeftID = object.properties.at(0).value<qint64>();
} else if (object.properties.at(1).toByteArray().startsWith("jointEyeRight")) {
jointEyeRightID = object.properties.at(0).value<qint64>();
}
} else if (object.name == "Deformer" && object.properties.at(2) == "Cluster") {
foreach (const FBXNode& subobject, object.children) {
if (subobject.name == "TransformLink") {
QVector<double> values = subobject.properties.at(0).value<QVector<double> >();
pivots.insert(object.properties.at(0).value<qint64>(),
glm::vec3(values.at(12), values.at(13), values.at(14))); // matrix translation component
}
}
} else if (object.name == "Deformer" && object.properties.at(2) == "BlendShape") {
blendshapeId = object.properties.at(0).value<qint64>();
}
}
} else if (child.name == "Connections") {
foreach (const FBXNode& connection, child.children) {
if (connection.name == "C") {
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>());
}
}
}
}
// get the mesh that owns the blendshape
FBXGeometry geometry;
if (meshMap.size() == 1) {
geometry = *meshMap.begin();
} else {
geometry = meshMap.take(parentMap.value(blendshapeId));
// assign the blendshapes to their corresponding meshes
foreach (const ExtractedBlendshape& extracted, blendshapes) {
qint64 blendshapeChannelID = parentMap.value(extracted.id);
qint64 blendshapeID = parentMap.value(blendshapeChannelID);
qint64 meshID = parentMap.value(blendshapeID);
FBXMesh& mesh = meshes[meshID];
mesh.blendshapes.resize(max(mesh.blendshapes.size(), extracted.index + 1));
mesh.blendshapes[extracted.index] = extracted.blendshape;
}
// as a temporary hack, put the mesh with the most blendshapes on top; assume it to be the face
FBXGeometry geometry;
int mostBlendshapes = 0;
for (QHash<qint64, FBXMesh>::iterator it = meshes.begin(); it != meshes.end(); it++) {
FBXMesh& mesh = it.value();
// look for a limb pivot
foreach (qint64 childID, childMap.values(it.key())) {
qint64 clusterID = childMap.value(childID);
if (pivots.contains(clusterID)) {
mesh.pivot = pivots.value(clusterID);
qint64 jointID = childMap.value(clusterID);
if (jointID == jointEyeLeftID || jointID == jointEyeRightID) {
mesh.isEye = true;
}
}
}
if (mesh.blendshapes.size() > mostBlendshapes) {
geometry.meshes.prepend(mesh);
mostBlendshapes = mesh.blendshapes.size();
} else {
geometry.meshes.append(mesh);
}
}
geometry.blendshapes = blendshapes;
return geometry;
}

View file

@ -38,8 +38,8 @@ public:
QVector<glm::vec3> normals;
};
/// Base geometry with blendshapes mapped by name.
class FBXGeometry {
/// A single mesh (with optional blendshapes) extracted from an FBX document.
class FBXMesh {
public:
QVector<int> quadIndices;
@ -47,9 +47,20 @@ public:
QVector<glm::vec3> vertices;
QVector<glm::vec3> normals;
glm::vec3 pivot;
bool isEye;
QVector<FBXBlendshape> blendshapes;
};
/// A set of meshes extracted from an FBX document.
class FBXGeometry {
public:
QVector<FBXMesh> meshes;
};
/// Parses the input from the supplied data as an FBX file.
/// \exception QString if an error occurs in parsing
FBXNode parseFBX(const QByteArray& data);