diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 9b1c445e78..7989d8b715 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -128,7 +128,8 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : _bytesPerSecond(0), _bytesCount(0), _swatch(NULL), - _pasteMode(false) + _pasteMode(false), + _finishedNudge(true) { _applicationStartupTime = startup_time; _window->setWindowTitle("Interface"); @@ -701,6 +702,9 @@ void Application::keyPressEvent(QKeyEvent* event) { case Qt::Key_O: Menu::getInstance()->triggerOption(MenuOption::VoxelSelectMode); break; + case Qt::Key_N: + Menu::getInstance()->triggerOption(MenuOption::VoxelNudgeMode); + break; case Qt::Key_Slash: Menu::getInstance()->triggerOption(MenuOption::Stats); break; @@ -841,6 +845,14 @@ void Application::mousePressEvent(QMouseEvent* event) { pasteVoxels(); } + if (Menu::getInstance()->isOptionChecked(MenuOption::VoxelNudgeMode)) { + VoxelNode* clickedNode = _voxels.getVoxelAt(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s); + if (clickedNode) { + _nudgeVoxel = _mouseVoxel; + _finishedNudge = false; + } + } + if (MAKE_SOUND_ON_VOXEL_CLICK && _isHoverVoxel && !_isHoverVoxelSounding) { _hoverVoxelOriginalColor[0] = _hoverVoxel.red; _hoverVoxelOriginalColor[1] = _hoverVoxel.green; @@ -1279,6 +1291,20 @@ void Application::pasteVoxels() { } } +void Application::nudgeVoxels() { + if (Menu::getInstance()->isOptionChecked(MenuOption::VoxelNudgeMode)) { + // calculate nudgeVec + glm::vec3 nudgeVec(_mouseVoxel.x - _nudgeVoxel.x, _mouseVoxel.y - _nudgeVoxel.y, _mouseVoxel.z - _nudgeVoxel.z); + + VoxelNode* nodeToNudge = _voxels.getVoxelAt(_nudgeVoxel.x, _nudgeVoxel.y, _nudgeVoxel.z, _nudgeVoxel.s); + + if (nodeToNudge) { + _voxels.getTree()->nudgeSubTree(nodeToNudge, nudgeVec, _voxelEditSender); + _finishedNudge = true; + } + } +} + void Application::setListenModeNormal() { _audio.setListenMode(AudioRingBuffer::NORMAL); } @@ -1375,6 +1401,7 @@ void Application::init() { _palette.addAction(Menu::getInstance()->getActionForOption(MenuOption::VoxelColorMode), 0, 2); _palette.addAction(Menu::getInstance()->getActionForOption(MenuOption::VoxelGetColorMode), 0, 3); _palette.addAction(Menu::getInstance()->getActionForOption(MenuOption::VoxelSelectMode), 0, 4); + _palette.addAction(Menu::getInstance()->getActionForOption(MenuOption::VoxelNudgeMode), 0, 5); _pieMenu.init("./resources/images/hifi-interface-tools-v2-pie.svg", _glWidget->width(), @@ -1556,7 +1583,7 @@ void Application::update(float deltaTime) { lookAtSpot = lookAtRayOrigin + lookAtRayDirection * FAR_AWAY_STARE; _myAvatar.getHead().setLookAtPosition(lookAtSpot); } - + // Find the voxel we are hovering over, and respond if clicked float distance; BoxFace face; @@ -1650,7 +1677,8 @@ void Application::update(float deltaTime) { _mouseVoxel.s = 0.0f; } } else if (Menu::getInstance()->isOptionChecked(MenuOption::VoxelAddMode) - || Menu::getInstance()->isOptionChecked(MenuOption::VoxelSelectMode)) { + || Menu::getInstance()->isOptionChecked(MenuOption::VoxelSelectMode) + || Menu::getInstance()->isOptionChecked(MenuOption::VoxelNudgeMode)) { // place the voxel a fixed distance away float worldMouseVoxelScale = _mouseVoxelScale * TREE_SCALE; glm::vec3 pt = mouseRayOrigin + mouseRayDirection * (2.0f + worldMouseVoxelScale * 0.5f); @@ -1665,9 +1693,11 @@ void Application::update(float deltaTime) { _mouseVoxel.red = 255; _mouseVoxel.green = _mouseVoxel.blue = 0; } else if (Menu::getInstance()->isOptionChecked(MenuOption::VoxelSelectMode)) { - // yellow indicates deletion + // yellow indicates selection _mouseVoxel.red = _mouseVoxel.green = 255; _mouseVoxel.blue = 0; + } else if (Menu::getInstance()->isOptionChecked(MenuOption::VoxelNudgeMode)) { + _mouseVoxel.red = _mouseVoxel.green = _mouseVoxel.blue = 255; } else { // _addVoxelMode->isChecked() || _colorVoxelMode->isChecked() QColor paintColor = Menu::getInstance()->getActionForOption(MenuOption::VoxelPaintColor)->data().value(); _mouseVoxel.red = paintColor.red(); @@ -2262,7 +2292,23 @@ void Application::displaySide(Camera& whichCamera) { glDisable(GL_LIGHTING); glPushMatrix(); glScalef(TREE_SCALE, TREE_SCALE, TREE_SCALE); - renderMouseVoxelGrid(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s); + if (Menu::getInstance()->isOptionChecked(MenuOption::VoxelNudgeMode)) { + if (!_finishedNudge) { + renderNudgeGuide(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _nudgeVoxel.s); + renderNudgeGrid(_nudgeVoxel.x, _nudgeVoxel.y, _nudgeVoxel.z, _nudgeVoxel.s, _mouseVoxel.s); + glPushMatrix(); + glTranslatef(_nudgeVoxel.x + _nudgeVoxel.s * 0.5f, + _nudgeVoxel.y + _nudgeVoxel.s * 0.5f, + _nudgeVoxel.z + _nudgeVoxel.s * 0.5f); + glColor3ub(255, 255, 255); + glLineWidth(4.0f); + glutWireCube(_nudgeVoxel.s); + glPopMatrix(); + } + } else { + renderMouseVoxelGrid(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s); + } + if (Menu::getInstance()->isOptionChecked(MenuOption::VoxelAddMode)) { // use a contrasting color so that we can see what we're doing glColor3ub(_mouseVoxel.red + 128, _mouseVoxel.green + 128, _mouseVoxel.blue + 128); @@ -2273,7 +2319,15 @@ void Application::displaySide(Camera& whichCamera) { _mouseVoxel.y + _mouseVoxel.s*0.5f, _mouseVoxel.z + _mouseVoxel.s*0.5f); glLineWidth(4.0f); - glutWireCube(_mouseVoxel.s); + if (Menu::getInstance()->isOptionChecked(MenuOption::VoxelNudgeMode)) { + if (_nudgeVoxel.s) { + glutWireCube(_nudgeVoxel.s); + } else { + glutWireCube(_mouseVoxel.s); + } + } else { + glutWireCube(_mouseVoxel.s); + } glLineWidth(1.0f); glPopMatrix(); glEnable(GL_LIGHTING); diff --git a/interface/src/Application.h b/interface/src/Application.h index 8bb3106375..6c9f2cf66d 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -150,6 +150,7 @@ public slots: void cutVoxels(); void copyVoxels(); void pasteVoxels(); + void nudgeVoxels(); void setRenderVoxels(bool renderVoxels); void doKillLocalVoxels(); @@ -306,6 +307,8 @@ private: glm::vec3 _lastMouseVoxelPos; // the position of the last mouse voxel edit bool _justEditedVoxel; // set when we've just added/deleted/colored a voxel + VoxelDetail _nudgeVoxel; // details of the voxel to be nudged + bool _isLookingAtOtherAvatar; glm::vec3 _lookatOtherPosition; float _lookatIndicatorScale; @@ -358,6 +361,7 @@ private: Swatch _swatch; bool _pasteMode; + bool _finishedNudge; PieMenu _pieMenu; diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index f96b45207c..1d9e12cc96 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -108,6 +108,7 @@ Menu::Menu() : addActionToQMenuAndActionHash(editMenu, MenuOption::CutVoxels, Qt::CTRL | Qt::Key_X, appInstance, SLOT(cutVoxels())); addActionToQMenuAndActionHash(editMenu, MenuOption::CopyVoxels, Qt::CTRL | Qt::Key_C, appInstance, SLOT(copyVoxels())); addActionToQMenuAndActionHash(editMenu, MenuOption::PasteVoxels, Qt::CTRL | Qt::Key_V, appInstance, SLOT(pasteVoxels())); + addActionToQMenuAndActionHash(editMenu, MenuOption::NudgeVoxels, Qt::CTRL | Qt::Key_N, appInstance, SLOT(nudgeVoxels())); addDisabledActionAndSeparator(editMenu, "Physics"); addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::Gravity, Qt::SHIFT | Qt::Key_G, true); @@ -131,6 +132,9 @@ Menu::Menu() : QAction* colorVoxelMode = addCheckableActionToQMenuAndActionHash(toolsMenu, MenuOption::VoxelColorMode, Qt::Key_B); _voxelModeActionsGroup->addAction(colorVoxelMode); + + QAction* nudgeVoxelMode = addCheckableActionToQMenuAndActionHash(toolsMenu, MenuOption::VoxelNudgeMode, Qt::Key_N); + _voxelModeActionsGroup->addAction(nudgeVoxelMode); QAction* selectVoxelMode = addCheckableActionToQMenuAndActionHash(toolsMenu, MenuOption::VoxelSelectMode, Qt::Key_O); _voxelModeActionsGroup->addAction(selectVoxelMode); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index da773ad39e..934cb1b7ae 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -169,6 +169,7 @@ namespace MenuOption { const QString LookAtVectors = "Look-at Vectors"; const QString LowRes = "Lower Resolution While Moving"; const QString Mirror = "Mirror"; + const QString NudgeVoxels = "Nudge Voxels"; const QString OcclusionCulling = "Occlusion Culling"; const QString OffAxisProjection = "Off-Axis Projection"; const QString Oscilloscope = "Audio Oscilloscope"; @@ -204,6 +205,7 @@ namespace MenuOption { const QString VoxelGetColorMode = "Get Color Mode"; const QString VoxelMode = "Cycle Voxel Mode"; const QString VoxelPaintColor = "Voxel Paint Color"; + const QString VoxelNudgeMode = "Nudge Voxel Mode"; const QString VoxelSelectMode = "Select Voxel Mode"; const QString VoxelStats = "Voxel Stats"; const QString VoxelTextures = "Voxel Textures"; diff --git a/interface/src/Util.cpp b/interface/src/Util.cpp index dc825ca765..8898ab9eab 100644 --- a/interface/src/Util.cpp +++ b/interface/src/Util.cpp @@ -414,6 +414,66 @@ void renderMouseVoxelGrid(const float& mouseVoxelX, const float& mouseVoxelY, co glEnd(); } +void renderNudgeGrid(float voxelX, float voxelY, float voxelZ, float voxelS, float voxelPrecision) { + glm::vec3 origin = glm::vec3(voxelX, voxelY, voxelZ); + + glLineWidth(1.0); + + const int GRID_DIMENSIONS = 4; + const int GRID_SCALER = voxelS / voxelPrecision; + const int GRID_SEGMENTS = GRID_DIMENSIONS * GRID_SCALER; + glBegin(GL_LINES); + + for (int xz = - (GRID_SEGMENTS / 2); xz <= GRID_SEGMENTS / 2 + GRID_SCALER; xz++) { + glm::vec3 xColor(0.0, 0.6, 0.0); + glColor3fv(&xColor.x); + + glVertex3f(origin.x + GRID_DIMENSIONS * voxelS, 0, origin.z + xz * voxelPrecision); + glVertex3f(origin.x - (GRID_DIMENSIONS - 1) * voxelS, 0, origin.z + xz * voxelPrecision); + + glm::vec3 zColor(0.0, 0.0, 0.6); + glColor3fv(&zColor.x); + + glVertex3f(origin.x + xz * voxelPrecision, 0, origin.z + GRID_DIMENSIONS * voxelS); + glVertex3f(origin.x + xz * voxelPrecision, 0, origin.z - (GRID_DIMENSIONS - 1) * voxelS); + } + glEnd(); + + glColor3f(1.0f,1.0f,1.0f); + + glBegin(GL_POLYGON);//begin drawing of square + glVertex3f(voxelX, 0.0f, voxelZ);//first vertex + glVertex3f(voxelX + voxelS, 0.0f, voxelZ);//second vertex + glVertex3f(voxelX + voxelS, 0.0f, voxelZ + voxelS);//third vertex + glVertex3f(voxelX, 0.0f, voxelZ + voxelS);//fourth vertex + glEnd();//end drawing of polygon +} + +void renderNudgeGuide(float voxelX, float voxelY, float voxelZ, float voxelS) { + glm::vec3 origin = glm::vec3(voxelX, voxelY, voxelZ); + + glLineWidth(3.0); + + glBegin(GL_LINES); + + glm::vec3 guideColor(1.0, 1.0, 1.0); + glColor3fv(&guideColor.x); + + glVertex3f(origin.x + voxelS, 0, origin.z); + glVertex3f(origin.x, 0, origin.z); + + glVertex3f(origin.x, 0, origin.z); + glVertex3f(origin.x, 0, origin.z + voxelS); + + glVertex3f(origin.x + voxelS, 0, origin.z); + glVertex3f(origin.x + voxelS, 0, origin.z + voxelS); + + glVertex3f(origin.x, 0, origin.z + voxelS); + glVertex3f(origin.x + voxelS, 0, origin.z + voxelS); + + glEnd(); +} + void renderDiskShadow(glm::vec3 position, glm::vec3 upDirection, float radius, float darkness) { glColor4f(0.0f, 0.0f, 0.0f, darkness); diff --git a/interface/src/Util.h b/interface/src/Util.h index 2926d5bfc4..3039ab1a31 100644 --- a/interface/src/Util.h +++ b/interface/src/Util.h @@ -61,6 +61,10 @@ void renderGroundPlaneGrid(float size, float impact); void renderMouseVoxelGrid(const float& mouseVoxelX, const float& mouseVoxelY, const float& mouseVoxelZ, const float& mouseVoxelS); +void renderNudgeGrid(float voxelX, float voxelY, float voxelZ, float voxelS, float voxelPrecision); + +void renderNudgeGuide(float voxelX, float voxelY, float voxelZ, float voxelS); + void renderCollisionOverlay(int width, int height, float magnitude); void renderDiskShadow(glm::vec3 position, glm::vec3 upDirection, float radius, float darkness); diff --git a/libraries/voxels/src/VoxelEditPacketSender.h b/libraries/voxels/src/VoxelEditPacketSender.h index e07bd11baa..bda2c8006d 100644 --- a/libraries/voxels/src/VoxelEditPacketSender.h +++ b/libraries/voxels/src/VoxelEditPacketSender.h @@ -12,6 +12,7 @@ #define __shared__VoxelEditPacketSender__ #include +#include #include // for VoxelDetail #include "JurisdictionMap.h" diff --git a/libraries/voxels/src/VoxelNode.cpp b/libraries/voxels/src/VoxelNode.cpp index 7355e91243..72fb72d466 100644 --- a/libraries/voxels/src/VoxelNode.cpp +++ b/libraries/voxels/src/VoxelNode.cpp @@ -422,4 +422,4 @@ void VoxelNode::notifyDeleteHooks() { for (int i = 0; i < _hooks.size(); i++) { _hooks[i]->nodeDeleted(this); } -} +} \ No newline at end of file diff --git a/libraries/voxels/src/VoxelTree.cpp b/libraries/voxels/src/VoxelTree.cpp index 22b0f55b46..2e4fbdf26e 100644 --- a/libraries/voxels/src/VoxelTree.cpp +++ b/libraries/voxels/src/VoxelTree.cpp @@ -31,6 +31,7 @@ #include "VoxelConstants.h" #include "VoxelNodeBag.h" #include "VoxelTree.h" +#include float boundaryDistanceForRenderLevel(unsigned int renderLevel) { return ::VOXEL_SIZE_SCALE / powf(2, renderLevel); @@ -151,7 +152,7 @@ VoxelNode* VoxelTree::nodeForOctalCode(VoxelNode* ancestorNode, return childNode; } else { // we need to go deeper - return nodeForOctalCode(childNode, needleCode,parentOfFoundNode); + return nodeForOctalCode(childNode, needleCode, parentOfFoundNode); } } } @@ -397,10 +398,10 @@ void VoxelTree::deleteVoxelCodeFromTreeRecursion(VoxelNode* node, void* extraDat } } } - int lengthOfancestorNode = numberOfThreeBitSectionsInCode(ancestorNode->getOctalCode()); + int lengthOfAncestorNode = numberOfThreeBitSectionsInCode(ancestorNode->getOctalCode()); // If we've reached the parent of the target, then stop breaking up children - if (lengthOfancestorNode == (args->lengthOfCode - 1)) { + if (lengthOfAncestorNode == (args->lengthOfCode - 1)) { break; } ancestorNode->addChildAtIndex(index); @@ -1856,3 +1857,133 @@ void VoxelTree::emptyDeleteQueue() { void VoxelTree::cancelImport() { _stopImport = true; } + +class NodeChunkArgs { +public: + VoxelTree* thisVoxelTree; + float ancestorSize; + glm::vec3 nudgeVec; + VoxelEditPacketSender* voxelEditSenderPtr; +}; + +float findNewLeafSize(const glm::vec3& nudgeAmount, float leafSize) { + // we want the smallest non-zero and non-negative new leafSize + float newLeafSizeX = fabs(fmod(nudgeAmount.x, leafSize)); + float newLeafSizeY = fabs(fmod(nudgeAmount.y, leafSize)); + float newLeafSizeZ = fabs(fmod(nudgeAmount.z, leafSize)); + + float newLeafSize = leafSize; + if (newLeafSizeX) { + newLeafSize = fmin(newLeafSize, newLeafSizeX); + } + if (newLeafSizeY) { + newLeafSize = fmin(newLeafSize, newLeafSizeY); + } + if (newLeafSizeZ) { + newLeafSize = fmin(newLeafSize, newLeafSizeZ); + } + return newLeafSize; +} + +bool VoxelTree::nudgeCheck(VoxelNode* node, void* extraData) { + if (node->isLeaf()) { + // we have reached the deepest level of nodes/voxels + // now there are two scenarios + // 1) this node's size is <= the minNudgeAmount + // in which case we will simply call nudgeLeaf on this leaf + // 2) this node's size is still not <= the minNudgeAmount + // in which case we need to break this leaf down until the leaf sizes are <= minNudgeAmount + + NodeChunkArgs* args = (NodeChunkArgs*)extraData; + + // get octal code of this node + unsigned char* octalCode = node->getOctalCode(); + + // get voxel position/size + VoxelPositionSize unNudgedDetails; + voxelDetailsForCode(octalCode, unNudgedDetails); + + // find necessary leaf size + float newLeafSize = findNewLeafSize(args->nudgeVec, unNudgedDetails.s); + + // check to see if this unNudged node can be nudged + if (unNudgedDetails.s <= newLeafSize) { + args->thisVoxelTree->nudgeLeaf(node, extraData); + return false; + } else { + // break the current leaf into smaller chunks + args->thisVoxelTree->chunkifyLeaf(node); + } + } + return true; +} + +void VoxelTree::chunkifyLeaf(VoxelNode* node) { + // because this function will continue being called recursively + // we only need to worry about breaking this specific leaf down + if (!node->isColored()) { + return; + } + for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { + node->addChildAtIndex(i); + node->getChildAtIndex(i)->setColor(node->getColor()); + } +} + +// This function is called to nudge the leaves of a tree, given that the +// nudge amount is >= to the leaf scale. +void VoxelTree::nudgeLeaf(VoxelNode* node, void* extraData) { + NodeChunkArgs* args = (NodeChunkArgs*)extraData; + + // get octal code of this node + unsigned char* octalCode = node->getOctalCode(); + + // get voxel position/size + VoxelPositionSize unNudgedDetails; + voxelDetailsForCode(octalCode, unNudgedDetails); + + VoxelDetail voxelDetails; + voxelDetails.x = unNudgedDetails.x; + voxelDetails.y = unNudgedDetails.y; + voxelDetails.z = unNudgedDetails.z; + voxelDetails.s = unNudgedDetails.s; + voxelDetails.red = node->getColor()[RED_INDEX]; + voxelDetails.green = node->getColor()[GREEN_INDEX]; + voxelDetails.blue = node->getColor()[BLUE_INDEX]; + glm::vec3 nudge = args->nudgeVec; + + // delete the old node + // if the nudge replaces the node in an area outside of the ancestor node + if (fabs(nudge.x) >= args->ancestorSize || fabs(nudge.y) >= args->ancestorSize || fabs(nudge.z) >= args->ancestorSize) { + args->voxelEditSenderPtr->sendVoxelEditMessage(PACKET_TYPE_ERASE_VOXEL, voxelDetails); + } + + // nudge the old node + voxelDetails.x = unNudgedDetails.x + nudge.x; + voxelDetails.y = unNudgedDetails.y + nudge.y; + voxelDetails.z = unNudgedDetails.z + nudge.z; + + // create a new voxel in its stead + args->voxelEditSenderPtr->sendVoxelEditMessage(PACKET_TYPE_SET_VOXEL_DESTRUCTIVE, voxelDetails); +} + +void VoxelTree::nudgeSubTree(VoxelNode* nodeToNudge, const glm::vec3& nudgeAmount, VoxelEditPacketSender& voxelEditSender) { + if (nudgeAmount == glm::vec3(0, 0, 0)) { + return; + } + + // get octal code of this node + unsigned char* octalCode = nodeToNudge->getOctalCode(); + + // get voxel position/size + VoxelPositionSize ancestorDetails; + voxelDetailsForCode(octalCode, ancestorDetails); + + NodeChunkArgs args; + args.thisVoxelTree = this; + args.ancestorSize = ancestorDetails.s; + args.nudgeVec = nudgeAmount; + args.voxelEditSenderPtr = &voxelEditSender; + + recurseNodeWithOperation(nodeToNudge, nudgeCheck, &args); +} diff --git a/libraries/voxels/src/VoxelTree.h b/libraries/voxels/src/VoxelTree.h index 24fa723300..48550956b2 100644 --- a/libraries/voxels/src/VoxelTree.h +++ b/libraries/voxels/src/VoxelTree.h @@ -18,6 +18,7 @@ #include "VoxelNode.h" #include "VoxelNodeBag.h" #include "VoxelSceneStats.h" +#include "VoxelEditPacketSender.h" #include @@ -188,6 +189,9 @@ public: void recurseNodeWithOperation(VoxelNode* node, RecurseVoxelTreeOperation operation, void* extraData); void recurseNodeWithOperationDistanceSorted(VoxelNode* node, RecurseVoxelTreeOperation operation, const glm::vec3& point, void* extraData); + + void nudgeSubTree(VoxelNode* nodeToNudge, const glm::vec3& nudgeAmount, VoxelEditPacketSender& voxelEditSender); + signals: void importSize(float x, float y, float z); void importProgress(int progress); @@ -247,6 +251,11 @@ private: void queueForLaterDelete(unsigned char* codeBuffer); /// flushes out any Octal Codes that had to be queued void emptyDeleteQueue(); + + // helper functions for nudgeSubTree + static bool nudgeCheck(VoxelNode* node, void* extraData); + void nudgeLeaf(VoxelNode* node, void* extraData); + void chunkifyLeaf(VoxelNode* node); }; float boundaryDistanceForRenderLevel(unsigned int renderLevel);