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