diff --git a/interface/interface_en.ts b/interface/interface_en.ts index 23ea3cc3ba..c52ec91671 100644 --- a/interface/interface_en.ts +++ b/interface/interface_en.ts @@ -14,12 +14,12 @@ - + Open Script - + JavaScript Files (*.js) @@ -113,18 +113,18 @@ Menu - + Open .ini config file - - + + Text files (*.ini) - + Save .ini config file diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index b08f8f77f1..fedde89a38 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1534,6 +1534,10 @@ void Application::init() { } qDebug("Loaded settings"); + // initialize Visage and Faceshift after loading the menu settings + _faceshift.init(); + _visage.init(); + // fire off an immediate domain-server check in now that settings are loaded NodeList::getInstance()->sendDomainServerCheckIn(); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index d8544fd7ae..b322e6567c 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -279,11 +279,16 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::LookAtVectors, 0, false); addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, - MenuOption::FaceshiftTCP, + MenuOption::Faceshift, 0, - false, + true, appInstance->getFaceshift(), SLOT(setTCPEnabled(bool))); +#ifdef HAVE_VISAGE + addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::Visage, 0, true, + appInstance->getVisage(), SLOT(updateEnabled())); +#endif + addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::ChatCircling, 0, false); QMenu* handOptionsMenu = developerMenu->addMenu("Hand Options"); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 90d70f4232..b124315cbb 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -243,7 +243,7 @@ namespace MenuOption { const QString DontFadeOnVoxelServerChanges = "Don't Fade In/Out on Voxel Server Changes"; const QString HeadMouse = "Head Mouse"; const QString HandsCollideWithSelf = "Collide With Self"; - const QString FaceshiftTCP = "Faceshift (TCP)"; + const QString Faceshift = "Faceshift"; const QString FirstPerson = "First Person"; const QString FrameTimer = "Show Timer"; const QString FrustumRenderMode = "Render Mode"; @@ -298,6 +298,7 @@ namespace MenuOption { const QString TransmitterDrive = "Transmitter Drive"; const QString UploaderAvatarHead = "Upload Avatar Head"; const QString UploaderAvatarSkeleton = "Upload Avatar Skeleton"; + const QString Visage = "Visage"; const QString Quit = "Quit"; const QString Voxels = "Voxels"; const QString VoxelMode = "Cycle Voxel Mode"; diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index b7e3d675a5..bcf24a7bfb 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -56,7 +56,8 @@ Avatar::Avatar() : _owningAvatarMixer(), _collisionFlags(0), _initialized(false), - _shouldRenderBillboard(true) + _shouldRenderBillboard(true), + _modelsDirty(true) { // we may have been created in the network thread, but we live in the main thread moveToThread(Application::getInstance()->thread()); @@ -109,6 +110,11 @@ void Avatar::simulate(float deltaTime) { _shouldRenderBillboard = true; } + // simple frustum check + float boundingRadius = getBillboardSize(); + bool inViewFrustum = Application::getInstance()->getViewFrustum()->sphereInFrustum(_position, boundingRadius) != + ViewFrustum::OUTSIDE; + getHand()->simulate(deltaTime, false); _skeletonModel.setLODDistance(getLODDistance()); @@ -118,8 +124,9 @@ void Avatar::simulate(float deltaTime) { _skeletonModel.setJointState(i, data.valid, data.rotation); } glm::vec3 headPosition = _position; - if (!_shouldRenderBillboard) { - _skeletonModel.simulate(deltaTime); + if (!_shouldRenderBillboard && inViewFrustum) { + _skeletonModel.simulate(deltaTime, _modelsDirty); + _modelsDirty = false; _skeletonModel.getHeadPosition(headPosition); } Head* head = getHead(); @@ -183,6 +190,12 @@ static TextRenderer* textRenderer(TextRendererType type) { } void Avatar::render(bool forShadowMap) { + // simple frustum check + float boundingRadius = getBillboardSize(); + if (Application::getInstance()->getViewFrustum()->sphereInFrustum(_position, boundingRadius) == ViewFrustum::OUTSIDE) { + return; + } + glm::vec3 toTarget = _position - Application::getInstance()->getAvatar()->getPosition(); float lengthToTarget = glm::length(toTarget); @@ -336,7 +349,7 @@ void Avatar::renderBillboard() { glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z); // compute the size from the billboard camera parameters and scale - float size = _scale * BILLBOARD_DISTANCE * tanf(glm::radians(BILLBOARD_FIELD_OF_VIEW / 2.0f)); + float size = getBillboardSize(); glScalef(size, size, size); glColor3f(1.0f, 1.0f, 1.0f); @@ -361,6 +374,10 @@ void Avatar::renderBillboard() { glBindTexture(GL_TEXTURE_2D, 0); } +float Avatar::getBillboardSize() const { + return _scale * BILLBOARD_DISTANCE * tanf(glm::radians(BILLBOARD_FIELD_OF_VIEW / 2.0f)); +} + void Avatar::renderDisplayName() { if (_displayName.isEmpty() || _displayNameAlpha == 0.0f) { @@ -618,6 +635,9 @@ int Avatar::parseData(const QByteArray& packet) { const float MOVE_DISTANCE_THRESHOLD = 0.001f; _moving = glm::distance(oldPosition, _position) > MOVE_DISTANCE_THRESHOLD; + // note that we need to update our models + _modelsDirty = true; + return bytesRead; } diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index bba86828cb..2bd7fc89e8 100755 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -187,9 +187,12 @@ private: bool _initialized; QScopedPointer _billboardTexture; bool _shouldRenderBillboard; + bool _modelsDirty; void renderBody(); void renderBillboard(); + + float getBillboardSize() const; }; #endif diff --git a/interface/src/avatar/FaceModel.cpp b/interface/src/avatar/FaceModel.cpp index eba8f7bf1a..db6c3fe98d 100644 --- a/interface/src/avatar/FaceModel.cpp +++ b/interface/src/avatar/FaceModel.cpp @@ -18,9 +18,9 @@ FaceModel::FaceModel(Head* owningHead) : { } -void FaceModel::simulate(float deltaTime, bool delayLoad) { +void FaceModel::simulate(float deltaTime) { + QVector newJointStates = updateGeometry(); if (!isActive()) { - Model::simulate(deltaTime, delayLoad); return; } Avatar* owningAvatar = static_cast(_owningHead->_owningAvatar); @@ -36,12 +36,13 @@ void FaceModel::simulate(float deltaTime, bool delayLoad) { setRotation(neckRotation); const float MODEL_SCALE = 0.0006f; setScale(glm::vec3(1.0f, 1.0f, 1.0f) * _owningHead->getScale() * MODEL_SCALE); + setOffset(-_geometry->getFBXGeometry().neckPivot); setPupilDilation(_owningHead->getPupilDilation()); setBlendshapeCoefficients(_owningHead->getBlendshapeCoefficients()); - Model::simulate(deltaTime, delayLoad); + Model::simulate(deltaTime, true, newJointStates); } bool FaceModel::render(float alpha) { diff --git a/interface/src/avatar/FaceModel.h b/interface/src/avatar/FaceModel.h index 597075dc42..d0f0f6baef 100644 --- a/interface/src/avatar/FaceModel.h +++ b/interface/src/avatar/FaceModel.h @@ -21,7 +21,7 @@ public: FaceModel(Head* owningHead); - void simulate(float deltaTime, bool delayLoad = false); + void simulate(float deltaTime); bool render(float alpha); protected: diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index f405358710..44d1dd6d07 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -18,13 +18,13 @@ SkeletonModel::SkeletonModel(Avatar* owningAvatar) : _owningAvatar(owningAvatar) { } -void SkeletonModel::simulate(float deltaTime, bool delayLoad) { +void SkeletonModel::simulate(float deltaTime, bool fullUpdate) { setTranslation(_owningAvatar->getPosition()); setRotation(_owningAvatar->getOrientation() * glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f))); const float MODEL_SCALE = 0.0006f; setScale(glm::vec3(1.0f, 1.0f, 1.0f) * _owningAvatar->getScale() * MODEL_SCALE); - Model::simulate(deltaTime, delayLoad); + Model::simulate(deltaTime, fullUpdate); if (!(isActive() && _owningAvatar->isMyAvatar())) { return; // only simulate for own avatar diff --git a/interface/src/avatar/SkeletonModel.h b/interface/src/avatar/SkeletonModel.h index 3d95d805ea..7018e331f0 100644 --- a/interface/src/avatar/SkeletonModel.h +++ b/interface/src/avatar/SkeletonModel.h @@ -22,7 +22,7 @@ public: SkeletonModel(Avatar* owningAvatar); - void simulate(float deltaTime, bool delayLoad = false); + void simulate(float deltaTime, bool fullUpdate = true); bool render(float alpha); /// \param jointIndex index of hand joint diff --git a/interface/src/devices/Faceshift.cpp b/interface/src/devices/Faceshift.cpp index 4fe89ff98b..88974ce493 100644 --- a/interface/src/devices/Faceshift.cpp +++ b/interface/src/devices/Faceshift.cpp @@ -21,7 +21,7 @@ using namespace std; const quint16 FACESHIFT_PORT = 33433; Faceshift::Faceshift() : - _tcpEnabled(false), + _tcpEnabled(true), _tcpRetryCount(0), _lastTrackingStateReceived(0), _eyeGazeLeftPitch(0.0f), @@ -49,12 +49,22 @@ Faceshift::Faceshift() : connect(&_tcpSocket, SIGNAL(connected()), SLOT(noteConnected())); connect(&_tcpSocket, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(noteError(QAbstractSocket::SocketError))); connect(&_tcpSocket, SIGNAL(readyRead()), SLOT(readFromSocket())); + connect(&_tcpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), SIGNAL(connectionStateChanged())); connect(&_udpSocket, SIGNAL(readyRead()), SLOT(readPendingDatagrams())); _udpSocket.bind(FACESHIFT_PORT); } +void Faceshift::init() { + setTCPEnabled(Menu::getInstance()->isOptionChecked(MenuOption::Faceshift)); +} + +bool Faceshift::isConnectedOrConnecting() const { + return _tcpSocket.state() == QAbstractSocket::ConnectedState || + (_tcpRetryCount == 0 && _tcpSocket.state() != QAbstractSocket::UnconnectedState); +} + bool Faceshift::isActive() const { const quint64 ACTIVE_TIMEOUT_USECS = 1000000; return (usecTimestampNow() - _lastTrackingStateReceived) < ACTIVE_TIMEOUT_USECS; diff --git a/interface/src/devices/Faceshift.h b/interface/src/devices/Faceshift.h index 908354ae9d..a0898c446d 100644 --- a/interface/src/devices/Faceshift.h +++ b/interface/src/devices/Faceshift.h @@ -27,6 +27,10 @@ public: Faceshift(); + void init(); + + bool isConnectedOrConnecting() const; + bool isActive() const; const glm::quat& getHeadRotation() const { return _headRotation; } @@ -66,6 +70,10 @@ public: void updateFakeCoefficients(float leftBlink, float rightBlink, float browUp, float jawOpen, std::vector& coefficients) const; +signals: + + void connectionStateChanged(); + public slots: void setTCPEnabled(bool enabled); diff --git a/interface/src/devices/Visage.cpp b/interface/src/devices/Visage.cpp index 727c083265..b96ef1ee72 100644 --- a/interface/src/devices/Visage.cpp +++ b/interface/src/devices/Visage.cpp @@ -32,6 +32,7 @@ using namespace VisageSDK; const glm::vec3 DEFAULT_HEAD_ORIGIN(0.0f, 0.0f, 0.7f); Visage::Visage() : + _enabled(false), _active(false), _headOrigin(DEFAULT_HEAD_ORIGIN), _estimatedEyePitch(0.0f), @@ -41,23 +42,15 @@ Visage::Visage() : QByteArray licensePath = Application::resourcesPath().toLatin1() + "visage/license.vlc"; initializeLicenseManager(licensePath.data()); _tracker = new VisageTracker2(Application::resourcesPath().toLatin1() + "visage/tracker.cfg"); - if (_tracker->trackFromCam()) { - _data = new FaceData(); - - } else { - delete _tracker; - _tracker = NULL; - } + _data = new FaceData(); #endif } Visage::~Visage() { #ifdef HAVE_VISAGE - if (_tracker) { - _tracker->stop(); - delete _tracker; - delete _data; - } + _tracker->stop(); + delete _tracker; + delete _data; #endif } @@ -117,9 +110,14 @@ static const QMultiHash >& getActionUnitNameMap() const float TRANSLATION_SCALE = 20.0f; +void Visage::init() { + connect(Application::getInstance()->getFaceshift(), SIGNAL(connectionStateChanged()), SLOT(updateEnabled())); + updateEnabled(); +} + void Visage::update() { #ifdef HAVE_VISAGE - _active = (_tracker && _tracker->getTrackingData(_data) == TRACK_STAT_OK); + _active = (_tracker->getTrackingData(_data) == TRACK_STAT_OK); if (!_active) { return; } @@ -160,3 +158,22 @@ void Visage::update() { void Visage::reset() { _headOrigin += _headTranslation / TRANSLATION_SCALE; } + +void Visage::updateEnabled() { + setEnabled(Menu::getInstance()->isOptionChecked(MenuOption::Visage) && + !(Menu::getInstance()->isOptionChecked(MenuOption::Faceshift) && + Application::getInstance()->getFaceshift()->isConnectedOrConnecting())); +} + +void Visage::setEnabled(bool enabled) { +#ifdef HAVE_VISAGE + if (_enabled == enabled) { + return; + } + if ((_enabled = enabled)) { + _tracker->trackFromCam(); + } else { + _tracker->stop(); + } +#endif +} diff --git a/interface/src/devices/Visage.h b/interface/src/devices/Visage.h index c238b3bb8c..7e50812ba7 100644 --- a/interface/src/devices/Visage.h +++ b/interface/src/devices/Visage.h @@ -24,11 +24,15 @@ namespace VisageSDK { } /// Handles input from the Visage webcam feature tracking software. -class Visage { +class Visage : public QObject { + Q_OBJECT + public: Visage(); - ~Visage(); + virtual ~Visage(); + + void init(); bool isActive() const { return _active; } @@ -42,6 +46,10 @@ public: void update(); void reset(); + +public slots: + + void updateEnabled(); private: @@ -51,6 +59,9 @@ private: QMultiHash > _actionUnitIndexMap; #endif + void setEnabled(bool enabled); + + bool _enabled; bool _active; glm::quat _headRotation; glm::vec3 _headTranslation; diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 21fdc80858..60fae5e596 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -156,142 +156,9 @@ void Model::updateShapePositions() { } } -void Model::simulate(float deltaTime, bool delayLoad) { - // update our LOD - QVector newJointStates = updateGeometry(delayLoad); - if (!isActive()) { - return; - } - - // set up world vertices on first simulate after load - const FBXGeometry& geometry = _geometry->getFBXGeometry(); - if (_jointStates.isEmpty()) { - _jointStates = newJointStates.isEmpty() ? createJointStates(geometry) : newJointStates; - foreach (const FBXMesh& mesh, geometry.meshes) { - MeshState state; - state.clusterMatrices.resize(mesh.clusters.size()); - 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); - } - foreach (const FBXAttachment& attachment, geometry.attachments) { - Model* model = new Model(this); - model->init(); - model->setURL(attachment.url); - _attachments.append(model); - } - _resetStates = true; - createCollisionShapes(); - } - - // update the world space transforms for all joints - for (int i = 0; i < _jointStates.size(); i++) { - updateJointState(i); - } - - // update the attachment transforms and simulate them - for (int i = 0; i < _attachments.size(); i++) { - const FBXAttachment& attachment = geometry.attachments.at(i); - Model* model = _attachments.at(i); - - glm::vec3 jointTranslation = _translation; - glm::quat jointRotation = _rotation; - getJointPosition(attachment.jointIndex, jointTranslation); - getJointRotation(attachment.jointIndex, jointRotation); - - model->setTranslation(jointTranslation + jointRotation * attachment.translation * _scale); - model->setRotation(jointRotation * attachment.rotation); - model->setScale(_scale * attachment.scale); - - model->simulate(deltaTime); - } - - for (int i = 0; i < _meshStates.size(); i++) { - MeshState& state = _meshStates[i]; - const FBXMesh& mesh = geometry.meshes.at(i); - for (int j = 0; j < mesh.clusters.size(); j++) { - const FBXCluster& cluster = mesh.clusters.at(j); - state.clusterMatrices[j] = _jointStates[cluster.jointIndex].transform * cluster.inverseBindMatrix; - } - int vertexCount = state.worldSpaceVertices.size(); - if (vertexCount == 0) { - continue; - } - glm::vec3* destVertices = state.worldSpaceVertices.data(); - glm::vec3* destVelocities = state.vertexVelocities.data(); - glm::vec3* destNormals = state.worldSpaceNormals.data(); - - 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 - for (unsigned int j = 0; j < _blendshapeCoefficients.size(); j++) { - float coefficient = _blendshapeCoefficients[j]; - if (coefficient == 0.0f || j >= (unsigned int)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(); - } - glm::mat4 transform = glm::translate(_translation); - if (mesh.clusters.size() > 1) { - _blendedVertices.resize(max(_blendedVertices.size(), vertexCount)); - - // skin each vertex - const glm::vec4* clusterIndices = mesh.clusterIndices.constData(); - const glm::vec4* clusterWeights = mesh.clusterWeights.constData(); - for (int j = 0; j < vertexCount; j++) { - _blendedVertices[j] = - glm::vec3(state.clusterMatrices[clusterIndices[j][0]] * - glm::vec4(sourceVertices[j], 1.0f)) * clusterWeights[j][0] + - glm::vec3(state.clusterMatrices[clusterIndices[j][1]] * - glm::vec4(sourceVertices[j], 1.0f)) * clusterWeights[j][1] + - glm::vec3(state.clusterMatrices[clusterIndices[j][2]] * - glm::vec4(sourceVertices[j], 1.0f)) * clusterWeights[j][2] + - glm::vec3(state.clusterMatrices[clusterIndices[j][3]] * - glm::vec4(sourceVertices[j], 1.0f)) * clusterWeights[j][3]; - } - sourceVertices = _blendedVertices.constData(); - - } else { - transform = state.clusterMatrices[0]; - } - if (_resetStates) { - for (int j = 0; j < vertexCount; j++) { - destVertices[j] = glm::vec3(transform * glm::vec4(sourceVertices[j], 1.0f)); - destVelocities[j] = glm::vec3(); - } - } else { - const float SPRINGINESS_MULTIPLIER = 200.0f; - const float DAMPING = 5.0f; - for (int j = 0; j < vertexCount; 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; - } - } - for (int j = 0; j < vertexCount; j++) { - destNormals[j] = glm::vec3(); - - 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)); - } - } - } - _resetStates = false; +void Model::simulate(float deltaTime, bool fullUpdate) { + // update our LOD, then simulate + simulate(deltaTime, fullUpdate, updateGeometry()); } bool Model::render(float alpha) { @@ -572,6 +439,186 @@ bool Model::findSphereCollisions(const glm::vec3& sphereCenter, float sphereRadi return collided; } +QVector Model::updateGeometry() { + QVector newJointStates; + if (_nextGeometry) { + _nextGeometry = _nextGeometry->getLODOrFallback(_lodDistance, _nextLODHysteresis); + _nextGeometry->setLoadPriority(this, -_lodDistance); + _nextGeometry->ensureLoading(); + if (_nextGeometry->isLoaded()) { + applyNextGeometry(); + return newJointStates; + } + } + if (!_geometry) { + return newJointStates; + } + QSharedPointer geometry = _geometry->getLODOrFallback(_lodDistance, _lodHysteresis); + if (_geometry != geometry) { + if (!_jointStates.isEmpty()) { + // copy the existing joint states + const FBXGeometry& oldGeometry = _geometry->getFBXGeometry(); + const FBXGeometry& newGeometry = geometry->getFBXGeometry(); + newJointStates = createJointStates(newGeometry); + for (QHash::const_iterator it = oldGeometry.jointIndices.constBegin(); + it != oldGeometry.jointIndices.constEnd(); it++) { + int oldIndex = it.value() - 1; + int newIndex = newGeometry.getJointIndex(it.key()); + if (newIndex != -1) { + newJointStates[newIndex] = _jointStates.at(oldIndex); + } + } + } + deleteGeometry(); + _dilatedTextures.clear(); + _geometry = geometry; + } + _geometry->setLoadPriority(this, -_lodDistance); + _geometry->ensureLoading(); + return newJointStates; +} + +void Model::simulate(float deltaTime, bool fullUpdate, const QVector& newJointStates) { + if (!isActive()) { + return; + } + + // set up world vertices on first simulate after load + const FBXGeometry& geometry = _geometry->getFBXGeometry(); + if (_jointStates.isEmpty()) { + _jointStates = newJointStates.isEmpty() ? createJointStates(geometry) : newJointStates; + foreach (const FBXMesh& mesh, geometry.meshes) { + MeshState state; + state.clusterMatrices.resize(mesh.clusters.size()); + 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); + } + foreach (const FBXAttachment& attachment, geometry.attachments) { + Model* model = new Model(this); + model->init(); + model->setURL(attachment.url); + _attachments.append(model); + } + _resetStates = fullUpdate = true; + createCollisionShapes(); + } + + // exit early if we don't have to perform a full update + if (!(fullUpdate || _resetStates)) { + return; + } + + // update the world space transforms for all joints + for (int i = 0; i < _jointStates.size(); i++) { + updateJointState(i); + } + + // update the attachment transforms and simulate them + for (int i = 0; i < _attachments.size(); i++) { + const FBXAttachment& attachment = geometry.attachments.at(i); + Model* model = _attachments.at(i); + + glm::vec3 jointTranslation = _translation; + glm::quat jointRotation = _rotation; + getJointPosition(attachment.jointIndex, jointTranslation); + getJointRotation(attachment.jointIndex, jointRotation); + + model->setTranslation(jointTranslation + jointRotation * attachment.translation * _scale); + model->setRotation(jointRotation * attachment.rotation); + model->setScale(_scale * attachment.scale); + + model->simulate(deltaTime); + } + + for (int i = 0; i < _meshStates.size(); i++) { + MeshState& state = _meshStates[i]; + const FBXMesh& mesh = geometry.meshes.at(i); + for (int j = 0; j < mesh.clusters.size(); j++) { + const FBXCluster& cluster = mesh.clusters.at(j); + state.clusterMatrices[j] = _jointStates[cluster.jointIndex].transform * cluster.inverseBindMatrix; + } + int vertexCount = state.worldSpaceVertices.size(); + if (vertexCount == 0) { + continue; + } + glm::vec3* destVertices = state.worldSpaceVertices.data(); + glm::vec3* destVelocities = state.vertexVelocities.data(); + glm::vec3* destNormals = state.worldSpaceNormals.data(); + + 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 + for (unsigned int j = 0; j < _blendshapeCoefficients.size(); j++) { + float coefficient = _blendshapeCoefficients[j]; + if (coefficient == 0.0f || j >= (unsigned int)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(); + } + glm::mat4 transform = glm::translate(_translation); + if (mesh.clusters.size() > 1) { + _blendedVertices.resize(max(_blendedVertices.size(), vertexCount)); + + // skin each vertex + const glm::vec4* clusterIndices = mesh.clusterIndices.constData(); + const glm::vec4* clusterWeights = mesh.clusterWeights.constData(); + for (int j = 0; j < vertexCount; j++) { + _blendedVertices[j] = + glm::vec3(state.clusterMatrices[clusterIndices[j][0]] * + glm::vec4(sourceVertices[j], 1.0f)) * clusterWeights[j][0] + + glm::vec3(state.clusterMatrices[clusterIndices[j][1]] * + glm::vec4(sourceVertices[j], 1.0f)) * clusterWeights[j][1] + + glm::vec3(state.clusterMatrices[clusterIndices[j][2]] * + glm::vec4(sourceVertices[j], 1.0f)) * clusterWeights[j][2] + + glm::vec3(state.clusterMatrices[clusterIndices[j][3]] * + glm::vec4(sourceVertices[j], 1.0f)) * clusterWeights[j][3]; + } + sourceVertices = _blendedVertices.constData(); + + } else { + transform = state.clusterMatrices[0]; + } + if (_resetStates) { + for (int j = 0; j < vertexCount; j++) { + destVertices[j] = glm::vec3(transform * glm::vec4(sourceVertices[j], 1.0f)); + destVelocities[j] = glm::vec3(); + } + } else { + const float SPRINGINESS_MULTIPLIER = 200.0f; + const float DAMPING = 5.0f; + for (int j = 0; j < vertexCount; 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; + } + } + for (int j = 0; j < vertexCount; j++) { + destNormals[j] = glm::vec3(); + + 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)); + } + } + } + _resetStates = false; +} + void Model::updateJointState(int index) { _shapesAreDirty = true; JointState& state = _jointStates[index]; @@ -868,49 +915,6 @@ void Model::applyCollision(CollisionInfo& collision) { } } -QVector Model::updateGeometry(bool delayLoad) { - QVector newJointStates; - if (_nextGeometry) { - _nextGeometry = _nextGeometry->getLODOrFallback(_lodDistance, _nextLODHysteresis, delayLoad); - if (!delayLoad) { - _nextGeometry->setLoadPriority(this, -_lodDistance); - _nextGeometry->ensureLoading(); - } - if (_nextGeometry->isLoaded()) { - applyNextGeometry(); - return newJointStates; - } - } - if (!_geometry) { - return newJointStates; - } - QSharedPointer geometry = _geometry->getLODOrFallback(_lodDistance, _lodHysteresis, delayLoad); - if (_geometry != geometry) { - if (!_jointStates.isEmpty()) { - // copy the existing joint states - const FBXGeometry& oldGeometry = _geometry->getFBXGeometry(); - const FBXGeometry& newGeometry = geometry->getFBXGeometry(); - newJointStates = createJointStates(newGeometry); - for (QHash::const_iterator it = oldGeometry.jointIndices.constBegin(); - it != oldGeometry.jointIndices.constEnd(); it++) { - int oldIndex = it.value() - 1; - int newIndex = newGeometry.getJointIndex(it.key()); - if (newIndex != -1) { - newJointStates[newIndex] = _jointStates.at(oldIndex); - } - } - } - deleteGeometry(); - _dilatedTextures.clear(); - _geometry = geometry; - } - if (!delayLoad) { - _geometry->setLoadPriority(this, -_lodDistance); - _geometry->ensureLoading(); - } - return newJointStates; -} - void Model::applyNextGeometry() { // delete our local geometry and custom textures deleteGeometry(); diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index c7aadee8dc..165465d2cc 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -57,7 +57,7 @@ public: void clearShapes(); void createCollisionShapes(); void updateShapePositions(); - void simulate(float deltaTime, bool delayLoad = false); + void simulate(float deltaTime, bool fullUpdate = true); bool render(float alpha); /// Sets the URL of the model to render. @@ -226,6 +226,9 @@ protected: QVector _meshStates; + QVector updateGeometry(); + void simulate(float deltaTime, bool fullUpdate, const QVector& newJointStates); + /// Updates the state of the joint at the specified index. virtual void updateJointState(int index); @@ -256,7 +259,6 @@ protected: private: - QVector updateGeometry(bool delayLoad); void applyNextGeometry(); void deleteGeometry(); void renderMeshes(float alpha, bool translucent);