diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 3cbeff7801..b95c0e7b38 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -230,6 +230,9 @@ target_link_libraries( "${GNUTLS_LIBRARY}" ) +# assume we are using a Qt build without bearer management +add_definitions(-DQT_NO_BEARERMANAGEMENT) + if (APPLE) # link in required OS X frameworks and include the right GL headers find_library(AppKit AppKit) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index fc23a50f7b..487965b3ec 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -169,6 +169,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : _voxelHideShowThread(&_voxels), _packetsPerSecond(0), _bytesPerSecond(0), + _nodeBoundsDisplay(this), _previousScriptLocation(), _runningScriptsWidget(new RunningScriptsWidget(_window)), _runningScriptsWidgetWasVisible(false) @@ -2527,6 +2528,9 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { // restore default, white specular glMaterialfv(GL_FRONT, GL_SPECULAR, WHITE_SPECULAR_COLOR); + + _nodeBoundsDisplay.draw(); + } bool mirrorMode = (whichCamera.getInterpolatedMode() == CAMERA_MODE_MIRROR); @@ -2760,6 +2764,7 @@ void Application::displayOverlay() { ? 80 : 20; drawText(_glWidget->width() - 100, _glWidget->height() - timerBottom, 0.30f, 0.0f, 0, frameTimer, WHITE_TEXT); } + _nodeBoundsDisplay.drawOverlay(); // give external parties a change to hook in emit renderingOverlay(); @@ -3417,8 +3422,9 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript } // start the script on a new thread... - ScriptEngine* scriptEngine = new ScriptEngine(QUrl(scriptName), &_controllerScriptingInterface); - _scriptEnginesHash.insert(scriptName, scriptEngine); + QUrl scriptUrl(scriptName); + ScriptEngine* scriptEngine = new ScriptEngine(scriptUrl, &_controllerScriptingInterface); + _scriptEnginesHash.insert(scriptUrl.toString(), scriptEngine); if (!scriptEngine->hasScript()) { qDebug() << "Application::loadScript(), script failed to load..."; diff --git a/interface/src/Application.h b/interface/src/Application.h index b9b73ac86a..5460093cbd 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -74,6 +74,7 @@ #include "ui/BandwidthDialog.h" #include "ui/BandwidthMeter.h" #include "ui/ModelsBrowser.h" +#include "ui/NodeBounds.h" #include "ui/OctreeStatsDialog.h" #include "ui/RearMirrorTools.h" #include "ui/SnapshotShareDialog.h" @@ -191,6 +192,8 @@ public: bool isMouseHidden() const { return _mouseHidden; } const glm::vec3& getMouseRayOrigin() const { return _mouseRayOrigin; } const glm::vec3& getMouseRayDirection() const { return _mouseRayDirection; } + int getMouseX() const { return _mouseX; } + int getMouseY() const { return _mouseY; } Faceplus* getFaceplus() { return &_faceplus; } Faceshift* getFaceshift() { return &_faceshift; } Visage* getVisage() { return &_visage; } @@ -246,7 +249,7 @@ public: void computeOffAxisFrustum(float& left, float& right, float& bottom, float& top, float& nearVal, float& farVal, glm::vec4& nearClipPlane, glm::vec4& farClipPlane) const; - + NodeBounds& getNodeBoundsDisplay() { return _nodeBoundsDisplay; } VoxelShader& getVoxelShader() { return _voxelShader; } PointShader& getPointShader() { return _pointShader; } @@ -526,6 +529,8 @@ private: NodeToOctreeSceneStats _octreeServerSceneStats; QReadWriteLock _octreeSceneStatsLock; + NodeBounds _nodeBoundsDisplay; + std::vector _voxelFades; ControllerScriptingInterface _controllerScriptingInterface; QPointer _logDialog; diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 00f9288950..bff08d5221 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -42,6 +42,7 @@ #include "ui/MetavoxelEditor.h" #include "ui/ModelsBrowser.h" #include "ui/LoginDialog.h" +#include "ui/NodeBounds.h" Menu* Menu::_instance = NULL; @@ -242,6 +243,19 @@ Menu::Menu() : SLOT(setEnable3DTVMode(bool))); + QMenu* nodeBordersMenu = viewMenu->addMenu("Server Borders"); + NodeBounds& nodeBounds = appInstance->getNodeBoundsDisplay(); + addCheckableActionToQMenuAndActionHash(nodeBordersMenu, MenuOption::ShowBordersVoxelNodes, + Qt::CTRL | Qt::SHIFT | Qt::Key_1, false, + &nodeBounds, SLOT(setShowVoxelNodes(bool))); + addCheckableActionToQMenuAndActionHash(nodeBordersMenu, MenuOption::ShowBordersModelNodes, + Qt::CTRL | Qt::SHIFT | Qt::Key_2, false, + &nodeBounds, SLOT(setShowModelNodes(bool))); + addCheckableActionToQMenuAndActionHash(nodeBordersMenu, MenuOption::ShowBordersParticleNodes, + Qt::CTRL | Qt::SHIFT | Qt::Key_3, false, + &nodeBounds, SLOT(setShowParticleNodes(bool))); + + QMenu* avatarSizeMenu = viewMenu->addMenu("Avatar Size"); addActionToQMenuAndActionHash(avatarSizeMenu, diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 2a521bf59c..0a21a27960 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -367,6 +367,9 @@ namespace MenuOption { const QString SettingsExport = "Export Settings"; const QString SettingsImport = "Import Settings"; const QString Shadows = "Shadows"; + const QString ShowBordersVoxelNodes = "Show Voxel Nodes"; + const QString ShowBordersModelNodes = "Show Model Nodes"; + const QString ShowBordersParticleNodes = "Show Particle Nodes"; const QString ShowIKConstraints = "Show IK Constraints"; const QString Stars = "Stars"; const QString Stats = "Stats"; diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 20f6275441..0a3411f858 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -214,37 +214,37 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) { if (Menu::getInstance()->isOptionChecked(MenuOption::Avatars)) { renderBody(renderMode, glowLevel); } - if (renderMode != SHADOW_RENDER_MODE && - Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionShapes)) { - _skeletonModel.updateShapePositions(); - _skeletonModel.renderJointCollisionShapes(0.7f); - } - if (renderMode != SHADOW_RENDER_MODE && - Menu::getInstance()->isOptionChecked(MenuOption::RenderHeadCollisionShapes)) { - if (shouldRenderHead(cameraPosition, renderMode)) { - getHead()->getFaceModel().updateShapePositions(); + if (renderMode != SHADOW_RENDER_MODE) { + bool renderSkeleton = Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionShapes); + bool renderHead = Menu::getInstance()->isOptionChecked(MenuOption::RenderHeadCollisionShapes); + bool renderBounding = Menu::getInstance()->isOptionChecked(MenuOption::RenderBoundingCollisionShapes); + if (renderSkeleton || renderHead || renderBounding) { + updateShapePositions(); + } + + if (renderSkeleton) { + _skeletonModel.renderJointCollisionShapes(0.7f); + } + + if (renderHead && shouldRenderHead(cameraPosition, renderMode)) { getHead()->getFaceModel().renderJointCollisionShapes(0.7f); } - } - if (renderMode != SHADOW_RENDER_MODE && - Menu::getInstance()->isOptionChecked(MenuOption::RenderBoundingCollisionShapes)) { - if (shouldRenderHead(cameraPosition, renderMode)) { - getHead()->getFaceModel().updateShapePositions(); + if (renderBounding && shouldRenderHead(cameraPosition, renderMode)) { getHead()->getFaceModel().renderBoundingCollisionShapes(0.7f); - _skeletonModel.updateShapePositions(); _skeletonModel.renderBoundingCollisionShapes(0.7f); } - } - // If this is the avatar being looked at, render a little ball above their head - if (renderMode != SHADOW_RENDER_MODE &&_isLookAtTarget) { - const float LOOK_AT_INDICATOR_RADIUS = 0.03f; - const float LOOK_AT_INDICATOR_HEIGHT = 0.60f; - const float LOOK_AT_INDICATOR_COLOR[] = { 0.8f, 0.0f, 0.0f, 0.5f }; - glPushMatrix(); - glColor4fv(LOOK_AT_INDICATOR_COLOR); - glTranslatef(_position.x, _position.y + (getSkeletonHeight() * LOOK_AT_INDICATOR_HEIGHT), _position.z); - glutSolidSphere(LOOK_AT_INDICATOR_RADIUS, 15, 15); - glPopMatrix(); + + // If this is the avatar being looked at, render a little ball above their head + if (_isLookAtTarget) { + const float LOOK_AT_INDICATOR_RADIUS = 0.03f; + const float LOOK_AT_INDICATOR_HEIGHT = 0.60f; + const float LOOK_AT_INDICATOR_COLOR[] = { 0.8f, 0.0f, 0.0f, 0.5f }; + glPushMatrix(); + glColor4fv(LOOK_AT_INDICATOR_COLOR); + glTranslatef(_position.x, _position.y + (getSkeletonHeight() * LOOK_AT_INDICATOR_HEIGHT), _position.z); + glutSolidSphere(LOOK_AT_INDICATOR_RADIUS, 15, 15); + glPopMatrix(); + } } // quick check before falling into the code below: @@ -585,6 +585,12 @@ void Avatar::updateShapePositions() { _skeletonModel.updateShapePositions(); Model& headModel = getHead()->getFaceModel(); headModel.updateShapePositions(); + /* KEEP FOR DEBUG: use this in rather than code above to see shapes + * in their default positions where the bounding shape is computed. + _skeletonModel.resetShapePositions(); + Model& headModel = getHead()->getFaceModel(); + headModel.resetShapePositions(); + */ } bool Avatar::findCollisions(const QVector& shapes, CollisionList& collisions) { diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 3786280a3c..09871ad38b 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -334,13 +334,11 @@ void SkeletonModel::setHandPosition(int jointIndex, const glm::vec3& position, c glm::vec3 forwardVector(rightHand ? -1.0f : 1.0f, 0.0f, 0.0f); - glm::quat shoulderRotation; - getJointRotation(shoulderJointIndex, shoulderRotation, true); - applyRotationDelta(shoulderJointIndex, rotationBetween(shoulderRotation * forwardVector, elbowPosition - shoulderPosition), false); + glm::quat shoulderRotation = rotationBetween(forwardVector, elbowPosition - shoulderPosition); + setJointRotation(shoulderJointIndex, shoulderRotation, true); - glm::quat elbowRotation; - getJointRotation(elbowJointIndex, elbowRotation, true); - applyRotationDelta(elbowJointIndex, rotationBetween(elbowRotation * forwardVector, wristPosition - elbowPosition), false); + setJointRotation(elbowJointIndex, rotationBetween(shoulderRotation * forwardVector, + wristPosition - elbowPosition) * shoulderRotation, true); setJointRotation(jointIndex, rotation, true); } diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 3d89080bb0..90ae9e5e46 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -39,6 +39,7 @@ Model::Model(QObject* parent) : _scaledToFit(false), _snapModelToCenter(false), _snappedToCenter(false), + _rootIndex(-1), _shapesAreDirty(true), _boundingRadius(0.f), _boundingShape(), @@ -128,7 +129,6 @@ QVector Model::createJointStates(const FBXGeometry& geometry) jointIsSet.fill(false, numJoints); int numJointsSet = 0; int lastNumJointsSet = -1; - glm::mat4 baseTransform = glm::mat4_cast(_rotation) * glm::scale(_scale) * glm::translate(_offset); while (numJointsSet < numJoints && numJointsSet != lastNumJointsSet) { lastNumJointsSet = numJointsSet; for (int i = 0; i < numJoints; ++i) { @@ -139,6 +139,8 @@ QVector Model::createJointStates(const FBXGeometry& geometry) const FBXJoint& joint = geometry.joints[i]; int parentIndex = joint.parentIndex; if (parentIndex == -1) { + _rootIndex = i; + 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; @@ -603,66 +605,20 @@ void Model::clearShapes() { void Model::rebuildShapes() { clearShapes(); - if (!_geometry) { + if (!_geometry || _rootIndex == -1) { return; } const FBXGeometry& geometry = _geometry->getFBXGeometry(); - if (geometry.joints.isEmpty()) { return; } - int numJoints = geometry.joints.size(); - QVector transforms; - transforms.fill(glm::mat4(), numJoints); - QVector combinedRotations; - combinedRotations.fill(glm::quat(), numJoints); - QVector shapeIsSet; - shapeIsSet.fill(false, numJoints); - int rootIndex = 0; - + // We create the shapes with proper dimensions, but we set their transforms later. float uniformScale = extractUniformScale(_scale); - int numShapesSet = 0; - int lastNumShapesSet = -1; - while (numShapesSet < numJoints && numShapesSet != lastNumShapesSet) { - lastNumShapesSet = numShapesSet; - for (int i = 0; i < numJoints; ++i) { - if (shapeIsSet[i]) { - continue; - } - const FBXJoint& joint = geometry.joints[i]; - int parentIndex = joint.parentIndex; - if (parentIndex == -1) { - rootIndex = i; - glm::mat4 baseTransform = glm::mat4_cast(_rotation) * uniformScale * glm::translate(_offset); - glm::quat combinedRotation = joint.preRotation * joint.rotation * joint.postRotation; - transforms[i] = baseTransform * geometry.offset * glm::translate(joint.translation) * joint.preTransform * - glm::mat4_cast(combinedRotation) * joint.postTransform; - combinedRotations[i] = _rotation * combinedRotation; - ++numShapesSet; - shapeIsSet[i] = true; - } else if (shapeIsSet[parentIndex]) { - glm::quat combinedRotation = joint.preRotation * joint.rotation * joint.postRotation; - transforms[i] = transforms[parentIndex] * glm::translate(joint.translation) * joint.preTransform * - glm::mat4_cast(combinedRotation) * joint.postTransform; - combinedRotations[i] = combinedRotations[parentIndex] * combinedRotation; - ++numShapesSet; - shapeIsSet[i] = true; - } - } - } - - // joint shapes - Extents totalExtents; - totalExtents.reset(); for (int i = 0; i < _jointStates.size(); i++) { const FBXJoint& joint = geometry.joints[i]; - glm::vec3 worldPosition = extractTranslation(transforms[i]); - Extents shapeExtents; - shapeExtents.reset(); - float radius = uniformScale * joint.boneRadius; float halfHeight = 0.5f * uniformScale * joint.distanceToParent; Shape::Type type = joint.shapeType; @@ -672,47 +628,150 @@ void Model::rebuildShapes() { } if (type == Shape::CAPSULE_SHAPE) { CapsuleShape* capsule = new CapsuleShape(radius, halfHeight); - capsule->setPosition(worldPosition); - capsule->setRotation(combinedRotations[i] * joint.shapeRotation); _jointShapes.push_back(capsule); - - // add the two furthest surface points of the capsule - glm::vec3 axis; - capsule->computeNormalizedAxis(axis); - axis = halfHeight * axis + glm::vec3(radius); - shapeExtents.addPoint(worldPosition + axis); - shapeExtents.addPoint(worldPosition - axis); - - totalExtents.addExtents(shapeExtents); } else if (type == Shape::SPHERE_SHAPE) { - SphereShape* sphere = new SphereShape(radius, worldPosition); + SphereShape* sphere = new SphereShape(radius, glm::vec3(0.0f)); _jointShapes.push_back(sphere); - - glm::vec3 axis = glm::vec3(radius); - shapeExtents.addPoint(worldPosition + axis); - shapeExtents.addPoint(worldPosition - axis); - totalExtents.addExtents(shapeExtents); } else { // this shape type is not handled and the joint shouldn't collide, // however we must have a shape for each joint, // so we make a bogus sphere with zero radius. // TODO: implement collision groups for more control over what collides with what - SphereShape* sphere = new SphereShape(0.f, worldPosition); + SphereShape* sphere = new SphereShape(0.f, glm::vec3(0.0f)); _jointShapes.push_back(sphere); } } - // bounding shape - // NOTE: we assume that the longest side of totalExtents is the yAxis + // This method moves the shapes to their default positions in Model frame + // which is where we compute the bounding shape's parameters. + computeBoundingShape(geometry); + + // finally sync shapes to joint positions + _shapesAreDirty = true; + updateShapePositions(); +} + +void Model::computeBoundingShape(const FBXGeometry& geometry) { + // compute default joint transforms and rotations + // (in local frame, ignoring Model translation and rotation) + int numJoints = geometry.joints.size(); + QVector transforms; + transforms.fill(glm::mat4(), numJoints); + QVector finalRotations; + finalRotations.fill(glm::quat(), numJoints); + + QVector shapeIsSet; + shapeIsSet.fill(false, numJoints); + int numShapesSet = 0; + int lastNumShapesSet = -1; + glm::vec3 rootOffset(0.0f); + while (numShapesSet < numJoints && numShapesSet != lastNumShapesSet) { + lastNumShapesSet = numShapesSet; + for (int i = 0; i < numJoints; i++) { + const FBXJoint& joint = geometry.joints.at(i); + int parentIndex = joint.parentIndex; + + if (parentIndex == -1) { + glm::mat4 baseTransform = glm::scale(_scale) * glm::translate(_offset); + glm::quat combinedRotation = joint.preRotation * joint.rotation * joint.postRotation; + transforms[i] = baseTransform * geometry.offset * glm::translate(joint.translation) + * joint.preTransform * glm::mat4_cast(combinedRotation) * joint.postTransform; + rootOffset = extractTranslation(transforms[i]); + finalRotations[i] = combinedRotation; + ++numShapesSet; + shapeIsSet[i] = true; + } else if (shapeIsSet[parentIndex]) { + glm::quat combinedRotation = joint.preRotation * joint.rotation * joint.postRotation; + transforms[i] = transforms[parentIndex] * glm::translate(joint.translation) + * joint.preTransform * glm::mat4_cast(combinedRotation) * joint.postTransform; + finalRotations[i] = finalRotations[parentIndex] * combinedRotation; + ++numShapesSet; + shapeIsSet[i] = true; + } + } + } + + // sync shapes to joints + _boundingRadius = 0.0f; + float uniformScale = extractUniformScale(_scale); + for (int i = 0; i < _jointShapes.size(); i++) { + const FBXJoint& joint = geometry.joints[i]; + glm::vec3 jointToShapeOffset = uniformScale * (finalRotations[i] * joint.shapePosition); + glm::vec3 localPosition = extractTranslation(transforms[i]) + jointToShapeOffset- rootOffset; + Shape* shape = _jointShapes[i]; + shape->setPosition(localPosition); + shape->setRotation(finalRotations[i] * joint.shapeRotation); + float distance = glm::length(localPosition) + shape->getBoundingRadius(); + if (distance > _boundingRadius) { + _boundingRadius = distance; + } + } + + // compute bounding box + Extents totalExtents; + totalExtents.reset(); + for (int i = 0; i < _jointShapes.size(); i++) { + Extents shapeExtents; + shapeExtents.reset(); + + Shape* shape = _jointShapes[i]; + glm::vec3 localPosition = shape->getPosition(); + int type = shape->getType(); + if (type == Shape::CAPSULE_SHAPE) { + // add the two furthest surface points of the capsule + CapsuleShape* capsule = static_cast(shape); + glm::vec3 axis; + capsule->computeNormalizedAxis(axis); + float radius = capsule->getRadius(); + float halfHeight = capsule->getHalfHeight(); + axis = halfHeight * axis + glm::vec3(radius); + + shapeExtents.addPoint(localPosition + axis); + shapeExtents.addPoint(localPosition - axis); + totalExtents.addExtents(shapeExtents); + } else if (type == Shape::SPHERE_SHAPE) { + float radius = shape->getBoundingRadius(); + glm::vec3 axis = glm::vec3(radius); + shapeExtents.addPoint(localPosition + axis); + shapeExtents.addPoint(localPosition - axis); + totalExtents.addExtents(shapeExtents); + } + } + + // compute bounding shape parameters + // NOTE: we assume that the longest side of totalExtents is the yAxis... glm::vec3 diagonal = totalExtents.maximum - totalExtents.minimum; - // the radius is half the RMS of the X and Z sides: + // ... and assume the radius is half the RMS of the X and Z sides: float capsuleRadius = 0.5f * sqrtf(0.5f * (diagonal.x * diagonal.x + diagonal.z * diagonal.z)); _boundingShape.setRadius(capsuleRadius); _boundingShape.setHalfHeight(0.5f * diagonal.y - capsuleRadius); + _boundingShapeLocalOffset = 0.5f * (totalExtents.maximum + totalExtents.minimum); +} - glm::quat inverseRotation = glm::inverse(_rotation); - glm::vec3 rootPosition = extractTranslation(transforms[rootIndex]); - _boundingShapeLocalOffset = inverseRotation * (0.5f * (totalExtents.maximum + totalExtents.minimum) - rootPosition); +void Model::resetShapePositions() { + // DEBUG method. + // Moves shapes to the joint default locations for debug visibility into + // how the bounding shape is computed. + + if (!_geometry || _rootIndex == -1) { + // geometry or joints have not yet been created + return; + } + + const FBXGeometry& geometry = _geometry->getFBXGeometry(); + if (geometry.joints.isEmpty() || _jointShapes.size() != geometry.joints.size()) { + return; + } + + // The shapes are moved to their default positions in computeBoundingShape(). + computeBoundingShape(geometry); + + // Then we move them into world frame for rendering at the Model's location. + for (int i = 0; i < _jointShapes.size(); i++) { + Shape* shape = _jointShapes[i]; + shape->setPosition(_translation + _rotation * shape->getPosition()); + shape->setRotation(_rotation * shape->getRotation()); + } _boundingShape.setPosition(_translation + _rotation * _boundingShapeLocalOffset); _boundingShape.setRotation(_rotation); } @@ -728,17 +787,17 @@ 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; - _jointShapes[i]->setPosition(worldPosition); - _jointShapes[i]->setRotation(_jointStates[i].combinedRotation * joint.shapeRotation); - float distance2 = glm::distance2(worldPosition, _translation); - if (distance2 > _boundingRadius) { - _boundingRadius = distance2; + Shape* shape = _jointShapes[i]; + shape->setPosition(worldPosition); + shape->setRotation(_jointStates[i].combinedRotation * joint.shapeRotation); + float distance = glm::distance(worldPosition, _translation) + shape->getBoundingRadius(); + if (distance > _boundingRadius) { + _boundingRadius = distance; } if (joint.parentIndex == -1) { rootPosition = worldPosition; } } - _boundingRadius = sqrtf(_boundingRadius); _shapesAreDirty = false; _boundingShape.setPosition(rootPosition + _rotation * _boundingShapeLocalOffset); _boundingShape.setRotation(_rotation); diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index b7a42930dc..5b2839baa2 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -187,6 +187,7 @@ public: void clearShapes(); void rebuildShapes(); + void resetShapePositions(); void updateShapePositions(); void renderJointCollisionShapes(float alpha); void renderBoundingCollisionShapes(float alpha); @@ -234,6 +235,7 @@ protected: bool _snapModelToCenter; /// is the model's offset automatically adjusted to center around 0,0,0 in model space bool _snappedToCenter; /// are we currently snapped to center + int _rootIndex; class JointState { public: @@ -293,6 +295,8 @@ protected: void applyRotationDelta(int jointIndex, const glm::quat& delta, bool constrain = true); + void computeBoundingShape(const FBXGeometry& geometry); + private: void applyNextGeometry(); diff --git a/interface/src/ui/NodeBounds.cpp b/interface/src/ui/NodeBounds.cpp new file mode 100644 index 0000000000..735dc66ddf --- /dev/null +++ b/interface/src/ui/NodeBounds.cpp @@ -0,0 +1,239 @@ +// +// NodeBounds.cpp +// interface/src/ui +// +// Created by Ryan Huffman on 05/14/14. +// Copyright 2014 High Fidelity, Inc. +// +// This class draws a border around the different Voxel, Model, and Particle nodes on the current domain, +// and a semi-transparent cube around the currently mouse-overed node. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "Application.h" +#include "Util.h" + +#include "NodeBounds.h" + +NodeBounds::NodeBounds(QObject* parent) : + QObject(parent), + _showVoxelNodes(false), + _showModelNodes(false), + _showParticleNodes(false), + _overlayText() { + +} + +void NodeBounds::draw() { + if (!(_showVoxelNodes || _showModelNodes || _showParticleNodes)) { + _overlayText[0] = '\0'; + return; + } + + NodeToJurisdictionMap& voxelServerJurisdictions = Application::getInstance()->getVoxelServerJurisdictions(); + NodeToJurisdictionMap& modelServerJurisdictions = Application::getInstance()->getModelServerJurisdictions(); + NodeToJurisdictionMap& particleServerJurisdictions = Application::getInstance()->getParticleServerJurisdictions(); + NodeToJurisdictionMap* serverJurisdictions; + + // Compute ray to find selected nodes later on. We can't use the pre-computed ray in Application because it centers + // itself after the cursor disappears. + Application* application = Application::getInstance(); + QGLWidget* glWidget = application->getGLWidget(); + float mouseX = application->getMouseX() / (float)glWidget->width(); + float mouseY = application->getMouseY() / (float)glWidget->height(); + glm::vec3 mouseRayOrigin; + glm::vec3 mouseRayDirection; + application->getViewFrustum()->computePickRay(mouseX, mouseY, mouseRayOrigin, mouseRayDirection); + + // Variables to keep track of the selected node and properties to draw the cube later if needed + Node* selectedNode = NULL; + float selectedDistance = FLT_MAX; + bool selectedIsInside = true; + glm::vec3 selectedCenter; + float selectedScale = 0; + + NodeList* nodeList = NodeList::getInstance(); + + foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { + NodeType_t nodeType = node->getType(); + + if (nodeType == NodeType::VoxelServer && _showVoxelNodes) { + serverJurisdictions = &voxelServerJurisdictions; + } else if (nodeType == NodeType::ModelServer && _showModelNodes) { + serverJurisdictions = &modelServerJurisdictions; + } else if (nodeType == NodeType::ParticleServer && _showParticleNodes) { + serverJurisdictions = &particleServerJurisdictions; + } else { + continue; + } + + QUuid nodeUUID = node->getUUID(); + if (serverJurisdictions->find(nodeUUID) != serverJurisdictions->end()) { + const JurisdictionMap& map = serverJurisdictions->value(nodeUUID); + + unsigned char* rootCode = map.getRootOctalCode(); + + if (rootCode) { + VoxelPositionSize rootDetails; + voxelDetailsForCode(rootCode, rootDetails); + glm::vec3 location(rootDetails.x, rootDetails.y, rootDetails.z); + location *= (float)TREE_SCALE; + + AABox serverBounds(location, rootDetails.s * TREE_SCALE); + + glm::vec3 center = serverBounds.getVertex(BOTTOM_RIGHT_NEAR) + + ((serverBounds.getVertex(TOP_LEFT_FAR) - serverBounds.getVertex(BOTTOM_RIGHT_NEAR)) / 2.0f); + + const float VOXEL_NODE_SCALE = 1.00f; + const float MODEL_NODE_SCALE = 0.99f; + const float PARTICLE_NODE_SCALE = 0.98f; + + float scaleFactor = rootDetails.s * TREE_SCALE; + + // Scale by 0.92 - 1.00 depending on the scale of the node. This allows smaller nodes to scale in + // a bit and not overlap larger nodes. + scaleFactor *= 0.92 + (rootDetails.s * 0.08); + + // Scale different node types slightly differently because it's common for them to overlap. + if (nodeType == NodeType::VoxelServer) { + scaleFactor *= VOXEL_NODE_SCALE; + } else if (nodeType == NodeType::ModelServer) { + scaleFactor *= MODEL_NODE_SCALE; + } else { + scaleFactor *= PARTICLE_NODE_SCALE; + } + + float red, green, blue; + getColorForNodeType(nodeType, red, green, blue); + drawNodeBorder(center, scaleFactor, red, green, blue); + + float distance; + BoxFace face; + bool inside = serverBounds.contains(mouseRayOrigin); + bool colliding = serverBounds.findRayIntersection(mouseRayOrigin, mouseRayDirection, distance, face); + + // If the camera is inside a node it will be "selected" if you don't have your cursor over another node + // that you aren't inside. + if (colliding && (!selectedNode || (!inside && (distance < selectedDistance || selectedIsInside)))) { + selectedNode = node.data(); + selectedDistance = distance; + selectedIsInside = inside; + selectedCenter = center; + selectedScale = scaleFactor; + } + } + } + } + + if (selectedNode) { + glPushMatrix(); + + glTranslatef(selectedCenter.x, selectedCenter.y, selectedCenter.z); + glScalef(selectedScale, selectedScale, selectedScale); + + NodeType_t selectedNodeType = selectedNode->getType(); + float red, green, blue; + getColorForNodeType(selectedNode->getType(), red, green, blue); + + glColor4f(red, green, blue, 0.2); + glutSolidCube(1.0); + + glPopMatrix(); + + HifiSockAddr addr = selectedNode->getPublicSocket(); + QString overlay = QString("%1:%2 %3ms") + .arg(addr.getAddress().toString()) + .arg(addr.getPort()) + .arg(selectedNode->getPingMs()) + .left(MAX_OVERLAY_TEXT_LENGTH); + + // Ideally we'd just use a QString, but I ran into weird blinking issues using + // constData() directly, as if the data was being overwritten. + strcpy(_overlayText, overlay.toLocal8Bit().constData()); + } else { + _overlayText[0] = '\0'; + } +} + +void NodeBounds::drawNodeBorder(const glm::vec3& center, float scale, float red, float green, float blue) { + glPushMatrix(); + + glTranslatef(center.x, center.y, center.z); + glScalef(scale, scale, scale); + + glLineWidth(2.5); + glColor3f(red, green, blue); + glBegin(GL_LINES); + + glVertex3f(-0.5, -0.5, -0.5); + glVertex3f( 0.5, -0.5, -0.5); + + glVertex3f(-0.5, -0.5, -0.5); + glVertex3f(-0.5, 0.5, -0.5); + + glVertex3f(-0.5, -0.5, -0.5); + glVertex3f(-0.5, -0.5, 0.5); + + glVertex3f(-0.5, 0.5, -0.5); + glVertex3f( 0.5, 0.5, -0.5); + + glVertex3f(-0.5, 0.5, -0.5); + glVertex3f(-0.5, 0.5, 0.5); + + glVertex3f( 0.5, 0.5, 0.5); + glVertex3f(-0.5, 0.5, 0.5); + + glVertex3f( 0.5, 0.5, 0.5); + glVertex3f( 0.5, -0.5, 0.5); + + glVertex3f( 0.5, 0.5, 0.5); + glVertex3f( 0.5, 0.5, -0.5); + + glVertex3f( 0.5, -0.5, 0.5); + glVertex3f(-0.5, -0.5, 0.5); + + glVertex3f( 0.5, -0.5, 0.5); + glVertex3f( 0.5, -0.5, -0.5); + + glVertex3f( 0.5, 0.5, -0.5); + glVertex3f( 0.5, -0.5, -0.5); + + glVertex3f(-0.5, 0.5, 0.5); + glVertex3f(-0.5, -0.5, 0.5); + + glEnd(); + + glPopMatrix(); +} + +void NodeBounds::getColorForNodeType(NodeType_t nodeType, float& red, float& green, float& blue) { + red = nodeType == NodeType::VoxelServer ? 1.0 : 0.0; + green = nodeType == NodeType::ParticleServer ? 1.0 : 0.0; + blue = nodeType == NodeType::ModelServer ? 1.0 : 0.0; +} + +void NodeBounds::drawOverlay() { + if (strlen(_overlayText) > 0) { + Application* application = Application::getInstance(); + + const float TEXT_COLOR[] = { 0.90f, 0.90f, 0.90f }; + const float TEXT_SCALE = 0.1f; + const int TEXT_HEIGHT = 10; + const float ROTATION = 0.0f; + const int FONT = 2; + const int PADDING = 10; + const int MOUSE_OFFSET = 10; + const int BACKGROUND_OFFSET_Y = -20; + const int BACKGROUND_BEVEL = 3; + + int mouseX = application->getMouseX(), + mouseY = application->getMouseY(), + textWidth = widthText(TEXT_SCALE, 0, _overlayText); + glColor4f(0.4, 0.4, 0.4, 0.6); + renderBevelCornersRect(mouseX + MOUSE_OFFSET, mouseY - TEXT_HEIGHT - PADDING, + textWidth + (2 * PADDING), TEXT_HEIGHT + (2 * PADDING), BACKGROUND_BEVEL); + drawText(mouseX + MOUSE_OFFSET + PADDING, mouseY, TEXT_SCALE, ROTATION, FONT, _overlayText, TEXT_COLOR); + } +} diff --git a/interface/src/ui/NodeBounds.h b/interface/src/ui/NodeBounds.h new file mode 100644 index 0000000000..bd54b84e98 --- /dev/null +++ b/interface/src/ui/NodeBounds.h @@ -0,0 +1,50 @@ +// +// NodeBounds.h +// interface/src/ui +// +// Created by Ryan Huffman on 05/14/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_NodeBounds_h +#define hifi_NodeBounds_h + +#include + +#include + +const int MAX_OVERLAY_TEXT_LENGTH = 64; + +class NodeBounds : public QObject { + Q_OBJECT +public: + NodeBounds(QObject* parent = NULL); + + bool getShowVoxelNodes() { return _showVoxelNodes; } + bool getShowModelNodes() { return _showModelNodes; } + bool getShowParticleNodes() { return _showParticleNodes; } + + void draw(); + void drawOverlay(); + +public slots: + void setShowVoxelNodes(bool value) { _showVoxelNodes = value; } + void setShowModelNodes(bool value) { _showModelNodes = value; } + void setShowParticleNodes(bool value) { _showParticleNodes = value; } + +protected: + void drawNodeBorder(const glm::vec3& center, float scale, float red, float green, float blue); + void getColorForNodeType(NodeType_t nodeType, float& red, float& green, float& blue); + +private: + bool _showVoxelNodes; + bool _showModelNodes; + bool _showParticleNodes; + char _overlayText[MAX_OVERLAY_TEXT_LENGTH + 1]; + +}; + +#endif // hifi_NodeBounds_h diff --git a/libraries/models/src/ModelItem.cpp b/libraries/models/src/ModelItem.cpp index c04f9a76ae..3dd1c99e60 100644 --- a/libraries/models/src/ModelItem.cpp +++ b/libraries/models/src/ModelItem.cpp @@ -714,6 +714,7 @@ void ModelItem::mapJoints(const QStringList& modelJointNames) { if (!_jointMappingCompleted) { QStringList animationJointNames = myAnimation->getJointNames(); + if (modelJointNames.size() > 0 && animationJointNames.size() > 0) { _jointMapping.resize(modelJointNames.size()); for (int i = 0; i < modelJointNames.size(); i++) { @@ -729,13 +730,17 @@ QVector ModelItem::getAnimationFrame() { if (hasAnimation() && _jointMappingCompleted) { Animation* myAnimation = getAnimation(_animationURL); QVector frames = myAnimation->getFrames(); - int animationFrameIndex = (int)std::floor(_animationFrameIndex) % frames.size(); - QVector rotations = frames[animationFrameIndex].rotations; - frameData.resize(_jointMapping.size()); - for (int j = 0; j < _jointMapping.size(); j++) { - int rotationIndex = _jointMapping[j]; - if (rotationIndex != -1 && rotationIndex < rotations.size()) { - frameData[j] = rotations[rotationIndex]; + int frameCount = frames.size(); + + if (frameCount > 0) { + int animationFrameIndex = (int)glm::floor(_animationFrameIndex) % frameCount; + QVector rotations = frames[animationFrameIndex].rotations; + frameData.resize(_jointMapping.size()); + for (int j = 0; j < _jointMapping.size(); j++) { + int rotationIndex = _jointMapping[j]; + if (rotationIndex != -1 && rotationIndex < rotations.size()) { + frameData[j] = rotations[rotationIndex]; + } } } } diff --git a/libraries/models/src/ModelTree.cpp b/libraries/models/src/ModelTree.cpp index 4e92544f40..637079560b 100644 --- a/libraries/models/src/ModelTree.cpp +++ b/libraries/models/src/ModelTree.cpp @@ -108,6 +108,7 @@ bool FindAndUpdateModelOperator::PostRecursion(OctreeElement* element) { return !_found; // if we haven't yet found it, keep looking } +// TODO: improve this to not use multiple recursions void ModelTree::storeModel(const ModelItem& model, const SharedNodePointer& senderNode) { // First, look for the existing model in the tree.. FindAndUpdateModelOperator theOperator(model); @@ -118,8 +119,13 @@ void ModelTree::storeModel(const ModelItem& model, const SharedNodePointer& send AABox modelBox = model.getAABox(); ModelTreeElement* element = (ModelTreeElement*)getOrCreateChildElementContaining(model.getAABox()); element->storeModel(model); + + // In the case where we stored it, we also need to mark the entire "path" down to the model as + // having changed. Otherwise viewers won't see this change. So we call this recursion now that + // we know it will be found, this find/update will correctly mark the tree as changed. + recurseTreeWithOperator(&theOperator); } - // what else do we need to do here to get reaveraging to work + _isDirty = true; } diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index 128677ff2b..3042626a51 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -1389,6 +1389,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* element, keepDiggingDeeper = (inViewNotLeafCount > 0); if (continueThisLevel && keepDiggingDeeper) { + // at this point, we need to iterate the children who are in view, even if not colored // and we need to determine if there's a deeper tree below them that we care about. // @@ -1433,7 +1434,12 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* element, // // This only applies in the view frustum case, in other cases, like file save and copy/past where // no viewFrustum was requested, we still want to recurse the child tree. - if (!params.viewFrustum || !oneAtBit(childrenColoredBits, originalIndex)) { + // + // NOTE: some octree styles (like models and particles) will store content in parent elements, and child + // elements. In this case, if we stop recursion when we include any data (the colorbits should really be + // called databits), then we wouldn't send the children. So those types of Octree's should tell us to keep + // recursing, by returning TRUE in recurseChildrenWithData(). + if (recurseChildrenWithData() || !params.viewFrustum || !oneAtBit(childrenColoredBits, originalIndex)) { childTreeBytesOut = encodeTreeBitstreamRecursion(childElement, packetData, bag, params, thisLevel, nodeLocationThisView); } @@ -1519,16 +1525,6 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* element, } // end keepDiggingDeeper // At this point all our BitMasks are complete... so let's output them to see how they compare... - /** - printf("This Level's BitMasks: childInTree:"); - outputBits(childrenExistInTreeBits, false, true); - printf(" childInPacket:"); - outputBits(childrenExistInPacketBits, false, true); - printf(" childrenColored:"); - outputBits(childrenColoredBits, false, true); - qDebug(""); - **/ - // if we were unable to fit this level in our packet, then rewind and add it to the element bag for // sending later... if (continueThisLevel) { diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index 84212586f8..1e71884faa 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -209,6 +209,8 @@ public: virtual bool handlesEditPacketType(PacketType packetType) const { return false; } virtual int processEditPacketData(PacketType packetType, const unsigned char* packetData, int packetLength, const unsigned char* editData, int maxLength, const SharedNodePointer& sourceNode) { return 0; } + + virtual bool recurseChildrenWithData() const { return true; } virtual void update() { }; // nothing to do by default diff --git a/libraries/voxels/src/VoxelTree.h b/libraries/voxels/src/VoxelTree.h index 2915774fe3..03d07a1649 100644 --- a/libraries/voxels/src/VoxelTree.h +++ b/libraries/voxels/src/VoxelTree.h @@ -55,6 +55,7 @@ public: virtual bool handlesEditPacketType(PacketType packetType) const; virtual int processEditPacketData(PacketType packetType, const unsigned char* packetData, int packetLength, const unsigned char* editData, int maxLength, const SharedNodePointer& node); + virtual bool recurseChildrenWithData() const { return false; } private: // helper functions for nudgeSubTree