From 3448ceccd212d0bd7d79bb7df164f2f70037074c Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Mon, 7 Oct 2013 17:25:46 -0700 Subject: [PATCH 1/9] Progress towards a spring-mass model. --- interface/src/avatar/BlendFace.cpp | 161 +++++++++++++++++++++------ interface/src/avatar/BlendFace.h | 10 ++ interface/src/avatar/Head.cpp | 2 + interface/src/renderer/FBXReader.cpp | 51 +++++++-- interface/src/renderer/FBXReader.h | 5 + 5 files changed, 190 insertions(+), 39 deletions(-) diff --git a/interface/src/avatar/BlendFace.cpp b/interface/src/avatar/BlendFace.cpp index d376c976eb..b857a56b72 100644 --- a/interface/src/avatar/BlendFace.cpp +++ b/interface/src/avatar/BlendFace.cpp @@ -8,6 +8,8 @@ #include +#include + #include "Application.h" #include "BlendFace.h" #include "Head.h" @@ -41,21 +43,104 @@ void BlendFace::init() { } } +void BlendFace::reset() { + _resetStates = true; +} + const glm::vec3 MODEL_TRANSLATION(0.0f, -120.0f, 40.0f); // temporary fudge factor const float MODEL_SCALE = 0.0006f; -bool BlendFace::render(float alpha) { +void BlendFace::simulate(float deltaTime) { if (!isActive()) { + return; + } + + // set up world vertices on first simulate after load + const FBXGeometry& geometry = _geometry->getFBXGeometry(); + bool first = false; + if (_meshStates.isEmpty()) { + QVector vertices; + foreach (const FBXMesh& mesh, geometry.meshes) { + MeshState state; + if (mesh.springiness > 0.0f) { + state.worldSpaceVertices.resize(mesh.vertices.size()); + state.worldSpaceNormals.resize(mesh.vertices.size()); + } + _meshStates.append(state); + } + _resetStates = true; + } + + glm::mat4 baseTransform = glm::translate(_owningHead->getPosition()) * glm::mat4_cast(_owningHead->getOrientation()) * + glm::scale(glm::vec3(-1.0f, 1.0f, -1.0f) * _owningHead->getScale() * MODEL_SCALE) * + glm::translate(MODEL_TRANSLATION - _geometry->getFBXGeometry().neckPivot); + + for (int i = 0; i < _meshStates.size(); i++) { + MeshState& state = _meshStates[i]; + int vertexCount = state.worldSpaceVertices.size(); + if (vertexCount == 0) { + continue; + } + glm::vec3* destVertices = state.worldSpaceVertices.data(); + glm::vec3* destNormals = state.worldSpaceNormals.data(); + const FBXMesh& mesh = geometry.meshes.at(i); + const glm::vec3* sourceVertices = mesh.vertices.constData(); + if (!mesh.blendshapes.isEmpty()) { + _blendedVertices.resize(max(_blendedVertices.size(), vertexCount)); + memcpy(_blendedVertices.data(), mesh.vertices.constData(), vertexCount * sizeof(glm::vec3)); + + // blend in each coefficient + const vector& coefficients = _owningHead->getBlendshapeCoefficients(); + 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 glm::vec3* vertex = mesh.blendshapes[j].vertices.constData(); + for (const int* index = mesh.blendshapes[j].indices.constData(), + *end = index + mesh.blendshapes[j].indices.size(); index != end; index++, vertex++) { + _blendedVertices[*index] += *vertex * coefficient; + } + } + + sourceVertices = _blendedVertices.constData(); + } + if (_resetStates) { + for (int j = 0; j < vertexCount; j++) { + destVertices[j] = glm::vec3(baseTransform * glm::vec4(sourceVertices[j], 1.0f)); + } + _resetStates = false; + + } else { + for (int j = 0; j < vertexCount; j++) { + destVertices[j] = glm::mix(destVertices[j], glm::vec3(baseTransform * glm::vec4(sourceVertices[j], 1.0f)), 0.25f); + } + } + for (int j = 0; j < vertexCount; j++) { + destNormals[j] = glm::vec3(0.0f, 0.0f, 0.0f); + + const glm::vec3& middle = destVertices[j]; + for (QVarLengthArray, 4>::const_iterator connection = mesh.vertexConnections.at(j).constBegin(); + connection != mesh.vertexConnections.at(j).constEnd(); connection++) { + destNormals[j] += glm::normalize(glm::cross(destVertices[connection->second] - middle, + destVertices[connection->first] - middle)); + } + } + } +} + +bool BlendFace::render(float alpha) { + if (_meshStates.isEmpty()) { return false; } - // set up blended buffer ids on first render after load + // set up blended buffer ids on first render after load/simulate const FBXGeometry& geometry = _geometry->getFBXGeometry(); const QVector& networkMeshes = _geometry->getMeshes(); if (_blendedVertexBufferIDs.isEmpty()) { foreach (const FBXMesh& mesh, geometry.meshes) { GLuint id = 0; - if (!mesh.blendshapes.isEmpty()) { + if (!mesh.blendshapes.isEmpty() || mesh.springiness > 0.0f) { glGenBuffers(1, &id); glBindBuffer(GL_ARRAY_BUFFER, id); glBufferData(GL_ARRAY_BUFFER, (mesh.vertices.size() + mesh.normals.size()) * sizeof(glm::vec3), @@ -69,6 +154,9 @@ bool BlendFace::render(float alpha) { _dilatedTextures.resize(geometry.meshes.size()); } + glm::mat4 viewMatrix; + glGetFloatv(GL_MODELVIEW_MATRIX, (GLfloat*)&viewMatrix); + glPushMatrix(); glTranslatef(_owningHead->getPosition().x, _owningHead->getPosition().y, _owningHead->getPosition().z); glm::quat orientation = _owningHead->getOrientation(); @@ -130,39 +218,49 @@ bool BlendFace::render(float alpha) { glBindTexture(GL_TEXTURE_2D, texture == NULL ? 0 : texture->getID()); glBindBuffer(GL_ARRAY_BUFFER, networkMesh.vertexBufferID); - if (mesh.blendshapes.isEmpty()) { + if (mesh.blendshapes.isEmpty() && mesh.springiness == 0.0f) { 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)); - memcpy(_blendedNormals.data(), mesh.normals.constData(), vertexCount * sizeof(glm::vec3)); - - // blend in each coefficient - const vector& coefficients = _owningHead->getBlendshapeCoefficients(); - 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[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()); + + const MeshState& state = _meshStates.at(i); + if (!state.worldSpaceVertices.isEmpty()) { + glLoadMatrixf((const GLfloat*)&viewMatrix); + + glBufferSubData(GL_ARRAY_BUFFER, 0, vertexCount * sizeof(glm::vec3), state.worldSpaceVertices.constData()); + glBufferSubData(GL_ARRAY_BUFFER, vertexCount * sizeof(glm::vec3), + vertexCount * sizeof(glm::vec3), state.worldSpaceNormals.constData()); + + } else { + _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& coefficients = _owningHead->getBlendshapeCoefficients(); + 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[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; + } + } + + 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))); @@ -243,4 +341,5 @@ void BlendFace::deleteGeometry() { glDeleteBuffers(1, &id); } _blendedVertexBufferIDs.clear(); + _meshStates.clear(); } diff --git a/interface/src/avatar/BlendFace.h b/interface/src/avatar/BlendFace.h index 9307dc5c89..6851d63261 100644 --- a/interface/src/avatar/BlendFace.h +++ b/interface/src/avatar/BlendFace.h @@ -33,6 +33,8 @@ public: bool isActive() const { return _geometry && _geometry->isLoaded(); } void init(); + void reset(); + void simulate(float deltaTime); bool render(float alpha); Q_INVOKABLE void setModelURL(const QUrl& url); @@ -50,8 +52,16 @@ private: QSharedPointer _geometry; + class MeshState { + public: + QVector worldSpaceVertices; + QVector worldSpaceNormals; + }; + + QVector _meshStates; QVector _blendedVertexBufferIDs; QVector > _dilatedTextures; + bool _resetStates; QVector _blendedVertices; QVector _blendedNormals; diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index e54d2fb18c..bd5847a44f 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -235,6 +235,8 @@ void Head::simulate(float deltaTime, bool isMine) { if (USING_PHYSICAL_MOHAWK) { updateHairPhysics(deltaTime); } + + _blendFace.simulate(deltaTime); } void Head::calculateGeometry() { diff --git a/interface/src/renderer/FBXReader.cpp b/interface/src/renderer/FBXReader.cpp index afacc86204..2390aa4991 100644 --- a/interface/src/renderer/FBXReader.cpp +++ b/interface/src/renderer/FBXReader.cpp @@ -300,6 +300,7 @@ const char* FACESHIFT_BLENDSHAPES[] = { class Transform { public: + QByteArray name; bool inheritScale; glm::mat4 withScale; glm::mat4 withoutScale; @@ -536,7 +537,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) glm::vec3 preRotation, rotation, postRotation; glm::vec3 scale = glm::vec3(1.0f, 1.0f, 1.0f); glm::vec3 scalePivot, rotationPivot; - Transform transform = { true }; + Transform transform = { name, true }; foreach (const FBXNode& subobject, object.children) { if (subobject.name == "Properties70") { foreach (const FBXNode& property, subobject.children) { @@ -679,14 +680,14 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) mapping.value("ry").toFloat(), mapping.value("rz").toFloat())))) * glm::scale(offsetScale, offsetScale, offsetScale); - // as a temporary hack, put the mesh with the most blendshapes on top; assume it to be the face FBXGeometry geometry; - int mostBlendshapes = 0; + QVariantHash springs = mapping.value("spring").toHash(); for (QHash::iterator it = meshes.begin(); it != meshes.end(); it++) { FBXMesh& mesh = it.value(); // accumulate local transforms qint64 modelID = parentMap.value(it.key()); + mesh.springiness = springs.value(localTransforms.value(modelID).name).toFloat(); glm::mat4 modelTransform = getGlobalTransform(parentMap, localTransforms, modelID); // look for textures, material properties @@ -731,13 +732,47 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) } } - if (mesh.blendshapes.size() > mostBlendshapes) { - geometry.meshes.prepend(mesh); - mostBlendshapes = mesh.blendshapes.size(); + // extract spring edges, connections if springy + if (mesh.springiness > 0.0f) { + QSet > edges; - } else { - geometry.meshes.append(mesh); + mesh.vertexConnections.resize(mesh.vertices.size()); + for (int i = 0; i < mesh.quadIndices.size(); i += 4) { + int index0 = mesh.quadIndices.at(i); + int index1 = mesh.quadIndices.at(i + 1); + int index2 = mesh.quadIndices.at(i + 2); + int index3 = mesh.quadIndices.at(i + 3); + + edges.insert(QPair(qMin(index0, index1), qMax(index0, index1))); + edges.insert(QPair(qMin(index1, index2), qMax(index1, index2))); + edges.insert(QPair(qMin(index2, index3), qMax(index2, index3))); + edges.insert(QPair(qMin(index3, index0), qMax(index3, index0))); + + mesh.vertexConnections[index0].append(QPair(index3, index1)); + mesh.vertexConnections[index1].append(QPair(index0, index2)); + mesh.vertexConnections[index2].append(QPair(index1, index3)); + mesh.vertexConnections[index3].append(QPair(index2, index0)); + } + for (int i = 0; i < mesh.triangleIndices.size(); i += 3) { + int index0 = mesh.triangleIndices.at(i); + int index1 = mesh.triangleIndices.at(i + 1); + int index2 = mesh.triangleIndices.at(i + 2); + + edges.insert(QPair(qMin(index0, index1), qMax(index0, index1))); + edges.insert(QPair(qMin(index1, index2), qMax(index1, index2))); + edges.insert(QPair(qMin(index2, index0), qMax(index2, index0))); + + mesh.vertexConnections[index0].append(QPair(index2, index1)); + mesh.vertexConnections[index1].append(QPair(index0, index2)); + mesh.vertexConnections[index2].append(QPair(index1, index0)); + } + + for (QSet >::const_iterator edge = edges.constBegin(); edge != edges.constEnd(); edge++) { + mesh.springEdges.append(*edge); + } } + + geometry.meshes.append(mesh); } // extract translation component for neck pivot diff --git a/interface/src/renderer/FBXReader.h b/interface/src/renderer/FBXReader.h index 310dea265f..5c2ffd9ee0 100644 --- a/interface/src/renderer/FBXReader.h +++ b/interface/src/renderer/FBXReader.h @@ -9,6 +9,7 @@ #ifndef __interface__FBXReader__ #define __interface__FBXReader__ +#include #include #include @@ -59,6 +60,10 @@ public: QByteArray normalFilename; QVector blendshapes; + + float springiness; + QVector > springEdges; + QVector, 4> > vertexConnections; }; /// A set of meshes extracted from an FBX document. From b0bc122a48617432e5a736f884ddc4fca60aa15f Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Mon, 7 Oct 2013 18:02:03 -0700 Subject: [PATCH 2/9] Got some actual damped oscillation going. --- interface/src/avatar/BlendFace.cpp | 11 +++++++++-- interface/src/avatar/BlendFace.h | 1 + 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/interface/src/avatar/BlendFace.cpp b/interface/src/avatar/BlendFace.cpp index b857a56b72..0857a2c24f 100644 --- a/interface/src/avatar/BlendFace.cpp +++ b/interface/src/avatar/BlendFace.cpp @@ -64,6 +64,7 @@ void BlendFace::simulate(float deltaTime) { MeshState state; if (mesh.springiness > 0.0f) { state.worldSpaceVertices.resize(mesh.vertices.size()); + state.vertexVelocities.resize(mesh.vertices.size()); state.worldSpaceNormals.resize(mesh.vertices.size()); } _meshStates.append(state); @@ -82,6 +83,7 @@ void BlendFace::simulate(float deltaTime) { continue; } glm::vec3* destVertices = state.worldSpaceVertices.data(); + glm::vec3* destVelocities = state.vertexVelocities.data(); glm::vec3* destNormals = state.worldSpaceNormals.data(); const FBXMesh& mesh = geometry.meshes.at(i); const glm::vec3* sourceVertices = mesh.vertices.constData(); @@ -108,16 +110,21 @@ void BlendFace::simulate(float deltaTime) { if (_resetStates) { for (int j = 0; j < vertexCount; j++) { destVertices[j] = glm::vec3(baseTransform * glm::vec4(sourceVertices[j], 1.0f)); + destVelocities[j] = glm::vec3(); } _resetStates = false; } else { + const float SPRINGINESS_MULTIPLIER = 20.0f; + const float DAMPING = 1.0f; for (int j = 0; j < vertexCount; j++) { - destVertices[j] = glm::mix(destVertices[j], glm::vec3(baseTransform * glm::vec4(sourceVertices[j], 1.0f)), 0.25f); + destVelocities[j] += ((glm::vec3(baseTransform * glm::vec4(sourceVertices[j], 1.0f)) - destVertices[j]) * + mesh.springiness * SPRINGINESS_MULTIPLIER - destVelocities[j] * DAMPING) * deltaTime; + destVertices[j] += destVelocities[j] * deltaTime; } } for (int j = 0; j < vertexCount; j++) { - destNormals[j] = glm::vec3(0.0f, 0.0f, 0.0f); + destNormals[j] = glm::vec3(); const glm::vec3& middle = destVertices[j]; for (QVarLengthArray, 4>::const_iterator connection = mesh.vertexConnections.at(j).constBegin(); diff --git a/interface/src/avatar/BlendFace.h b/interface/src/avatar/BlendFace.h index 6851d63261..bd8ad55541 100644 --- a/interface/src/avatar/BlendFace.h +++ b/interface/src/avatar/BlendFace.h @@ -55,6 +55,7 @@ private: class MeshState { public: QVector worldSpaceVertices; + QVector vertexVelocities; QVector worldSpaceNormals; }; From b22334730db1e3f07a43e99e91da43c98bd1ea3d Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 8 Oct 2013 11:01:31 -0700 Subject: [PATCH 3/9] Fix for eyes rolling back in head. --- interface/src/avatar/BlendFace.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/avatar/BlendFace.cpp b/interface/src/avatar/BlendFace.cpp index 0857a2c24f..1d567d3e01 100644 --- a/interface/src/avatar/BlendFace.cpp +++ b/interface/src/avatar/BlendFace.cpp @@ -199,7 +199,7 @@ bool BlendFace::render(float alpha) { if (mesh.isEye) { 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()); + ((mesh.pivot + offset) * scale) + _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); From ce23b415e049d075557fe022fb83e9af9c58c705 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 8 Oct 2013 11:11:01 -0700 Subject: [PATCH 4/9] Fix for macaw head without mapping file. --- interface/src/renderer/FBXReader.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/interface/src/renderer/FBXReader.cpp b/interface/src/renderer/FBXReader.cpp index 2390aa4991..872020a952 100644 --- a/interface/src/renderer/FBXReader.cpp +++ b/interface/src/renderer/FBXReader.cpp @@ -384,7 +384,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) QList mappings = blendshapeMappings.values(blendshapeName); if (mappings.isEmpty()) { blendshapeIndices.insert(blendshapeName, QPair(i, 1.0f)); - blendshapeIndices.insert("ExpressionBlendshapes." + blendshapeName, QPair(i, 1.0f)); + } else { foreach (const QVariant& mapping, mappings) { QVariantList blendshapeMapping = mapping.toList(); @@ -637,8 +637,13 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) } } else if (object.properties.at(2) == "BlendShapeChannel") { QByteArray name = object.properties.at(1).toByteArray(); + name = name.left(name.indexOf('\0')); + if (!blendshapeIndices.contains(name)) { + // try everything after the dot + name = name.mid(name.lastIndexOf('.') + 1); + } blendshapeChannelIndices.insert(object.properties.at(0).value(), - blendshapeIndices.value(name.left(name.indexOf('\0')))); + blendshapeIndices.value(name)); } } } From 506f6670832f4582fde20525975b9e844931a9f0 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 8 Oct 2013 11:26:15 -0700 Subject: [PATCH 5/9] More springy, more damp. Removed unused variable. --- interface/src/avatar/BlendFace.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/interface/src/avatar/BlendFace.cpp b/interface/src/avatar/BlendFace.cpp index 1d567d3e01..c6b098235c 100644 --- a/interface/src/avatar/BlendFace.cpp +++ b/interface/src/avatar/BlendFace.cpp @@ -57,7 +57,6 @@ void BlendFace::simulate(float deltaTime) { // set up world vertices on first simulate after load const FBXGeometry& geometry = _geometry->getFBXGeometry(); - bool first = false; if (_meshStates.isEmpty()) { QVector vertices; foreach (const FBXMesh& mesh, geometry.meshes) { @@ -115,8 +114,8 @@ void BlendFace::simulate(float deltaTime) { _resetStates = false; } else { - const float SPRINGINESS_MULTIPLIER = 20.0f; - const float DAMPING = 1.0f; + const float SPRINGINESS_MULTIPLIER = 200.0f; + const float DAMPING = 5.0f; for (int j = 0; j < vertexCount; j++) { destVelocities[j] += ((glm::vec3(baseTransform * glm::vec4(sourceVertices[j], 1.0f)) - destVertices[j]) * mesh.springiness * SPRINGINESS_MULTIPLIER - destVelocities[j] * DAMPING) * deltaTime; From 60306af3e9d2c6986c47d554dad79ac0cf04bed3 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 8 Oct 2013 13:35:02 -0700 Subject: [PATCH 6/9] Default springiness, allow springiness on non-blended meshes. --- interface/src/avatar/BlendFace.cpp | 14 +++++++++++--- interface/src/renderer/FBXReader.cpp | 3 ++- interface/src/renderer/GeometryCache.cpp | 2 +- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/interface/src/avatar/BlendFace.cpp b/interface/src/avatar/BlendFace.cpp index c6b098235c..f970a3eca8 100644 --- a/interface/src/avatar/BlendFace.cpp +++ b/interface/src/avatar/BlendFace.cpp @@ -71,9 +71,11 @@ void BlendFace::simulate(float deltaTime) { _resetStates = true; } - glm::mat4 baseTransform = glm::translate(_owningHead->getPosition()) * glm::mat4_cast(_owningHead->getOrientation()) * - glm::scale(glm::vec3(-1.0f, 1.0f, -1.0f) * _owningHead->getScale() * MODEL_SCALE) * - glm::translate(MODEL_TRANSLATION - _geometry->getFBXGeometry().neckPivot); + glm::quat orientation = _owningHead->getOrientation(); + glm::vec3 scale = glm::vec3(-1.0f, 1.0f, -1.0f) * _owningHead->getScale() * MODEL_SCALE; + glm::vec3 offset = MODEL_TRANSLATION - _geometry->getFBXGeometry().neckPivot; + glm::mat4 baseTransform = glm::translate(_owningHead->getPosition()) * glm::mat4_cast(orientation) * + glm::scale(scale) * glm::translate(offset); for (int i = 0; i < _meshStates.size(); i++) { MeshState& state = _meshStates[i]; @@ -106,6 +108,12 @@ void BlendFace::simulate(float deltaTime) { sourceVertices = _blendedVertices.constData(); } + glm::mat4 transform = baseTransform; + if (mesh.isEye) { + transform = transform * glm::translate(mesh.pivot) * glm::mat4_cast(glm::inverse(orientation) * + _owningHead->getEyeRotation(orientation * ((mesh.pivot + offset) * scale) + _owningHead->getPosition())) * + glm::translate(-mesh.pivot); + } if (_resetStates) { for (int j = 0; j < vertexCount; j++) { destVertices[j] = glm::vec3(baseTransform * glm::vec4(sourceVertices[j], 1.0f)); diff --git a/interface/src/renderer/FBXReader.cpp b/interface/src/renderer/FBXReader.cpp index bc31dc3c7c..8091687488 100644 --- a/interface/src/renderer/FBXReader.cpp +++ b/interface/src/renderer/FBXReader.cpp @@ -686,12 +686,13 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) FBXGeometry geometry; QVariantHash springs = mapping.value("spring").toHash(); + QVariant defaultSpring = springs.value("default"); for (QHash::iterator it = meshes.begin(); it != meshes.end(); it++) { FBXMesh& mesh = it.value(); // accumulate local transforms qint64 modelID = parentMap.value(it.key()); - mesh.springiness = springs.value(localTransforms.value(modelID).name).toFloat(); + mesh.springiness = springs.value(localTransforms.value(modelID).name, defaultSpring).toFloat(); glm::mat4 modelTransform = getGlobalTransform(parentMap, localTransforms, modelID); // look for textures, material properties diff --git a/interface/src/renderer/GeometryCache.cpp b/interface/src/renderer/GeometryCache.cpp index c63ec7eed4..aa717175a0 100644 --- a/interface/src/renderer/GeometryCache.cpp +++ b/interface/src/renderer/GeometryCache.cpp @@ -346,7 +346,7 @@ void NetworkGeometry::maybeReadModelWithMapping() { glGenBuffers(1, &networkMesh.vertexBufferID); glBindBuffer(GL_ARRAY_BUFFER, networkMesh.vertexBufferID); - if (mesh.blendshapes.isEmpty()) { + if (mesh.blendshapes.isEmpty() && mesh.springiness == 0.0f) { 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()); From 15f7b702690bf2d710993db6bef6eace240a7220 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 8 Oct 2013 13:54:54 -0700 Subject: [PATCH 7/9] Let's actually use the transform we constructed. --- interface/src/avatar/BlendFace.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/avatar/BlendFace.cpp b/interface/src/avatar/BlendFace.cpp index f970a3eca8..e4599ecaf4 100644 --- a/interface/src/avatar/BlendFace.cpp +++ b/interface/src/avatar/BlendFace.cpp @@ -116,7 +116,7 @@ void BlendFace::simulate(float deltaTime) { } if (_resetStates) { for (int j = 0; j < vertexCount; j++) { - destVertices[j] = glm::vec3(baseTransform * glm::vec4(sourceVertices[j], 1.0f)); + destVertices[j] = glm::vec3(transform * glm::vec4(sourceVertices[j], 1.0f)); destVelocities[j] = glm::vec3(); } _resetStates = false; @@ -125,7 +125,7 @@ void BlendFace::simulate(float deltaTime) { const float SPRINGINESS_MULTIPLIER = 200.0f; const float DAMPING = 5.0f; for (int j = 0; j < vertexCount; j++) { - destVelocities[j] += ((glm::vec3(baseTransform * glm::vec4(sourceVertices[j], 1.0f)) - destVertices[j]) * + destVelocities[j] += ((glm::vec3(transform * glm::vec4(sourceVertices[j], 1.0f)) - destVertices[j]) * mesh.springiness * SPRINGINESS_MULTIPLIER - destVelocities[j] * DAMPING) * deltaTime; destVertices[j] += destVelocities[j] * deltaTime; } From 3cca3dc894e6864460234a2a6dae89662a9865af Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 8 Oct 2013 14:06:04 -0700 Subject: [PATCH 8/9] Reset fix. --- interface/src/avatar/BlendFace.cpp | 5 ++--- interface/src/avatar/Head.cpp | 2 ++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/interface/src/avatar/BlendFace.cpp b/interface/src/avatar/BlendFace.cpp index e4599ecaf4..1dd643e0ab 100644 --- a/interface/src/avatar/BlendFace.cpp +++ b/interface/src/avatar/BlendFace.cpp @@ -118,9 +118,7 @@ void BlendFace::simulate(float deltaTime) { for (int j = 0; j < vertexCount; j++) { destVertices[j] = glm::vec3(transform * glm::vec4(sourceVertices[j], 1.0f)); destVelocities[j] = glm::vec3(); - } - _resetStates = false; - + } } else { const float SPRINGINESS_MULTIPLIER = 200.0f; const float DAMPING = 5.0f; @@ -141,6 +139,7 @@ void BlendFace::simulate(float deltaTime) { } } } + _resetStates = false; } bool BlendFace::render(float alpha) { diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index bd5847a44f..62c0f22348 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -117,6 +117,8 @@ void Head::reset() { if (USING_PHYSICAL_MOHAWK) { resetHairPhysics(); } + + _blendFace.reset(); } void Head::resetHairPhysics() { From 87bdd2fb1aa78964cdcbc3c298d4875979a74c52 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 8 Oct 2013 15:09:56 -0700 Subject: [PATCH 9/9] When in mirror mode and using a blend face, try to position the camera so as to look directly into the face's eyes. --- interface/src/Application.cpp | 10 +++++++++- interface/src/avatar/BlendFace.cpp | 16 ++++++++++------ interface/src/avatar/BlendFace.h | 5 ++++- interface/src/avatar/Head.cpp | 13 ++++++------- interface/src/avatar/Head.h | 1 + 5 files changed, 30 insertions(+), 15 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 46a2b9afde..4299fcbf6a 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -332,7 +332,15 @@ void Application::paintGL() { if (_myCamera.getMode() == CAMERA_MODE_MIRROR) { _myCamera.setTightness (100.0f); - _myCamera.setTargetPosition(_myAvatar.getUprightHeadPosition()); + glm::vec3 targetPosition = _myAvatar.getUprightHeadPosition(); + if (_myAvatar.getHead().getBlendFace().isActive()) { + // make sure we're aligned to the blend face eyes + glm::vec3 leftEyePosition, rightEyePosition; + if (_myAvatar.getHead().getBlendFace().getEyePositions(leftEyePosition, rightEyePosition, true)) { + targetPosition = (leftEyePosition + rightEyePosition) * 0.5f; + } + } + _myCamera.setTargetPosition(targetPosition); _myCamera.setTargetRotation(_myAvatar.getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PIf, 0.0f))); } else if (OculusManager::isConnected()) { diff --git a/interface/src/avatar/BlendFace.cpp b/interface/src/avatar/BlendFace.cpp index 1dd643e0ab..280dd6a5cc 100644 --- a/interface/src/avatar/BlendFace.cpp +++ b/interface/src/avatar/BlendFace.cpp @@ -310,12 +310,16 @@ bool BlendFace::render(float alpha) { return true; } -void BlendFace::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const { +bool BlendFace::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition, bool upright) const { if (!isActive()) { - return; + return false; } - + glm::vec3 translation = _owningHead->getPosition(); glm::quat orientation = _owningHead->getOrientation(); + if (upright) { + translation = static_cast(_owningHead->_owningAvatar)->getUprightHeadPosition(); + orientation = static_cast(_owningHead->_owningAvatar)->getWorldAlignedOrientation(); + } glm::vec3 scale(-_owningHead->getScale() * MODEL_SCALE, _owningHead->getScale() * MODEL_SCALE, -_owningHead->getScale() * MODEL_SCALE); bool foundFirst = false; @@ -323,16 +327,16 @@ void BlendFace::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEy const FBXGeometry& geometry = _geometry->getFBXGeometry(); foreach (const FBXMesh& mesh, geometry.meshes) { if (mesh.isEye) { - glm::vec3 position = orientation * ((mesh.pivot + MODEL_TRANSLATION - geometry.neckPivot) * scale) + - _owningHead->getPosition(); + glm::vec3 position = orientation * ((mesh.pivot + MODEL_TRANSLATION - geometry.neckPivot) * scale) + translation; if (foundFirst) { secondEyePosition = position; - return; + return true; } firstEyePosition = position; foundFirst = true; } } + return false; } void BlendFace::setModelURL(const QUrl& url) { diff --git a/interface/src/avatar/BlendFace.h b/interface/src/avatar/BlendFace.h index bd8ad55541..ea2d2eb597 100644 --- a/interface/src/avatar/BlendFace.h +++ b/interface/src/avatar/BlendFace.h @@ -40,7 +40,10 @@ public: Q_INVOKABLE void setModelURL(const QUrl& url); const QUrl& getModelURL() const { return _modelURL; } - void getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const; + /// Retrieve the positions of up to two eye meshes. + /// \param upright if true, retrieve the locations of the eyes in the upright position + /// \return whether or not both eye meshes were found + bool getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition, bool upright = false) const; private: diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index 62c0f22348..635edf29d4 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -304,15 +304,14 @@ void Head::render(float alpha, bool isMine) { renderEyeBrows(); } } + + if (_blendFace.isActive()) { + // the blend face may have custom eye meshes + _blendFace.getEyePositions(_leftEyePosition, _rightEyePosition); + } if (_renderLookatVectors) { - glm::vec3 firstEyePosition = _leftEyePosition; - glm::vec3 secondEyePosition = _rightEyePosition; - if (_blendFace.isActive()) { - // the blend face may have custom eye meshes - _blendFace.getEyePositions(firstEyePosition, secondEyePosition); - } - renderLookatVectors(firstEyePosition, secondEyePosition, _lookAtPosition); + renderLookatVectors(_leftEyePosition, _rightEyePosition, _lookAtPosition); } } diff --git a/interface/src/avatar/Head.h b/interface/src/avatar/Head.h index 38574fa430..7c5491ae39 100644 --- a/interface/src/avatar/Head.h +++ b/interface/src/avatar/Head.h @@ -158,6 +158,7 @@ private: void resetHairPhysics(); void updateHairPhysics(float deltaTime); + friend class BlendFace; friend class PerlinFace; };