From 540649071970e5a2f3f25609af1768ecac7f6b3a Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 28 Mar 2014 11:00:30 -0700 Subject: [PATCH 01/16] rename _shapes -> _jointShapes also stubbed Model::createBoundingShape() --- interface/src/avatar/SkeletonModel.cpp | 8 +++--- interface/src/renderer/Model.cpp | 37 +++++++++++++++----------- interface/src/renderer/Model.h | 5 ++-- 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 3c5f5cae6a..9d67709fde 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -63,7 +63,7 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) { } void SkeletonModel::getHandShapes(int jointIndex, QVector& shapes) const { - if (jointIndex < 0 || jointIndex >= int(_shapes.size())) { + if (jointIndex < 0 || jointIndex >= int(_jointShapes.size())) { return; } if (jointIndex == getLeftHandJointIndex() @@ -75,16 +75,16 @@ void SkeletonModel::getHandShapes(int jointIndex, QVector& shapes) int parentIndex = joint.parentIndex; if (i == jointIndex) { // this shape is the hand - shapes.push_back(_shapes[i]); + shapes.push_back(_jointShapes[i]); if (parentIndex != -1) { // also add the forearm - shapes.push_back(_shapes[parentIndex]); + shapes.push_back(_jointShapes[parentIndex]); } } else { while (parentIndex != -1) { if (parentIndex == jointIndex) { // this shape is a child of the hand - shapes.push_back(_shapes[i]); + shapes.push_back(_jointShapes[i]); break; } parentIndex = geometry.joints[parentIndex].parentIndex; diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index df5560a4b1..ceddcd009a 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -136,13 +136,13 @@ void Model::reset() { } void Model::clearShapes() { - for (int i = 0; i < _shapes.size(); ++i) { - delete _shapes[i]; + for (int i = 0; i < _jointShapes.size(); ++i) { + delete _jointShapes[i]; } - _shapes.clear(); + _jointShapes.clear(); } -void Model::createCollisionShapes() { +void Model::createJointCollisionShapes() { clearShapes(); const FBXGeometry& geometry = _geometry->getFBXGeometry(); float uniformScale = extractUniformScale(_scale); @@ -156,16 +156,21 @@ void Model::createCollisionShapes() { float halfHeight = 0.5f * uniformScale * joint.distanceToParent; CapsuleShape* shape = new CapsuleShape(radius, halfHeight); shape->setPosition(position); - _shapes.push_back(shape); + _jointShapes.push_back(shape); } else { SphereShape* shape = new SphereShape(radius, position); - _shapes.push_back(shape); + _jointShapes.push_back(shape); } } } +void Model::createBoundingShape() { + //const FBXGeometry& geometry = _geometry->getFBXGeometry(); + //float uniformScale = extractUniformScale(_scale); +} + void Model::updateShapePositions() { - if (_shapesAreDirty && _shapes.size() == _jointStates.size()) { + if (_shapesAreDirty && _jointShapes.size() == _jointStates.size()) { _boundingRadius = 0.f; float uniformScale = extractUniformScale(_scale); const FBXGeometry& geometry = _geometry->getFBXGeometry(); @@ -174,8 +179,8 @@ void Model::updateShapePositions() { // shape position and rotation need to be in world-frame glm::vec3 jointToShapeOffset = uniformScale * (_jointStates[i].combinedRotation * joint.shapePosition); glm::vec3 worldPosition = extractTranslation(_jointStates[i].transform) + jointToShapeOffset + _translation; - _shapes[i]->setPosition(worldPosition); - _shapes[i]->setRotation(_jointStates[i].combinedRotation * joint.shapeRotation); + _jointShapes[i]->setPosition(worldPosition); + _jointShapes[i]->setRotation(_jointStates[i].combinedRotation * joint.shapeRotation); float distance2 = glm::distance2(worldPosition, _translation); if (distance2 > _boundingRadius) { _boundingRadius = distance2; @@ -401,8 +406,8 @@ bool Model::findCollisions(const QVector shapes, CollisionList& co bool collided = false; for (int i = 0; i < shapes.size(); ++i) { const Shape* theirShape = shapes[i]; - for (int j = 0; j < _shapes.size(); ++j) { - const Shape* ourShape = _shapes[j]; + for (int j = 0; j < _jointShapes.size(); ++j) { + const Shape* ourShape = _jointShapes[j]; if (ShapeCollider::shapeShape(theirShape, ourShape, collisions)) { collided = true; } @@ -417,7 +422,7 @@ bool Model::findSphereCollisions(const glm::vec3& sphereCenter, float sphereRadi updateShapePositions(); SphereShape sphere(sphereRadius, sphereCenter); const FBXGeometry& geometry = _geometry->getFBXGeometry(); - for (int i = 0; i < _shapes.size(); i++) { + for (int i = 0; i < _jointShapes.size(); i++) { const FBXJoint& joint = geometry.joints[i]; if (joint.parentIndex != -1) { if (skipIndex != -1) { @@ -431,7 +436,7 @@ bool Model::findSphereCollisions(const glm::vec3& sphereCenter, float sphereRadi } while (ancestorIndex != -1); } } - if (ShapeCollider::shapeShape(&sphere, _shapes[i], collisions)) { + if (ShapeCollider::shapeShape(&sphere, _jointShapes[i], collisions)) { CollisionInfo* collision = collisions.getLastCollision(); collision->_type = MODEL_COLLISION; collision->_data = (void*)(this); @@ -578,7 +583,7 @@ void Model::simulate(float deltaTime, bool fullUpdate, const QVector _attachments.append(model); } fullUpdate = true; - createCollisionShapes(); + createJointCollisionShapes(); } // exit early if we don't have to perform a full update @@ -825,10 +830,10 @@ void Model::renderCollisionProxies(float alpha) { Application::getInstance()->loadTranslatedViewMatrix(_translation); updateShapePositions(); const int BALL_SUBDIVISIONS = 10; - for (int i = 0; i < _shapes.size(); i++) { + for (int i = 0; i < _jointShapes.size(); i++) { glPushMatrix(); - Shape* shape = _shapes[i]; + Shape* shape = _jointShapes[i]; if (shape->getType() == Shape::SPHERE_SHAPE) { // shapes are stored in world-frame, so we have to transform into model frame diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 3fdf1cd291..0b05fab2bf 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -55,7 +55,8 @@ public: void init(); void reset(); void clearShapes(); - void createCollisionShapes(); + void createJointCollisionShapes(); + void createBoundingShape(); void updateShapePositions(); void simulate(float deltaTime, bool fullUpdate = true); bool render(float alpha = 1.0f, bool forShadowMap = false); @@ -203,7 +204,7 @@ protected: bool _shapesAreDirty; QVector _jointStates; - QVector _shapes; + QVector _jointShapes; class MeshState { public: From 19b8f101d8994bd0f9b4a86a45fd0144ed9bc6eb Mon Sep 17 00:00:00 2001 From: James Brotchie Date: Tue, 1 Apr 2014 11:31:43 +1000 Subject: [PATCH 02/16] Improved hifi:// style URL scheme handling. First steps towards implementing #1463. - Moved url parsing code from Application::event to Menu::goTo(QString). - Handle destination component of URL as either a @username, #place, or x-xx,y-yy,z-zz style coordinate. - Added support for entering hifi:// style URL into @ invoked goto menu option. --- interface/interface_en.ts | 8 +++---- interface/src/Application.cpp | 27 ++++------------------ interface/src/Application.h | 1 + interface/src/Menu.cpp | 43 +++++++++++++++++++++++++++++++++-- interface/src/Menu.h | 1 + 5 files changed, 51 insertions(+), 29 deletions(-) diff --git a/interface/interface_en.ts b/interface/interface_en.ts index 7c5d1ecbcf..49906379c4 100644 --- a/interface/interface_en.ts +++ b/interface/interface_en.ts @@ -4,22 +4,22 @@ Application - + Export Voxels - + Sparse Voxel Octree Files (*.svo) - + Open Script - + JavaScript Files (*.js) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index d91e686844..883da9b7bc 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -112,8 +112,6 @@ const QString SKIP_FILENAME = QStandardPaths::writableLocation(QStandardPaths::D const int STATS_PELS_PER_LINE = 20; -const QString CUSTOM_URL_SCHEME = "hifi:"; - void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { if (message.size() > 0) { QString dateString = QDateTime::currentDateTime().toTimeSpec(Qt::LocalTime).toString(Qt::ISODate); @@ -676,30 +674,13 @@ void Application::controlledBroadcastToNodes(const QByteArray& packet, const Nod } bool Application::event(QEvent* event) { - + // handle custom URL if (event->type() == QEvent::FileOpen) { QFileOpenEvent* fileEvent = static_cast(event); - if (!fileEvent->url().isEmpty() && fileEvent->url().toLocalFile().startsWith(CUSTOM_URL_SCHEME)) { - QString destination = fileEvent->url().toLocalFile().remove(CUSTOM_URL_SCHEME); - QStringList urlParts = destination.split('/', QString::SkipEmptyParts); - - if (urlParts.count() > 1) { - // if url has 2 or more parts, the first one is domain name - Menu::getInstance()->goToDomain(urlParts[0]); - - // location coordinates - Menu::getInstance()->goToDestination(urlParts[1]); - if (urlParts.count() > 2) { - - // location orientation - Menu::getInstance()->goToOrientation(urlParts[2]); - } - } else if (urlParts.count() == 1) { - - // location coordinates - Menu::getInstance()->goToDestination(urlParts[0]); - } + bool isHifiSchemeURL = !fileEvent->url().isEmpty() && fileEvent->url().toLocalFile().startsWith(CUSTOM_URL_SCHEME); + if (isHifiSchemeURL) { + Menu::getInstance()->goTo(fileEvent->url().toString()); } return false; diff --git a/interface/src/Application.h b/interface/src/Application.h index bab7578ca4..a0b81a740e 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -93,6 +93,7 @@ static const float NODE_KILLED_GREEN = 0.0f; static const float NODE_KILLED_BLUE = 0.0f; static const QString SNAPSHOT_EXTENSION = ".jpg"; +static const QString CUSTOM_URL_SCHEME = "hifi:"; static const float BILLBOARD_FIELD_OF_VIEW = 30.0f; // degrees static const float BILLBOARD_DISTANCE = 5.0f; // meters diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index ec20401fef..31471ce60b 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -912,19 +912,58 @@ bool Menu::goToDestination(QString destination) { return LocationManager::getInstance().goToDestination(destination); } +void Menu::goTo(QString destination) { + LocationManager::getInstance().goTo(destination); +} + void Menu::goTo() { QInputDialog gotoDialog(Application::getInstance()->getWindow()); gotoDialog.setWindowTitle("Go to"); - gotoDialog.setLabelText("Destination:"); + gotoDialog.setLabelText("Destination or URL:\n @user, #place, hifi://domain/location/orientation"); QString destination = QString(); + gotoDialog.setTextValue(destination); gotoDialog.setWindowFlags(Qt::Sheet); gotoDialog.resize(gotoDialog.parentWidget()->size().width() * DIALOG_RATIO_OF_WINDOW, gotoDialog.size().height()); int dialogReturn = gotoDialog.exec(); if (dialogReturn == QDialog::Accepted && !gotoDialog.textValue().isEmpty()) { - goToUser(gotoDialog.textValue()); + QString desiredDestination = gotoDialog.textValue(); + + if (desiredDestination.startsWith(CUSTOM_URL_SCHEME + "//")) { + QStringList urlParts = desiredDestination.remove(0, CUSTOM_URL_SCHEME.length() + 2).split('/', QString::SkipEmptyParts); + + if (urlParts.count() > 1) { + // if url has 2 or more parts, the first one is domain name + QString domain = urlParts[0]; + + // second part is either a destination coordinate or + // a place name + QString destination = urlParts[1]; + + // any third part is an avatar orientation. + QString orientation = urlParts.count() > 2 ? urlParts[2] : QString(); + + goToDomain(domain); + + // goto either @user, #place, or x-xx,y-yy,z-zz + // style co-ordinate. + goTo(destination); + + if (!orientation.isEmpty()) { + // location orientation + goToOrientation(orientation); + } + } else if (urlParts.count() == 1) { + // location coordinates or place name + QString destination = urlParts[0]; + goTo(destination); + } + + } else { + goToUser(gotoDialog.textValue()); + } } sendFakeEnterEvent(); } diff --git a/interface/src/Menu.h b/interface/src/Menu.h index c7c4c6ecea..f31803e65d 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -110,6 +110,7 @@ public: bool goToDestination(QString destination); void goToOrientation(QString orientation); void goToDomain(const QString newDomain); + void goTo(QString destination); public slots: From 1bbdc9d78b771ceafd9679c3fc02dafddd66d485 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 1 Apr 2014 09:25:25 -0700 Subject: [PATCH 03/16] Adding bounding capsule shape for Model Also: Cleaned up the automatic geometry LOD loading Removed staticExtents data member from FBXGeometry Split debug rendering of bounding shapes from joint shapes --- interface/interface_en.ts | 8 +- interface/src/Menu.cpp | 5 +- interface/src/Menu.h | 5 +- interface/src/avatar/Avatar.cpp | 25 +- interface/src/avatar/Avatar.h | 1 - interface/src/avatar/FaceModel.cpp | 6 +- interface/src/avatar/Hand.cpp | 2 +- interface/src/avatar/MyAvatar.cpp | 47 +-- interface/src/avatar/SkeletonModel.cpp | 12 +- interface/src/avatar/SkeletonModel.h | 8 +- interface/src/renderer/FBXReader.cpp | 12 +- interface/src/renderer/FBXReader.h | 5 +- interface/src/renderer/Model.cpp | 389 +++++++++++++++---------- interface/src/renderer/Model.h | 33 ++- 14 files changed, 337 insertions(+), 221 deletions(-) diff --git a/interface/interface_en.ts b/interface/interface_en.ts index da8827d89d..ade5275c30 100644 --- a/interface/interface_en.ts +++ b/interface/interface_en.ts @@ -113,18 +113,18 @@ Menu - + Open .ini config file - - + + Text files (*.ini) - + Save .ini config file diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 79b0a23ce5..d77f236300 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -282,8 +282,9 @@ Menu::Menu() : QMenu* avatarOptionsMenu = developerMenu->addMenu("Avatar Options"); addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::Avatars, 0, true); - addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderSkeletonCollisionProxies); - addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderHeadCollisionProxies); + addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderSkeletonCollisionShapes); + addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderHeadCollisionShapes); + addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderBoundingCollisionShapes); addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::LookAtVectors, 0, false); addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, diff --git a/interface/src/Menu.h b/interface/src/Menu.h index cab5645304..cc5bb2c186 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -285,8 +285,9 @@ namespace MenuOption { const QString PlaySlaps = "Play Slaps"; const QString Preferences = "Preferences..."; const QString ReloadAllScripts = "Reload All Scripts"; - const QString RenderSkeletonCollisionProxies = "Skeleton Collision Proxies"; - const QString RenderHeadCollisionProxies = "Head Collision Proxies"; + const QString RenderSkeletonCollisionShapes = "Skeleton Collision Shapes"; + const QString RenderHeadCollisionShapes = "Head Collision Shapes"; + const QString RenderBoundingCollisionShapes = "Bounding Collision Shapes"; const QString ResetAvatarSize = "Reset Avatar Size"; const QString RunTimingTests = "Run Timing Tests"; const QString SettingsImport = "Import Settings"; diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 75b0a4c66a..b36baf15b1 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -56,8 +56,7 @@ Avatar::Avatar() : _owningAvatarMixer(), _collisionFlags(0), _initialized(false), - _shouldRenderBillboard(true), - _modelsDirty(true) + _shouldRenderBillboard(true) { // we may have been created in the network thread, but we live in the main thread moveToThread(Application::getInstance()->thread()); @@ -125,8 +124,7 @@ void Avatar::simulate(float deltaTime) { } glm::vec3 headPosition = _position; if (!_shouldRenderBillboard && inViewFrustum) { - _skeletonModel.simulate(deltaTime, _modelsDirty); - _modelsDirty = false; + _skeletonModel.simulate(deltaTime); _skeletonModel.getHeadPosition(headPosition); } Head* head = getHead(); @@ -210,11 +208,19 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) { if (Menu::getInstance()->isOptionChecked(MenuOption::Avatars)) { renderBody(renderMode); } - if (Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionProxies)) { - _skeletonModel.renderCollisionProxies(0.7f); + if (Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionShapes)) { + _skeletonModel.updateShapePositions(); + _skeletonModel.renderJointCollisionShapes(0.7f); } - if (Menu::getInstance()->isOptionChecked(MenuOption::RenderHeadCollisionProxies)) { - getHead()->getFaceModel().renderCollisionProxies(0.7f); + if (Menu::getInstance()->isOptionChecked(MenuOption::RenderHeadCollisionShapes)) { + getHead()->getFaceModel().updateShapePositions(); + getHead()->getFaceModel().renderJointCollisionShapes(0.7f); + } + if (Menu::getInstance()->isOptionChecked(MenuOption::RenderBoundingCollisionShapes)) { + _skeletonModel.updateShapePositions(); + _skeletonModel.renderBoundingCollisionShapes(0.7f); + getHead()->getFaceModel().updateShapePositions(); + getHead()->getFaceModel().renderBoundingCollisionShapes(0.7f); } // quick check before falling into the code below: @@ -650,9 +656,6 @@ int Avatar::parseDataAtOffset(const QByteArray& packet, int offset) { 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 f2ee400ba2..f6d5669859 100755 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -192,7 +192,6 @@ private: bool _initialized; QScopedPointer _billboardTexture; bool _shouldRenderBillboard; - bool _modelsDirty; void renderBillboard(); diff --git a/interface/src/avatar/FaceModel.cpp b/interface/src/avatar/FaceModel.cpp index 19120d10be..c483642d15 100644 --- a/interface/src/avatar/FaceModel.cpp +++ b/interface/src/avatar/FaceModel.cpp @@ -19,8 +19,8 @@ FaceModel::FaceModel(Head* owningHead) : } void FaceModel::simulate(float deltaTime) { - QVector newJointStates = updateGeometry(); - if (!isActive()) { + bool geometryIsUpToDate = updateGeometry(); + if (!geometryIsUpToDate) { return; } Avatar* owningAvatar = static_cast(_owningHead->_owningAvatar); @@ -42,7 +42,7 @@ void FaceModel::simulate(float deltaTime) { setPupilDilation(_owningHead->getPupilDilation()); setBlendshapeCoefficients(_owningHead->getBlendshapeCoefficients()); - Model::simulate(deltaTime, true, newJointStates); + Model::simulateInternal(deltaTime); } void FaceModel::maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) { diff --git a/interface/src/avatar/Hand.cpp b/interface/src/avatar/Hand.cpp index 750fae7b2a..4351a53ab4 100644 --- a/interface/src/avatar/Hand.cpp +++ b/interface/src/avatar/Hand.cpp @@ -253,7 +253,7 @@ void Hand::render(bool isMine) { _renderAlpha = 1.0; - if (Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionProxies)) { + if (Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionShapes)) { // draw a green sphere at hand joint location, which is actually near the wrist) for (size_t i = 0; i < getNumPalms(); i++) { PalmData& palm = getPalms()[i]; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index e18735e3e5..6e01ad3292 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -20,6 +20,11 @@ #include #include +#ifdef ANDREW_HACKERY +#include +#include +#endif ANDREW_HACKERY + #include "Application.h" #include "Audio.h" #include "Environment.h" @@ -867,6 +872,8 @@ bool findAvatarAvatarPenetration(const glm::vec3 positionA, float radiusA, float return false; } +static CollisionList bodyCollisions(16); + void MyAvatar::updateCollisionWithAvatars(float deltaTime) { // Reset detector for nearest avatar _distanceToNearestAvatar = std::numeric_limits::max(); @@ -878,15 +885,6 @@ void MyAvatar::updateCollisionWithAvatars(float deltaTime) { updateShapePositions(); float myBoundingRadius = getBoundingRadius(); - /* TODO: Andrew to fix Avatar-Avatar body collisions - // HACK: body-body collision uses two coaxial capsules with axes parallel to y-axis - // TODO: make the collision work without assuming avatar orientation - Extents myStaticExtents = _skeletonModel.getStaticExtents(); - glm::vec3 staticScale = myStaticExtents.maximum - myStaticExtents.minimum; - float myCapsuleRadius = 0.25f * (staticScale.x + staticScale.z); - float myCapsuleHeight = staticScale.y; - */ - foreach (const AvatarSharedPointer& avatarPointer, avatars) { Avatar* avatar = static_cast(avatarPointer.data()); if (static_cast(this) == avatar) { @@ -900,19 +898,26 @@ void MyAvatar::updateCollisionWithAvatars(float deltaTime) { } float theirBoundingRadius = avatar->getBoundingRadius(); if (distance < myBoundingRadius + theirBoundingRadius) { - /* TODO: Andrew to fix Avatar-Avatar body collisions - Extents theirStaticExtents = _skeletonModel.getStaticExtents(); - glm::vec3 staticScale = theirStaticExtents.maximum - theirStaticExtents.minimum; - float theirCapsuleRadius = 0.25f * (staticScale.x + staticScale.z); - float theirCapsuleHeight = staticScale.y; - - glm::vec3 penetration(0.f); - if (findAvatarAvatarPenetration(_position, myCapsuleRadius, myCapsuleHeight, - avatar->getPosition(), theirCapsuleRadius, theirCapsuleHeight, penetration)) { - // move the avatar out by half the penetration - setPosition(_position - 0.5f * penetration); +#ifdef ANDREW_HACKERY + QVector myShapes; + _skeletonModel.getBodyShapes(myShapes); + QVector theirShapes; + avatar->getSkeletonModel().getBodyShapes(theirShapes); + bodyCollisions.clear(); + foreach (const Shape* myShape, myShapes) { + foreach (const Shape* theirShape, theirShapes) { + ShapeCollider::shapeShape(myShape, theirShape, bodyCollisions); + if (bodyCollisions.size() > 0) { + std::cout << "adebug myPos = " << myShape->getPosition() + << " myRadius = " << myShape->getBoundingRadius() + << " theirPos = " << theirShape->getPosition() + << " theirRadius = " << theirShape->getBoundingRadius() + << std::endl; // adebug + std::cout << "adebug collision count = " << bodyCollisions.size() << std::endl; // adebug + } + } } - */ +#endif // ANDREW_HACKERY // collide our hands against them // TODO: make this work when we can figure out when the other avatar won't yeild diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 9d67709fde..0986021ac8 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -14,17 +14,17 @@ #include "Menu.h" #include "SkeletonModel.h" -SkeletonModel::SkeletonModel(Avatar* owningAvatar) : +SkeletonModel::SkeletonModel(Avatar* owningAvatar) : _owningAvatar(owningAvatar) { } -void SkeletonModel::simulate(float deltaTime, bool fullUpdate) { +void SkeletonModel::simulate(float deltaTime) { 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, fullUpdate); + Model::simulate(deltaTime); if (!(isActive() && _owningAvatar->isMyAvatar())) { return; // only simulate for own avatar @@ -94,6 +94,12 @@ void SkeletonModel::getHandShapes(int jointIndex, QVector& shapes) } } +void SkeletonModel::getBodyShapes(QVector& shapes) const { + // for now we push a single bounding shape, + // but later we could push a subset of joint shapes + shapes.push_back(&_boundingShape); +} + class IndexValue { public: int index; diff --git a/interface/src/avatar/SkeletonModel.h b/interface/src/avatar/SkeletonModel.h index 213a53d9ed..60b12eb8f5 100644 --- a/interface/src/avatar/SkeletonModel.h +++ b/interface/src/avatar/SkeletonModel.h @@ -9,7 +9,6 @@ #ifndef __interface__SkeletonModel__ #define __interface__SkeletonModel__ - #include "renderer/Model.h" class Avatar; @@ -22,14 +21,17 @@ public: SkeletonModel(Avatar* owningAvatar); - void simulate(float deltaTime, bool fullUpdate = true); + void simulate(float deltaTime); /// \param jointIndex index of hand joint /// \param shapes[out] list in which is stored pointers to hand shapes void getHandShapes(int jointIndex, QVector& shapes) const; + /// \param shapes[out] list of shapes for body collisions + void getBodyShapes(QVector& shapes) const; + protected: - + void applyHandPosition(int jointIndex, const glm::vec3& position); void applyPalmData(int jointIndex, const QVector& fingerJointIndices, diff --git a/interface/src/renderer/FBXReader.cpp b/interface/src/renderer/FBXReader.cpp index 53f4e04b0b..87b7d43938 100644 --- a/interface/src/renderer/FBXReader.cpp +++ b/interface/src/renderer/FBXReader.cpp @@ -41,6 +41,11 @@ bool Extents::containsPoint(const glm::vec3& point) const { && point.z >= minimum.z && point.z <= maximum.z); } +void Extents::addExtents(const Extents& extents) { + minimum = glm::min(minimum, extents.minimum); + maximum = glm::max(maximum, extents.maximum); +} + void Extents::addPoint(const glm::vec3& point) { minimum = glm::min(minimum, point); maximum = glm::max(maximum, point); @@ -1337,7 +1342,6 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) } geometry.bindExtents.reset(); - geometry.staticExtents.reset(); geometry.meshExtents.reset(); for (QHash::iterator it = meshes.begin(); it != meshes.end(); it++) { @@ -1505,8 +1509,6 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) JointShapeInfo& jointShapeInfo = jointShapeInfos[jointIndex]; jointShapeInfo.boneBegin = rotateMeshToJoint * (radiusScale * (boneBegin - boneEnd)); - bool jointIsStatic = joint.freeLineage.isEmpty(); - glm::vec3 jointTranslation = extractTranslation(geometry.offset * joint.bindTransform); float totalWeight = 0.0f; for (int j = 0; j < cluster.indices.size(); j++) { int oldIndex = cluster.indices.at(j); @@ -1528,10 +1530,6 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) jointShapeInfo.extents.addPoint(vertexInJointFrame); jointShapeInfo.averageVertex += vertexInJointFrame; ++jointShapeInfo.numVertices; - if (jointIsStatic) { - // expand the extents of static (nonmovable) joints - geometry.staticExtents.addPoint(vertex + jointTranslation); - } } // look for an unused slot in the weights vector diff --git a/interface/src/renderer/FBXReader.h b/interface/src/renderer/FBXReader.h index 5f6a4f51ba..2847d2a971 100644 --- a/interface/src/renderer/FBXReader.h +++ b/interface/src/renderer/FBXReader.h @@ -30,6 +30,10 @@ public: /// set minimum and maximum to FLT_MAX and -FLT_MAX respectively void reset(); + /// \param extents another intance of extents + /// expand current limits to contain other extents + void addExtents(const Extents& extents); + /// \param point new point to compare against existing limits /// compare point to current limits and expand them if necessary to contain point void addPoint(const glm::vec3& point); @@ -174,7 +178,6 @@ public: glm::vec3 neckPivot; Extents bindExtents; - Extents staticExtents; Extents meshExtents; QVector attachments; diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index ceddcd009a..8251b67b1e 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -32,9 +32,11 @@ Model::Model(QObject* parent) : QObject(parent), _scale(1.0f, 1.0f, 1.0f), _shapesAreDirty(true), + _boundingRadius(0.f), + _boundingShape(), + _boundingShapeLocalOffset(0.f), _lodDistance(0.0f), - _pupilDilation(0.0f), - _boundingRadius(0.f) { + _pupilDilation(0.0f) { // we may have been created in the network thread, but we live in the main thread moveToThread(Application::getInstance()->thread()); } @@ -73,6 +75,44 @@ QVector Model::createJointStates(const FBXGeometry& geometry) state.rotation = joint.rotation; jointStates.append(state); } + + // compute transforms + // Unfortunately, the joints are not neccessarily in order from parents to children, + // so we must iterate over the list multiple times until all are set correctly. + QVector jointIsSet; + int numJoints = jointStates.size(); + jointIsSet.fill(false, numJoints); + int numJointsSet = 0; + int lastNumJointsSet = -1; + while (numJointsSet < numJoints && numJointsSet != lastNumJointsSet) { + lastNumJointsSet = numJointsSet; + for (int i = 0; i < numJoints; ++i) { + if (jointIsSet[i]) { + continue; + } + JointState& state = jointStates[i]; + const FBXJoint& joint = geometry.joints[i]; + int parentIndex = joint.parentIndex; + if (parentIndex == -1) { + glm::mat4 baseTransform = glm::mat4_cast(_rotation) * glm::scale(_scale) * glm::translate(_offset); + glm::quat combinedRotation = joint.preRotation * state.rotation * joint.postRotation; + state.transform = baseTransform * geometry.offset * glm::translate(state.translation) * joint.preTransform * + glm::mat4_cast(combinedRotation) * joint.postTransform; + state.combinedRotation = _rotation * combinedRotation; + ++numJointsSet; + jointIsSet[i] = true; + } else if (jointIsSet[parentIndex]) { + const JointState& parentState = jointStates.at(parentIndex); + glm::quat combinedRotation = joint.preRotation * state.rotation * joint.postRotation; + state.transform = parentState.transform * glm::translate(state.translation) * joint.preTransform * + glm::mat4_cast(combinedRotation) * joint.postTransform; + state.combinedRotation = parentState.combinedRotation * combinedRotation; + ++numJointsSet; + jointIsSet[i] = true; + } + } + } + return jointStates; } @@ -135,65 +175,87 @@ void Model::reset() { } } -void Model::clearShapes() { - for (int i = 0; i < _jointShapes.size(); ++i) { - delete _jointShapes[i]; - } - _jointShapes.clear(); -} +// return 'true' if geometry is up to date +bool Model::updateGeometry() { + bool needToRebuild = false; -void Model::createJointCollisionShapes() { - clearShapes(); - const FBXGeometry& geometry = _geometry->getFBXGeometry(); - float uniformScale = extractUniformScale(_scale); - for (int i = 0; i < _jointStates.size(); i++) { - const FBXJoint& joint = geometry.joints[i]; - glm::vec3 meshCenter = _jointStates[i].combinedRotation * joint.shapePosition; - glm::vec3 position = _rotation * (extractTranslation(_jointStates[i].transform) + uniformScale * meshCenter) + _translation; - - float radius = uniformScale * joint.boneRadius; - if (joint.shapeType == Shape::CAPSULE_SHAPE) { - float halfHeight = 0.5f * uniformScale * joint.distanceToParent; - CapsuleShape* shape = new CapsuleShape(radius, halfHeight); - shape->setPosition(position); - _jointShapes.push_back(shape); - } else { - SphereShape* shape = new SphereShape(radius, position); - _jointShapes.push_back(shape); + if (_nextGeometry) { + _nextGeometry = _nextGeometry->getLODOrFallback(_lodDistance, _nextLODHysteresis); + _nextGeometry->setLoadPriority(this, -_lodDistance); + _nextGeometry->ensureLoading(); + if (_nextGeometry->isLoaded()) { + applyNextGeometry(); + needToRebuild = true; } } -} + if (!_geometry) { + // geometry is not ready + return false; + } -void Model::createBoundingShape() { - //const FBXGeometry& geometry = _geometry->getFBXGeometry(); - //float uniformScale = extractUniformScale(_scale); -} + QSharedPointer geometry = _geometry->getLODOrFallback(_lodDistance, _lodHysteresis); + if (_geometry != geometry) { + // NOTE: it is theoretically impossible to reach here after passing through the applyNextGeometry() call above. + // Which means we don't need to worry about calling deleteGeometry() below immediately after creating new geometry. -void Model::updateShapePositions() { - if (_shapesAreDirty && _jointShapes.size() == _jointStates.size()) { - _boundingRadius = 0.f; - float uniformScale = extractUniformScale(_scale); - const FBXGeometry& geometry = _geometry->getFBXGeometry(); - for (int i = 0; i < _jointStates.size(); i++) { - const FBXJoint& joint = geometry.joints[i]; - // shape position and rotation need to be in world-frame - glm::vec3 jointToShapeOffset = uniformScale * (_jointStates[i].combinedRotation * joint.shapePosition); - glm::vec3 worldPosition = extractTranslation(_jointStates[i].transform) + jointToShapeOffset + _translation; - _jointShapes[i]->setPosition(worldPosition); - _jointShapes[i]->setRotation(_jointStates[i].combinedRotation * joint.shapeRotation); - float distance2 = glm::distance2(worldPosition, _translation); - if (distance2 > _boundingRadius) { - _boundingRadius = distance2; + const FBXGeometry& newGeometry = geometry->getFBXGeometry(); + QVector newJointStates = createJointStates(newGeometry); + if (! _jointStates.isEmpty()) { + // copy the existing joint states + const FBXGeometry& oldGeometry = _geometry->getFBXGeometry(); + 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; + _jointStates = newJointStates; + needToRebuild = true; + } else if (_jointStates.isEmpty()) { + const FBXGeometry& fbxGeometry = geometry->getFBXGeometry(); + if (fbxGeometry.joints.size() > 0) { + _jointStates = createJointStates(fbxGeometry); + needToRebuild = true; } - _boundingRadius = sqrtf(_boundingRadius); - _shapesAreDirty = false; } -} - -void Model::simulate(float deltaTime, bool fullUpdate) { - // update our LOD, then simulate - simulate(deltaTime, fullUpdate, updateGeometry()); + _geometry->setLoadPriority(this, -_lodDistance); + _geometry->ensureLoading(); + + if (needToRebuild) { + const FBXGeometry& fbxGeometry = geometry->getFBXGeometry(); + foreach (const FBXMesh& mesh, fbxGeometry.meshes) { + MeshState state; + state.clusterMatrices.resize(mesh.clusters.size()); + _meshStates.append(state); + + QOpenGLBuffer buffer; + if (!mesh.blendshapes.isEmpty()) { + buffer.setUsagePattern(QOpenGLBuffer::DynamicDraw); + buffer.create(); + buffer.bind(); + buffer.allocate((mesh.vertices.size() + mesh.normals.size()) * sizeof(glm::vec3)); + buffer.write(0, mesh.vertices.constData(), mesh.vertices.size() * sizeof(glm::vec3)); + buffer.write(mesh.vertices.size() * sizeof(glm::vec3), mesh.normals.constData(), + mesh.normals.size() * sizeof(glm::vec3)); + buffer.release(); + } + _blendedVertexBuffers.append(buffer); + } + foreach (const FBXAttachment& attachment, fbxGeometry.attachments) { + Model* model = new Model(this); + model->init(); + model->setURL(attachment.url); + _attachments.append(model); + } + createShapes(); + } + return true; } bool Model::render(float alpha, bool forShadowMap) { @@ -262,15 +324,6 @@ Extents Model::getBindExtents() const { return scaledExtents; } -Extents Model::getStaticExtents() const { - if (!isActive()) { - return Extents(); - } - const Extents& staticExtents = _geometry->getFBXGeometry().staticExtents; - Extents scaledExtents = { staticExtents.minimum * _scale, staticExtents.maximum * _scale }; - return scaledExtents; -} - bool Model::getJointState(int index, glm::quat& rotation) const { if (index == -1 || index >= _jointStates.size()) { return false; @@ -373,6 +426,90 @@ void Model::setURL(const QUrl& url, const QUrl& fallback, bool retainCurrent, bo } } +void Model::clearShapes() { + for (int i = 0; i < _jointShapes.size(); ++i) { + delete _jointShapes[i]; + } + _jointShapes.clear(); +} + +void Model::createShapes() { + clearShapes(); + const FBXGeometry& geometry = _geometry->getFBXGeometry(); + float uniformScale = extractUniformScale(_scale); + glm::quat inverseRotation = glm::inverse(_rotation); + + for (int i = 0; i < _jointStates.size(); i++) { + updateJointState(i); + } + + // joint shapes + Extents totalExtents; + for (int i = 0; i < _jointStates.size(); i++) { + const FBXJoint& joint = geometry.joints[i]; + + glm::vec3 jointToShapeOffset = uniformScale * (_jointStates[i].combinedRotation * joint.shapePosition); + glm::vec3 worldPosition = extractTranslation(_jointStates[i].transform) + jointToShapeOffset + _translation; + Extents shapeExtents; + + float radius = uniformScale * joint.boneRadius; + float halfHeight = 0.5f * uniformScale * joint.distanceToParent; + if (joint.shapeType == Shape::CAPSULE_SHAPE && halfHeight > EPSILON) { + CapsuleShape* capsule = new CapsuleShape(radius, halfHeight); + capsule->setPosition(worldPosition); + capsule->setRotation(_jointStates[i].combinedRotation * joint.shapeRotation); + _jointShapes.push_back(capsule); + + glm::vec3 endPoint; + capsule->getEndPoint(endPoint); + glm::vec3 startPoint; + capsule->getStartPoint(startPoint); + glm::vec3 axis = (halfHeight + radius) * glm::normalize(endPoint - startPoint); + shapeExtents.addPoint(inverseRotation * (worldPosition + axis - _translation)); + shapeExtents.addPoint(inverseRotation * (worldPosition - axis - _translation)); + } else { + SphereShape* sphere = new SphereShape(radius, worldPosition); + _jointShapes.push_back(sphere); + + glm::vec3 axis = glm::vec3(radius); + shapeExtents.addPoint(inverseRotation * (worldPosition + axis - _translation)); + shapeExtents.addPoint(inverseRotation * (worldPosition - axis - _translation)); + } + totalExtents.addExtents(shapeExtents); + } + + // bounding shape + // NOTE: we assume that the longest side of totalExtents is the yAxis + glm::vec3 diagonal = totalExtents.maximum - totalExtents.minimum; + float capsuleRadius = 0.25f * (diagonal.x + diagonal.z); // half the average of x and z + _boundingShape.setRadius(capsuleRadius); + _boundingShape.setHalfHeight(0.5f * diagonal.y - capsuleRadius); + _boundingShapeLocalOffset = 0.5f * (totalExtents.maximum + totalExtents.minimum); +} + +void Model::updateShapePositions() { + if (_shapesAreDirty && _jointShapes.size() == _jointStates.size()) { + _boundingRadius = 0.f; + float uniformScale = extractUniformScale(_scale); + const FBXGeometry& geometry = _geometry->getFBXGeometry(); + for (int i = 0; i < _jointStates.size(); i++) { + const FBXJoint& joint = geometry.joints[i]; + // shape position and rotation need to be in world-frame + glm::vec3 jointToShapeOffset = uniformScale * (_jointStates[i].combinedRotation * joint.shapePosition); + glm::vec3 worldPosition = extractTranslation(_jointStates[i].transform) + jointToShapeOffset + _translation; + _jointShapes[i]->setPosition(worldPosition); + _jointShapes[i]->setRotation(_jointStates[i].combinedRotation * joint.shapeRotation); + float distance2 = glm::distance2(worldPosition, _translation); + if (distance2 > _boundingRadius) { + _boundingRadius = distance2; + } + } + _boundingRadius = sqrtf(_boundingRadius); + _shapesAreDirty = false; + } + _boundingShape.setPosition(_translation + _rotation * _boundingShapeLocalOffset); +} + bool Model::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const { const glm::vec3 relativeOrigin = origin - _translation; const FBXGeometry& geometry = _geometry->getFBXGeometry(); @@ -419,7 +556,6 @@ bool Model::findCollisions(const QVector shapes, CollisionList& co bool Model::findSphereCollisions(const glm::vec3& sphereCenter, float sphereRadius, CollisionList& collisions, int skipIndex) { bool collided = false; - updateShapePositions(); SphereShape sphere(sphereRadius, sphereCenter); const FBXGeometry& geometry = _geometry->getFBXGeometry(); for (int i = 0; i < _jointShapes.size(); i++) { @@ -448,45 +584,6 @@ 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; -} - class Blender : public QRunnable { public: @@ -549,53 +646,23 @@ void Blender::run() { Q_ARG(const QVector&, vertices), Q_ARG(const QVector&, normals)); } -void Model::simulate(float deltaTime, bool fullUpdate, const QVector& newJointStates) { - if (!isActive()) { + +void Model::simulate(float deltaTime) { + bool geometryIsUpToDate = updateGeometry(); + if (!geometryIsUpToDate) { 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()); - _meshStates.append(state); - - QOpenGLBuffer buffer; - if (!mesh.blendshapes.isEmpty()) { - buffer.setUsagePattern(QOpenGLBuffer::DynamicDraw); - buffer.create(); - buffer.bind(); - buffer.allocate((mesh.vertices.size() + mesh.normals.size()) * sizeof(glm::vec3)); - buffer.write(0, mesh.vertices.constData(), mesh.vertices.size() * sizeof(glm::vec3)); - buffer.write(mesh.vertices.size() * sizeof(glm::vec3), mesh.normals.constData(), - mesh.normals.size() * sizeof(glm::vec3)); - buffer.release(); - } - _blendedVertexBuffers.append(buffer); - } - foreach (const FBXAttachment& attachment, geometry.attachments) { - Model* model = new Model(this); - model->init(); - model->setURL(attachment.url); - _attachments.append(model); - } - fullUpdate = true; - createJointCollisionShapes(); - } - - // exit early if we don't have to perform a full update - if (!fullUpdate) { - return; - } - + simulateInternal(deltaTime); +} + +void Model::simulateInternal(float deltaTime) { // update the world space transforms for all joints for (int i = 0; i < _jointStates.size(); i++) { updateJointState(i); } + const FBXGeometry& geometry = _geometry->getFBXGeometry(); + // update the attachment transforms and simulate them for (int i = 0; i < _attachments.size(); i++) { const FBXAttachment& attachment = geometry.attachments.at(i); @@ -641,7 +708,7 @@ void Model::updateJointState(int index) { state.transform = baseTransform * geometry.offset * glm::translate(state.translation) * joint.preTransform * glm::mat4_cast(combinedRotation) * joint.postTransform; state.combinedRotation = _rotation * combinedRotation; - + } else { const JointState& parentState = _jointStates.at(joint.parentIndex); if (index == geometry.leanJointIndex) { @@ -825,11 +892,11 @@ void Model::applyRotationDelta(int jointIndex, const glm::quat& delta, bool cons state.rotation = newRotation; } -void Model::renderCollisionProxies(float alpha) { +const int BALL_SUBDIVISIONS = 10; + +void Model::renderJointCollisionShapes(float alpha) { glPushMatrix(); Application::getInstance()->loadTranslatedViewMatrix(_translation); - updateShapePositions(); - const int BALL_SUBDIVISIONS = 10; for (int i = 0; i < _jointShapes.size(); i++) { glPushMatrix(); @@ -876,6 +943,36 @@ void Model::renderCollisionProxies(float alpha) { glPopMatrix(); } +void Model::renderBoundingCollisionShapes(float alpha) { + glPushMatrix(); + + Application::getInstance()->loadTranslatedViewMatrix(_translation); + + // draw a blue sphere at the capsule endpoint + glm::vec3 endPoint; + _boundingShape.getEndPoint(endPoint); + endPoint = endPoint - _translation; + glTranslatef(endPoint.x, endPoint.y, endPoint.z); + glColor4f(0.6f, 0.6f, 0.8f, alpha); + glutSolidSphere(_boundingShape.getRadius(), BALL_SUBDIVISIONS, BALL_SUBDIVISIONS); + + // draw a yellow sphere at the capsule startpoint + glm::vec3 startPoint; + _boundingShape.getStartPoint(startPoint); + startPoint = startPoint - _translation; + glm::vec3 axis = endPoint - startPoint; + glTranslatef(-axis.x, -axis.y, -axis.z); + glColor4f(0.8f, 0.8f, 0.6f, alpha); + glutSolidSphere(_boundingShape.getRadius(), BALL_SUBDIVISIONS, BALL_SUBDIVISIONS); + + // draw a green cylinder between the two points + glm::vec3 origin(0.f); + glColor4f(0.6f, 0.8f, 0.6f, alpha); + Avatar::renderJointConnectingCone( origin, axis, _boundingShape.getRadius(), _boundingShape.getRadius()); + + glPopMatrix(); +} + bool Model::collisionHitsMoveableJoint(CollisionInfo& collision) const { if (collision._type == MODEL_COLLISION) { // the joint is pokable by a collision if it exists and is free to move diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 0b05fab2bf..205687baa9 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -12,6 +12,8 @@ #include #include +#include + #include "GeometryCache.h" #include "InterfaceConfig.h" #include "ProgramObject.h" @@ -54,13 +56,9 @@ public: void init(); void reset(); - void clearShapes(); - void createJointCollisionShapes(); - void createBoundingShape(); - void updateShapePositions(); - void simulate(float deltaTime, bool fullUpdate = true); + void simulate(float deltaTime); bool render(float alpha = 1.0f, bool forShadowMap = false); - + /// Sets the URL of the model to render. /// \param fallback the URL of a fallback model to render if the requested model fails to load /// \param retainCurrent if true, keep rendering the current model until the new one is loaded @@ -76,9 +74,6 @@ public: /// Returns the extents of the model in its bind pose. Extents getBindExtents() const; - /// Returns the extents of the unmovable joints of the model. - Extents getStaticExtents() const; - /// Returns a reference to the shared geometry. const QSharedPointer& getGeometry() const { return _geometry; } @@ -160,6 +155,12 @@ public: /// Returns the extended length from the right hand to its first free ancestor. float getRightArmLength() const; + void clearShapes(); + void createShapes(); + void updateShapePositions(); + void renderJointCollisionShapes(float alpha); + void renderBoundingCollisionShapes(float alpha); + bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const; /// \param shapes list of pointers shapes to test against Model @@ -170,8 +171,6 @@ public: bool findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius, CollisionList& collisions, int skipIndex = -1); - void renderCollisionProxies(float alpha); - /// \param collision details about the collisions /// \return true if the collision is against a moveable joint bool collisionHitsMoveableJoint(CollisionInfo& collision) const; @@ -206,6 +205,10 @@ protected: QVector _jointStates; QVector _jointShapes; + float _boundingRadius; + CapsuleShape _boundingShape; + glm::vec3 _boundingShapeLocalOffset; + class MeshState { public: QVector clusterMatrices; @@ -213,8 +216,8 @@ protected: QVector _meshStates; - QVector updateGeometry(); - void simulate(float deltaTime, bool fullUpdate, const QVector& newJointStates); + bool updateGeometry(); + void simulateInternal(float deltaTime); /// Updates the state of the joint at the specified index. virtual void updateJointState(int index); @@ -249,6 +252,7 @@ private: void applyNextGeometry(); void deleteGeometry(); void renderMeshes(float alpha, bool forShadowMap, bool translucent); + QVector createJointStates(const FBXGeometry& geometry); QSharedPointer _baseGeometry; ///< reference required to prevent collection of base QSharedPointer _nextBaseGeometry; @@ -268,8 +272,6 @@ private: QVector _attachments; - float _boundingRadius; - static ProgramObject _program; static ProgramObject _normalMapProgram; static ProgramObject _shadowProgram; @@ -292,7 +294,6 @@ private: static SkinLocations _skinShadowLocations; static void initSkinProgram(ProgramObject& program, SkinLocations& locations); - static QVector createJointStates(const FBXGeometry& geometry); }; Q_DECLARE_METATYPE(QPointer) From 03d04e194d2ceff66e79322c3cba0035c557c3e2 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 1 Apr 2014 10:39:58 -0700 Subject: [PATCH 04/16] Init bounding Extents for more correct shapes --- interface/src/renderer/Model.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 8251b67b1e..91b69d052c 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -445,12 +445,14 @@ void Model::createShapes() { // joint shapes Extents totalExtents; + totalExtents.reset(); for (int i = 0; i < _jointStates.size(); i++) { const FBXJoint& joint = geometry.joints[i]; glm::vec3 jointToShapeOffset = uniformScale * (_jointStates[i].combinedRotation * joint.shapePosition); glm::vec3 worldPosition = extractTranslation(_jointStates[i].transform) + jointToShapeOffset + _translation; Extents shapeExtents; + shapeExtents.reset(); float radius = uniformScale * joint.boneRadius; float halfHeight = 0.5f * uniformScale * joint.distanceToParent; From 9f9127b7165f0535fe63ad816bf94512a3f10cb9 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 1 Apr 2014 11:39:24 -0700 Subject: [PATCH 05/16] draw head bounding shape first --- interface/src/avatar/Avatar.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index b36baf15b1..490daf4cab 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -217,10 +217,10 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) { getHead()->getFaceModel().renderJointCollisionShapes(0.7f); } if (Menu::getInstance()->isOptionChecked(MenuOption::RenderBoundingCollisionShapes)) { - _skeletonModel.updateShapePositions(); - _skeletonModel.renderBoundingCollisionShapes(0.7f); getHead()->getFaceModel().updateShapePositions(); getHead()->getFaceModel().renderBoundingCollisionShapes(0.7f); + _skeletonModel.updateShapePositions(); + _skeletonModel.renderBoundingCollisionShapes(0.7f); } // quick check before falling into the code below: From 3d43956c111998eab97abb3731e195e9aa7ea86d Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 1 Apr 2014 11:39:40 -0700 Subject: [PATCH 06/16] bounding shape relative to model's root Joint --- interface/src/renderer/Model.cpp | 33 +++++++++++++++++++++++--------- interface/src/renderer/Model.h | 2 +- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 91b69d052c..3a12d76626 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -435,13 +435,20 @@ void Model::clearShapes() { void Model::createShapes() { clearShapes(); - const FBXGeometry& geometry = _geometry->getFBXGeometry(); - float uniformScale = extractUniformScale(_scale); - glm::quat inverseRotation = glm::inverse(_rotation); + if (_jointStates.isEmpty()) { + return; + } + + // make sure all the joints are updated correctly before we try to create their shapes for (int i = 0; i < _jointStates.size(); i++) { updateJointState(i); } + + const FBXGeometry& geometry = _geometry->getFBXGeometry(); + float uniformScale = extractUniformScale(_scale); + glm::quat inverseRotation = glm::inverse(_rotation); + glm::vec3 rootPosition(0.f); // joint shapes Extents totalExtents; @@ -454,6 +461,10 @@ void Model::createShapes() { Extents shapeExtents; shapeExtents.reset(); + if (joint.parentIndex == -1) { + rootPosition = worldPosition; + } + float radius = uniformScale * joint.boneRadius; float halfHeight = 0.5f * uniformScale * joint.distanceToParent; if (joint.shapeType == Shape::CAPSULE_SHAPE && halfHeight > EPSILON) { @@ -467,15 +478,15 @@ void Model::createShapes() { glm::vec3 startPoint; capsule->getStartPoint(startPoint); glm::vec3 axis = (halfHeight + radius) * glm::normalize(endPoint - startPoint); - shapeExtents.addPoint(inverseRotation * (worldPosition + axis - _translation)); - shapeExtents.addPoint(inverseRotation * (worldPosition - axis - _translation)); + shapeExtents.addPoint(worldPosition + axis); + shapeExtents.addPoint(worldPosition - axis); } else { SphereShape* sphere = new SphereShape(radius, worldPosition); _jointShapes.push_back(sphere); glm::vec3 axis = glm::vec3(radius); - shapeExtents.addPoint(inverseRotation * (worldPosition + axis - _translation)); - shapeExtents.addPoint(inverseRotation * (worldPosition - axis - _translation)); + shapeExtents.addPoint(worldPosition + axis); + shapeExtents.addPoint(worldPosition - axis); } totalExtents.addExtents(shapeExtents); } @@ -486,11 +497,12 @@ void Model::createShapes() { float capsuleRadius = 0.25f * (diagonal.x + diagonal.z); // half the average of x and z _boundingShape.setRadius(capsuleRadius); _boundingShape.setHalfHeight(0.5f * diagonal.y - capsuleRadius); - _boundingShapeLocalOffset = 0.5f * (totalExtents.maximum + totalExtents.minimum); + _boundingShapeLocalOffset = inverseRotation * (0.5f * (totalExtents.maximum + totalExtents.minimum) - rootPosition); } void Model::updateShapePositions() { if (_shapesAreDirty && _jointShapes.size() == _jointStates.size()) { + glm::vec3 rootPosition(0.f); _boundingRadius = 0.f; float uniformScale = extractUniformScale(_scale); const FBXGeometry& geometry = _geometry->getFBXGeometry(); @@ -505,11 +517,14 @@ void Model::updateShapePositions() { if (distance2 > _boundingRadius) { _boundingRadius = distance2; } + if (joint.parentIndex == -1) { + rootPosition = worldPosition; + } } _boundingRadius = sqrtf(_boundingRadius); _shapesAreDirty = false; + _boundingShape.setPosition(rootPosition + _rotation * _boundingShapeLocalOffset); } - _boundingShape.setPosition(_translation + _rotation * _boundingShapeLocalOffset); } bool Model::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const { diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 205687baa9..43eb7fda67 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -56,7 +56,7 @@ public: void init(); void reset(); - void simulate(float deltaTime); + virtual void simulate(float deltaTime); bool render(float alpha = 1.0f, bool forShadowMap = false); /// Sets the URL of the model to render. From 03958f2fa07d0ab8346790630b51c30d257a4f5a Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 1 Apr 2014 12:02:47 -0700 Subject: [PATCH 07/16] work on metadata in the model browser + real time updates --- interface/src/Menu.cpp | 23 +- interface/src/ui/ModelBrowser.cpp | 150 ------------ interface/src/ui/ModelBrowser.h | 62 ----- interface/src/ui/ModelsBrowser.cpp | 305 ++++++++++++++++++++++++ interface/src/ui/ModelsBrowser.h | 79 ++++++ libraries/shared/src/FileDownloader.cpp | 54 ++--- libraries/shared/src/FileDownloader.h | 13 +- 7 files changed, 421 insertions(+), 265 deletions(-) delete mode 100644 interface/src/ui/ModelBrowser.cpp delete mode 100644 interface/src/ui/ModelBrowser.h create mode 100644 interface/src/ui/ModelsBrowser.cpp create mode 100644 interface/src/ui/ModelsBrowser.h diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 79b0a23ce5..3e097008c9 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -29,7 +29,6 @@ #include #include #include -#include #include "Application.h" #include "Menu.h" @@ -37,7 +36,7 @@ #include "Util.h" #include "ui/InfoView.h" #include "ui/MetavoxelEditor.h" -#include "ui/ModelBrowser.h" +#include "ui/ModelsBrowser.h" Menu* Menu::_instance = NULL; @@ -714,8 +713,8 @@ void Menu::loginForCurrentDomain() { void Menu::editPreferences() { Application* applicationInstance = Application::getInstance(); - ModelBrowser headBrowser(Head); - ModelBrowser skeletonBrowser(Skeleton); + ModelsBrowser headBrowser(Head); + ModelsBrowser skeletonBrowser(Skeleton); const QString BROWSE_BUTTON_TEXT = "Browse"; @@ -816,15 +815,23 @@ void Menu::editPreferences() { if (ret == QDialog::Accepted) { bool shouldDispatchIdentityPacket = false; - if (headURLEdit.text() != faceURLString && !headURLEdit.text().isEmpty()) { + if (headURLEdit.text() != faceURLString) { // change the faceModelURL in the profile, it will also update this user's BlendFace - applicationInstance->getAvatar()->setFaceModelURL(QUrl(headURLEdit.text())); + if (headURLEdit.text().isEmpty()) { + applicationInstance->getAvatar()->setFaceModelURL(QUrl(headURLEdit.placeholderText())); + } else { + applicationInstance->getAvatar()->setFaceModelURL(QUrl(headURLEdit.text())); + } shouldDispatchIdentityPacket = true; } - if (skeletonURLEdit.text() != skeletonURLString && !skeletonURLEdit.text().isEmpty()) { + if (skeletonURLEdit.text() != skeletonURLString) { // change the skeletonModelURL in the profile, it will also update this user's Body - applicationInstance->getAvatar()->setSkeletonModelURL(QUrl(skeletonURLEdit.text())); + if (skeletonURLEdit.text().isEmpty()) { + applicationInstance->getAvatar()->setSkeletonModelURL(QUrl(skeletonURLEdit.placeholderText())); + } else { + applicationInstance->getAvatar()->setSkeletonModelURL(QUrl(skeletonURLEdit.text())); + } shouldDispatchIdentityPacket = true; } diff --git a/interface/src/ui/ModelBrowser.cpp b/interface/src/ui/ModelBrowser.cpp deleted file mode 100644 index e03081273f..0000000000 --- a/interface/src/ui/ModelBrowser.cpp +++ /dev/null @@ -1,150 +0,0 @@ -// -// ModelBrowser.cpp -// hifi -// -// Created by Clement on 3/17/14. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. -// - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "ModelBrowser.h" - -static const QString PREFIX_PARAMETER_NAME = "prefix"; -static const QString MARKER_PARAMETER_NAME = "marker"; -static const QString IS_TRUNCATED_NAME = "IsTruncated"; -static const QString CONTAINER_NAME = "Contents"; -static const QString KEY_NAME = "Key"; - -ModelBrowser::ModelBrowser(ModelType modelType, QWidget* parent) : QWidget(parent), _type(modelType) { - QUrl url(S3_URL); - QUrlQuery query; - - if (_type == Head) { - query.addQueryItem(PREFIX_PARAMETER_NAME, HEAD_MODELS_LOCATION); - } else if (_type == Skeleton) { - query.addQueryItem(PREFIX_PARAMETER_NAME, SKELETON_MODELS_LOCATION); - } - url.setQuery(query); - - _downloader = new FileDownloader(url); - connect(_downloader, SIGNAL(done(QNetworkReply::NetworkError)), SLOT(downloadFinished())); -} - -ModelBrowser::~ModelBrowser() { - delete _downloader; -} - -void ModelBrowser::downloadFinished() { - parseXML(_downloader->getData()); -} - -void ModelBrowser::browse() { - QDialog dialog(this); - dialog.setWindowTitle("Browse models"); - - QGridLayout* layout = new QGridLayout(&dialog); - dialog.setLayout(layout); - - QLineEdit* searchBar = new QLineEdit(&dialog); - layout->addWidget(searchBar, 0, 0); - - ListView* listView = new ListView(&dialog); - listView->setEditTriggers(QAbstractItemView::NoEditTriggers); - layout->addWidget(listView, 1, 0); - listView->connect(searchBar, SIGNAL(textChanged(const QString&)), SLOT(keyboardSearch(const QString&))); - dialog.connect(listView, SIGNAL(doubleClicked(const QModelIndex&)), SLOT(accept())); - - QStringListModel* model = new QStringListModel(_models.keys(), listView); - model->sort(0); - listView->setModel(model); - - QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - layout->addWidget(buttons, 2, 0); - dialog.connect(buttons, SIGNAL(accepted()), SLOT(accept())); - dialog.connect(buttons, SIGNAL(rejected()), SLOT(reject())); - - if (dialog.exec() == QDialog::Rejected) { - return; - } - - QString selectedKey = model->data(listView->currentIndex(), Qt::DisplayRole).toString(); - - emit selected(_models[selectedKey]); -} - -bool ModelBrowser::parseXML(QByteArray xmlFile) { - QXmlStreamReader xml(xmlFile); - QRegExp rx(".*fst"); - bool truncated = false; - QString lastKey; - - // Read xml until the end or an error is detected - while(!xml.atEnd() && !xml.hasError()) { - if(xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == IS_TRUNCATED_NAME) { - while(!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == IS_TRUNCATED_NAME)) { - // Let's check if there is more - xml.readNext(); - if (xml.text().toString() == "True") { - truncated = true; - } - } - } - - if(xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == CONTAINER_NAME) { - while(!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == CONTAINER_NAME)) { - // If a file is find, process it - if(xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == KEY_NAME) { - xml.readNext(); - lastKey = xml.text().toString(); - if (rx.exactMatch(xml.text().toString())) { - // Add the found file to the list - _models.insert(QFileInfo(xml.text().toString()).baseName(), - S3_URL + "/" + xml.text().toString()); - } - } - xml.readNext(); - } - } - xml.readNext(); - } - - // Error handling - if(xml.hasError()) { - _models.clear(); - QMessageBox::critical(this, - "ModelBrowser::ModelBrowser()", - xml.errorString(), - QMessageBox::Ok); - return false; - } - - // If we didn't all the files, download the next ones - if (truncated) { - QUrl url(S3_URL); - QUrlQuery query; - - if (_type == Head) { - query.addQueryItem(PREFIX_PARAMETER_NAME, HEAD_MODELS_LOCATION); - } else if (_type == Skeleton) { - query.addQueryItem(PREFIX_PARAMETER_NAME, SKELETON_MODELS_LOCATION); - } - query.addQueryItem(MARKER_PARAMETER_NAME, lastKey); - url.setQuery(query); - - delete _downloader; - _downloader = new FileDownloader(url); - connect(_downloader, SIGNAL(done(QNetworkReply::NetworkError)), SLOT(downloadFinished())); - } - - return true; -} \ No newline at end of file diff --git a/interface/src/ui/ModelBrowser.h b/interface/src/ui/ModelBrowser.h deleted file mode 100644 index 4628642e89..0000000000 --- a/interface/src/ui/ModelBrowser.h +++ /dev/null @@ -1,62 +0,0 @@ -// -// ModelBrowser.h -// hifi -// -// Created by Clement on 3/17/14. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. -// - -#ifndef __hifi__ModelBrowser__ -#define __hifi__ModelBrowser__ - -#include - -#include -#include - -static const QString S3_URL = "http://highfidelity-public.s3-us-west-1.amazonaws.com"; -static const QString HEAD_MODELS_LOCATION = "models/heads/"; -static const QString SKELETON_MODELS_LOCATION = "models/skeletons/"; - -enum ModelType { - Head, - Skeleton -}; - -class ModelBrowser : public QWidget { - Q_OBJECT - -public: - ModelBrowser(ModelType modelType, QWidget* parent = NULL); - ~ModelBrowser(); - -signals: - void selected(QString filename); - -public slots: - void browse(); - -private slots: - void downloadFinished(); - -private: - ModelType _type; - FileDownloader* _downloader; - QHash _models; - - bool parseXML(QByteArray xmlFile); -}; - - - -class ListView : public QListView { - Q_OBJECT -public: - ListView(QWidget* parent) : QListView(parent) {} - public slots: - void keyboardSearch(const QString& text) { - QAbstractItemView::keyboardSearch(text); - } -}; - -#endif /* defined(__hifi__ModelBrowser__) */ diff --git a/interface/src/ui/ModelsBrowser.cpp b/interface/src/ui/ModelsBrowser.cpp new file mode 100644 index 0000000000..3bdc4ca9ed --- /dev/null +++ b/interface/src/ui/ModelsBrowser.cpp @@ -0,0 +1,305 @@ +// +// ModelsBrowser.cpp +// hifi +// +// Created by Clement on 3/17/14. +// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "ModelsBrowser.h" + +static const QString S3_URL = "http://highfidelity-public.s3-us-west-1.amazonaws.com"; +static const QString PUBLIC_URL = "http://public.highfidelity.io"; +static const QString HEAD_MODELS_LOCATION = "models/heads"; +static const QString SKELETON_MODELS_LOCATION = "models/skeletons/"; + +static const QString PREFIX_PARAMETER_NAME = "prefix"; +static const QString MARKER_PARAMETER_NAME = "marker"; +static const QString IS_TRUNCATED_NAME = "IsTruncated"; +static const QString CONTAINER_NAME = "Contents"; +static const QString KEY_NAME = "Key"; +static const QString LOADING_MSG = "Loading..."; + +enum ModelMetaData { + Name = 0, + Creator, + UploadeDate, + Type, + Gender, + + ModelMetaDataCount +}; +static const QString propertiesNames[ModelMetaDataCount] = { + "Name", + "Creator", + "Upload Date", + "Type", + "Gender" +}; + +ModelsBrowser::ModelsBrowser(ModelType modelsType, QWidget* parent) : + QWidget(parent), + _handler(new ModelHandler(modelsType)) +{ + // Connect handler + _handler->connect(this, SIGNAL(startDownloading()), SLOT(download())); + _handler->connect(this, SIGNAL(startUpdating()), SLOT(update())); + _handler->connect(this, SIGNAL(destroyed()), SLOT(exit())); + + // Setup and launch update thread + QThread* thread = new QThread(); + thread->connect(_handler, SIGNAL(destroyed()), SLOT(quit())); + thread->connect(thread, SIGNAL(finished()), SLOT(deleteLater())); + _handler->moveToThread(thread); + thread->start(); + emit startDownloading(); + + // Initialize the view + _view.setEditTriggers(QAbstractItemView::NoEditTriggers); + _view.setRootIsDecorated(false); + _view.setModel(_handler->getModel()); +} + +ModelsBrowser::~ModelsBrowser() { +} + +void ModelsBrowser::applyFilter(const QString &filter) { + QStringList filters = filter.split(" "); + + // Try and match every filter with each rows + for (int i = 0; i < _handler->getModel()->rowCount(); ++i) { + bool match = false; + for (int k = 0; k < filters.count(); ++k) { + match = false; + for (int j = 0; j < ModelMetaDataCount; ++j) { + if (_handler->getModel()->item(i, j)->text().contains(filters.at(k))) { + match = true; + break; + } + } + if (!match) { + break; + } + } + + // Hid the row if it doesn't match (Make sure it's not it it does) + if (match) { + _view.setRowHidden(i, QModelIndex(), false); + } else { + _view.setRowHidden(i, QModelIndex(), true); + } + } +} + +void ModelsBrowser::browse() { + QDialog dialog; + dialog.setWindowTitle("Browse models"); + + QGridLayout* layout = new QGridLayout(&dialog); + dialog.setLayout(layout); + + QLineEdit* searchBar = new QLineEdit(&dialog); + layout->addWidget(searchBar, 0, 0); + + layout->addWidget(&_view, 1, 0); + dialog.connect(&_view, SIGNAL(doubleClicked(const QModelIndex&)), SLOT(accept())); + connect(searchBar, SIGNAL(textChanged(const QString&)), SLOT(applyFilter(const QString&))); + + QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + layout->addWidget(buttons, 2, 0); + dialog.connect(buttons, SIGNAL(accepted()), SLOT(accept())); + dialog.connect(buttons, SIGNAL(rejected()), SLOT(reject())); + + if (dialog.exec() == QDialog::Accepted) { + QVariant selectedFile = _handler->getModel()->data(_view.currentIndex(), Qt::UserRole); + if (selectedFile.isValid()) { + emit selected(selectedFile.toString()); + } + } + + // So that we don't have to reconstruct the view + _view.setParent(NULL); +} + + +ModelHandler::ModelHandler(ModelType modelsType, QWidget* parent) : + QObject(parent), + _initiateExit(false), + _type(modelsType) +{ + connect(&_downloader, SIGNAL(done(QNetworkReply::NetworkError)), SLOT(downloadFinished())); + + // set headers data + QStringList headerData; + for (int i = 0; i < ModelMetaDataCount; ++i) { + headerData << propertiesNames[i]; + } + _model.setHorizontalHeaderLabels(headerData); +} + +void ModelHandler::download() { + // Query models list + queryNewFiles(); + + QMutexLocker lockerModel(&_modelMutex); + if (_initiateExit) { + return; + } + // Show loading message + QStandardItem* loadingItem = new QStandardItem(LOADING_MSG); + loadingItem->setEnabled(false); + _model.appendRow(loadingItem); +} + +void ModelHandler::update() { + // Will be implemented in my next PR +} + +void ModelHandler::exit() { + QMutexLocker lockerDownload(&_downloadMutex); + QMutexLocker lockerModel(&_modelMutex); + _initiateExit = true; + + // Disconnect everything + _downloader.disconnect(); + disconnect(); + thread()->disconnect(); + + // Make sure the thread will exit correctly + thread()->connect(this, SIGNAL(destroyed()), SLOT(quit())); + thread()->connect(thread(), SIGNAL(finished()), SLOT(deleteLater())); + deleteLater(); +} + +void ModelHandler::downloadFinished() { + QMutexLocker lockerDownload(&_downloadMutex); + if (_initiateExit) { + return; + } + parseXML(_downloader.getData()); +} + +void ModelHandler::queryNewFiles(QString marker) { + QMutexLocker lockerDownload(&_downloadMutex); + if (_initiateExit) { + return; + } + + // Build query + QUrl url(S3_URL); + QUrlQuery query; + if (_type == Head) { + query.addQueryItem(PREFIX_PARAMETER_NAME, HEAD_MODELS_LOCATION); + } else if (_type == Skeleton) { + query.addQueryItem(PREFIX_PARAMETER_NAME, SKELETON_MODELS_LOCATION); + } + + if (!marker.isEmpty()) { + query.addQueryItem(MARKER_PARAMETER_NAME, marker); + } + + // Download + url.setQuery(query); + _downloader.download(url); +} + +bool ModelHandler::parseXML(QByteArray xmlFile) { + QMutexLocker lockerModel(&_modelMutex); + if (_initiateExit) { + return false; + } + + QXmlStreamReader xml(xmlFile); + QRegExp rx(".*fst"); + bool truncated = false; + QString lastKey; + + // Remove loading indication + int oldLastRow = _model.rowCount() - 1; + delete _model.takeRow(oldLastRow).first(); + + // Read xml until the end or an error is detected + while(!xml.atEnd() && !xml.hasError()) { + if (_initiateExit) { + return false; + } + + if(xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == IS_TRUNCATED_NAME) { + while(!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == IS_TRUNCATED_NAME)) { + // Let's check if there is more + xml.readNext(); + if (xml.text().toString() == "True") { + truncated = true; + } + } + } + + if(xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == CONTAINER_NAME) { + while(!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == CONTAINER_NAME)) { + // If a file is find, process it + if(xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == KEY_NAME) { + xml.readNext(); + lastKey = xml.text().toString(); + if (rx.exactMatch(xml.text().toString())) { + // Add the found file to the list + QList model; + model << new QStandardItem(QFileInfo(xml.text().toString()).baseName()); + model.first()->setData(PUBLIC_URL + "/" + xml.text().toString(), Qt::UserRole); + + // Rand properties for now (Will be taken out in the next PR) + static QString creator[] = {"Ryan", "Philip", "Andzrej"}; + static QString type[] = {"human", "beast", "pet", "elfe"}; + static QString gender[] = {"male", "female", "none"}; + model << new QStandardItem(creator[randIntInRange(0, 2)]); + model << new QStandardItem(QDate(randIntInRange(2013, 2014), + randIntInRange(1, 12), + randIntInRange(1, 30)).toString()); + model << new QStandardItem(type[randIntInRange(0, 3)]); + model << new QStandardItem(gender[randIntInRange(0, 2)]); + //////////////////////////////////////////////////////////// + + _model.appendRow(model); + } + } + xml.readNext(); + } + } + xml.readNext(); + } + + static const QString ERROR_MSG = "Error loading files"; + // Error handling + if(xml.hasError()) { + _model.clear(); + QStandardItem* errorItem = new QStandardItem(ERROR_MSG); + errorItem->setEnabled(false); + _model.appendRow(errorItem); + + return false; + } + + // If we didn't all the files, download the next ones + if (truncated) { + // Indicate more files are being loaded + QStandardItem* loadingItem = new QStandardItem(LOADING_MSG); + loadingItem->setEnabled(false); + _model.appendRow(loadingItem); + + // query those files + queryNewFiles(lastKey); + } + + return true; +} \ No newline at end of file diff --git a/interface/src/ui/ModelsBrowser.h b/interface/src/ui/ModelsBrowser.h new file mode 100644 index 0000000000..7343c0c14f --- /dev/null +++ b/interface/src/ui/ModelsBrowser.h @@ -0,0 +1,79 @@ +// +// ModelsBrowser.h +// hifi +// +// Created by Clement on 3/17/14. +// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// + +#ifndef __hifi__ModelsBrowser__ +#define __hifi__ModelsBrowser__ + +#include +#include +#include +#include + +#include + +typedef +enum { + Head, + Skeleton +} ModelType; + +class ModelHandler : public QObject { + Q_OBJECT +public: + ModelHandler(ModelType modelsType, QWidget* parent = NULL); + QStandardItemModel* getModel() { QMutexLocker locker(&_modelMutex); return &_model; } + +signals: + void doneDownloading(); + void doneUpdating(); + +public slots: + void download(); + void update(); + void exit(); + +private slots: + void downloadFinished(); + +private: + bool _initiateExit; + ModelType _type; + QMutex _downloadMutex; + FileDownloader _downloader; + QMutex _modelMutex; + QStandardItemModel _model; + + void queryNewFiles(QString marker = QString()); + bool parseXML(QByteArray xmlFile); +}; + + +class ModelsBrowser : public QWidget { + Q_OBJECT +public: + + ModelsBrowser(ModelType modelsType, QWidget* parent = NULL); + ~ModelsBrowser(); + +signals: + void startDownloading(); + void startUpdating(); + void selected(QString filename); + +public slots: + void browse(); + +private slots: + void applyFilter(const QString& filter); + +private: + ModelHandler* _handler; + QTreeView _view; +}; + +#endif /* defined(__hifi__ModelBrowser__) */ diff --git a/libraries/shared/src/FileDownloader.cpp b/libraries/shared/src/FileDownloader.cpp index 2b65bbd8df..69cf599fae 100644 --- a/libraries/shared/src/FileDownloader.cpp +++ b/libraries/shared/src/FileDownloader.cpp @@ -14,14 +14,25 @@ #include "FileDownloader.h" -FileDownloader::FileDownloader(const QUrl dataURL, QObject* parent) : - QObject(parent), - _done(false) -{ - connect(&_networkAccessManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(processReply(QNetworkReply*))); +FileDownloader::FileDownloader(QObject* parent) : QObject(parent) { + connect(&_networkAccessManager, SIGNAL(finished(QNetworkReply*)), SLOT(processReply(QNetworkReply*))); +} +void FileDownloader::download(const QUrl dataURL, QNetworkAccessManager::Operation operation) { QNetworkRequest request(dataURL); - _networkAccessManager.get(request); + + _downloadedData.clear(); + switch (operation) { + case QNetworkAccessManager::GetOperation: + _networkAccessManager.get(request); + break; + case QNetworkAccessManager::HeadOperation: + _networkAccessManager.head(request); + break; + default: + emit done(QNetworkReply::ProtocolInvalidOperationError); + break; + } } void FileDownloader::processReply(QNetworkReply *reply) { @@ -30,36 +41,5 @@ void FileDownloader::processReply(QNetworkReply *reply) { } reply->deleteLater(); - _done = true; emit done(reply->error()); -} - -void FileDownloader::waitForFile(int timeout) { - QTimer timer; - QEventLoop loop; - connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit())); - connect(this, SIGNAL(done(QNetworkReply::NetworkError)), &loop, SLOT(quit())); - - if (!_done) { - if (timeout > 0) { - timer.start(timeout); - } - loop.exec(); - } -} - -QByteArray FileDownloader::download(const QUrl dataURL, int timeout) { - QTimer timer; - QEventLoop loop; - connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit)); - - FileDownloader downloader(dataURL); - connect(&downloader, SIGNAL(done(QNetworkReply::NetworkError)), &loop, SLOT(quit())); - - if (timeout > 0) { - timer.start(timeout); - } - loop.exec(); - - return downloader.getData(); } \ No newline at end of file diff --git a/libraries/shared/src/FileDownloader.h b/libraries/shared/src/FileDownloader.h index 593b39b013..7c5a416600 100644 --- a/libraries/shared/src/FileDownloader.h +++ b/libraries/shared/src/FileDownloader.h @@ -13,31 +13,28 @@ #include #include #include +#include class FileDownloader : public QObject { Q_OBJECT public: - FileDownloader(const QUrl dataURL, QObject* parent = NULL); - - void waitForFile(int timeout = 0); - + FileDownloader(QObject* parent = NULL); QByteArray getData() const { return _downloadedData; } - bool done() { return _done; } - static QByteArray download(const QUrl dataURL, int timeout = 0); signals: void done(QNetworkReply::NetworkError error); +public slots: + void download(const QUrl dataURL, QNetworkAccessManager::Operation operation = QNetworkAccessManager::GetOperation); + private slots: void processReply(QNetworkReply* reply); private: QNetworkAccessManager _networkAccessManager; QByteArray _downloadedData; - - bool _done; }; From f87c82450a48b409db0ac55e33a20db9504ef8d1 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 1 Apr 2014 12:20:08 -0700 Subject: [PATCH 08/16] Modified dialog default size --- interface/src/ui/ModelsBrowser.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/interface/src/ui/ModelsBrowser.cpp b/interface/src/ui/ModelsBrowser.cpp index 3bdc4ca9ed..c47ee1b5ba 100644 --- a/interface/src/ui/ModelsBrowser.cpp +++ b/interface/src/ui/ModelsBrowser.cpp @@ -106,6 +106,7 @@ void ModelsBrowser::applyFilter(const QString &filter) { void ModelsBrowser::browse() { QDialog dialog; dialog.setWindowTitle("Browse models"); + dialog.setMinimumSize(570, 500); QGridLayout* layout = new QGridLayout(&dialog); dialog.setLayout(layout); From 59bba99c6f49f8344a0cd340a1283a9d8459596d Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 1 Apr 2014 12:48:17 -0700 Subject: [PATCH 09/16] Resolve body-body collisions with other avatars --- interface/src/avatar/MyAvatar.cpp | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 6e01ad3292..9710ad3b17 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -11,6 +11,7 @@ #include +#include #include #include @@ -20,10 +21,7 @@ #include #include -#ifdef ANDREW_HACKERY #include -#include -#endif ANDREW_HACKERY #include "Application.h" #include "Audio.h" @@ -873,6 +871,7 @@ bool findAvatarAvatarPenetration(const glm::vec3 positionA, float radiusA, float } static CollisionList bodyCollisions(16); +const float BODY_COLLISION_RESOLVE_TIMESCALE = 0.5f; // seconds void MyAvatar::updateCollisionWithAvatars(float deltaTime) { // Reset detector for nearest avatar @@ -885,6 +884,8 @@ void MyAvatar::updateCollisionWithAvatars(float deltaTime) { updateShapePositions(); float myBoundingRadius = getBoundingRadius(); + const float BODY_COLLISION_RESOLVE_FACTOR = deltaTime / BODY_COLLISION_RESOLVE_TIMESCALE; + foreach (const AvatarSharedPointer& avatarPointer, avatars) { Avatar* avatar = static_cast(avatarPointer.data()); if (static_cast(this) == avatar) { @@ -898,26 +899,27 @@ void MyAvatar::updateCollisionWithAvatars(float deltaTime) { } float theirBoundingRadius = avatar->getBoundingRadius(); if (distance < myBoundingRadius + theirBoundingRadius) { -#ifdef ANDREW_HACKERY + // collide our body against theirs QVector myShapes; _skeletonModel.getBodyShapes(myShapes); QVector theirShapes; avatar->getSkeletonModel().getBodyShapes(theirShapes); bodyCollisions.clear(); + // TODO: add method to ShapeCollider for colliding lists of shapes foreach (const Shape* myShape, myShapes) { foreach (const Shape* theirShape, theirShapes) { ShapeCollider::shapeShape(myShape, theirShape, bodyCollisions); - if (bodyCollisions.size() > 0) { - std::cout << "adebug myPos = " << myShape->getPosition() - << " myRadius = " << myShape->getBoundingRadius() - << " theirPos = " << theirShape->getPosition() - << " theirRadius = " << theirShape->getBoundingRadius() - << std::endl; // adebug - std::cout << "adebug collision count = " << bodyCollisions.size() << std::endl; // adebug - } } } -#endif // ANDREW_HACKERY + glm::vec3 totalPenetration(0.f); + for (int j = 0; j < bodyCollisions.size(); ++j) { + CollisionInfo* collision = bodyCollisions.getCollision(j); + totalPenetration = addPenetrations(totalPenetration, collision->_penetration); + } + + if (glm::length2(totalPenetration) > EPSILON) { + setPosition(getPosition() - BODY_COLLISION_RESOLVE_FACTOR * totalPenetration); + } // collide our hands against them // TODO: make this work when we can figure out when the other avatar won't yeild From 515593e41cb1b0b45fc079817a5ee3c72fe0c546 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 1 Apr 2014 17:25:26 -0700 Subject: [PATCH 10/16] modify avatar thrust during collisions --- interface/src/avatar/MyAvatar.cpp | 35 +++++++++++++++++++++++++------ interface/src/avatar/MyAvatar.h | 1 + interface/src/renderer/Model.h | 1 + 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 249929d774..9dcfaa09ba 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -86,9 +86,9 @@ void MyAvatar::reset() { getHead()->reset(); getHand()->reset(); - setVelocity(glm::vec3(0,0,0)); - setThrust(glm::vec3(0,0,0)); - setOrientation(glm::quat(glm::vec3(0,0,0))); + setVelocity(glm::vec3(0.f)); + setThrust(glm::vec3(0.f)); + setOrientation(glm::quat(glm::vec3(0.f))); } void MyAvatar::setMoveTarget(const glm::vec3 moveTarget) { @@ -677,6 +677,28 @@ void MyAvatar::updateThrust(float deltaTime) { _thrust -= _driveKeys[LEFT] * _scale * THRUST_MAG_LATERAL * _thrustMultiplier * deltaTime * right; _thrust += _driveKeys[UP] * _scale * THRUST_MAG_UP * _thrustMultiplier * deltaTime * up; _thrust -= _driveKeys[DOWN] * _scale * THRUST_MAG_DOWN * _thrustMultiplier * deltaTime * up; + + // attenuate thrust when in penetration + if (glm::dot(_thrust, _lastBodyPenetration) > 0.f) { + const float MAX_BODY_PENETRATION_DEPTH = 0.6f * _skeletonModel.getBoundingShapeRadius(); + float penetrationFactor = glm::min(1.f, glm::length(_lastBodyPenetration) / MAX_BODY_PENETRATION_DEPTH); + glm::vec3 penetrationDirection = glm::normalize(_lastBodyPenetration); + // attenuate parallel component + glm::vec3 parallelThrust = glm::dot(_thrust, penetrationDirection) * penetrationDirection; + // attenuate perpendicular component (friction) + glm::vec3 perpendicularThrust = _thrust - parallelThrust; + // recombine to get the final thrust + _thrust = (1.f - penetrationFactor) * parallelThrust + (1.f - penetrationFactor * penetrationFactor) * perpendicularThrust; + + // attenuate the growth of _thrustMultiplier when in penetration + // otherwise the avatar will eventually be able to tunnel through the obstacle + _thrustMultiplier *= (1.f - penetrationFactor * penetrationFactor); + } else if (_thrustMultiplier < 1.f) { + // rapid healing of attenuated thrustMultiplier after penetration event + _thrustMultiplier = 1.f; + } + _lastBodyPenetration = glm::vec3(0.f); + _bodyYawDelta -= _driveKeys[ROT_RIGHT] * YAW_SPEED * deltaTime; _bodyYawDelta += _driveKeys[ROT_LEFT] * YAW_SPEED * deltaTime; getHead()->setBasePitch(getHead()->getBasePitch() + (_driveKeys[ROT_UP] - _driveKeys[ROT_DOWN]) * PITCH_SPEED * deltaTime); @@ -686,8 +708,9 @@ void MyAvatar::updateThrust(float deltaTime) { const float THRUST_INCREASE_RATE = 1.05f; const float MAX_THRUST_MULTIPLIER = 75.0f; //printf("m = %.3f\n", _thrustMultiplier); - if (_thrustMultiplier < MAX_THRUST_MULTIPLIER) { - _thrustMultiplier *= 1.f + deltaTime * THRUST_INCREASE_RATE; + _thrustMultiplier *= 1.f + deltaTime * THRUST_INCREASE_RATE; + if (_thrustMultiplier > MAX_THRUST_MULTIPLIER) { + _thrustMultiplier = MAX_THRUST_MULTIPLIER; } } else { _thrustMultiplier = 1.f; @@ -917,10 +940,10 @@ void MyAvatar::updateCollisionWithAvatars(float deltaTime) { CollisionInfo* collision = bodyCollisions.getCollision(j); totalPenetration = addPenetrations(totalPenetration, collision->_penetration); } - if (glm::length2(totalPenetration) > EPSILON) { setPosition(getPosition() - BODY_COLLISION_RESOLVE_FACTOR * totalPenetration); } + _lastBodyPenetration += totalPenetration; // collide our hands against them // TODO: make this work when we can figure out when the other avatar won't yeild diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 38edc5356e..5c940f0f50 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -121,6 +121,7 @@ private: bool _isThrustOn; float _thrustMultiplier; glm::vec3 _moveTarget; + glm::vec3 _lastBodyPenetration; int _moveTargetStepCounter; QWeakPointer _lookAtTargetAvatar; glm::vec3 _targetAvatarPosition; diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 43eb7fda67..e7c94deb43 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -180,6 +180,7 @@ public: void applyCollision(CollisionInfo& collision); float getBoundingRadius() const { return _boundingRadius; } + float getBoundingShapeRadius() const { return _boundingShape.getRadius(); } /// Sets blended vertices computed in a separate thread. void setBlendedVertices(const QVector& vertices, const QVector& normals); From e60e1929f3f4b647c4ce4f609d6c73ab06de8ef2 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Wed, 2 Apr 2014 11:36:52 -0700 Subject: [PATCH 11/16] Review comments --- interface/src/ui/ModelsBrowser.cpp | 82 +++++++++++++++++------------- interface/src/ui/ModelsBrowser.h | 19 +++---- 2 files changed, 58 insertions(+), 43 deletions(-) diff --git a/interface/src/ui/ModelsBrowser.cpp b/interface/src/ui/ModelsBrowser.cpp index c47ee1b5ba..15f1e9111e 100644 --- a/interface/src/ui/ModelsBrowser.cpp +++ b/interface/src/ui/ModelsBrowser.cpp @@ -6,17 +6,17 @@ // Copyright (c) 2014 HighFidelity, Inc. All rights reserved. // +#include +#include +#include +#include +#include +#include +#include #include #include -#include -#include -#include -#include -#include -#include -#include -#include +#include "Application.h" #include "ModelsBrowser.h" @@ -31,17 +31,18 @@ static const QString IS_TRUNCATED_NAME = "IsTruncated"; static const QString CONTAINER_NAME = "Contents"; static const QString KEY_NAME = "Key"; static const QString LOADING_MSG = "Loading..."; +static const QString ERROR_MSG = "Error loading files"; enum ModelMetaData { - Name = 0, - Creator, - UploadeDate, - Type, - Gender, + NAME, + CREATOR, + UPLOAD_DATE, + TYPE, + GENDER, - ModelMetaDataCount + MODEL_METADATA_COUNT }; -static const QString propertiesNames[ModelMetaDataCount] = { +static const QString propertiesNames[MODEL_METADATA_COUNT] = { "Name", "Creator", "Upload Date", @@ -55,7 +56,7 @@ ModelsBrowser::ModelsBrowser(ModelType modelsType, QWidget* parent) : { // Connect handler _handler->connect(this, SIGNAL(startDownloading()), SLOT(download())); - _handler->connect(this, SIGNAL(startUpdating()), SLOT(update())); + _handler->connect(_handler, SIGNAL(doneDownloading()), SLOT(update())); _handler->connect(this, SIGNAL(destroyed()), SLOT(exit())); // Setup and launch update thread @@ -72,19 +73,20 @@ ModelsBrowser::ModelsBrowser(ModelType modelsType, QWidget* parent) : _view.setModel(_handler->getModel()); } -ModelsBrowser::~ModelsBrowser() { -} - void ModelsBrowser::applyFilter(const QString &filter) { QStringList filters = filter.split(" "); + _handler->lockModel(); + QStandardItemModel* model = _handler->getModel(); + int rows = model->rowCount(); + // Try and match every filter with each rows - for (int i = 0; i < _handler->getModel()->rowCount(); ++i) { + for (int i = 0; i < rows; ++i) { bool match = false; for (int k = 0; k < filters.count(); ++k) { match = false; - for (int j = 0; j < ModelMetaDataCount; ++j) { - if (_handler->getModel()->item(i, j)->text().contains(filters.at(k))) { + for (int j = 0; j < MODEL_METADATA_COUNT; ++j) { + if (model->item(i, j)->text().contains(filters.at(k))) { match = true; break; } @@ -101,6 +103,7 @@ void ModelsBrowser::applyFilter(const QString &filter) { _view.setRowHidden(i, QModelIndex(), true); } } + _handler->unlockModel(); } void ModelsBrowser::browse() { @@ -124,7 +127,9 @@ void ModelsBrowser::browse() { dialog.connect(buttons, SIGNAL(rejected()), SLOT(reject())); if (dialog.exec() == QDialog::Accepted) { + _handler->lockModel(); QVariant selectedFile = _handler->getModel()->data(_view.currentIndex(), Qt::UserRole); + _handler->unlockModel(); if (selectedFile.isValid()) { emit selected(selectedFile.toString()); } @@ -144,7 +149,7 @@ ModelHandler::ModelHandler(ModelType modelsType, QWidget* parent) : // set headers data QStringList headerData; - for (int i = 0; i < ModelMetaDataCount; ++i) { + for (int i = 0; i < MODEL_METADATA_COUNT; ++i) { headerData << propertiesNames[i]; } _model.setHorizontalHeaderLabels(headerData); @@ -154,14 +159,16 @@ void ModelHandler::download() { // Query models list queryNewFiles(); - QMutexLocker lockerModel(&_modelMutex); + _lock.lockForWrite(); if (_initiateExit) { + _lock.unlock(); return; } // Show loading message QStandardItem* loadingItem = new QStandardItem(LOADING_MSG); loadingItem->setEnabled(false); _model.appendRow(loadingItem); + _lock.unlock(); } void ModelHandler::update() { @@ -169,8 +176,7 @@ void ModelHandler::update() { } void ModelHandler::exit() { - QMutexLocker lockerDownload(&_downloadMutex); - QMutexLocker lockerModel(&_modelMutex); + _lock.lockForWrite(); _initiateExit = true; // Disconnect everything @@ -182,18 +188,18 @@ void ModelHandler::exit() { thread()->connect(this, SIGNAL(destroyed()), SLOT(quit())); thread()->connect(thread(), SIGNAL(finished()), SLOT(deleteLater())); deleteLater(); + _lock.unlock(); } void ModelHandler::downloadFinished() { - QMutexLocker lockerDownload(&_downloadMutex); - if (_initiateExit) { - return; + if (_downloader.getData().startsWith("setEnabled(false); _model.appendRow(errorItem); - + + _lock.unlock(); return false; } @@ -301,6 +309,12 @@ bool ModelHandler::parseXML(QByteArray xmlFile) { // query those files queryNewFiles(lastKey); } + _lock.unlock(); + + if (!truncated) { + qDebug() << "Emitting..."; + emit doneDownloading(); + } return true; } \ No newline at end of file diff --git a/interface/src/ui/ModelsBrowser.h b/interface/src/ui/ModelsBrowser.h index 7343c0c14f..9bc239ba18 100644 --- a/interface/src/ui/ModelsBrowser.h +++ b/interface/src/ui/ModelsBrowser.h @@ -11,22 +11,25 @@ #include #include -#include #include +#include +#include -#include +#include "FileDownloader.h" -typedef -enum { +enum ModelType { Head, Skeleton -} ModelType; +}; class ModelHandler : public QObject { Q_OBJECT public: ModelHandler(ModelType modelsType, QWidget* parent = NULL); - QStandardItemModel* getModel() { QMutexLocker locker(&_modelMutex); return &_model; } + + void lockModel() { _lock.lockForRead(); } + QStandardItemModel* getModel() { return &_model; } + void unlockModel() { _lock.unlock(); } signals: void doneDownloading(); @@ -43,9 +46,8 @@ private slots: private: bool _initiateExit; ModelType _type; - QMutex _downloadMutex; FileDownloader _downloader; - QMutex _modelMutex; + QReadWriteLock _lock; QStandardItemModel _model; void queryNewFiles(QString marker = QString()); @@ -58,7 +60,6 @@ class ModelsBrowser : public QWidget { public: ModelsBrowser(ModelType modelsType, QWidget* parent = NULL); - ~ModelsBrowser(); signals: void startDownloading(); From 97ba5250a5d920fc93770c50ee372a948c279954 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 2 Apr 2014 12:01:18 -0700 Subject: [PATCH 12/16] Only simulate() Avatar Models when necessary Also: rebuild collision shapes when Model scale changes --- interface/src/avatar/Avatar.cpp | 33 ++++++++++++++-------- interface/src/avatar/FaceModel.cpp | 5 ++-- interface/src/avatar/MyAvatar.cpp | 2 ++ interface/src/avatar/SkeletonModel.cpp | 3 ++ interface/src/renderer/Model.cpp | 38 ++++++++++++++++---------- interface/src/renderer/Model.h | 9 +++--- libraries/avatars/src/AvatarData.cpp | 2 ++ libraries/avatars/src/AvatarData.h | 2 ++ 8 files changed, 59 insertions(+), 35 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 5e12282f53..ec8539954a 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -117,20 +117,29 @@ void Avatar::simulate(float deltaTime) { getHand()->simulate(deltaTime, false); _skeletonModel.setLODDistance(getLODDistance()); - // copy joint data to skeleton - for (int i = 0; i < _jointData.size(); i++) { - const JointData& data = _jointData.at(i); - _skeletonModel.setJointState(i, data.valid, data.rotation); - } - glm::vec3 headPosition = _position; if (!_shouldRenderBillboard && inViewFrustum) { - _skeletonModel.simulate(deltaTime); - _skeletonModel.getHeadPosition(headPosition); + glm::vec3 headPosition = _position; + + _skeletonModel.updateGeometry(); + if (_skeletonModel.isActive()) { + // copy joint data to skeleton + if (_hasNewJointRotations) { + for (int i = 0; i < _jointData.size(); i++) { + const JointData& data = _jointData.at(i); + _skeletonModel.setJointState(i, data.valid, data.rotation); + } + _skeletonModel.simulate(deltaTime); + _hasNewJointRotations = false; + } + _skeletonModel.getHeadPosition(headPosition); + } + + Head* head = getHead(); + head->setPosition(headPosition); + head->setScale(_scale); + head->getFaceModel().updateGeometry(); + head->simulate(deltaTime, false, _shouldRenderBillboard); } - Head* head = getHead(); - head->setPosition(headPosition); - head->setScale(_scale); - head->simulate(deltaTime, false, _shouldRenderBillboard); // use speed and angular velocity to determine walking vs. standing if (_speed + fabs(_bodyYawDelta) > 0.2) { diff --git a/interface/src/avatar/FaceModel.cpp b/interface/src/avatar/FaceModel.cpp index c483642d15..5d9b71f11d 100644 --- a/interface/src/avatar/FaceModel.cpp +++ b/interface/src/avatar/FaceModel.cpp @@ -19,8 +19,7 @@ FaceModel::FaceModel(Head* owningHead) : } void FaceModel::simulate(float deltaTime) { - bool geometryIsUpToDate = updateGeometry(); - if (!geometryIsUpToDate) { + if (!isActive()) { return; } Avatar* owningAvatar = static_cast(_owningHead->_owningAvatar); @@ -42,7 +41,7 @@ void FaceModel::simulate(float deltaTime) { setPupilDilation(_owningHead->getPupilDilation()); setBlendshapeCoefficients(_owningHead->getBlendshapeCoefficients()); - Model::simulateInternal(deltaTime); + Model::simulate(deltaTime); } void FaceModel::maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) { diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 9dcfaa09ba..0b220a532c 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -289,6 +289,7 @@ void MyAvatar::simulate(float deltaTime) { getHand()->collideAgainstOurself(); getHand()->simulate(deltaTime, true); + _skeletonModel.updateGeometry(); _skeletonModel.simulate(deltaTime); // copy out the skeleton joints from the model @@ -305,6 +306,7 @@ void MyAvatar::simulate(float deltaTime) { } head->setPosition(headPosition); head->setScale(_scale); + head->getFaceModel().updateGeometry(); head->simulate(deltaTime, true); // Zero thrust out now that we've added it to velocity in this frame diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index e6318e2003..0ce97f4ef9 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -19,6 +19,9 @@ SkeletonModel::SkeletonModel(Avatar* owningAvatar) : } void SkeletonModel::simulate(float deltaTime) { + if (!isActive()) { + return; + } setTranslation(_owningAvatar->getPosition()); setRotation(_owningAvatar->getOrientation() * glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f))); const float MODEL_SCALE = 0.0006f; diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 1e609afe33..000a41b7d5 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -56,6 +56,14 @@ Model::SkinLocations Model::_skinLocations; Model::SkinLocations Model::_skinNormalMapLocations; Model::SkinLocations Model::_skinShadowLocations; +void Model::setScale(const glm::vec3& scale) { + glm::vec3 deltaScale = _scale - scale; + if (glm::length2(deltaScale) > EPSILON) { + _scale = scale; + rebuildShapes(); + } +} + void Model::initSkinProgram(ProgramObject& program, Model::SkinLocations& locations) { program.bind(); locations.clusterMatrices = program.uniformLocation("clusterMatrices"); @@ -182,10 +190,14 @@ void Model::reset() { } } -// return 'true' if geometry is up to date -bool Model::updateGeometry() { - bool needToRebuild = false; +void Model::updateGeometry() { + // NOTE: this is a recursive call that walks all attachments, and their attachments + for (int i = 0; i < _attachments.size(); i++) { + Model* model = _attachments.at(i); + model->updateGeometry(); + } + bool needToRebuild = false; if (_nextGeometry) { _nextGeometry = _nextGeometry->getLODOrFallback(_lodDistance, _nextLODHysteresis); _nextGeometry->setLoadPriority(this, -_lodDistance); @@ -197,7 +209,7 @@ bool Model::updateGeometry() { } if (!_geometry) { // geometry is not ready - return false; + return; } QSharedPointer geometry = _geometry->getLODOrFallback(_lodDistance, _lodHysteresis); @@ -260,9 +272,9 @@ bool Model::updateGeometry() { model->setURL(attachment.url); _attachments.append(model); } - createShapes(); + rebuildShapes(); } - return true; + return; } bool Model::render(float alpha, bool forShadowMap) { @@ -440,7 +452,7 @@ void Model::clearShapes() { _jointShapes.clear(); } -void Model::createShapes() { +void Model::rebuildShapes() { clearShapes(); if (_jointStates.isEmpty()) { @@ -670,20 +682,16 @@ void Blender::run() { Q_ARG(const QVector&, vertices), Q_ARG(const QVector&, normals)); } - void Model::simulate(float deltaTime) { - bool geometryIsUpToDate = updateGeometry(); - if (!geometryIsUpToDate) { + // NOTE: this is a recursive call that walks all attachments, and their attachments + if (!isActive()) { return; } - simulateInternal(deltaTime); -} - -void Model::simulateInternal(float deltaTime) { // update the world space transforms for all joints for (int i = 0; i < _jointStates.size(); i++) { updateJointState(i); } + _shapesAreDirty = true; const FBXGeometry& geometry = _geometry->getFBXGeometry(); @@ -720,7 +728,6 @@ void Model::simulateInternal(float deltaTime) { } void Model::updateJointState(int index) { - _shapesAreDirty = true; JointState& state = _jointStates[index]; const FBXGeometry& geometry = _geometry->getFBXGeometry(); const FBXJoint& joint = geometry.joints.at(index); @@ -838,6 +845,7 @@ bool Model::setJointPosition(int jointIndex, const glm::vec3& position, int last for (int j = freeLineage.size() - 1; j >= 0; j--) { updateJointState(freeLineage.at(j)); } + _shapesAreDirty = true; return true; } diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index e7c94deb43..0c05b18429 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -36,7 +36,7 @@ public: void setRotation(const glm::quat& rotation) { _rotation = rotation; } const glm::quat& getRotation() const { return _rotation; } - void setScale(const glm::vec3& scale) { _scale = scale; } + void setScale(const glm::vec3& scale); const glm::vec3& getScale() const { return _scale; } void setOffset(const glm::vec3& offset) { _offset = offset; } @@ -156,7 +156,7 @@ public: float getRightArmLength() const; void clearShapes(); - void createShapes(); + void rebuildShapes(); void updateShapePositions(); void renderJointCollisionShapes(float alpha); void renderBoundingCollisionShapes(float alpha); @@ -185,6 +185,8 @@ public: /// Sets blended vertices computed in a separate thread. void setBlendedVertices(const QVector& vertices, const QVector& normals); + void updateGeometry(); + protected: QSharedPointer _geometry; @@ -217,9 +219,6 @@ protected: QVector _meshStates; - bool updateGeometry(); - void simulateInternal(float deltaTime); - /// Updates the state of the joint at the specified index. virtual void updateJointState(int index); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 31639b6836..930e3f7350 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -40,6 +40,7 @@ AvatarData::AvatarData() : _handState(0), _keyState(NO_KEY_DOWN), _isChatCirclingEnabled(false), + _hasNewJointRotations(true), _headData(NULL), _handData(NULL), _displayNameBoundingRect(), @@ -483,6 +484,7 @@ int AvatarData::parseDataAtOffset(const QByteArray& packet, int offset) { } } } // numJoints * 8 bytes + _hasNewJointRotations = true; return sourceBuffer - startPosition; } diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 2ea20c1041..221bbd0428 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -242,6 +242,8 @@ protected: bool _isChatCirclingEnabled; + bool _hasNewJointRotations; // set in AvatarData, cleared in Avatar + HeadData* _headData; HandData* _handData; From 933cade6b0391a8975e0a7367ba8da247ceba029 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 2 Apr 2014 16:34:33 -0700 Subject: [PATCH 13/16] Re-add fullUpdate hint to Model::simulate() --- interface/src/avatar/Avatar.cpp | 23 +++++++++------------ interface/src/avatar/FaceModel.cpp | 10 ++++----- interface/src/avatar/MyAvatar.cpp | 2 -- interface/src/avatar/SkeletonModel.cpp | 7 ++----- interface/src/avatar/SkeletonModel.h | 2 +- interface/src/renderer/Model.cpp | 28 +++++++++++++++++--------- interface/src/renderer/Model.h | 9 ++++++--- 7 files changed, 42 insertions(+), 39 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index ec8539954a..899514d1c1 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -118,26 +118,21 @@ void Avatar::simulate(float deltaTime) { _skeletonModel.setLODDistance(getLODDistance()); if (!_shouldRenderBillboard && inViewFrustum) { - glm::vec3 headPosition = _position; - - _skeletonModel.updateGeometry(); - if (_skeletonModel.isActive()) { - // copy joint data to skeleton - if (_hasNewJointRotations) { - for (int i = 0; i < _jointData.size(); i++) { - const JointData& data = _jointData.at(i); - _skeletonModel.setJointState(i, data.valid, data.rotation); - } - _skeletonModel.simulate(deltaTime); - _hasNewJointRotations = false; + if (_hasNewJointRotations) { + for (int i = 0; i < _jointData.size(); i++) { + const JointData& data = _jointData.at(i); + _skeletonModel.setJointState(i, data.valid, data.rotation); } - _skeletonModel.getHeadPosition(headPosition); + _skeletonModel.simulate(deltaTime); } + _skeletonModel.simulate(deltaTime, _hasNewJointRotations); + _hasNewJointRotations = false; + glm::vec3 headPosition = _position; + _skeletonModel.getHeadPosition(headPosition); Head* head = getHead(); head->setPosition(headPosition); head->setScale(_scale); - head->getFaceModel().updateGeometry(); head->simulate(deltaTime, false, _shouldRenderBillboard); } diff --git a/interface/src/avatar/FaceModel.cpp b/interface/src/avatar/FaceModel.cpp index 5d9b71f11d..2934a022af 100644 --- a/interface/src/avatar/FaceModel.cpp +++ b/interface/src/avatar/FaceModel.cpp @@ -19,9 +19,7 @@ FaceModel::FaceModel(Head* owningHead) : } void FaceModel::simulate(float deltaTime) { - if (!isActive()) { - return; - } + updateGeometry(); Avatar* owningAvatar = static_cast(_owningHead->_owningAvatar); glm::vec3 neckPosition; if (!owningAvatar->getSkeletonModel().getNeckPosition(neckPosition)) { @@ -36,12 +34,14 @@ void FaceModel::simulate(float deltaTime) { const float MODEL_SCALE = 0.0006f; setScale(glm::vec3(1.0f, 1.0f, 1.0f) * _owningHead->getScale() * MODEL_SCALE); - setOffset(-_geometry->getFBXGeometry().neckPivot); + if (isActive()) { + setOffset(-_geometry->getFBXGeometry().neckPivot); + } setPupilDilation(_owningHead->getPupilDilation()); setBlendshapeCoefficients(_owningHead->getBlendshapeCoefficients()); - Model::simulate(deltaTime); + Model::simulateInternal(deltaTime); } void FaceModel::maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) { diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 0b220a532c..9dcfaa09ba 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -289,7 +289,6 @@ void MyAvatar::simulate(float deltaTime) { getHand()->collideAgainstOurself(); getHand()->simulate(deltaTime, true); - _skeletonModel.updateGeometry(); _skeletonModel.simulate(deltaTime); // copy out the skeleton joints from the model @@ -306,7 +305,6 @@ void MyAvatar::simulate(float deltaTime) { } head->setPosition(headPosition); head->setScale(_scale); - head->getFaceModel().updateGeometry(); head->simulate(deltaTime, true); // Zero thrust out now that we've added it to velocity in this frame diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 0ce97f4ef9..69229d9e8d 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -18,16 +18,13 @@ SkeletonModel::SkeletonModel(Avatar* owningAvatar) : _owningAvatar(owningAvatar) { } -void SkeletonModel::simulate(float deltaTime) { - if (!isActive()) { - return; - } +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); + 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 60b12eb8f5..31867dec5c 100644 --- a/interface/src/avatar/SkeletonModel.h +++ b/interface/src/avatar/SkeletonModel.h @@ -21,7 +21,7 @@ public: SkeletonModel(Avatar* owningAvatar); - void simulate(float deltaTime); + void simulate(float deltaTime, bool fullUpdate = true); /// \param jointIndex index of hand joint /// \param shapes[out] list in which is stored pointers to hand shapes diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 000a41b7d5..9d2a031a6e 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -190,11 +190,14 @@ void Model::reset() { } } -void Model::updateGeometry() { +bool Model::updateGeometry() { // NOTE: this is a recursive call that walks all attachments, and their attachments + bool needFullUpdate = false; for (int i = 0; i < _attachments.size(); i++) { Model* model = _attachments.at(i); - model->updateGeometry(); + if (model->updateGeometry()) { + needFullUpdate = true; + } } bool needToRebuild = false; @@ -209,7 +212,7 @@ void Model::updateGeometry() { } if (!_geometry) { // geometry is not ready - return; + return false; } QSharedPointer geometry = _geometry->getLODOrFallback(_lodDistance, _lodHysteresis); @@ -273,8 +276,9 @@ void Model::updateGeometry() { _attachments.append(model); } rebuildShapes(); + needFullUpdate = true; } - return; + return needFullUpdate; } bool Model::render(float alpha, bool forShadowMap) { @@ -682,11 +686,15 @@ void Blender::run() { Q_ARG(const QVector&, vertices), Q_ARG(const QVector&, normals)); } -void Model::simulate(float deltaTime) { - // NOTE: this is a recursive call that walks all attachments, and their attachments - if (!isActive()) { - return; +void Model::simulate(float deltaTime, bool fullUpdate) { + fullUpdate = updateGeometry() || fullUpdate; + if (isActive() && fullUpdate) { + simulateInternal(deltaTime); } +} + +void Model::simulateInternal(float deltaTime) { + // NOTE: this is a recursive call that walks all attachments, and their attachments // update the world space transforms for all joints for (int i = 0; i < _jointStates.size(); i++) { updateJointState(i); @@ -709,7 +717,9 @@ void Model::simulate(float deltaTime) { model->setRotation(jointRotation * attachment.rotation); model->setScale(_scale * attachment.scale); - model->simulate(deltaTime); + if (model->isActive()) { + model->simulateInternal(deltaTime); + } } for (int i = 0; i < _meshStates.size(); i++) { diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 0c05b18429..11374a6369 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -56,7 +56,7 @@ public: void init(); void reset(); - virtual void simulate(float deltaTime); + virtual void simulate(float deltaTime, bool fullUpdate = true); bool render(float alpha = 1.0f, bool forShadowMap = false); /// Sets the URL of the model to render. @@ -185,8 +185,6 @@ public: /// Sets blended vertices computed in a separate thread. void setBlendedVertices(const QVector& vertices, const QVector& normals); - void updateGeometry(); - protected: QSharedPointer _geometry; @@ -219,6 +217,11 @@ protected: QVector _meshStates; + // returns 'true' if needs fullUpdate after geometry change + bool updateGeometry(); + + void simulateInternal(float deltaTime); + /// Updates the state of the joint at the specified index. virtual void updateJointState(int index); From 5acc8313ba13223b6c5085bce923e9933b3ed823 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Wed, 2 Apr 2014 18:07:55 -0700 Subject: [PATCH 14/16] Manual merge --- interface/interface_en.ts | 71 +++- interface/resources/images/kill-script.svg | 7 + interface/resources/images/reload.svg | 24 ++ interface/resources/images/stop.svg | 7 + interface/resources/resources.qrc | 3 + interface/src/Application.cpp | 332 ++++++++++--------- interface/src/Application.h | 39 ++- interface/src/Menu.cpp | 119 ++++--- interface/src/Menu.h | 10 +- interface/src/ui/RunningScriptsWidget.cpp | 203 ++++++++++++ interface/src/ui/RunningScriptsWidget.h | 46 +++ interface/ui/runningScriptsWidget.ui | 248 ++++++++++++++ libraries/script-engine/src/ScriptEngine.cpp | 131 +++----- libraries/script-engine/src/ScriptEngine.h | 33 +- 14 files changed, 930 insertions(+), 343 deletions(-) create mode 100644 interface/resources/images/kill-script.svg create mode 100644 interface/resources/images/reload.svg create mode 100644 interface/resources/images/stop.svg create mode 100644 interface/src/ui/RunningScriptsWidget.cpp create mode 100644 interface/src/ui/RunningScriptsWidget.h create mode 100644 interface/ui/runningScriptsWidget.ui diff --git a/interface/interface_en.ts b/interface/interface_en.ts index 7628407039..4473672b01 100644 --- a/interface/interface_en.ts +++ b/interface/interface_en.ts @@ -4,22 +4,30 @@ Application - + Export Voxels - + Sparse Voxel Octree Files (*.svo) +<<<<<<< HEAD +======= + +>>>>>>> 0fa910c61fd4ffb4e4eb4b7c53f1e67da9ac0913 Open Script +<<<<<<< HEAD +======= + +>>>>>>> 0fa910c61fd4ffb4e4eb4b7c53f1e67da9ac0913 JavaScript Files (*.js) @@ -106,18 +114,18 @@ Menu - + Open .ini config file - - + + Text files (*.ini) - + Save .ini config file @@ -151,4 +159,55 @@ + + RunningScriptsWidget + + + + Form + + + + + + <html><head/><body><p><span style=" font-size:18pt;">Running Scripts</span></p></body></html> + + + + + + <html><head/><body><p><span style=" font-weight:600;">Currently running</span></p></body></html> + + + + + + Reload All + + + + + + Stop All + + + + + + <html><head/><body><p><span style=" font-weight:600;">Recently loaded</span></p></body></html> + + + + + + (click a script or use the 1-9 keys to load and run it) + + + + + + There are no scripts currently running. + + + diff --git a/interface/resources/images/kill-script.svg b/interface/resources/images/kill-script.svg new file mode 100644 index 0000000000..d98fc4555a --- /dev/null +++ b/interface/resources/images/kill-script.svg @@ -0,0 +1,7 @@ + + + + + + diff --git a/interface/resources/images/reload.svg b/interface/resources/images/reload.svg new file mode 100644 index 0000000000..a596f03301 --- /dev/null +++ b/interface/resources/images/reload.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + diff --git a/interface/resources/images/stop.svg b/interface/resources/images/stop.svg new file mode 100644 index 0000000000..ea22bb592a --- /dev/null +++ b/interface/resources/images/stop.svg @@ -0,0 +1,7 @@ + + + + + + diff --git a/interface/resources/resources.qrc b/interface/resources/resources.qrc index 372fa8b1d4..35c0e40270 100644 --- a/interface/resources/resources.qrc +++ b/interface/resources/resources.qrc @@ -1,5 +1,8 @@ images/close.svg + images/kill-script.svg + images/reload.svg + images/stop.svg diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index bdbe0194e6..d070256035 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -49,7 +49,7 @@ #include #include #include -#include +#include #include #include @@ -118,7 +118,7 @@ void messageHandler(QtMsgType type, const QMessageLogContext& context, const QSt if (message.size() > 0) { QString dateString = QDateTime::currentDateTime().toTimeSpec(Qt::LocalTime).toString(Qt::ISODate); QString formattedMessage = QString("[%1] %2\n").arg(dateString).arg(message); - + fprintf(stdout, "%s", qPrintable(formattedMessage)); Application::getInstance()->getLogger()->addMessage(qPrintable(formattedMessage)); } @@ -175,23 +175,23 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : { // read the ApplicationInfo.ini file for Name/Version/Domain information QSettings applicationInfo(Application::resourcesPath() + "info/ApplicationInfo.ini", QSettings::IniFormat); - + // set the associated application properties applicationInfo.beginGroup("INFO"); - + qDebug() << "[VERSION] Build sequence: " << qPrintable(applicationVersion()); - + setApplicationName(applicationInfo.value("name").toString()); setApplicationVersion(BUILD_VERSION); setOrganizationName(applicationInfo.value("organizationName").toString()); setOrganizationDomain(applicationInfo.value("organizationDomain").toString()); - + QSettings::setDefaultFormat(QSettings::IniFormat); - + _myAvatar = _avatarManager.getMyAvatar(); _applicationStartupTime = startup_time; - + QFontDatabase::addApplicationFont(Application::resourcesPath() + "styles/Inconsolata.otf"); _window->setWindowTitle("Interface"); @@ -206,19 +206,19 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : if (portStr) { listenPort = atoi(portStr); } - + // start the nodeThread so its event loop is running _nodeThread->start(); - + // make sure the node thread is given highest priority _nodeThread->setPriority(QThread::TimeCriticalPriority); - + // put the NodeList and datagram processing on the node thread NodeList* nodeList = NodeList::createInstance(NodeType::Agent, listenPort); - + nodeList->moveToThread(_nodeThread); _datagramProcessor.moveToThread(_nodeThread); - + // connect the DataProcessor processDatagrams slot to the QUDPSocket readyRead() signal connect(&nodeList->getNodeSocket(), SIGNAL(readyRead()), &_datagramProcessor, SLOT(processDatagrams())); @@ -240,20 +240,20 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : connect(nodeList, SIGNAL(nodeKilled(SharedNodePointer)), &_voxels, SLOT(nodeKilled(SharedNodePointer))); connect(nodeList, &NodeList::uuidChanged, this, &Application::updateWindowTitle); connect(nodeList, &NodeList::limitOfSilentDomainCheckInsReached, nodeList, &NodeList::reset); - + // connect to appropriate slots on AccountManager AccountManager& accountManager = AccountManager::getInstance(); connect(&accountManager, &AccountManager::authRequired, Menu::getInstance(), &Menu::loginForCurrentDomain); connect(&accountManager, &AccountManager::usernameChanged, this, &Application::updateWindowTitle); - + // set the account manager's root URL and trigger a login request if we don't have the access token accountManager.setAuthURL(DEFAULT_NODE_AUTH_URL); - + // once the event loop has started, check and signal for an access token QMetaObject::invokeMethod(&accountManager, "checkAndSignalForAccessToken", Qt::QueuedConnection); _settings = new QSettings(this); - + // Check to see if the user passed in a command line option for loading a local // Voxel File. _voxelsFilename = getCmdOption(argc, constArgv, "-i"); @@ -267,7 +267,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : nodeList->addSetOfNodeTypesToNodeInterestSet(NodeSet() << NodeType::AudioMixer << NodeType::AvatarMixer << NodeType::VoxelServer << NodeType::ParticleServer << NodeType::MetavoxelServer); - + // connect to the packet sent signal of the _voxelEditSender and the _particleEditSender connect(&_voxelEditSender, &VoxelEditPacketSender::packetSent, this, &Application::packetSent); connect(&_particleEditSender, &ParticleEditPacketSender::packetSent, this, &Application::packetSent); @@ -277,7 +277,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : connect(silentNodeTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes())); silentNodeTimer->moveToThread(_nodeThread); silentNodeTimer->start(NODE_SILENCE_THRESHOLD_USECS / 1000); - + // send the identity packet for our avatar each second to our avatar mixer QTimer* identityPacketTimer = new QTimer(); connect(identityPacketTimer, &QTimer::timeout, _myAvatar, &MyAvatar::sendIdentityPacket); @@ -325,14 +325,18 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : // Set the sixense filtering _sixenseManager.setFilter(Menu::getInstance()->isOptionChecked(MenuOption::FilterSixense)); - + checkVersion(); - + _overlays.init(_glWidget); // do this before scripts load - + LocalVoxelsList::getInstance()->addPersistantTree(DOMAIN_TREE_NAME, _voxels.getTree()); LocalVoxelsList::getInstance()->addPersistantTree(CLIPBOARD_TREE_NAME, &_clipboard); + _window->addDockWidget(Qt::NoDockWidgetArea, _runningScriptsWidget = new RunningScriptsWidget()); + _runningScriptsWidget->setRunningScripts(getRunningScripts()); + connect(_runningScriptsWidget, &RunningScriptsWidget::stopScriptName, this, &Application::stopScript); + // check first run... QVariant firstRunValue = _settings->value("firstRun",QVariant(true)); if (firstRunValue.isValid() && firstRunValue.toBool()) { @@ -340,7 +344,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : // clear the scripts, and set out script to our default scripts clearScriptsBeforeRunning(); loadScript("http://public.highfidelity.io/scripts/defaultScripts.js"); - + _settings->setValue("firstRun",QVariant(false)); } else { // do this as late as possible so that all required subsystems are inialized @@ -351,34 +355,34 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : Application::~Application() { qInstallMessageHandler(NULL); - + // make sure we don't call the idle timer any more delete idleTimer; - + Menu::getInstance()->saveSettings(); _rearMirrorTools->saveSettings(_settings); - + _sharedVoxelSystem.changeTree(new VoxelTree); if (_voxelImporter) { _voxelImporter->saveSettings(_settings); delete _voxelImporter; } _settings->sync(); - + // let the avatar mixer know we're out MyAvatar::sendKillAvatar(); - + // ask the datagram processing thread to quit and wait until it is done _nodeThread->quit(); _nodeThread->wait(); - + // stop the audio process QMetaObject::invokeMethod(&_audio, "stop"); - + // ask the audio thread to quit and wait until it is done _audio.thread()->quit(); _audio.thread()->wait(); - + _voxelProcessor.terminate(); _voxelHideShowThread.terminate(); _voxelEditSender.terminate(); @@ -391,9 +395,9 @@ Application::~Application() { Menu::getInstance()->deleteLater(); _myAvatar = NULL; - + delete _glWidget; - + AccountManager::getInstance().destroy(); } @@ -585,7 +589,7 @@ void Application::paintGL() { if (Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) { renderRearViewMirror(_mirrorViewRect); - + } else if (Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror)) { _rearMirrorTools->render(true); } @@ -658,10 +662,10 @@ void Application::controlledBroadcastToNodes(const QByteArray& packet, const Nod if (type == NodeType::VoxelServer && !Menu::getInstance()->isOptionChecked(MenuOption::Voxels)) { continue; } - + // Perform the broadcast for one type int nReceivingNodes = NodeList::getInstance()->broadcastToNodes(packet, NodeSet() << type); - + // Feed number of bytes to corresponding channel of the bandwidth meter, if any (done otherwise) BandwidthMeter::ChannelIndex channel; switch (type) { @@ -680,7 +684,7 @@ void Application::controlledBroadcastToNodes(const QByteArray& packet, const Nod } bool Application::event(QEvent* event) { - + // handle custom URL if (event->type() == QEvent::FileOpen) { QFileOpenEvent* fileEvent = static_cast(event); @@ -691,11 +695,11 @@ bool Application::event(QEvent* event) { if (urlParts.count() > 1) { // if url has 2 or more parts, the first one is domain name Menu::getInstance()->goToDomain(urlParts[0]); - + // location coordinates Menu::getInstance()->goToDestination(urlParts[1]); if (urlParts.count() > 2) { - + // location orientation Menu::getInstance()->goToOrientation(urlParts[2]); } @@ -705,7 +709,7 @@ bool Application::event(QEvent* event) { Menu::getInstance()->goToDestination(urlParts[0]); } } - + return false; } return QApplication::event(event); @@ -716,7 +720,7 @@ void Application::keyPressEvent(QKeyEvent* event) { _keysPressed.insert(event->key()); _controllerScriptingInterface.emitKeyPressEvent(event); // send events to any registered scripts - + // if one of our scripts have asked to capture this event, then stop processing it if (_controllerScriptingInterface.isKeyCaptured(event)) { return; @@ -1075,7 +1079,7 @@ void Application::mouseReleaseEvent(QMouseEvent* event) { checkBandwidthMeterClick(); if (Menu::getInstance()->isOptionChecked(MenuOption::Stats)) { checkStatsClick(); - } + } } } } @@ -1124,7 +1128,7 @@ void Application::touchBeginEvent(QTouchEvent* event) { if (_controllerScriptingInterface.isTouchCaptured()) { return; } - + // put any application specific touch behavior below here.. _lastTouchAvgX = _touchAvgX; _lastTouchAvgY = _touchAvgY; @@ -1167,13 +1171,13 @@ void Application::dropEvent(QDropEvent *event) { break; } } - + SnapshotMetaData* snapshotData = Snapshot::parseSnapshotData(snapshotPath); if (snapshotData) { if (!snapshotData->getDomain().isEmpty()) { Menu::getInstance()->goToDomain(snapshotData->getDomain()); } - + _myAvatar->setPosition(snapshotData->getLocation()); _myAvatar->setOrientation(snapshotData->getOrientation()); } else { @@ -1201,19 +1205,19 @@ void Application::timer() { } _fps = (float)_frameCount / ((float)diffclock(&_timerStart, &_timerEnd) / 1000.f); - + _packetsPerSecond = (float) _datagramProcessor.getPacketCount() / ((float)diffclock(&_timerStart, &_timerEnd) / 1000.f); _bytesPerSecond = (float) _datagramProcessor.getByteCount() / ((float)diffclock(&_timerStart, &_timerEnd) / 1000.f); _frameCount = 0; - + _datagramProcessor.resetCounters(); gettimeofday(&_timerStart, NULL); // ask the node list to check in with the domain server NodeList::getInstance()->sendDomainServerCheckIn(); - - + + } void Application::idle() { @@ -1250,11 +1254,11 @@ void Application::idle() { _idleLoopMeasuredJitter = _idleLoopStdev.getStDev(); _idleLoopStdev.reset(); } - + if (Menu::getInstance()->isOptionChecked(MenuOption::BuckyBalls)) { _buckyBalls.simulate(timeSinceLastUpdate / 1000.f, Application::getInstance()->getAvatar()->getHandData()); } - + // After finishing all of the above work, restart the idle timer, allowing 2ms to process events. idleTimer->start(2); } @@ -1386,7 +1390,7 @@ void Application::exportVoxels(const VoxelDetail& sourceVoxel) { tr("Sparse Voxel Octree Files (*.svo)")); QByteArray fileNameAscii = fileNameString.toLocal8Bit(); const char* fileName = fileNameAscii.data(); - + VoxelTreeElement* selectedNode = _voxels.getTree()->getVoxelAt(sourceVoxel.x, sourceVoxel.y, sourceVoxel.z, sourceVoxel.s); if (selectedNode) { VoxelTree exportTree; @@ -1400,12 +1404,12 @@ void Application::exportVoxels(const VoxelDetail& sourceVoxel) { void Application::importVoxels() { _importSucceded = false; - + if (!_voxelImporter) { _voxelImporter = new VoxelImporter(_window); _voxelImporter->loadSettings(_settings); } - + if (!_voxelImporter->exec()) { qDebug() << "[DEBUG] Import succeeded." << endl; _importSucceded = true; @@ -1419,7 +1423,7 @@ void Application::importVoxels() { // restore the main window's active state _window->activateWindow(); - + emit importDone(); } @@ -1475,7 +1479,7 @@ void Application::pasteVoxels(const VoxelDetail& sourceVoxel) { } pasteVoxelsToOctalCode(octalCodeDestination); - + if (calculatedOctCode) { delete[] calculatedOctCode; } @@ -1512,9 +1516,9 @@ void Application::init() { // Cleanup of the original shared tree _sharedVoxelSystem.init(); - + _voxelImporter = new VoxelImporter(_window); - + _environment.init(); _glowEffect.init(); @@ -1556,11 +1560,11 @@ void Application::init() { _audio.setJitterBufferSamples(Menu::getInstance()->getAudioJitterBufferSamples()); } 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(); @@ -1579,20 +1583,20 @@ void Application::init() { _particleCollisionSystem.init(&_particleEditSender, _particles.getTree(), _voxels.getTree(), &_audio, &_avatarManager); // connect the _particleCollisionSystem to our script engine's ParticleScriptingInterface - connect(&_particleCollisionSystem, + connect(&_particleCollisionSystem, SIGNAL(particleCollisionWithVoxel(const ParticleID&, const VoxelDetail&, const glm::vec3&)), - ScriptEngine::getParticlesScriptingInterface(), + ScriptEngine::getParticlesScriptingInterface(), SLOT(forwardParticleCollisionWithVoxel(const ParticleID&, const VoxelDetail&, const glm::vec3&))); - connect(&_particleCollisionSystem, + connect(&_particleCollisionSystem, SIGNAL(particleCollisionWithParticle(const ParticleID&, const ParticleID&, const glm::vec3&)), - ScriptEngine::getParticlesScriptingInterface(), + ScriptEngine::getParticlesScriptingInterface(), SLOT(forwardParticleCollisionWithParticle(const ParticleID&, const ParticleID&, const glm::vec3&))); - + _audio.init(_glWidget); _rearMirrorTools = new RearMirrorTools(_glWidget, _mirrorViewRect, _settings); - + connect(_rearMirrorTools, SIGNAL(closeView()), SLOT(closeMirrorView())); connect(_rearMirrorTools, SIGNAL(restoreView()), SLOT(restoreMirrorView())); connect(_rearMirrorTools, SIGNAL(shrinkView()), SLOT(shrinkMirrorView())); @@ -1715,7 +1719,7 @@ void Application::updateMyAvatarLookAtPosition() { float distance = TREE_SCALE; if (_myAvatar->getLookAtTargetAvatar() && _myAvatar != _myAvatar->getLookAtTargetAvatar()) { distance = glm::distance(_mouseRayOrigin, - static_cast(_myAvatar->getLookAtTargetAvatar())->getHead()->calculateAverageEyePosition()); + static_cast(_myAvatar->getLookAtTargetAvatar())->getHead()->calculateAverageEyePosition()); } const float FIXED_MIN_EYE_DISTANCE = 0.3f; float minEyeDistance = FIXED_MIN_EYE_DISTANCE + (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON ? 0.0f : @@ -1728,7 +1732,7 @@ void Application::updateMyAvatarLookAtPosition() { eyePitch = _faceshift.getEstimatedEyePitch(); eyeYaw = _faceshift.getEstimatedEyeYaw(); trackerActive = true; - + } else if (_visage.isActive()) { eyePitch = _visage.getEstimatedEyePitch(); eyeYaw = _visage.getEstimatedEyeYaw(); @@ -1901,9 +1905,9 @@ void Application::update(float deltaTime) { _particles.update(); // update the particles... _particleCollisionSystem.update(); // collide the particles... - + _overlays.update(deltaTime); - + // let external parties know we're updating emit simulating(deltaTime); } @@ -1927,7 +1931,7 @@ void Application::updateMyAvatar(float deltaTime) { // actually need to calculate the view frustum planes to send these details // to the server. loadViewFrustum(_myCamera, _viewFrustum); - + // Update my voxel servers with my current voxel query... quint64 now = usecTimestampNow(); quint64 sinceLastQuery = now - _lastQueriedTime; @@ -2203,7 +2207,7 @@ void Application::updateShadowMap() { } center = inverseRotation * center; glm::vec3 minima(center.x - radius, center.y - radius, center.z - radius); - glm::vec3 maxima(center.x + radius, center.y + radius, center.z + radius); + glm::vec3 maxima(center.x + radius, center.y + radius, center.z + radius); // stretch out our extents in z so that we get all of the avatars minima.z -= _viewFrustum.getFarClip() * 0.5f; @@ -2224,7 +2228,7 @@ void Application::updateShadowMap() { _shadowViewFrustum.setEyeOffsetPosition(glm::vec3()); _shadowViewFrustum.setEyeOffsetOrientation(glm::quat()); _shadowViewFrustum.calculate(); - + glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); @@ -2279,19 +2283,19 @@ void Application::setupWorldLight() { QImage Application::renderAvatarBillboard() { _textureCache.getPrimaryFramebufferObject()->bind(); - + glDisable(GL_BLEND); const int BILLBOARD_SIZE = 64; renderRearViewMirror(QRect(0, _glWidget->height() - BILLBOARD_SIZE, BILLBOARD_SIZE, BILLBOARD_SIZE), true); - + QImage image(BILLBOARD_SIZE, BILLBOARD_SIZE, QImage::Format_ARGB32); glReadPixels(0, 0, BILLBOARD_SIZE, BILLBOARD_SIZE, GL_BGRA, GL_UNSIGNED_BYTE, image.bits()); - + glEnable(GL_BLEND); - + _textureCache.getPrimaryFramebufferObject()->release(); - + return image; } @@ -2392,7 +2396,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { "Application::displaySide() ... metavoxels..."); _metavoxels.render(); } - + if (Menu::getInstance()->isOptionChecked(MenuOption::BuckyBalls)) { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide() ... bucky balls..."); @@ -2405,7 +2409,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { "Application::displaySide() ... particles..."); _particles.render(); } - + // render the ambient occlusion effect if enabled if (Menu::getInstance()->isOptionChecked(MenuOption::AmbientOcclusion)) { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), @@ -2449,7 +2453,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { // give external parties a change to hook in emit renderingInWorldInterface(); - + // render JS/scriptable overlays _overlays.render3D(); } @@ -2498,7 +2502,7 @@ void Application::displayOverlay() { renderCollisionOverlay(_glWidget->width(), _glWidget->height(), _audio.getCollisionSoundMagnitude()); } } - + // Audio Scope const int AUDIO_SCOPE_Y_OFFSET = 135; if (Menu::getInstance()->isOptionChecked(MenuOption::Stats)) { @@ -2507,7 +2511,7 @@ void Application::displayOverlay() { _audioScope.render(MIRROR_VIEW_LEFT_PADDING, oscilloscopeTop); } } - + // Audio VU Meter and Mute Icon const int MUTE_ICON_SIZE = 24; const int AUDIO_METER_INSET = 2; @@ -2516,7 +2520,7 @@ void Application::displayOverlay() { const int AUDIO_METER_HEIGHT = 8; const int AUDIO_METER_Y_GAP = 8; const int AUDIO_METER_X = MIRROR_VIEW_LEFT_PADDING + MUTE_ICON_SIZE + AUDIO_METER_INSET; - + int audioMeterY; if (Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) { audioMeterY = MIRROR_VIEW_HEIGHT + AUDIO_METER_Y_GAP; @@ -2524,8 +2528,8 @@ void Application::displayOverlay() { audioMeterY = AUDIO_METER_Y_GAP; } _audio.renderMuteIcon(MIRROR_VIEW_LEFT_PADDING, audioMeterY); - - + + const float AUDIO_METER_BLUE[] = {0.0, 0.0, 1.0}; const float AUDIO_METER_GREEN[] = {0.0, 1.0, 0.0}; const float AUDIO_METER_RED[] = {1.0, 0.0, 0.0}; @@ -2627,8 +2631,8 @@ void Application::displayOverlay() { char frameTimer[10]; quint64 mSecsNow = floor(usecTimestampNow() / 1000.0 + 0.5); sprintf(frameTimer, "%d\n", (int)(mSecsNow % 1000)); - int timerBottom = - (Menu::getInstance()->isOptionChecked(MenuOption::Stats) && + int timerBottom = + (Menu::getInstance()->isOptionChecked(MenuOption::Stats) && Menu::getInstance()->isOptionChecked(MenuOption::Bandwidth)) ? 80 : 20; drawText(_glWidget->width() - 100, _glWidget->height() - timerBottom, 0.30f, 1.0f, 0.f, frameTimer, WHITE_TEXT); @@ -2643,7 +2647,7 @@ void Application::displayOverlay() { void Application::displayStatsBackground(unsigned int rgba, int x, int y, int width, int height) { glBegin(GL_QUADS); glColor4f(((rgba >> 24) & 0xff) / 255.0f, - ((rgba >> 16) & 0xff) / 255.0f, + ((rgba >> 16) & 0xff) / 255.0f, ((rgba >> 8) & 0xff) / 255.0f, (rgba & 0xff) / 255.0f); glVertex3f(x, y, 0); @@ -2651,7 +2655,7 @@ void Application::displayStatsBackground(unsigned int rgba, int x, int y, int wi glVertex3f(x + width, y + height, 0); glVertex3f(x , y + height, 0); glEnd(); - glColor4f(1, 1, 1, 1); + glColor4f(1, 1, 1, 1); } // display expanded or contracted stats @@ -2745,12 +2749,12 @@ void Application::displayStats() { (float) (_audio.getNetworkBufferLengthSamplesPerChannel() + (float) _audio.getJitterBufferSamples()) / (float)_audio.getNetworkSampleRate() * 1000.f); drawText(30, _glWidget->height() - 22, 0.10f, 0.f, 2.f, audioJitter, WHITE_TEXT); - - + + char audioPing[30]; sprintf(audioPing, "Audio ping: %d", pingAudio); - - + + char avatarPing[30]; sprintf(avatarPing, "Avatar ping: %d", pingAvatar); char voxelAvgPing[30]; @@ -2788,7 +2792,7 @@ void Application::displayStats() { } else { // longhand way sprintf(avatarPosition, "Position: %.1f, %.1f, %.1f", avatarPos.x, avatarPos.y, avatarPos.z); - } + } char avatarVelocity[30]; sprintf(avatarVelocity, "Velocity: %.1f", glm::length(_myAvatar->getVelocity())); char avatarBodyYaw[30]; @@ -2814,7 +2818,7 @@ void Application::displayStats() { verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, avatarMixerStats, WHITE_TEXT); - + stringstream downloadStats; downloadStats << "Downloads: "; foreach (Resource* resource, ResourceCache::getLoadingRequests()) { @@ -2822,7 +2826,7 @@ void Application::displayStats() { downloadStats << roundf(resource->getProgress() * MAXIMUM_PERCENTAGE) << "% "; } downloadStats << "(" << ResourceCache::getPendingRequestCount() << " pending)"; - + verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, downloadStats.str().c_str(), WHITE_TEXT); } @@ -2842,7 +2846,7 @@ void Application::displayStats() { drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, (char*)voxelStats.str().c_str(), WHITE_TEXT); voxelStats.str(""); - voxelStats << + voxelStats << "Geometry RAM: " << _voxels.getVoxelMemoryUsageRAM() / 1000000.f << "MB / " << "VBO: " << _voxels.getVoxelMemoryUsageVBO() / 1000000.f << "MB"; if (_voxels.hasVoxelMemoryUsageGPU()) { @@ -2894,7 +2898,7 @@ void Application::displayStats() { totalNodes += stats.getTotalElements(); if (_statsExpanded) { totalInternal += stats.getTotalInternal(); - totalLeaves += stats.getTotalLeaves(); + totalLeaves += stats.getTotalLeaves(); } } if (_statsExpanded) { @@ -2918,7 +2922,7 @@ void Application::displayStats() { QString packetsString = locale.toString((int)voxelPacketsToProcess); QString maxString = locale.toString((int)_recentMaxPackets); voxelStats << "Voxel Packets to Process: " << qPrintable(packetsString) - << " [Recent Max: " << qPrintable(maxString) << "]"; + << " [Recent Max: " << qPrintable(maxString) << "]"; verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, (char*)voxelStats.str().c_str(), WHITE_TEXT); } @@ -3080,12 +3084,12 @@ void Application::renderRearViewMirror(const QRect& region, bool billboard) { _mirrorCamera.setFieldOfView(BILLBOARD_FIELD_OF_VIEW); // degees _mirrorCamera.setDistance(BILLBOARD_DISTANCE * _myAvatar->getScale()); _mirrorCamera.setTargetPosition(_myAvatar->getPosition()); - + } else if (_rearMirrorTools->getZoomLevel() == BODY) { _mirrorCamera.setFieldOfView(MIRROR_FIELD_OF_VIEW); // degrees _mirrorCamera.setDistance(MIRROR_REARVIEW_BODY_DISTANCE * _myAvatar->getScale()); _mirrorCamera.setTargetPosition(_myAvatar->getChestPosition()); - + } else { // HEAD zoom level _mirrorCamera.setFieldOfView(MIRROR_FIELD_OF_VIEW); // degrees _mirrorCamera.setDistance(MIRROR_REARVIEW_DISTANCE * _myAvatar->getScale()); @@ -3100,7 +3104,7 @@ void Application::renderRearViewMirror(const QRect& region, bool billboard) { } } _mirrorCamera.setAspectRatio((float)region.width() / region.height()); - + _mirrorCamera.setTargetRotation(_myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PI, 0.0f))); _mirrorCamera.update(1.0f/_fps); @@ -3145,7 +3149,7 @@ void Application::renderRearViewMirror(const QRect& region, bool billboard) { if (!billboard) { _rearMirrorTools->render(false); } - + // reset Viewport and projection matrix glViewport(0, 0, _glWidget->width(), _glWidget->height()); glDisable(GL_SCISSOR_TEST); @@ -3365,14 +3369,14 @@ void Application::setMenuShortcutsEnabled(bool enabled) { } void Application::updateWindowTitle(){ - + QString buildVersion = " (build " + applicationVersion() + ")"; NodeList* nodeList = NodeList::getInstance(); - + QString username = AccountManager::getInstance().getUsername(); QString title = QString() + (!username.isEmpty() ? username + " " : QString()) + nodeList->getSessionUUID().toString() + " @ " + nodeList->getDomainInfo().getHostname() + buildVersion; - + qDebug("Application title set to: %s", title.toStdString().c_str()); _window->setWindowTitle(title); } @@ -3387,7 +3391,7 @@ void Application::domainChanged(const QString& domainHostname) { _voxelServerJurisdictions.clear(); _octreeServerSceneStats.clear(); _particleServerJurisdictions.clear(); - + // reset the particle renderer _particles.clear(); @@ -3397,12 +3401,12 @@ void Application::domainChanged(const QString& domainHostname) { void Application::connectedToDomain(const QString& hostname) { AccountManager& accountManager = AccountManager::getInstance(); - + if (accountManager.isLoggedIn()) { // update our domain-server with the data-server we're logged in with - + QString domainPutJsonString = "{\"address\":{\"domain\":\"" + hostname + "\"}}"; - + accountManager.authenticatedRequest("/api/v1/users/address", QNetworkAccessManager::PutOperation, JSONCallbackParameters(), domainPutJsonString.toUtf8()); } @@ -3567,13 +3571,13 @@ void Application::loadScripts() { // loads all saved scripts QSettings* settings = new QSettings(this); int size = settings->beginReadArray("Settings"); - + for (int i = 0; i < size; ++i){ settings->setArrayIndex(i); QString string = settings->value("script").toString(); loadScript(string); } - + settings->endArray(); } @@ -3588,42 +3592,72 @@ void Application::saveScripts() { // saves all current running scripts QSettings* settings = new QSettings(this); settings->beginWriteArray("Settings"); - for (int i = 0; i < _activeScripts.size(); ++i){ + for (int i = 0; i < getRunningScripts().size(); ++i){ settings->setArrayIndex(i); - settings->setValue("script", _activeScripts.at(i)); + settings->setValue("script", getRunningScripts().at(i)); } - + settings->endArray(); } void Application::stopAllScripts() { // stops all current running scripts - QList scriptActions = Menu::getInstance()->getActiveScriptsMenu()->actions(); - foreach (QAction* scriptAction, scriptActions) { - scriptAction->activate(QAction::Trigger); - qDebug() << "stopping script..." << scriptAction->text(); + for (int i = 0; i < _scriptEnginesHash.size(); ++i) { + _scriptEnginesHash.values().at(i)->stop(); + qDebug() << "stopping script..." << getRunningScripts().at(i); } - _activeScripts.clear(); + _scriptEnginesHash.clear(); + _runningScriptsWidget->setRunningScripts(getRunningScripts()); +} + +void Application::stopScript(const QString &scriptName) +{ + _scriptEnginesHash.value(scriptName)->stop(); + qDebug() << "stopping script..." << scriptName; + _scriptEnginesHash.remove(scriptName); + _runningScriptsWidget->setRunningScripts(getRunningScripts()); } void Application::reloadAllScripts() { // remember all the current scripts so we can reload them - QStringList reloadList = _activeScripts; + QStringList reloadList = getRunningScripts(); // reloads all current running scripts - QList scriptActions = Menu::getInstance()->getActiveScriptsMenu()->actions(); - foreach (QAction* scriptAction, scriptActions) { - scriptAction->activate(QAction::Trigger); - qDebug() << "stopping script..." << scriptAction->text(); - } + stopAllScripts(); - // NOTE: we don't need to clear the _activeScripts list because that is handled on script shutdown. - foreach (QString scriptName, reloadList){ qDebug() << "reloading script..." << scriptName; loadScript(scriptName); } } +void Application::toggleRunningScriptsWidget() +{ + if (!_runningScriptsWidget->toggleViewAction()->isChecked()) { + _runningScriptsWidget->move(_window->geometry().topLeft().x(), _window->geometry().topLeft().y()); + _runningScriptsWidget->resize(0, _window->height()); + _runningScriptsWidget->toggleViewAction()->trigger(); + _runningScriptsWidget->grabKeyboard(); + + QPropertyAnimation* slideAnimation = new QPropertyAnimation(_runningScriptsWidget, "geometry", _runningScriptsWidget); + slideAnimation->setStartValue(_runningScriptsWidget->geometry()); + slideAnimation->setEndValue(QRect(_window->geometry().topLeft().x(), _window->geometry().topLeft().y(), + 310, _runningScriptsWidget->height())); + slideAnimation->setDuration(250); + slideAnimation->start(QAbstractAnimation::DeleteWhenStopped); + } else { + _runningScriptsWidget->releaseKeyboard(); + + QPropertyAnimation* slideAnimation = new QPropertyAnimation(_runningScriptsWidget, "geometry", _runningScriptsWidget); + slideAnimation->setStartValue(_runningScriptsWidget->geometry()); + slideAnimation->setEndValue(QRect(_window->geometry().topLeft().x(), _window->geometry().topLeft().y(), + 0, _runningScriptsWidget->height())); + slideAnimation->setDuration(250); + slideAnimation->start(QAbstractAnimation::DeleteWhenStopped); + + QTimer::singleShot(260, _runningScriptsWidget->toggleViewAction(), SLOT(trigger())); + } +} + void Application::uploadFST(bool isHead) { FstReader reader(isHead); if (reader.zip()) { @@ -3639,29 +3673,17 @@ void Application::uploadSkeleton() { uploadFST(false); } -void Application::removeScriptName(const QString& fileNameString) { - _activeScripts.removeOne(fileNameString); -} - -void Application::cleanupScriptMenuItem(const QString& scriptMenuName) { - Menu::getInstance()->removeAction(Menu::getInstance()->getActiveScriptsMenu(), scriptMenuName); -} - void Application::loadScript(const QString& scriptName) { // start the script on a new thread... - bool wantMenuItems = true; // tells the ScriptEngine object to add menu items for itself - ScriptEngine* scriptEngine = new ScriptEngine(QUrl(scriptName), wantMenuItems, &_controllerScriptingInterface); + ScriptEngine* scriptEngine = new ScriptEngine(QUrl(scriptName), &_controllerScriptingInterface); + _scriptEnginesHash.insert(scriptName, scriptEngine); if (!scriptEngine->hasScript()) { qDebug() << "Application::loadScript(), script failed to load..."; return; } - _activeScripts.append(scriptName); - - // add a stop menu item - Menu::getInstance()->addActionToQMenuAndActionHash(Menu::getInstance()->getActiveScriptsMenu(), - scriptEngine->getScriptMenuName(), 0, scriptEngine, SLOT(stop())); + _runningScriptsWidget->setRunningScripts(getRunningScripts()); // setup the packet senders and jurisdiction listeners of the script engine's scripting interfaces so // we can use the same ones from the application. @@ -3669,7 +3691,7 @@ void Application::loadScript(const QString& scriptName) { scriptEngine->getVoxelsScriptingInterface()->setVoxelTree(_voxels.getTree()); scriptEngine->getParticlesScriptingInterface()->setPacketSender(&_particleEditSender); scriptEngine->getParticlesScriptingInterface()->setParticleTree(_particles.getTree()); - + // hook our avatar object into this script engine scriptEngine->setAvatarData(_myAvatar, "MyAvatar"); // leave it as a MyAvatar class to expose thrust features @@ -3694,8 +3716,6 @@ void Application::loadScript(const QString& scriptName) { // when the thread is terminated, add both scriptEngine and thread to the deleteLater queue connect(scriptEngine, SIGNAL(finished(const QString&)), scriptEngine, SLOT(deleteLater())); connect(workerThread, SIGNAL(finished()), workerThread, SLOT(deleteLater())); - connect(scriptEngine, SIGNAL(finished(const QString&)), this, SLOT(removeScriptName(const QString&))); - connect(scriptEngine, SIGNAL(cleanupMenuItem(const QString&)), this, SLOT(cleanupScriptMenuItem(const QString&))); // when the application is about to quit, stop our script engine so it unwinds properly connect(this, SIGNAL(aboutToQuit()), scriptEngine, SLOT(stop())); @@ -3719,12 +3739,12 @@ void Application::loadDialog() { suggestedName = _previousScriptLocation; } - QString fileNameString = QFileDialog::getOpenFileName(_glWidget, tr("Open Script"), suggestedName, + QString fileNameString = QFileDialog::getOpenFileName(_glWidget, tr("Open Script"), suggestedName, tr("JavaScript Files (*.js)")); if (!fileNameString.isEmpty()) { _previousScriptLocation = fileNameString; } - + loadScript(fileNameString); } @@ -3735,7 +3755,7 @@ void Application::loadScriptURLDialog() { scriptURLDialog.setLabelText("Script:"); scriptURLDialog.setWindowFlags(Qt::Sheet); const float DIALOG_RATIO_OF_WINDOW = 0.30f; - scriptURLDialog.resize(scriptURLDialog.parentWidget()->size().width() * DIALOG_RATIO_OF_WINDOW, + scriptURLDialog.resize(scriptURLDialog.parentWidget()->size().width() * DIALOG_RATIO_OF_WINDOW, scriptURLDialog.size().height()); int dialogReturn = scriptURLDialog.exec(); @@ -3773,29 +3793,29 @@ void Application::checkVersion() { } void Application::parseVersionXml() { - + #ifdef Q_OS_WIN32 QString operatingSystem("win"); #endif - + #ifdef Q_OS_MAC QString operatingSystem("mac"); #endif - + #ifdef Q_OS_LINUX QString operatingSystem("ubuntu"); #endif - + QString releaseDate; QString releaseNotes; QString latestVersion; QUrl downloadUrl; QObject* sender = QObject::sender(); - + QXmlStreamReader xml(qobject_cast(sender)); while (!xml.atEnd() && !xml.hasError()) { QXmlStreamReader::TokenType token = xml.readNext(); - + if (token == QXmlStreamReader::StartElement) { if (xml.name() == "ReleaseDate") { xml.readNext(); diff --git a/interface/src/Application.h b/interface/src/Application.h index e12d0b307f..221a81478e 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -65,6 +66,7 @@ #include "ui/LogDialog.h" #include "ui/UpdateDialog.h" #include "ui/overlays/Overlays.h" +#include "ui/RunningScriptsWidget.h" #include "voxels/VoxelFade.h" #include "voxels/VoxelHideShowThread.h" #include "voxels/VoxelImporter.h" @@ -112,7 +114,7 @@ public: ~Application(); void restoreSizeAndPosition(); - void loadScript(const QString& fileNameString); + void loadScript(const QString& fileNameString); void loadScripts(); void storeSizeAndPosition(); void clearScriptsBeforeRunning(); @@ -136,9 +138,9 @@ public: void wheelEvent(QWheelEvent* event); void dropEvent(QDropEvent *event); - + bool event(QEvent* event); - + void makeVoxel(glm::vec3 position, float scale, unsigned char red, @@ -226,6 +228,8 @@ public: void skipVersion(QString latestVersion); + QStringList getRunningScripts() { return _scriptEnginesHash.keys(); } + signals: /// Fired when we're simulating; allows external parties to hook in. @@ -233,10 +237,10 @@ signals: /// Fired when we're rendering in-world interface elements; allows external parties to hook in. void renderingInWorldInterface(); - + /// Fired when the import window is closed void importDone(); - + public slots: void domainChanged(const QString& domainHostname); void updateWindowTitle(); @@ -259,8 +263,10 @@ public slots: void toggleLogDialog(); void initAvatarAndViewFrustum(); void stopAllScripts(); + void stopScript(const QString& scriptName); void reloadAllScripts(); - + void toggleRunningScriptsWidget(); + void uploadFST(bool isHead); void uploadHead(); void uploadSkeleton(); @@ -268,24 +274,21 @@ public slots: private slots: void timer(); void idle(); - + void connectedToDomain(const QString& hostname); void setFullscreen(bool fullscreen); void setEnable3DTVMode(bool enable3DTVMode); void cameraMenuChanged(); - + glm::vec2 getScaledScreenPoint(glm::vec2 projectedPoint); void closeMirrorView(); void restoreMirrorView(); void shrinkMirrorView(); void resetSensors(); - - void parseVersionXml(); - void removeScriptName(const QString& fileNameString); - void cleanupScriptMenuItem(const QString& scriptMenuName); + void parseVersionXml(); private: void resetCamerasOnResizeGL(Camera& camera, int width, int height); @@ -355,7 +358,7 @@ private: bool _statsExpanded; BandwidthMeter _bandwidthMeter; - + QThread* _nodeThread; DatagramProcessor _datagramProcessor; @@ -374,7 +377,7 @@ private: timeval _lastTimeUpdated; bool _justStarted; Stars _stars; - + BuckyBalls _buckyBalls; VoxelSystem _voxels; @@ -409,7 +412,6 @@ private: Visage _visage; SixenseManager _sixenseManager; - QStringList _activeScripts; Camera _myCamera; // My view onto the world Camera _viewFrustumOffsetCamera; // The camera we use to sometimes show the view frustum from an offset mode @@ -493,10 +495,13 @@ private: void displayUpdateDialog(); bool shouldSkipVersion(QString latestVersion); void takeSnapshot(); - + TouchEvent _lastTouchEvent; - + Overlays _overlays; + + RunningScriptsWidget* _runningScriptsWidget; + QHash _scriptEnginesHash; }; #endif /* defined(__interface__Application__) */ diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 092f9090b0..4e2680944a 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -86,7 +86,7 @@ Menu::Menu() : _loginAction(NULL) { Application *appInstance = Application::getInstance(); - + QMenu* fileMenu = addMenu("File"); #ifdef Q_OS_MAC @@ -99,23 +99,24 @@ Menu::Menu() : #endif AccountManager& accountManager = AccountManager::getInstance(); - + _loginAction = addActionToQMenuAndActionHash(fileMenu, MenuOption::Logout); - + // call our toggle login function now so the menu option is setup properly toggleLoginMenuItem(); - + // connect to the appropriate slots of the AccountManager so that we can change the Login/Logout menu item connect(&accountManager, &AccountManager::accessTokenChanged, this, &Menu::toggleLoginMenuItem); connect(&accountManager, &AccountManager::logoutComplete, this, &Menu::toggleLoginMenuItem); addDisabledActionAndSeparator(fileMenu, "Scripts"); addActionToQMenuAndActionHash(fileMenu, MenuOption::LoadScript, Qt::CTRL | Qt::Key_O, appInstance, SLOT(loadDialog())); - addActionToQMenuAndActionHash(fileMenu, MenuOption::LoadScriptURL, + addActionToQMenuAndActionHash(fileMenu, MenuOption::LoadScriptURL, Qt::CTRL | Qt::SHIFT | Qt::Key_O, appInstance, SLOT(loadScriptURLDialog())); addActionToQMenuAndActionHash(fileMenu, MenuOption::StopAllScripts, 0, appInstance, SLOT(stopAllScripts())); addActionToQMenuAndActionHash(fileMenu, MenuOption::ReloadAllScripts, 0, appInstance, SLOT(reloadAllScripts())); - _activeScriptsMenu = fileMenu->addMenu("Running Scripts"); + addActionToQMenuAndActionHash(fileMenu, MenuOption::RunningScripts, Qt::CTRL | Qt::Key_J, + appInstance, SLOT(toggleRunningScriptsWidget())); addDisabledActionAndSeparator(fileMenu, "Go"); addActionToQMenuAndActionHash(fileMenu, @@ -147,7 +148,6 @@ Menu::Menu() : addDisabledActionAndSeparator(fileMenu, "Upload Avatar Model"); addActionToQMenuAndActionHash(fileMenu, MenuOption::UploadHead, 0, Application::getInstance(), SLOT(uploadHead())); addActionToQMenuAndActionHash(fileMenu, MenuOption::UploadSkeleton, 0, Application::getInstance(), SLOT(uploadSkeleton())); - addDisabledActionAndSeparator(fileMenu, "Settings"); addActionToQMenuAndActionHash(fileMenu, MenuOption::SettingsImport, 0, this, SLOT(importSettings())); addActionToQMenuAndActionHash(fileMenu, MenuOption::SettingsExport, 0, this, SLOT(exportSettings())); @@ -172,8 +172,8 @@ Menu::Menu() : addDisabledActionAndSeparator(editMenu, "Physics"); addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::Gravity, Qt::SHIFT | Qt::Key_G, false); - - + + addAvatarCollisionSubMenu(editMenu); @@ -207,7 +207,7 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Mirror, Qt::SHIFT | Qt::Key_H, true); addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FullscreenMirror, Qt::Key_H, false, appInstance, SLOT(cameraMenuChanged())); - + addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Enable3DTVMode, 0, false, appInstance, @@ -334,16 +334,16 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(renderDebugMenu, MenuOption::PipelineWarnings, Qt::CTRL | Qt::SHIFT | Qt::Key_P); addCheckableActionToQMenuAndActionHash(renderDebugMenu, MenuOption::SuppressShortTimings, Qt::CTRL | Qt::SHIFT | Qt::Key_S); - addCheckableActionToQMenuAndActionHash(renderDebugMenu, - MenuOption::CullSharedFaces, - Qt::CTRL | Qt::SHIFT | Qt::Key_C, + addCheckableActionToQMenuAndActionHash(renderDebugMenu, + MenuOption::CullSharedFaces, + Qt::CTRL | Qt::SHIFT | Qt::Key_C, false, appInstance->getVoxels(), SLOT(cullSharedFaces())); - addCheckableActionToQMenuAndActionHash(renderDebugMenu, - MenuOption::ShowCulledSharedFaces, - Qt::CTRL | Qt::SHIFT | Qt::Key_X, + addCheckableActionToQMenuAndActionHash(renderDebugMenu, + MenuOption::ShowCulledSharedFaces, + Qt::CTRL | Qt::SHIFT | Qt::Key_X, false, appInstance->getVoxels(), SLOT(showCulledSharedFaces())); @@ -367,14 +367,13 @@ Menu::Menu() : appInstance->getAudio(), SLOT(toggleToneInjection())); - addActionToQMenuAndActionHash(developerMenu, MenuOption::PasteToVoxel, Qt::CTRL | Qt::SHIFT | Qt::Key_V, this, SLOT(pasteToVoxel())); connect(appInstance->getAudio(), SIGNAL(muteToggled()), this, SLOT(audioMuteToggled())); - + #ifndef Q_OS_MAC QMenu* helpMenu = addMenu("Help"); QAction* helpAction = helpMenu->addAction(MenuOption::AboutApp); @@ -578,7 +577,7 @@ void Menu::addDisabledActionAndSeparator(QMenu* destinationMenu, const QString& QAction* separatorText = new QAction(actionName,destinationMenu); separatorText->setEnabled(false); destinationMenu->insertAction(actionBefore, separatorText); - + } else { destinationMenu->addSeparator(); (destinationMenu->addAction(actionName))->setEnabled(false); @@ -630,7 +629,7 @@ QAction* Menu::addCheckableActionToQMenuAndActionHash(QMenu* destinationMenu, const char* member, int menuItemLocation) { - QAction* action = addActionToQMenuAndActionHash(destinationMenu, actionName, shortcut, receiver, member, + QAction* action = addActionToQMenuAndActionHash(destinationMenu, actionName, shortcut, receiver, member, QAction::NoRole, menuItemLocation); action->setCheckable(true); action->setChecked(checked); @@ -684,40 +683,40 @@ const float DIALOG_RATIO_OF_WINDOW = 0.30f; void Menu::loginForCurrentDomain() { QDialog loginDialog(Application::getInstance()->getWindow()); loginDialog.setWindowTitle("Login"); - + QBoxLayout* layout = new QBoxLayout(QBoxLayout::TopToBottom); loginDialog.setLayout(layout); loginDialog.setWindowFlags(Qt::Sheet); - + QFormLayout* form = new QFormLayout(); layout->addLayout(form, 1); - + QLineEdit* loginLineEdit = new QLineEdit(); loginLineEdit->setMinimumWidth(QLINE_MINIMUM_WIDTH); form->addRow("Login:", loginLineEdit); - + QLineEdit* passwordLineEdit = new QLineEdit(); passwordLineEdit->setMinimumWidth(QLINE_MINIMUM_WIDTH); passwordLineEdit->setEchoMode(QLineEdit::Password); form->addRow("Password:", passwordLineEdit); - + QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); loginDialog.connect(buttons, SIGNAL(accepted()), SLOT(accept())); loginDialog.connect(buttons, SIGNAL(rejected()), SLOT(reject())); layout->addWidget(buttons); - + int dialogReturn = loginDialog.exec(); - + if (dialogReturn == QDialog::Accepted && !loginLineEdit->text().isEmpty() && !passwordLineEdit->text().isEmpty()) { // attempt to get an access token given this username and password AccountManager::getInstance().requestAccessToken(loginLineEdit->text(), passwordLineEdit->text()); } - + sendFakeEnterEvent(); } void Menu::editPreferences() { - Application* applicationInstance = Application::getInstance(); + Application* applicationInstance = Application::getInstance(); ModelsBrowser headBrowser(Head); ModelsBrowser skeletonBrowser(Skeleton); @@ -725,14 +724,14 @@ void Menu::editPreferences() { QDialog dialog(applicationInstance->getWindow()); dialog.setWindowTitle("Interface Preferences"); - + QBoxLayout* layout = new QBoxLayout(QBoxLayout::TopToBottom); dialog.setLayout(layout); - + QFormLayout* form = new QFormLayout(); layout->addLayout(form, 1); - + QHBoxLayout headModelLayout; QString faceURLString = applicationInstance->getAvatar()->getHead()->getFaceModel().getURL().toString(); QLineEdit headURLEdit(faceURLString); @@ -744,7 +743,7 @@ void Menu::editPreferences() { headModelLayout.addWidget(&headURLEdit); headModelLayout.addWidget(&headBrowseButton); form->addRow("Head URL:", &headModelLayout); - + QHBoxLayout skeletonModelLayout; QString skeletonURLString = applicationInstance->getAvatar()->getSkeletonModel().getURL().toString(); QLineEdit skeletonURLEdit(skeletonURLString); @@ -756,7 +755,7 @@ void Menu::editPreferences() { skeletonModelLayout.addWidget(&skeletonURLEdit); skeletonModelLayout.addWidget(&SkeletonBrowseButton); form->addRow("Skeleton URL:", &skeletonModelLayout); - + QString displayNameString = applicationInstance->getAvatar()->getDisplayName(); QLineEdit* displayNameEdit = new QLineEdit(displayNameString); @@ -841,12 +840,12 @@ void Menu::editPreferences() { } QString displayNameStr(displayNameEdit->text()); - + if (displayNameStr != displayNameString) { applicationInstance->getAvatar()->setDisplayName(displayNameStr); shouldDispatchIdentityPacket = true; } - + if (shouldDispatchIdentityPacket) { applicationInstance->getAvatar()->sendIdentityPacket(); } @@ -879,10 +878,10 @@ void Menu::editPreferences() { void Menu::goToDomain(const QString newDomain) { if (NodeList::getInstance()->getDomainInfo().getHostname() != newDomain) { - + // send a node kill request, indicating to other clients that they should play the "disappeared" effect Application::getInstance()->getAvatar()->sendKillAvatar(); - + // give our nodeList the new domain-server hostname NodeList::getInstance()->getDomainInfo().setHostname(newDomain); } @@ -912,7 +911,7 @@ void Menu::goToDomainDialog() { // the user input a new hostname, use that newHostname = domainDialog.textValue(); } - + goToDomain(newHostname); } @@ -928,7 +927,7 @@ bool Menu::goToDestination(QString destination) { } void Menu::goTo() { - + QInputDialog gotoDialog(Application::getInstance()->getWindow()); gotoDialog.setWindowTitle("Go to"); gotoDialog.setLabelText("Destination:"); @@ -936,7 +935,7 @@ void Menu::goTo() { gotoDialog.setTextValue(destination); gotoDialog.setWindowFlags(Qt::Sheet); gotoDialog.resize(gotoDialog.parentWidget()->size().width() * DIALOG_RATIO_OF_WINDOW, gotoDialog.size().height()); - + int dialogReturn = gotoDialog.exec(); if (dialogReturn == QDialog::Accepted && !gotoDialog.textValue().isEmpty()) { goToUser(gotoDialog.textValue()); @@ -1085,7 +1084,7 @@ void Menu::toggleLoginMenuItem() { AccountManager& accountManager = AccountManager::getInstance(); disconnect(_loginAction, 0, 0, 0); - + if (accountManager.isLoggedIn()) { // change the menu item to logout _loginAction->setText("Logout " + accountManager.getUsername()); @@ -1093,7 +1092,7 @@ void Menu::toggleLoginMenuItem() { } else { // change the menu item to login _loginAction->setText("Login"); - + connect(_loginAction, &QAction::triggered, this, &Menu::loginForCurrentDomain); } } @@ -1200,7 +1199,7 @@ QString Menu::getLODFeedbackText() { } break; } - // distance feedback + // distance feedback float voxelSizeScale = getVoxelSizeScale(); float relativeToDefault = voxelSizeScale / DEFAULT_OCTREE_SIZE_SCALE; QString result; @@ -1215,7 +1214,7 @@ QString Menu::getLODFeedbackText() { } void Menu::autoAdjustLOD(float currentFPS) { - // NOTE: our first ~100 samples at app startup are completely all over the place, and we don't + // NOTE: our first ~100 samples at app startup are completely all over the place, and we don't // really want to count them in our average, so we will ignore the real frame rates and stuff // our moving average with simulated good data const int IGNORE_THESE_SAMPLES = 100; @@ -1227,7 +1226,7 @@ void Menu::autoAdjustLOD(float currentFPS) { _fastFPSAverage.updateAverage(currentFPS); quint64 now = usecTimestampNow(); - + const quint64 ADJUST_AVATAR_LOD_DOWN_DELAY = 1000 * 1000; if (_fastFPSAverage.getAverage() < ADJUST_LOD_DOWN_FPS) { if (now - _lastAvatarDetailDrop > ADJUST_AVATAR_LOD_DOWN_DELAY) { @@ -1246,11 +1245,11 @@ void Menu::autoAdjustLOD(float currentFPS) { _avatarLODDistanceMultiplier = qMax(MINIMUM_DISTANCE_MULTIPLIER, _avatarLODDistanceMultiplier - DISTANCE_DECREASE_RATE); } - + bool changed = false; quint64 elapsed = now - _lastAdjust; - if (elapsed > ADJUST_LOD_DOWN_DELAY && _fpsAverage.getAverage() < ADJUST_LOD_DOWN_FPS + if (elapsed > ADJUST_LOD_DOWN_DELAY && _fpsAverage.getAverage() < ADJUST_LOD_DOWN_FPS && _voxelSizeScale > ADJUST_LOD_MIN_SIZE_SCALE) { _voxelSizeScale *= ADJUST_LOD_DOWN_BY; @@ -1263,7 +1262,7 @@ void Menu::autoAdjustLOD(float currentFPS) { << "_voxelSizeScale=" << _voxelSizeScale; } - if (elapsed > ADJUST_LOD_UP_DELAY && _fpsAverage.getAverage() > ADJUST_LOD_UP_FPS + if (elapsed > ADJUST_LOD_UP_DELAY && _fpsAverage.getAverage() > ADJUST_LOD_UP_FPS && _voxelSizeScale < ADJUST_LOD_MAX_SIZE_SCALE) { _voxelSizeScale *= ADJUST_LOD_UP_BY; if (_voxelSizeScale > ADJUST_LOD_MAX_SIZE_SCALE) { @@ -1274,7 +1273,7 @@ void Menu::autoAdjustLOD(float currentFPS) { qDebug() << "adjusting LOD up... average fps for last approximately 5 seconds=" << _fpsAverage.getAverage() << "_voxelSizeScale=" << _voxelSizeScale; } - + if (changed) { if (_lodToolsDialog) { _lodToolsDialog->reloadSliders(); @@ -1352,13 +1351,13 @@ void Menu::addAvatarCollisionSubMenu(QMenu* overMenu) { Application* appInstance = Application::getInstance(); QObject* avatar = appInstance->getAvatar(); - addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithEnvironment, + addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithEnvironment, 0, false, avatar, SLOT(updateCollisionFlags())); - addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithAvatars, + addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithAvatars, 0, true, avatar, SLOT(updateCollisionFlags())); - addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithVoxels, + addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithVoxels, 0, false, avatar, SLOT(updateCollisionFlags())); - addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithParticles, + addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithParticles, 0, true, avatar, SLOT(updateCollisionFlags())); } @@ -1367,9 +1366,9 @@ QAction* Menu::getActionFromName(const QString& menuName, QMenu* menu) { if (menu) { menuActions = menu->actions(); } else { - menuActions = actions(); + menuActions = actions(); } - + foreach (QAction* menuAction, menuActions) { if (menuName == menuAction->text()) { return menuAction; @@ -1476,7 +1475,7 @@ QMenu* Menu::addMenu(const QString& menuName) { void Menu::removeMenu(const QString& menuName) { QAction* action = getMenuAction(menuName); - + // only proceed if the menu actually exists if (action) { QString finalMenuPart; @@ -1528,7 +1527,7 @@ void Menu::addMenuItem(const MenuItemProperties& properties) { if (!properties.shortcutKeySequence.isEmpty()) { shortcut = new QShortcut(properties.shortcutKeySequence, this); } - + // check for positioning requests int requestedPosition = properties.position; if (requestedPosition == UNSPECIFIED_POSITION && !properties.beforeItem.isEmpty()) { @@ -1542,13 +1541,13 @@ void Menu::addMenuItem(const MenuItemProperties& properties) { requestedPosition = afterPosition + 1; } } - + QAction* menuItemAction = NULL; if (properties.isSeparator) { addDisabledActionAndSeparator(menuObj, properties.menuItemName, requestedPosition); } else if (properties.isCheckable) { menuItemAction = addCheckableActionToQMenuAndActionHash(menuObj, properties.menuItemName, - properties.shortcutKeySequence, properties.isChecked, + properties.shortcutKeySequence, properties.isChecked, MenuScriptingInterface::getInstance(), SLOT(menuItemTriggered()), requestedPosition); } else { menuItemAction = addActionToQMenuAndActionHash(menuObj, properties.menuItemName, properties.shortcutKeySequence, diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 55eeff6498..b54266f915 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -95,8 +95,6 @@ public: // User Tweakable PPS from Voxel Server int getMaxVoxelPacketsPerSecond() const { return _maxVoxelPacketsPerSecond; } - QMenu* getActiveScriptsMenu() { return _activeScriptsMenu;} - QAction* addActionToQMenuAndActionHash(QMenu* destinationMenu, const QString& actionName, const QKeySequence& shortcut = 0, @@ -124,7 +122,7 @@ public slots: void goTo(); void goToUser(const QString& user); void pasteToVoxel(); - + void toggleLoginMenuItem(); QMenu* addMenu(const QString& menuName); @@ -166,7 +164,7 @@ private: void scanMenu(QMenu* menu, settingsAction modifySetting, QSettings* set); /// helper method to have separators with labels that are also compatible with OS X - void addDisabledActionAndSeparator(QMenu* destinationMenu, const QString& actionName, + void addDisabledActionAndSeparator(QMenu* destinationMenu, const QString& actionName, int menuItemLocation = UNSPECIFIED_POSITION); QAction* addCheckableActionToQMenuAndActionHash(QMenu* destinationMenu, @@ -189,7 +187,7 @@ private: int findPositionOfMenuItem(QMenu* menu, const QString& searchMenuItem); int positionBeforeSeparatorIfNeeded(QMenu* menu, int requestedPosition); QMenu* getMenu(const QString& menuName); - + QHash _actionHash; int _audioJitterBufferSamples; /// number of extra samples to wait before starting audio playback @@ -208,7 +206,6 @@ private: int _boundaryLevelAdjust; QAction* _useVoxelShader; int _maxVoxelPacketsPerSecond; - QMenu* _activeScriptsMenu; QString replaceLastOccurrence(QChar search, QChar replace, QString string); quint64 _lastAdjust; quint64 _lastAvatarDetailDrop; @@ -292,6 +289,7 @@ namespace MenuOption { const QString RenderHeadCollisionShapes = "Head Collision Shapes"; const QString RenderBoundingCollisionShapes = "Bounding Collision Shapes"; const QString ResetAvatarSize = "Reset Avatar Size"; + const QString RunningScripts = "Running Scripts"; const QString RunTimingTests = "Run Timing Tests"; const QString SettingsImport = "Import Settings"; const QString Shadows = "Shadows"; diff --git a/interface/src/ui/RunningScriptsWidget.cpp b/interface/src/ui/RunningScriptsWidget.cpp new file mode 100644 index 0000000000..2238cab2df --- /dev/null +++ b/interface/src/ui/RunningScriptsWidget.cpp @@ -0,0 +1,203 @@ +// +// RunningScripts.cpp +// interface +// +// Created by Mohammed Nafees on 03/28/2014. +// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. + +#include "ui_runningScriptsWidget.h" +#include "RunningScriptsWidget.h" + +#include +#include + +#include "Application.h" + +RunningScriptsWidget::RunningScriptsWidget(QDockWidget *parent) : + QDockWidget(parent), + ui(new Ui::RunningScriptsWidget) +{ + ui->setupUi(this); + + // remove the title bar (see the Qt docs on setTitleBarWidget) + setTitleBarWidget(new QWidget()); + + ui->runningScriptsTableWidget->setColumnCount(2); + ui->runningScriptsTableWidget->verticalHeader()->setVisible(false); + ui->runningScriptsTableWidget->horizontalHeader()->setVisible(false); + ui->runningScriptsTableWidget->setSelectionMode(QAbstractItemView::NoSelection); + ui->runningScriptsTableWidget->setShowGrid(false); + ui->runningScriptsTableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); + ui->runningScriptsTableWidget->setColumnWidth(0, 235); + ui->runningScriptsTableWidget->setColumnWidth(1, 25); + connect(ui->runningScriptsTableWidget, &QTableWidget::cellClicked, this, &RunningScriptsWidget::stopScript); + + ui->recentlyLoadedScriptsTableWidget->setColumnCount(2); + ui->recentlyLoadedScriptsTableWidget->verticalHeader()->setVisible(false); + ui->recentlyLoadedScriptsTableWidget->horizontalHeader()->setVisible(false); + ui->recentlyLoadedScriptsTableWidget->setSelectionMode(QAbstractItemView::NoSelection); + ui->recentlyLoadedScriptsTableWidget->setShowGrid(false); + ui->recentlyLoadedScriptsTableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); + ui->recentlyLoadedScriptsTableWidget->setColumnWidth(0, 25); + ui->recentlyLoadedScriptsTableWidget->setColumnWidth(1, 235); + connect(ui->recentlyLoadedScriptsTableWidget, &QTableWidget::cellClicked, + this, &RunningScriptsWidget::loadScript); + + connect(ui->hideWidgetButton, &QPushButton::clicked, + Application::getInstance(), &Application::toggleRunningScriptsWidget); + connect(ui->reloadAllButton, &QPushButton::clicked, + Application::getInstance(), &Application::reloadAllScripts); + connect(ui->stopAllButton, &QPushButton::clicked, + this, &RunningScriptsWidget::allScriptsStopped); +} + +RunningScriptsWidget::~RunningScriptsWidget() +{ + delete ui; +} + +void RunningScriptsWidget::setRunningScripts(const QStringList& list) +{ + ui->runningScriptsTableWidget->setRowCount(list.size()); + + ui->noRunningScriptsLabel->setVisible(list.isEmpty()); + ui->currentlyRunningLabel->setVisible(!list.isEmpty()); + ui->line1->setVisible(!list.isEmpty()); + ui->runningScriptsTableWidget->setVisible(!list.isEmpty()); + ui->reloadAllButton->setVisible(!list.isEmpty()); + ui->stopAllButton->setVisible(!list.isEmpty()); + + for (int i = 0; i < list.size(); ++i) { + QTableWidgetItem *scriptName = new QTableWidgetItem; + scriptName->setText(list.at(i)); + scriptName->setToolTip(list.at(i)); + scriptName->setTextAlignment(Qt::AlignCenter); + QTableWidgetItem *closeIcon = new QTableWidgetItem; + closeIcon->setIcon(QIcon(":/images/kill-script.svg")); + + ui->runningScriptsTableWidget->setItem(i, 0, scriptName); + ui->runningScriptsTableWidget->setItem(i, 1, closeIcon); + } + + createRecentlyLoadedScriptsTable(); +} + +void RunningScriptsWidget::keyPressEvent(QKeyEvent *e) +{ + switch(e->key()) { + case Qt::Key_Escape: + Application::getInstance()->toggleRunningScriptsWidget(); + break; + + case Qt::Key_1: + if (_recentlyLoadedScripts.size() > 0) { + Application::getInstance()->loadScript(_recentlyLoadedScripts.at(0)); + } + break; + + case Qt::Key_2: + if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 2) { + Application::getInstance()->loadScript(_recentlyLoadedScripts.at(1)); + } + break; + + case Qt::Key_3: + if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 3) { + Application::getInstance()->loadScript(_recentlyLoadedScripts.at(2)); + } + break; + + case Qt::Key_4: + if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 4) { + Application::getInstance()->loadScript(_recentlyLoadedScripts.at(3)); + } + break; + case Qt::Key_5: + if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 5) { + Application::getInstance()->loadScript(_recentlyLoadedScripts.at(4)); + } + break; + + case Qt::Key_6: + if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 6) { + Application::getInstance()->loadScript(_recentlyLoadedScripts.at(5)); + } + break; + + case Qt::Key_7: + if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 7) { + Application::getInstance()->loadScript(_recentlyLoadedScripts.at(6)); + } + break; + case Qt::Key_8: + if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 8) { + Application::getInstance()->loadScript(_recentlyLoadedScripts.at(7)); + } + break; + + case Qt::Key_9: + if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 9) { + Application::getInstance()->loadScript(_recentlyLoadedScripts.at(8)); + } + break; + + default: + break; + } +} + +void RunningScriptsWidget::stopScript(int row, int column) +{ + if (column == 1) { // make sure the user has clicked on the close icon + _lastStoppedScript = ui->runningScriptsTableWidget->item(row, 0)->text(); + emit stopScriptName(ui->runningScriptsTableWidget->item(row, 0)->text()); + } +} + +void RunningScriptsWidget::loadScript(int row, int column) +{ + Application::getInstance()->loadScript(ui->recentlyLoadedScriptsTableWidget->item(row, column)->text()); +} + +void RunningScriptsWidget::allScriptsStopped() +{ + QStringList list = Application::getInstance()->getRunningScripts(); + for (int i = 0; i < list.size(); ++i) { + _recentlyLoadedScripts.prepend(list.at(i)); + } + + Application::getInstance()->stopAllScripts(); +} + +void RunningScriptsWidget::createRecentlyLoadedScriptsTable() +{ + if (!_recentlyLoadedScripts.contains(_lastStoppedScript) && !_lastStoppedScript.isEmpty()) { + _recentlyLoadedScripts.prepend(_lastStoppedScript); + _lastStoppedScript = ""; + } + + for (int i = 0; i < _recentlyLoadedScripts.size(); ++i) { + if (Application::getInstance()->getRunningScripts().contains(_recentlyLoadedScripts.at(i))) { + _recentlyLoadedScripts.removeOne(_recentlyLoadedScripts.at(i)); + } + } + + ui->recentlyLoadedLabel->setVisible(!_recentlyLoadedScripts.isEmpty()); + ui->line2->setVisible(!_recentlyLoadedScripts.isEmpty()); + ui->recentlyLoadedScriptsTableWidget->setVisible(!_recentlyLoadedScripts.isEmpty()); + ui->recentlyLoadedInstruction->setVisible(!_recentlyLoadedScripts.isEmpty()); + + int limit = _recentlyLoadedScripts.size() > 9 ? 9 : _recentlyLoadedScripts.size(); + ui->recentlyLoadedScriptsTableWidget->setRowCount(limit); + for (int i = 0; i < limit; ++i) { + QTableWidgetItem *scriptName = new QTableWidgetItem; + scriptName->setText(_recentlyLoadedScripts.at(i)); + scriptName->setToolTip(_recentlyLoadedScripts.at(i)); + scriptName->setTextAlignment(Qt::AlignCenter); + QTableWidgetItem *number = new QTableWidgetItem; + number->setText(QString::number(i+1) + "."); + + ui->recentlyLoadedScriptsTableWidget->setItem(i, 0, number); + ui->recentlyLoadedScriptsTableWidget->setItem(i, 1, scriptName); + } +} diff --git a/interface/src/ui/RunningScriptsWidget.h b/interface/src/ui/RunningScriptsWidget.h new file mode 100644 index 0000000000..d92927347f --- /dev/null +++ b/interface/src/ui/RunningScriptsWidget.h @@ -0,0 +1,46 @@ +// +// RunningScripts.h +// interface +// +// Created by Mohammed Nafees on 03/28/2014. +// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. + +#ifndef __hifi__RunningScriptsWidget__ +#define __hifi__RunningScriptsWidget__ + +// Qt +#include + +namespace Ui { + class RunningScriptsWidget; +} + +class RunningScriptsWidget : public QDockWidget +{ + Q_OBJECT +public: + explicit RunningScriptsWidget(QDockWidget *parent = 0); + ~RunningScriptsWidget(); + + void setRunningScripts(const QStringList& list); + +signals: + void stopScriptName(const QString& name); + +protected: + void keyPressEvent(QKeyEvent *e); + +private slots: + void stopScript(int row, int column); + void loadScript(int row, int column); + void allScriptsStopped(); + +private: + Ui::RunningScriptsWidget *ui; + QStringList _recentlyLoadedScripts; + QString _lastStoppedScript; + + void createRecentlyLoadedScriptsTable(); +}; + +#endif /* defined(__hifi__RunningScriptsWidget__) */ diff --git a/interface/ui/runningScriptsWidget.ui b/interface/ui/runningScriptsWidget.ui new file mode 100644 index 0000000000..9494d4ed09 --- /dev/null +++ b/interface/ui/runningScriptsWidget.ui @@ -0,0 +1,248 @@ + + + RunningScriptsWidget + + + + 0 + 0 + 310 + 651 + + + + Form + + + background: #f7f7f7; +font-family: Helvetica, Arial, "DejaVu Sans"; + + + + + 20 + 10 + 221 + 31 + + + + color: #0e7077; + + + <html><head/><body><p><span style=" font-size:18pt;">Running Scripts</span></p></body></html> + + + + + + 20 + 40 + 301 + 20 + + + + color: #0e7077; + + + <html><head/><body><p><span style=" font-weight:600;">Currently running</span></p></body></html> + + + + + + 40 + 230 + 111 + 31 + + + + PointingHandCursor + + + background: #0e7077; +color: #fff; +border-radius: 6px; + + + Reload All + + + + :/images/reload.svg:/images/reload.svg + + + + + + 160 + 230 + 101 + 31 + + + + PointingHandCursor + + + background: #0e7077; +color: #fff; +border-radius: 6px; + + + Stop All + + + + :/images/stop.svg:/images/stop.svg + + + + + + 20 + 280 + 301 + 20 + + + + color: #0e7077; + + + <html><head/><body><p><span style=" font-weight:600;">Recently loaded</span></p></body></html> + + + + + + 20 + 300 + 271 + 8 + + + + + + + Qt::Horizontal + + + + + + 20 + 590 + 271 + 41 + + + + color: #95a5a6; + + + (click a script or use the 1-9 keys to load and run it) + + + true + + + + + + 270 + 10 + 31 + 31 + + + + PointingHandCursor + + + + + + + :/images/close.svg:/images/close.svg + + + + 20 + 20 + + + + true + + + + + + 20 + 70 + 271 + 141 + + + + background: transparent; + + + + + + 20 + 60 + 271 + 8 + + + + + + + Qt::Horizontal + + + + + + 20 + 310 + 271 + 281 + + + + background: transparent; + + + + + + 20 + 40 + 271 + 51 + + + + font: 14px; + + + There are no scripts currently running. + + + Qt::AlignCenter + + + + + + + + diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 1f1eab6baf..7572638a30 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -29,7 +29,6 @@ #include "LocalVoxels.h" #include "ScriptEngine.h" -int ScriptEngine::_scriptNumber = 1; VoxelsScriptingInterface ScriptEngine::_voxelsScriptingInterface; ParticlesScriptingInterface ScriptEngine::_particlesScriptingInterface; @@ -41,7 +40,7 @@ static QScriptValue soundConstructor(QScriptContext* context, QScriptEngine* eng } -ScriptEngine::ScriptEngine(const QString& scriptContents, bool wantMenuItems, const QString& fileNameString, +ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNameString, AbstractControllerScriptingInterface* controllerScriptingInterface) : _scriptContents(scriptContents), @@ -58,26 +57,15 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, bool wantMenuItems, co _numAvatarSoundSentBytes(0), _controllerScriptingInterface(controllerScriptingInterface), _avatarData(NULL), - _wantMenuItems(wantMenuItems), - _scriptMenuName(), + _scriptName(), _fileNameString(fileNameString), _quatLibrary(), _vec3Library() { - // some clients will use these menu features - if (!fileNameString.isEmpty()) { - _scriptMenuName = "Stop "; - _scriptMenuName.append(qPrintable(fileNameString)); - _scriptMenuName.append(QString(" [%1]").arg(_scriptNumber)); - } else { - _scriptMenuName = "Stop Script "; - _scriptMenuName.append(_scriptNumber); - } - _scriptNumber++; } -ScriptEngine::ScriptEngine(const QUrl& scriptURL, bool wantMenuItems, - AbstractControllerScriptingInterface* controllerScriptingInterface) : +ScriptEngine::ScriptEngine(const QUrl& scriptURL, + AbstractControllerScriptingInterface* controllerScriptingInterface) : _scriptContents(), _isFinished(false), _isRunning(false), @@ -92,32 +80,21 @@ ScriptEngine::ScriptEngine(const QUrl& scriptURL, bool wantMenuItems, _numAvatarSoundSentBytes(0), _controllerScriptingInterface(controllerScriptingInterface), _avatarData(NULL), - _wantMenuItems(wantMenuItems), - _scriptMenuName(), + _scriptName(), _fileNameString(), _quatLibrary(), _vec3Library() { QString scriptURLString = scriptURL.toString(); _fileNameString = scriptURLString; - // some clients will use these menu features - if (!scriptURLString.isEmpty()) { - _scriptMenuName = "Stop "; - _scriptMenuName.append(qPrintable(scriptURLString)); - _scriptMenuName.append(QString(" [%1]").arg(_scriptNumber)); - } else { - _scriptMenuName = "Stop Script "; - _scriptMenuName.append(_scriptNumber); - } - _scriptNumber++; - + QUrl url(scriptURL); - + // if the scheme is empty, maybe they typed in a file, let's try if (url.scheme().isEmpty()) { url = QUrl::fromLocalFile(scriptURLString); } - + // ok, let's see if it's valid... and if so, load it if (url.isValid()) { if (url.scheme() == "file") { @@ -144,16 +121,16 @@ ScriptEngine::ScriptEngine(const QUrl& scriptURL, bool wantMenuItems, void ScriptEngine::setIsAvatar(bool isAvatar) { _isAvatar = isAvatar; - + if (_isAvatar && !_avatarIdentityTimer) { // set up the avatar timers _avatarIdentityTimer = new QTimer(this); _avatarBillboardTimer = new QTimer(this); - + // connect our slot connect(_avatarIdentityTimer, &QTimer::timeout, this, &ScriptEngine::sendAvatarIdentityPacket); connect(_avatarBillboardTimer, &QTimer::timeout, this, &ScriptEngine::sendAvatarBillboardPacket); - + // start the timers _avatarIdentityTimer->start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS); _avatarBillboardTimer->start(AVATAR_BILLBOARD_PACKET_SEND_INTERVAL_MSECS); @@ -162,20 +139,14 @@ void ScriptEngine::setIsAvatar(bool isAvatar) { void ScriptEngine::setAvatarData(AvatarData* avatarData, const QString& objectName) { _avatarData = avatarData; - + // remove the old Avatar property, if it exists _engine.globalObject().setProperty(objectName, QScriptValue()); - + // give the script engine the new Avatar script property registerGlobalObject(objectName, _avatarData); } -void ScriptEngine::cleanupMenuItems() { - if (_wantMenuItems) { - emit cleanupMenuItem(_scriptMenuName); - } -} - bool ScriptEngine::setScriptContents(const QString& scriptContents, const QString& fileNameString) { if (_isRunning) { return false; @@ -203,7 +174,7 @@ void ScriptEngine::init() { registerVoxelMetaTypes(&_engine); registerEventTypes(&_engine); registerMenuItemProperties(&_engine); - + qScriptRegisterMetaType(&_engine, ParticlePropertiesToScriptValue, ParticlePropertiesFromScriptValue); qScriptRegisterMetaType(&_engine, ParticleIDtoScriptValue, ParticleIDfromScriptValue); qScriptRegisterSequenceMetaType >(&_engine); @@ -216,7 +187,7 @@ void ScriptEngine::init() { QScriptValue injectionOptionValue = _engine.scriptValueFromQMetaObject(); _engine.globalObject().setProperty("AudioInjectionOptions", injectionOptionValue); - + QScriptValue localVoxelsValue = _engine.scriptValueFromQMetaObject(); _engine.globalObject().setProperty("LocalVoxels", localVoxelsValue); @@ -285,9 +256,9 @@ void ScriptEngine::run() { gettimeofday(&startTime, NULL); int thisFrame = 0; - + NodeList* nodeList = NodeList::getInstance(); - + qint64 lastUpdate = usecTimestampNow(); while (!_isFinished) { @@ -325,36 +296,36 @@ void ScriptEngine::run() { _particlesScriptingInterface.getParticlePacketSender()->process(); } } - + if (_isAvatar && _avatarData) { - + const int SCRIPT_AUDIO_BUFFER_SAMPLES = floor(((SCRIPT_DATA_CALLBACK_USECS * SAMPLE_RATE) / (1000 * 1000)) + 0.5); const int SCRIPT_AUDIO_BUFFER_BYTES = SCRIPT_AUDIO_BUFFER_SAMPLES * sizeof(int16_t); - + QByteArray avatarPacket = byteArrayWithPopulatedHeader(PacketTypeAvatarData); avatarPacket.append(_avatarData->toByteArray()); - + nodeList->broadcastToNodes(avatarPacket, NodeSet() << NodeType::AvatarMixer); - + if (_isListeningToAudioStream || _avatarSound) { // if we have an avatar audio stream then send it out to our audio-mixer bool silentFrame = true; - + int16_t numAvailableSamples = SCRIPT_AUDIO_BUFFER_SAMPLES; const int16_t* nextSoundOutput = NULL; - + if (_avatarSound) { - + const QByteArray& soundByteArray = _avatarSound->getByteArray(); nextSoundOutput = reinterpret_cast(soundByteArray.data() + _numAvatarSoundSentBytes); - + int numAvailableBytes = (soundByteArray.size() - _numAvatarSoundSentBytes) > SCRIPT_AUDIO_BUFFER_BYTES ? SCRIPT_AUDIO_BUFFER_BYTES : soundByteArray.size() - _numAvatarSoundSentBytes; numAvailableSamples = numAvailableBytes / sizeof(int16_t); - - + + // check if the all of the _numAvatarAudioBufferSamples to be sent are silence for (int i = 0; i < numAvailableSamples; ++i) { if (nextSoundOutput[i] != 0) { @@ -362,7 +333,7 @@ void ScriptEngine::run() { break; } } - + _numAvatarSoundSentBytes += numAvailableBytes; if (_numAvatarSoundSentBytes == soundByteArray.size()) { // we're done with this sound object - so set our pointer back to NULL @@ -371,24 +342,24 @@ void ScriptEngine::run() { _numAvatarSoundSentBytes = 0; } } - + QByteArray audioPacket = byteArrayWithPopulatedHeader(silentFrame ? PacketTypeSilentAudioFrame : PacketTypeMicrophoneAudioNoEcho); - + QDataStream packetStream(&audioPacket, QIODevice::Append); - + // use the orientation and position of this avatar for the source of this audio packetStream.writeRawData(reinterpret_cast(&_avatarData->getPosition()), sizeof(glm::vec3)); glm::quat headOrientation = _avatarData->getHeadOrientation(); packetStream.writeRawData(reinterpret_cast(&headOrientation), sizeof(glm::quat)); - + if (silentFrame) { if (!_isListeningToAudioStream) { // if we have a silent frame and we're not listening then just send nothing and break out of here break; } - + // write the number of silent samples so the audio-mixer can uphold timing packetStream.writeRawData(reinterpret_cast(&SCRIPT_AUDIO_BUFFER_SAMPLES), sizeof(int16_t)); } else if (nextSoundOutput) { @@ -396,7 +367,7 @@ void ScriptEngine::run() { packetStream.writeRawData(reinterpret_cast(nextSoundOutput), numAvailableSamples * sizeof(int16_t)); } - + nodeList->broadcastToNodes(audioPacket, NodeSet() << NodeType::AudioMixer); } } @@ -412,10 +383,10 @@ void ScriptEngine::run() { } } emit scriptEnding(); - + // kill the avatar identity timer delete _avatarIdentityTimer; - + if (_voxelsScriptingInterface.getVoxelPacketSender()->serversExist()) { // release the queue of edit voxel messages. _voxelsScriptingInterface.getVoxelPacketSender()->releaseQueuedMessages(); @@ -435,8 +406,6 @@ void ScriptEngine::run() { _particlesScriptingInterface.getParticlePacketSender()->process(); } } - - cleanupMenuItems(); // If we were on a thread, then wait till it's done if (thread()) { @@ -444,7 +413,7 @@ void ScriptEngine::run() { } emit finished(_fileNameString); - + _isRunning = false; } @@ -454,13 +423,13 @@ void ScriptEngine::stop() { void ScriptEngine::timerFired() { QTimer* callingTimer = reinterpret_cast(sender()); - + // call the associated JS function, if it exists QScriptValue timerFunction = _timerFunctionMap.value(callingTimer); if (timerFunction.isValid()) { timerFunction.call(); } - + if (!callingTimer->isActive()) { // this timer is done, we can kill it delete callingTimer; @@ -471,14 +440,14 @@ QObject* ScriptEngine::setupTimerWithInterval(const QScriptValue& function, int // create the timer, add it to the map, and start it QTimer* newTimer = new QTimer(this); newTimer->setSingleShot(isSingleShot); - + connect(newTimer, &QTimer::timeout, this, &ScriptEngine::timerFired); - + // make sure the timer stops when the script does connect(this, &ScriptEngine::scriptEnding, newTimer, &QTimer::stop); - + _timerFunctionMap.insert(newTimer, function); - + newTimer->start(intervalMS); return newTimer; } @@ -505,17 +474,17 @@ QUrl ScriptEngine::resolveInclude(const QString& include) const { if (!url.scheme().isEmpty()) { return url; } - - // we apparently weren't a fully qualified url, so, let's assume we're relative + + // we apparently weren't a fully qualified url, so, let's assume we're relative // to the original URL of our script QUrl parentURL(_fileNameString); - + // if the parent URL's scheme is empty, then this is probably a local file... if (parentURL.scheme().isEmpty()) { parentURL = QUrl::fromLocalFile(_fileNameString); } - - // at this point we should have a legitimate fully qualified URL for our parent + + // at this point we should have a legitimate fully qualified URL for our parent url = parentURL.resolved(url); return url; } @@ -543,7 +512,7 @@ void ScriptEngine::include(const QString& includeFile) { loop.exec(); includeContents = reply->readAll(); } - + QScriptValue result = _engine.evaluate(includeContents); if (_engine.hasUncaughtException()) { int line = _engine.uncaughtExceptionLineNumber(); diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 4fc90d2959..964f64a005 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -33,11 +33,11 @@ const unsigned int SCRIPT_DATA_CALLBACK_USECS = floor(((1.0 / 60.0f) * 1000 * 10 class ScriptEngine : public QObject { Q_OBJECT public: - ScriptEngine(const QUrl& scriptURL, bool wantMenuItems = false, + ScriptEngine(const QUrl& scriptURL, AbstractControllerScriptingInterface* controllerScriptingInterface = NULL); - ScriptEngine(const QString& scriptContents = NO_SCRIPT, bool wantMenuItems = false, - const QString& fileNameString = QString(""), + ScriptEngine(const QString& scriptContents = NO_SCRIPT, + const QString& fileNameString = QString(""), AbstractControllerScriptingInterface* controllerScriptingInterface = NULL); /// Access the VoxelsScriptingInterface in order to initialize it with a custom packet sender and jurisdiction listener @@ -49,39 +49,39 @@ public: /// sets the script contents, will return false if failed, will fail if script is already running bool setScriptContents(const QString& scriptContents, const QString& fileNameString = QString("")); - const QString& getScriptMenuName() const { return _scriptMenuName; } + const QString& getScriptName() const { return _scriptName; } void cleanupMenuItems(); void registerGlobalObject(const QString& name, QObject* object); /// registers a global object by name - + Q_INVOKABLE void setIsAvatar(bool isAvatar); bool isAvatar() const { return _isAvatar; } - + void setAvatarData(AvatarData* avatarData, const QString& objectName); - + bool isListeningToAudioStream() const { return _isListeningToAudioStream; } void setIsListeningToAudioStream(bool isListeningToAudioStream) { _isListeningToAudioStream = isListeningToAudioStream; } - + void setAvatarSound(Sound* avatarSound) { _avatarSound = avatarSound; } bool isPlayingAvatarSound() const { return _avatarSound != NULL; } - + void init(); void run(); /// runs continuously until Agent.stop() is called void evaluate(); /// initializes the engine, and evaluates the script, but then returns control to caller - + void timerFired(); bool hasScript() const { return !_scriptContents.isEmpty(); } public slots: void stop(); - + QObject* setInterval(const QScriptValue& function, int intervalMS); QObject* setTimeout(const QScriptValue& function, int timeoutMS); void clearInterval(QObject* timer) { stopTimer(reinterpret_cast(timer)); } void clearTimeout(QObject* timer) { stopTimer(reinterpret_cast(timer)); } void include(const QString& includeFile); - + signals: void update(float deltaTime); void scriptEnding(); @@ -106,19 +106,18 @@ private: QUrl resolveInclude(const QString& include) const; void sendAvatarIdentityPacket(); void sendAvatarBillboardPacket(); - + QObject* setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot); void stopTimer(QTimer* timer); - + static VoxelsScriptingInterface _voxelsScriptingInterface; static ParticlesScriptingInterface _particlesScriptingInterface; static int _scriptNumber; - + AbstractControllerScriptingInterface* _controllerScriptingInterface; AudioScriptingInterface _audioScriptingInterface; AvatarData* _avatarData; - bool _wantMenuItems; - QString _scriptMenuName; + QString _scriptName; QString _fileNameString; Quat _quatLibrary; Vec3 _vec3Library; From 7a602aedd1e07a36cff0c59b98c1d25eb117dc09 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 3 Apr 2014 09:06:49 -0700 Subject: [PATCH 15/16] Fix crash: don't simulate until geometry exists --- interface/src/avatar/FaceModel.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/interface/src/avatar/FaceModel.cpp b/interface/src/avatar/FaceModel.cpp index 2934a022af..b0ef947f65 100644 --- a/interface/src/avatar/FaceModel.cpp +++ b/interface/src/avatar/FaceModel.cpp @@ -34,14 +34,13 @@ void FaceModel::simulate(float deltaTime) { const float MODEL_SCALE = 0.0006f; setScale(glm::vec3(1.0f, 1.0f, 1.0f) * _owningHead->getScale() * MODEL_SCALE); - if (isActive()) { - setOffset(-_geometry->getFBXGeometry().neckPivot); - } - setPupilDilation(_owningHead->getPupilDilation()); setBlendshapeCoefficients(_owningHead->getBlendshapeCoefficients()); - Model::simulateInternal(deltaTime); + if (isActive()) { + setOffset(-_geometry->getFBXGeometry().neckPivot); + Model::simulateInternal(deltaTime); + } } void FaceModel::maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) { From 716858e6f69c1487cf6dae3be793a9e8bb4d3412 Mon Sep 17 00:00:00 2001 From: Dimitar Dobrev Date: Thu, 3 Apr 2014 19:45:46 +0300 Subject: [PATCH 16/16] Informed users who want to chat they have to log in first. --- interface/interface_en.ts | 33 +++++++++++++++++++-------------- interface/src/Menu.cpp | 10 ++-------- interface/src/ui/ChatWindow.cpp | 4 ++++ 3 files changed, 25 insertions(+), 22 deletions(-) diff --git a/interface/interface_en.ts b/interface/interface_en.ts index afd77ecc07..192f5d4015 100644 --- a/interface/interface_en.ts +++ b/interface/interface_en.ts @@ -44,32 +44,37 @@ online now: + + + You must be logged in to chat with others. + + - + day - - + %n day + %n days - + hour - - + %n hour + %n hours - + minute - - + %n minute + %n minutes - + %1 online now: @@ -106,18 +111,18 @@ Menu - + Open .ini config file - - + + Text files (*.ini) - + Save .ini config file diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index c07dfb9222..5530c57281 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -180,18 +180,17 @@ Menu::Menu() : QMenu* toolsMenu = addMenu("Tools"); addActionToQMenuAndActionHash(toolsMenu, MenuOption::MetavoxelEditor, 0, this, SLOT(showMetavoxelEditor())); +#ifdef HAVE_QXMPP _chatAction = addActionToQMenuAndActionHash(toolsMenu, MenuOption::Chat, Qt::Key_Return, this, SLOT(showChat())); -#ifdef HAVE_QXMPP + const QXmppClient& xmppClient = XmppClient::getInstance().getXMPPClient(); toggleChat(); connect(&xmppClient, SIGNAL(connected()), this, SLOT(toggleChat())); connect(&xmppClient, SIGNAL(disconnected()), this, SLOT(toggleChat())); -#else - _chatAction->setEnabled(false); #endif QMenu* viewMenu = addMenu("View"); @@ -1155,11 +1154,6 @@ void Menu::showMetavoxelEditor() { } void Menu::showChat() { - if (!_chatAction->isEnabled()) { - // Don't do anything if chat is disabled (No - // QXMPP library or xmpp is disconnected). - return; - } QMainWindow* mainWindow = Application::getInstance()->getWindow(); if (!_chatWindow) { mainWindow->addDockWidget(Qt::NoDockWidgetArea, _chatWindow = new ChatWindow()); diff --git a/interface/src/ui/ChatWindow.cpp b/interface/src/ui/ChatWindow.cpp index 6963c208c5..a667afddae 100644 --- a/interface/src/ui/ChatWindow.cpp +++ b/interface/src/ui/ChatWindow.cpp @@ -42,6 +42,10 @@ ChatWindow::ChatWindow() : ui->messagesGridLayout->setColumnStretch(1, 3); ui->messagePlainTextEdit->installEventFilter(this); + + if (!AccountManager::getInstance().isLoggedIn()) { + ui->connectingToXMPPLabel->setText(tr("You must be logged in to chat with others.")); + } #ifdef HAVE_QXMPP const QXmppClient& xmppClient = XmppClient::getInstance().getXMPPClient();