diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 407a64d7a8..5fd9efedd7 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -21,12 +21,10 @@ #include #ifdef _WIN32 -#include "Systime.h" #include #else #include #include -#include #include #endif //_WIN32 @@ -389,9 +387,8 @@ void AudioMixer::run() { nodeList->linkedDataCreateCallback = attachNewBufferToNode; int nextFrame = 0; - timeval startTime; - - gettimeofday(&startTime, NULL); + QElapsedTimer timer; + timer.start(); char* clientMixBuffer = new char[NETWORK_BUFFER_LENGTH_BYTES_STEREO + numBytesForPacketHeaderGivenPacketType(PacketTypeMixedAudio)]; @@ -490,7 +487,7 @@ void AudioMixer::run() { break; } - usecToSleep = usecTimestamp(&startTime) + (++nextFrame * BUFFER_SEND_INTERVAL_USECS) - usecTimestampNow(); + usecToSleep = (++nextFrame * BUFFER_SEND_INTERVAL_USECS) - timer.nsecsElapsed() / 1000; // ns to us if (usecToSleep > 0) { usleep(usecToSleep); diff --git a/assignment-client/src/main.cpp b/assignment-client/src/main.cpp index 24d19ddef6..5e103cf767 100644 --- a/assignment-client/src/main.cpp +++ b/assignment-client/src/main.cpp @@ -17,7 +17,6 @@ #include "AssignmentClientMonitor.h" int main(int argc, char* argv[]) { - #ifndef WIN32 setvbuf(stdout, NULL, _IOLBF, 0); #endif diff --git a/domain-server/src/main.cpp b/domain-server/src/main.cpp index bdb10cb56f..1d9e837f0a 100644 --- a/domain-server/src/main.cpp +++ b/domain-server/src/main.cpp @@ -18,11 +18,11 @@ #include #include +#include #include "DomainServer.h" int main(int argc, char* argv[]) { - #ifndef WIN32 setvbuf(stdout, NULL, _IOLBF, 0); #endif diff --git a/examples/editVoxels.js b/examples/editVoxels.js index 12c571b09c..453ac28649 100644 --- a/examples/editVoxels.js +++ b/examples/editVoxels.js @@ -1088,7 +1088,6 @@ function keyPressEvent(event) { red: colors[whichColor].red, green: colors[whichColor].green, blue: colors[whichColor].blue }; - Voxels.eraseVoxel(newVoxel.x, newVoxel.y, newVoxel.z, newVoxel.s); Voxels.setVoxel(newVoxel.x, newVoxel.y, newVoxel.z, newVoxel.s, newVoxel.red, newVoxel.green, newVoxel.blue); setAudioPosition(); initialVoxelSound.playRandom(); @@ -1394,7 +1393,6 @@ function checkControllers() { if (Vec3.length(Vec3.subtract(fingerTipPosition,lastFingerAddVoxel)) > (FINGERTIP_VOXEL_SIZE / 2)) { newColor = { red: colors[whichColor].red, green: colors[whichColor].green, blue: colors[whichColor].blue }; - Voxels.eraseVoxel(fingerTipPosition.x, fingerTipPosition.y, fingerTipPosition.z, FINGERTIP_VOXEL_SIZE); Voxels.setVoxel(fingerTipPosition.x, fingerTipPosition.y, fingerTipPosition.z, FINGERTIP_VOXEL_SIZE, newColor.red, newColor.green, newColor.blue); diff --git a/interface/resources/images/pin.svg b/interface/resources/images/pin.svg deleted file mode 100644 index ec968a1ec1..0000000000 --- a/interface/resources/images/pin.svg +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - image/svg+xml - - Slice 1 - - - - - Slice 1 - Created with Sketch (http://www.bohemiancoding.com/sketch) - - - - - - - - diff --git a/interface/resources/images/pinned.svg b/interface/resources/images/pinned.svg deleted file mode 100644 index bda6f0e747..0000000000 --- a/interface/resources/images/pinned.svg +++ /dev/null @@ -1,106 +0,0 @@ - - - - - - image/svg+xml - - Slice 1 - - - - - Slice 1 - Created with Sketch (http://www.bohemiancoding.com/sketch) - - - - - - - - - diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index e9c208bace..97b5c05f25 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -9,10 +9,6 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#ifdef WIN32 -#include -#endif - #include #include @@ -134,7 +130,7 @@ QString& Application::resourcesPath() { return staticResourcePath; } -Application::Application(int& argc, char** argv, timeval &startup_time) : +Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : QApplication(argc, argv), _window(new QMainWindow(desktop())), _glWidget(new GLCanvas()), @@ -507,7 +503,7 @@ void Application::initializeGL() { _idleLoopStdev.reset(); if (_justStarted) { - float startupTime = (usecTimestampNow() - usecTimestamp(&_applicationStartupTime)) / 1000000.0; + float startupTime = (float)_applicationStartupTime.elapsed() / 1000.0; _justStarted = false; qDebug("Startup time: %4.2f seconds.", startupTime); const char LOGSTASH_INTERFACE_START_TIME_KEY[] = "interface-start-time"; @@ -1222,7 +1218,6 @@ void Application::touchEndEvent(QTouchEvent* event) { if (_controllerScriptingInterface.isTouchCaptured()) { return; } - // put any application specific touch behavior below here.. _touchDragStartedAvgX = _touchAvgX; _touchDragStartedAvgY = _touchAvgY; @@ -1276,21 +1271,21 @@ void Application::sendPingPackets() { // Every second, check the frame rates and other stuff void Application::timer() { - gettimeofday(&_timerEnd, NULL); - if (Menu::getInstance()->isOptionChecked(MenuOption::TestPing)) { sendPingPackets(); } + + float diffTime = (float)_timerStart.nsecsElapsed() / 1000000000.0f; - _fps = (float)_frameCount / ((float)diffclock(&_timerStart, &_timerEnd) / 1000.f); + _fps = (float)_frameCount / diffTime; - _packetsPerSecond = (float) _datagramProcessor.getPacketCount() / ((float)diffclock(&_timerStart, &_timerEnd) / 1000.f); - _bytesPerSecond = (float) _datagramProcessor.getByteCount() / ((float)diffclock(&_timerStart, &_timerEnd) / 1000.f); + _packetsPerSecond = (float) _datagramProcessor.getPacketCount() / diffTime; + _bytesPerSecond = (float) _datagramProcessor.getByteCount() / diffTime; _frameCount = 0; _datagramProcessor.resetCounters(); - gettimeofday(&_timerStart, NULL); + _timerStart.start(); // ask the node list to check in with the domain server NodeList::getInstance()->sendDomainServerCheckIn(); @@ -1303,13 +1298,11 @@ void Application::idle() { bool showWarnings = getLogger()->extraDebugging(); PerformanceWarning warn(showWarnings, "Application::idle()"); - timeval check; - gettimeofday(&check, NULL); - // Only run simulation code if more than IDLE_SIMULATE_MSECS have passed since last time we ran - double timeSinceLastUpdate = diffclock(&_lastTimeUpdated, &check); + double timeSinceLastUpdate = (double)_lastTimeUpdated.nsecsElapsed() / 1000000.0; if (timeSinceLastUpdate > IDLE_SIMULATE_MSECS) { + _lastTimeUpdated.start(); { PerformanceWarning warn(showWarnings, "Application::idle()... update()"); const float BIGGEST_DELTA_TIME_SECS = 0.25f; @@ -1321,7 +1314,6 @@ void Application::idle() { } { PerformanceWarning warn(showWarnings, "Application::idle()... rest of it"); - _lastTimeUpdated = check; _idleLoopStdev.addValue(timeSinceLastUpdate); // Record standard deviation and reset counter if needed @@ -1637,8 +1629,8 @@ void Application::init() { Qt::QueuedConnection); } - gettimeofday(&_timerStart, NULL); - gettimeofday(&_lastTimeUpdated, NULL); + _timerStart.start(); + _lastTimeUpdated.start(); Menu::getInstance()->loadSettings(); if (Menu::getInstance()->getAudioJitterBufferSamples() != 0) { @@ -3528,35 +3520,30 @@ void Application::parseVersionXml() { QString operatingSystem("ubuntu"); #endif - QString releaseDate; - QString releaseNotes; QString latestVersion; QUrl downloadUrl; + QString releaseNotes("Unavailable"); 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") { + if (xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == operatingSystem) { + while (!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == operatingSystem)) { + if (xml.tokenType() == QXmlStreamReader::StartElement && xml.name().toString() == "version") { + xml.readNext(); + latestVersion = xml.text().toString(); + } + if (xml.tokenType() == QXmlStreamReader::StartElement && xml.name().toString() == "url") { + xml.readNext(); + downloadUrl = QUrl(xml.text().toString()); + } xml.readNext(); - releaseDate = xml.text().toString(); - } - if (xml.name() == "ReleaseNotes") { - xml.readNext(); - releaseNotes = xml.text().toString(); - } - if (xml.name() == "Version") { - xml.readNext(); - latestVersion = xml.text().toString(); - } - if (xml.name() == operatingSystem) { - xml.readNext(); - downloadUrl = QUrl(xml.text().toString()); } } + xml.readNext(); } + if (!shouldSkipVersion(latestVersion) && applicationVersion() != latestVersion) { new UpdateDialog(_glWidget, releaseNotes, latestVersion, downloadUrl); } diff --git a/interface/src/Application.h b/interface/src/Application.h index fb48acb721..325770a8df 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -117,7 +118,7 @@ public: static Application* getInstance() { return static_cast(QCoreApplication::instance()); } static QString& resourcesPath(); - Application(int& argc, char** argv, timeval &startup_time); + Application(int& argc, char** argv, QElapsedTimer &startup_time); ~Application(); void restoreSizeAndPosition(); @@ -392,9 +393,9 @@ private: int _frameCount; float _fps; - timeval _applicationStartupTime; - timeval _timerStart, _timerEnd; - timeval _lastTimeUpdated; + QElapsedTimer _applicationStartupTime; + QElapsedTimer _timerStart; + QElapsedTimer _lastTimeUpdated; bool _justStarted; Stars _stars; diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index 0aba5a8ae5..68e38615bf 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -643,13 +643,12 @@ void Audio::handleAudioInput() { void Audio::addReceivedAudioToBuffer(const QByteArray& audioByteArray) { const int NUM_INITIAL_PACKETS_DISCARD = 3; const int STANDARD_DEVIATION_SAMPLE_COUNT = 500; - - timeval currentReceiveTime; - gettimeofday(¤tReceiveTime, NULL); + _totalPacketsReceived++; - - double timeDiff = diffclock(&_lastReceiveTime, ¤tReceiveTime); - + + double timeDiff = (double)_timeSinceLastReceived.nsecsElapsed() / 1000000.0; // ns to ms + _timeSinceLastReceived.start(); + // Discard first few received packets for computing jitter (often they pile up on start) if (_totalPacketsReceived > NUM_INITIAL_PACKETS_DISCARD) { _stdev.addValue(timeDiff); @@ -673,8 +672,6 @@ void Audio::addReceivedAudioToBuffer(const QByteArray& audioByteArray) { } Application::getInstance()->getBandwidthMeter()->inputStream(BandwidthMeter::AUDIO).updateValue(audioByteArray.size()); - - _lastReceiveTime = currentReceiveTime; } // NOTE: numSamples is the total number of single channel samples, since callers will always call this with stereo @@ -1268,13 +1265,13 @@ bool Audio::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDeviceInfo) // setup a procedural audio output device _proceduralAudioOutput = new QAudioOutput(outputDeviceInfo, _outputFormat, this); - gettimeofday(&_lastReceiveTime, NULL); + _timeSinceLastReceived.start(); // setup spatial audio ringbuffer int numFrameSamples = _outputFormat.sampleRate() * _desiredOutputFormat.channelCount(); _spatialAudioRingBuffer.resizeForFrameSize(numFrameSamples); _spatialAudioStart = _spatialAudioFinish = 0; - + supportedFormat = true; } } diff --git a/interface/src/Audio.h b/interface/src/Audio.h index ebd7f8612a..277c606d4b 100644 --- a/interface/src/Audio.h +++ b/interface/src/Audio.h @@ -12,10 +12,6 @@ #ifndef hifi_Audio_h #define hifi_Audio_h -#ifdef _WIN32 -#include -#endif - #include #include @@ -23,6 +19,7 @@ #include #include +#include #include #include #include @@ -132,7 +129,7 @@ private: QString _outputAudioDeviceName; StDev _stdev; - timeval _lastReceiveTime; + QElapsedTimer _timeSinceLastReceived; float _averagedLatency; float _measuredJitter; int16_t _jitterBufferSamples; diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 4aeb4dd203..44117df55c 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -335,6 +336,8 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHandTargets, 0, false); addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::PlaySlaps, 0, false); addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::HandsCollideWithSelf, 0, false); + addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::ShowIKConstraints, 0, false); + addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::AlignForearmsWithWrists, 0, true); addDisabledActionAndSeparator(developerMenu, "Testing"); @@ -968,6 +971,17 @@ void Menu::goToUser(const QString& user) { connect(manager, &LocationManager::multipleDestinationsFound, this, &Menu::multipleDestinationsDecision); } +/// Open a url, shortcutting any "hifi" scheme URLs to the local application. +void Menu::openUrl(const QUrl& url) { + if (url.scheme() == "hifi") { + QString path = url.toString(QUrl::RemoveScheme); + path = path.remove(QRegExp("^:?/*")); + goTo(path); + } else { + QDesktopServices::openUrl(url); + } +} + void Menu::multipleDestinationsDecision(const QJsonObject& userData, const QJsonObject& placeData) { QMessageBox msgBox; msgBox.setText("Both user and location exists with same name"); @@ -1143,23 +1157,22 @@ void Menu::showScriptEditor() { void Menu::showChat() { QMainWindow* mainWindow = Application::getInstance()->getWindow(); if (!_chatWindow) { - mainWindow->addDockWidget(Qt::RightDockWidgetArea, _chatWindow = new ChatWindow()); + _chatWindow = new ChatWindow(mainWindow); } - if (!_chatWindow->toggleViewAction()->isChecked()) { - const QRect& windowGeometry = mainWindow->geometry(); - _chatWindow->move(windowGeometry.topRight().x() - _chatWindow->width(), - windowGeometry.topRight().y() + (windowGeometry.height() / 2) - (_chatWindow->height() / 2)); - - _chatWindow->resize(0, _chatWindow->height()); - _chatWindow->toggleViewAction()->trigger(); + if (_chatWindow->isHidden()) { + _chatWindow->show(); } } void Menu::toggleChat() { #ifdef HAVE_QXMPP _chatAction->setEnabled(XmppClient::getInstance().getXMPPClient().isConnected()); - if (!_chatAction->isEnabled() && _chatWindow && _chatWindow->toggleViewAction()->isChecked()) { - _chatWindow->toggleViewAction()->trigger(); + if (!_chatAction->isEnabled() && _chatWindow) { + if (_chatWindow->isHidden()) { + _chatWindow->show(); + } else { + _chatWindow->hide(); + } } #endif } diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 8b2368d22f..bc70f8f83f 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -154,6 +154,7 @@ public slots: void goTo(); void goToUser(const QString& user); void pasteToVoxel(); + void openUrl(const QUrl& url); void toggleLoginMenuItem(); @@ -256,6 +257,7 @@ private: namespace MenuOption { const QString AboutApp = "About Interface"; + const QString AlignForearmsWithWrists = "Align Forearms with Wrists"; const QString AmbientOcclusion = "Ambient Occlusion"; const QString Atmosphere = "Atmosphere"; const QString AudioNoiseReduction = "Audio Noise Reduction"; @@ -354,6 +356,7 @@ namespace MenuOption { const QString SettingsImport = "Import Settings"; const QString Shadows = "Shadows"; const QString ShowCulledSharedFaces = "Show Culled Shared Voxel Faces"; + const QString ShowIKConstraints = "Show IK Constraints"; const QString Stars = "Stars"; const QString Stats = "Stats"; const QString StopAllScripts = "Stop All Scripts"; diff --git a/interface/src/Util.cpp b/interface/src/Util.cpp index febcb2f36e..79a2e31d80 100644 --- a/interface/src/Util.cpp +++ b/interface/src/Util.cpp @@ -172,14 +172,6 @@ void renderWorldBox() { } -double diffclock(timeval *clock1,timeval *clock2) -{ - double diffms = (clock2->tv_sec - clock1->tv_sec) * 1000.0; - diffms += (clock2->tv_usec - clock1->tv_usec) / 1000.0; // us to ms - - return diffms; -} - // Return a random vector of average length 1 const glm::vec3 randVector() { return glm::vec3(randFloat() - 0.5f, randFloat() - 0.5f, randFloat() - 0.5f) * 2.f; @@ -411,69 +403,63 @@ void runTimingTests() { int iResults[numTests]; float fTest = 1.0; float fResults[numTests]; - timeval startTime, endTime; - float elapsedMsecs; - gettimeofday(&startTime, NULL); - for (int i = 1; i < numTests; i++) { - gettimeofday(&endTime, NULL); - } - elapsedMsecs = diffclock(&startTime, &endTime); - qDebug("gettimeofday() usecs: %f", 1000.0f * elapsedMsecs / (float) numTests); + QElapsedTimer startTime; + startTime.start(); + float elapsedUsecs; + + float NSEC_TO_USEC = 1.0f / 1000.0f; + elapsedUsecs = (float)startTime.nsecsElapsed() * NSEC_TO_USEC; + qDebug("QElapsedTimer::nsecElapsed() usecs: %f", elapsedUsecs / (float) numTests); // Random number generation - gettimeofday(&startTime, NULL); + startTime.start(); for (int i = 1; i < numTests; i++) { iResults[i] = rand(); } - gettimeofday(&endTime, NULL); - elapsedMsecs = diffclock(&startTime, &endTime); - qDebug("rand() stored in array usecs: %f, first result:%d", 1000.0f * elapsedMsecs / (float) numTests, iResults[0]); + elapsedUsecs = (float)startTime.nsecsElapsed() * NSEC_TO_USEC; + qDebug("rand() stored in array usecs: %f, first result:%d", elapsedUsecs / (float) numTests, iResults[0]); // Random number generation using randFloat() - gettimeofday(&startTime, NULL); + startTime.start(); for (int i = 1; i < numTests; i++) { fResults[i] = randFloat(); } - gettimeofday(&endTime, NULL); - elapsedMsecs = diffclock(&startTime, &endTime); - qDebug("randFloat() stored in array usecs: %f, first result: %f", 1000.0f * elapsedMsecs / (float) numTests, fResults[0]); + elapsedUsecs = (float)startTime.nsecsElapsed() * NSEC_TO_USEC; + qDebug("randFloat() stored in array usecs: %f, first result: %f", elapsedUsecs / (float) numTests, fResults[0]); // PowF function fTest = 1145323.2342f; - gettimeofday(&startTime, NULL); + startTime.start(); for (int i = 1; i < numTests; i++) { fTest = powf(fTest, 0.5f); } - gettimeofday(&endTime, NULL); - elapsedMsecs = diffclock(&startTime, &endTime); - qDebug("powf(f, 0.5) usecs: %f", 1000.0f * elapsedMsecs / (float) numTests); + elapsedUsecs = (float)startTime.nsecsElapsed() * NSEC_TO_USEC; + qDebug("powf(f, 0.5) usecs: %f", elapsedUsecs / (float) numTests); // Vector Math float distance; glm::vec3 pointA(randVector()), pointB(randVector()); - gettimeofday(&startTime, NULL); + startTime.start(); for (int i = 1; i < numTests; i++) { //glm::vec3 temp = pointA - pointB; //float distanceSquared = glm::dot(temp, temp); distance = glm::distance(pointA, pointB); } - gettimeofday(&endTime, NULL); - elapsedMsecs = diffclock(&startTime, &endTime); - qDebug("vector math usecs: %f [%f msecs total for %d tests], last result:%f", - 1000.0f * elapsedMsecs / (float) numTests, elapsedMsecs, numTests, distance); + elapsedUsecs = (float)startTime.nsecsElapsed() * NSEC_TO_USEC; + qDebug("vector math usecs: %f [%f usecs total for %d tests], last result:%f", + elapsedUsecs / (float) numTests, elapsedUsecs, numTests, distance); // Vec3 test glm::vec3 vecA(randVector()), vecB(randVector()); float result; - - gettimeofday(&startTime, NULL); + + startTime.start(); for (int i = 1; i < numTests; i++) { glm::vec3 temp = vecA-vecB; result = glm::dot(temp,temp); } - gettimeofday(&endTime, NULL); - elapsedMsecs = diffclock(&startTime, &endTime); - qDebug("vec3 assign and dot() usecs: %f, last result:%f", 1000.0f * elapsedMsecs / (float) numTests, result); + elapsedUsecs = (float)startTime.nsecsElapsed() * NSEC_TO_USEC; + qDebug("vec3 assign and dot() usecs: %f, last result:%f", elapsedUsecs / (float) numTests, result); } float loadSetting(QSettings* settings, const char* name, float defaultValue) { diff --git a/interface/src/Util.h b/interface/src/Util.h index 4bd1ed604c..02cfd99f9a 100644 --- a/interface/src/Util.h +++ b/interface/src/Util.h @@ -12,12 +12,6 @@ #ifndef hifi_Util_h #define hifi_Util_h -#ifdef _WIN32 -#include "Systime.h" -#else -#include -#endif - #include #include #include @@ -44,8 +38,6 @@ void drawVector(glm::vec3* vector); void printVector(glm::vec3 vec); -double diffclock(timeval *clock1,timeval *clock2); - void renderCollisionOverlay(int width, int height, float magnitude, float red = 0, float blue = 0, float green = 0); void renderOrientationDirections( glm::vec3 position, const glm::quat& orientation, float size ); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 467b1f2613..26dbc09d5b 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include @@ -413,8 +414,13 @@ void MyAvatar::renderDebugBodyPoints() { // virtual void MyAvatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) { - if (_shouldRender) { - Avatar::render(cameraPosition, renderMode); + // don't render if we've been asked to disable local rendering + if (!_shouldRender) { + return; // exit early + } + Avatar::render(cameraPosition, renderMode); + if (Menu::getInstance()->isOptionChecked(MenuOption::ShowIKConstraints)) { + _skeletonModel.renderIKConstraints(); } } @@ -1134,6 +1140,8 @@ void MyAvatar::goToLocationFromResponse(const QJsonObject& jsonObject) { coordinateItems[2].toFloat()) - newOrientation * IDENTITY_FRONT * DISTANCE_TO_USER; setPosition(newPosition); emit transformChanged(); + } else { + QMessageBox::warning(Application::getInstance()->getWindow(), "", "That user or location could not be found."); } } diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index b9ac280711..f8ebba676f 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -103,6 +103,11 @@ void SkeletonModel::getBodyShapes(QVector& shapes) const { shapes.push_back(&_boundingShape); } +void SkeletonModel::renderIKConstraints() { + renderJointConstraints(getRightHandJointIndex()); + renderJointConstraints(getLeftHandJointIndex()); +} + class IndexValue { public: int index; @@ -133,7 +138,7 @@ void SkeletonModel::applyHandPosition(int jointIndex, const glm::vec3& position) // align hand with forearm float sign = (jointIndex == geometry.rightHandJointIndex) ? 1.0f : -1.0f; - applyRotationDelta(jointIndex, rotationBetween(handRotation * glm::vec3(-sign, 0.0f, 0.0f), forearmVector), false); + applyRotationDelta(jointIndex, rotationBetween(handRotation * glm::vec3(-sign, 0.0f, 0.0f), forearmVector)); } void SkeletonModel::applyPalmData(int jointIndex, const QVector& fingerJointIndices, @@ -148,12 +153,15 @@ void SkeletonModel::applyPalmData(int jointIndex, const QVector& fingerJoin return; } - // rotate forearm to align with palm direction + // rotate palm to align with palm direction glm::quat palmRotation; - getJointRotation(parentJointIndex, palmRotation, true); - applyRotationDelta(parentJointIndex, rotationBetween(palmRotation * geometry.palmDirection, palm.getNormal()), false); - getJointRotation(parentJointIndex, palmRotation, true); - + if (Menu::getInstance()->isOptionChecked(MenuOption::AlignForearmsWithWrists)) { + getJointRotation(parentJointIndex, palmRotation, true); + } else { + getJointRotation(jointIndex, palmRotation, true); + } + palmRotation = rotationBetween(palmRotation * geometry.palmDirection, palm.getNormal()) * palmRotation; + // sort the finger indices by raw x, get the average direction QVector fingerIndices; glm::vec3 direction; @@ -173,17 +181,20 @@ void SkeletonModel::applyPalmData(int jointIndex, const QVector& fingerJoin float directionLength = glm::length(direction); const unsigned int MIN_ROTATION_FINGERS = 3; if (directionLength > EPSILON && palm.getNumFingers() >= MIN_ROTATION_FINGERS) { - applyRotationDelta(parentJointIndex, rotationBetween(palmRotation * glm::vec3(-sign, 0.0f, 0.0f), direction), false); - getJointRotation(parentJointIndex, palmRotation, true); + palmRotation = rotationBetween(palmRotation * glm::vec3(-sign, 0.0f, 0.0f), direction) * palmRotation; } - // let wrist inherit forearm rotation - _jointStates[jointIndex].rotation = glm::quat(); - - // set elbow position from wrist position - glm::vec3 forearmVector = palmRotation * glm::vec3(sign, 0.0f, 0.0f); - setJointPosition(parentJointIndex, palm.getPosition() + forearmVector * - geometry.joints.at(jointIndex).distanceToParent * extractUniformScale(_scale)); + // set hand position, rotation + if (Menu::getInstance()->isOptionChecked(MenuOption::AlignForearmsWithWrists)) { + glm::vec3 forearmVector = palmRotation * glm::vec3(sign, 0.0f, 0.0f); + setJointPosition(parentJointIndex, palm.getPosition() + forearmVector * + geometry.joints.at(jointIndex).distanceToParent * extractUniformScale(_scale)); + setJointRotation(parentJointIndex, palmRotation, true); + _jointStates[jointIndex].rotation = glm::quat(); + + } else { + setJointPosition(jointIndex, palm.getPosition(), palmRotation, true); + } } void SkeletonModel::updateJointState(int index) { @@ -210,3 +221,59 @@ void SkeletonModel::maybeUpdateLeanRotation(const JointState& parentState, const glm::normalize(inverse * axes[0])) * joint.rotation; } +void SkeletonModel::renderJointConstraints(int jointIndex) { + if (jointIndex == -1) { + return; + } + const FBXGeometry& geometry = _geometry->getFBXGeometry(); + const float BASE_DIRECTION_SIZE = 300.0f; + float directionSize = BASE_DIRECTION_SIZE * extractUniformScale(_scale); + glLineWidth(3.0f); + do { + const FBXJoint& joint = geometry.joints.at(jointIndex); + const JointState& jointState = _jointStates.at(jointIndex); + glm::vec3 position = extractTranslation(jointState.transform) + _translation; + + glPushMatrix(); + glTranslatef(position.x, position.y, position.z); + glm::quat parentRotation = (joint.parentIndex == -1) ? _rotation : _jointStates.at(joint.parentIndex).combinedRotation; + glm::vec3 rotationAxis = glm::axis(parentRotation); + glRotatef(glm::degrees(glm::angle(parentRotation)), rotationAxis.x, rotationAxis.y, rotationAxis.z); + float fanScale = directionSize * 0.75f; + glScalef(fanScale, fanScale, fanScale); + const int AXIS_COUNT = 3; + for (int i = 0; i < AXIS_COUNT; i++) { + if (joint.rotationMin[i] <= -PI + EPSILON && joint.rotationMax[i] >= PI - EPSILON) { + continue; // unconstrained + } + glm::vec3 axis; + axis[i] = 1.0f; + + glm::vec3 otherAxis; + if (i == 0) { + otherAxis.y = 1.0f; + } else { + otherAxis.x = 1.0f; + } + glColor4f(otherAxis.r, otherAxis.g, otherAxis.b, 0.75f); + + glBegin(GL_TRIANGLE_FAN); + glVertex3f(0.0f, 0.0f, 0.0f); + const int FAN_SEGMENTS = 16; + for (int j = 0; j < FAN_SEGMENTS; j++) { + glm::vec3 rotated = glm::angleAxis(glm::mix(joint.rotationMin[i], joint.rotationMax[i], + (float)j / (FAN_SEGMENTS - 1)), axis) * otherAxis; + glVertex3f(rotated.x, rotated.y, rotated.z); + } + glEnd(); + } + glPopMatrix(); + + renderOrientationDirections(position, jointState.combinedRotation, directionSize); + jointIndex = joint.parentIndex; + + } while (jointIndex != -1 && geometry.joints.at(jointIndex).isFree); + + glLineWidth(1.0f); +} + diff --git a/interface/src/avatar/SkeletonModel.h b/interface/src/avatar/SkeletonModel.h index 2020ccf3b2..0a87fcf89d 100644 --- a/interface/src/avatar/SkeletonModel.h +++ b/interface/src/avatar/SkeletonModel.h @@ -33,6 +33,8 @@ public: /// \param shapes[out] list of shapes for body collisions void getBodyShapes(QVector& shapes) const; + void renderIKConstraints(); + protected: void applyHandPosition(int jointIndex, const glm::vec3& position); @@ -46,6 +48,8 @@ protected: virtual void maybeUpdateLeanRotation(const JointState& parentState, const FBXJoint& joint, JointState& state); private: + + void renderJointConstraints(int jointIndex); Avatar* _owningAvatar; }; diff --git a/interface/src/devices/OculusManager.cpp b/interface/src/devices/OculusManager.cpp index d3da8fe7c3..854b19236d 100644 --- a/interface/src/devices/OculusManager.cpp +++ b/interface/src/devices/OculusManager.cpp @@ -11,10 +11,6 @@ #include "InterfaceConfig.h" -#ifdef WIN32 -#include -#endif - #include #include diff --git a/interface/src/devices/TV3DManager.cpp b/interface/src/devices/TV3DManager.cpp index fcbef0385c..b5cc28b07f 100644 --- a/interface/src/devices/TV3DManager.cpp +++ b/interface/src/devices/TV3DManager.cpp @@ -15,11 +15,6 @@ #include - -#ifdef WIN32 -#include -#endif - #include "Application.h" #include "TV3DManager.h" diff --git a/interface/src/location/LocationManager.cpp b/interface/src/location/LocationManager.cpp index 8009551b6c..f80c331df4 100644 --- a/interface/src/location/LocationManager.cpp +++ b/interface/src/location/LocationManager.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include + #include "Application.h" #include "LocationManager.h" @@ -118,6 +120,8 @@ void LocationManager::checkForMultipleDestinations() { Application::getInstance()->getAvatar()->goToLocationFromResponse(_placeData); return; } + + QMessageBox::warning(Application::getInstance()->getWindow(), "", "That user or location could not be found."); } } diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 40e2a9ab27..2bb0633f24 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -16,8 +16,8 @@ #include int main(int argc, const char * argv[]) { - timeval startup_time; - gettimeofday(&startup_time, NULL); + QElapsedTimer startupTime; + startupTime.start(); // Debug option to demonstrate that the client's local time does not // need to be in sync with any other network node. This forces clock @@ -33,7 +33,7 @@ int main(int argc, const char * argv[]) { int exitCode; { QSettings::setDefaultFormat(QSettings::IniFormat); - Application app(argc, const_cast(argv), startup_time); + Application app(argc, const_cast(argv), startupTime); QTranslator translator; translator.load("interface_en"); diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index ef0911f673..3484ac5fc8 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -882,12 +882,12 @@ bool Model::getJointRotation(int jointIndex, glm::quat& rotation, bool fromBind) return true; } -bool Model::setJointPosition(int jointIndex, const glm::vec3& position, int lastFreeIndex, - bool allIntermediatesFree, const glm::vec3& alignment) { +bool Model::setJointPosition(int jointIndex, const glm::vec3& translation, const glm::quat& rotation, bool useRotation, + int lastFreeIndex, bool allIntermediatesFree, const glm::vec3& alignment) { if (jointIndex == -1 || _jointStates.isEmpty()) { return false; } - glm::vec3 relativePosition = position - _translation; + glm::vec3 relativePosition = translation - _translation; const FBXGeometry& geometry = _geometry->getFBXGeometry(); const QVector& freeLineage = geometry.joints.at(jointIndex).freeLineage; if (freeLineage.isEmpty()) { @@ -896,13 +896,21 @@ bool Model::setJointPosition(int jointIndex, const glm::vec3& position, int last if (lastFreeIndex == -1) { lastFreeIndex = freeLineage.last(); } - + // this is a cyclic coordinate descent algorithm: see // http://www.ryanjuckett.com/programming/animation/21-cyclic-coordinate-descent-in-2d const int ITERATION_COUNT = 1; glm::vec3 worldAlignment = _rotation * alignment; for (int i = 0; i < ITERATION_COUNT; i++) { - // first, we go from the joint upwards, rotating the end as close as possible to the target + // first, try to rotate the end effector as close as possible to the target rotation, if any + glm::quat endRotation; + if (useRotation) { + getJointRotation(jointIndex, endRotation, true); + applyRotationDelta(jointIndex, rotation * glm::inverse(endRotation)); + getJointRotation(jointIndex, endRotation, true); + } + + // then, we go from the joint upwards, rotating the end as close as possible to the target glm::vec3 endPosition = extractTranslation(_jointStates[jointIndex].transform); for (int j = 1; freeLineage.at(j - 1) != lastFreeIndex; j++) { int index = freeLineage.at(j); @@ -914,8 +922,17 @@ bool Model::setJointPosition(int jointIndex, const glm::vec3& position, int last glm::vec3 jointPosition = extractTranslation(state.transform); glm::vec3 jointVector = endPosition - jointPosition; glm::quat oldCombinedRotation = state.combinedRotation; - applyRotationDelta(index, rotationBetween(jointVector, relativePosition - jointPosition)); - endPosition = state.combinedRotation * glm::inverse(oldCombinedRotation) * jointVector + jointPosition; + glm::quat combinedDelta; + float combinedWeight; + if (useRotation) { + combinedDelta = safeMix(rotation * glm::inverse(endRotation), + rotationBetween(jointVector, relativePosition - jointPosition), 0.5f); + combinedWeight = 2.0f; + + } else { + combinedDelta = rotationBetween(jointVector, relativePosition - jointPosition); + combinedWeight = 1.0f; + } if (alignment != glm::vec3() && j > 1) { jointVector = endPosition - jointPosition; glm::vec3 positionSum; @@ -929,9 +946,16 @@ bool Model::setJointPosition(int jointIndex, const glm::vec3& position, int last glm::vec3 projectedAlignment = glm::cross(jointVector, glm::cross(worldAlignment, jointVector)); const float LENGTH_EPSILON = 0.001f; if (glm::length(projectedCenterOfMass) > LENGTH_EPSILON && glm::length(projectedAlignment) > LENGTH_EPSILON) { - applyRotationDelta(index, rotationBetween(projectedCenterOfMass, projectedAlignment)); + combinedDelta = safeMix(combinedDelta, rotationBetween(projectedCenterOfMass, projectedAlignment), + 1.0f / (combinedWeight + 1.0f)); } } + applyRotationDelta(index, combinedDelta); + glm::quat actualDelta = state.combinedRotation * glm::inverse(oldCombinedRotation); + endPosition = actualDelta * jointVector + jointPosition; + if (useRotation) { + endRotation = actualDelta * endRotation; + } } } @@ -1012,8 +1036,9 @@ void Model::applyRotationDelta(int jointIndex, const glm::quat& delta, bool cons state.combinedRotation = delta * state.combinedRotation; return; } - glm::quat newRotation = glm::quat(glm::clamp(safeEulerAngles(state.rotation * - glm::inverse(state.combinedRotation) * delta * state.combinedRotation), joint.rotationMin, joint.rotationMax)); + glm::quat targetRotation = delta * state.combinedRotation; + glm::vec3 eulers = safeEulerAngles(state.rotation * glm::inverse(state.combinedRotation) * targetRotation); + glm::quat newRotation = glm::quat(glm::clamp(eulers, joint.rotationMin, joint.rotationMax)); state.combinedRotation = state.combinedRotation * glm::inverse(state.rotation) * newRotation; state.rotation = newRotation; } @@ -1141,7 +1166,7 @@ void Model::applyCollision(CollisionInfo& collision) { getJointPosition(jointIndex, end); glm::vec3 newEnd = start + glm::angleAxis(angle, axis) * (end - start); // try to move it - setJointPosition(jointIndex, newEnd, -1, true); + setJointPosition(jointIndex, newEnd, glm::quat(), false, -1, true); } } } diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index a14b58c11a..64295cb915 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -242,8 +242,9 @@ protected: bool getJointPosition(int jointIndex, glm::vec3& position) const; bool getJointRotation(int jointIndex, glm::quat& rotation, bool fromBind = false) const; - bool setJointPosition(int jointIndex, const glm::vec3& position, int lastFreeIndex = -1, - bool allIntermediatesFree = false, const glm::vec3& alignment = glm::vec3(0.0f, -1.0f, 0.0f)); + bool setJointPosition(int jointIndex, const glm::vec3& translation, const glm::quat& rotation = glm::quat(), + bool useRotation = false, int lastFreeIndex = -1, bool allIntermediatesFree = false, + const glm::vec3& alignment = glm::vec3(0.0f, -1.0f, 0.0f)); bool setJointRotation(int jointIndex, const glm::quat& rotation, bool fromBind = false); void setJointTranslation(int jointIndex, const glm::vec3& translation); diff --git a/interface/src/starfield/Controller.cpp b/interface/src/starfield/Controller.cpp index 771029c689..2279a68422 100755 --- a/interface/src/starfield/Controller.cpp +++ b/interface/src/starfield/Controller.cpp @@ -9,24 +9,23 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#ifdef _WIN32 -#include -#endif +#include #include "starfield/Controller.h" using namespace starfield; bool Controller::computeStars(unsigned numStars, unsigned seed) { - timeval startTime; - gettimeofday(&startTime, NULL); + QElapsedTimer startTime; + startTime.start(); Generator::computeStarPositions(_inputSequence, numStars, seed); this->retile(numStars, _tileResolution); - qDebug() << "Total time to retile and generate stars: " - << ((usecTimestampNow() - usecTimestamp(&startTime)) / 1000) << "msec"; + double NSEC_TO_MSEC = 1.0 / 1000000.0; + double timeDiff = (double)startTime.nsecsElapsed() * NSEC_TO_MSEC; + qDebug() << "Total time to retile and generate stars: " << timeDiff << "msec"; return true; } diff --git a/interface/src/starfield/Generator.cpp b/interface/src/starfield/Generator.cpp index b18e1834be..d9773e4452 100644 --- a/interface/src/starfield/Generator.cpp +++ b/interface/src/starfield/Generator.cpp @@ -9,9 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#ifdef _WIN32 -#include -#endif +#include #include "starfield/Generator.h" @@ -24,8 +22,8 @@ void Generator::computeStarPositions(InputVertices& destination, unsigned limit, InputVertices* vertices = & destination; //_limit = limit; - timeval startTime; - gettimeofday(&startTime, NULL); + QElapsedTimer startTime; + startTime.start(); srand(seed); @@ -70,7 +68,8 @@ void Generator::computeStarPositions(InputVertices& destination, unsigned limit, vertices->push_back(InputVertex(azimuth, altitude, computeStarColor(STAR_COLORIZATION))); } - qDebug() << "Total time to generate stars: " << ((usecTimestampNow() - usecTimestamp(&startTime)) / 1000) << " msec"; + double timeDiff = (double)startTime.nsecsElapsed() / 1000000.0; // ns to ms + qDebug() << "Total time to generate stars: " << timeDiff << " msec"; } // computeStarColor diff --git a/interface/src/ui/BandwidthMeter.cpp b/interface/src/ui/BandwidthMeter.cpp index 3ed66c53e1..0f41a1a5cf 100644 --- a/interface/src/ui/BandwidthMeter.cpp +++ b/interface/src/ui/BandwidthMeter.cpp @@ -62,26 +62,21 @@ BandwidthMeter::~BandwidthMeter() { free(_channels); } -BandwidthMeter::Stream::Stream(float msToAverage) : - _value(0.0f), - _msToAverage(msToAverage) { - - gettimeofday(& _prevTime, NULL); +BandwidthMeter::Stream::Stream(float msToAverage) : _value(0.0f), _msToAverage(msToAverage) { + _prevTime.start(); } void BandwidthMeter::Stream::updateValue(double amount) { // Determine elapsed time - timeval now; - gettimeofday(& now, NULL); - double dt = diffclock(& _prevTime, & now); + double dt = (double)_prevTime.nsecsElapsed() / 1000000.0; // ns to ms // Ignore this value when timer imprecision yields dt = 0 if (dt == 0.0) { return; } - memcpy(& _prevTime, & now, sizeof(timeval)); + _prevTime.start(); // Compute approximate average _value = glm::mix(_value, amount / dt, diff --git a/interface/src/ui/BandwidthMeter.h b/interface/src/ui/BandwidthMeter.h index 6838f28c70..c6a28a21c3 100644 --- a/interface/src/ui/BandwidthMeter.h +++ b/interface/src/ui/BandwidthMeter.h @@ -12,9 +12,7 @@ #ifndef hifi_BandwidthMeter_h #define hifi_BandwidthMeter_h -#ifdef _WIN32 -#include -#endif +#include #include @@ -59,7 +57,7 @@ public: private: double _value; // Current value. double _msToAverage; // Milliseconds to average. - timeval _prevTime; // Time of last feed. + QElapsedTimer _prevTime; // Time of last feed. }; // Data model accessors diff --git a/interface/src/ui/ChatInputArea.cpp b/interface/src/ui/ChatInputArea.cpp new file mode 100644 index 0000000000..3e8fc84fe2 --- /dev/null +++ b/interface/src/ui/ChatInputArea.cpp @@ -0,0 +1,19 @@ +// +// ChatInputArea.cpp +// interface/src/ui +// +// Created by Ryan Huffman on 4/24/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 +// + +#include "ChatInputArea.h" + +ChatInputArea::ChatInputArea(QWidget* parent) : QTextEdit(parent) { +}; + +void ChatInputArea::insertFromMimeData(const QMimeData* source) { + insertPlainText(source->text()); +}; diff --git a/interface/src/ui/ChatInputArea.h b/interface/src/ui/ChatInputArea.h new file mode 100644 index 0000000000..31d1584df7 --- /dev/null +++ b/interface/src/ui/ChatInputArea.h @@ -0,0 +1,27 @@ +// +// ChatInputArea.h +// interface/src/ui +// +// Created by Ryan Huffman on 4/11/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_ChatInputArea_h +#define hifi_ChatInputArea_h + +#include +#include + +class ChatInputArea : public QTextEdit { + Q_OBJECT +public: + ChatInputArea(QWidget* parent); + +protected: + void insertFromMimeData(const QMimeData* source); +}; + +#endif // hifi_ChatInputArea_h diff --git a/interface/src/ui/ChatMessageArea.cpp b/interface/src/ui/ChatMessageArea.cpp index f15b788990..1e16a8a2db 100644 --- a/interface/src/ui/ChatMessageArea.cpp +++ b/interface/src/ui/ChatMessageArea.cpp @@ -9,13 +9,18 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "Application.h" #include "ChatMessageArea.h" #include #include -ChatMessageArea::ChatMessageArea() : QTextBrowser() { +ChatMessageArea::ChatMessageArea(bool useFixedHeight) : QTextBrowser(), _useFixedHeight(useFixedHeight) { + setOpenLinks(false); + connect(document()->documentLayout(), &QAbstractTextDocumentLayout::documentSizeChanged, this, &ChatMessageArea::updateLayout); + connect(this, &QTextBrowser::anchorClicked, + Menu::getInstance(), &Menu::openUrl); } void ChatMessageArea::setHtml(const QString& html) { @@ -34,7 +39,15 @@ void ChatMessageArea::setHtml(const QString& html) { } void ChatMessageArea::updateLayout() { - setFixedHeight(document()->size().height()); + if (_useFixedHeight) { + setFixedHeight(document()->size().height()); + updateGeometry(); + emit sizeChanged(size()); + } +} + +void ChatMessageArea::setSize(const QSize& size) { + setFixedHeight(size.height()); updateGeometry(); } diff --git a/interface/src/ui/ChatMessageArea.h b/interface/src/ui/ChatMessageArea.h index 1c49c60b08..57199538fd 100644 --- a/interface/src/ui/ChatMessageArea.h +++ b/interface/src/ui/ChatMessageArea.h @@ -19,15 +19,19 @@ const int CHAT_MESSAGE_LINE_HEIGHT = 130; class ChatMessageArea : public QTextBrowser { Q_OBJECT public: - ChatMessageArea(); + ChatMessageArea(bool useFixedHeight = true); virtual void setHtml(const QString& html); public slots: void updateLayout(); + void setSize(const QSize& size); + +signals: + void sizeChanged(QSize newSize); protected: virtual void wheelEvent(QWheelEvent* event); - + bool _useFixedHeight; }; #endif // hifi_ChatMessageArea_h diff --git a/interface/src/ui/ChatWindow.cpp b/interface/src/ui/ChatWindow.cpp index 635f1f3d10..e0802c6bc5 100644 --- a/interface/src/ui/ChatWindow.cpp +++ b/interface/src/ui/ChatWindow.cpp @@ -12,12 +12,10 @@ #include #include #include -#include #include #include #include #include -#include #include "Application.h" #include "FlowLayout.h" @@ -31,32 +29,40 @@ const int NUM_MESSAGES_TO_TIME_STAMP = 20; const QRegularExpression regexLinks("((?:(?:ftp)|(?:https?))://\\S+)"); +const QRegularExpression regexHifiLinks("([#@]\\S+)"); -ChatWindow::ChatWindow() : +ChatWindow::ChatWindow(QWidget* parent) : + FramelessDialog(parent, 0, POSITION_RIGHT), ui(new Ui::ChatWindow), numMessagesAfterLastTimeStamp(0), _mousePressed(false), _mouseStartPosition() { - ui->setupUi(this); + setAttribute(Qt::WA_DeleteOnClose, false); - // remove the title bar (see the Qt docs on setTitleBarWidget), but we keep it for undocking - // - titleBar = titleBarWidget(); - setTitleBarWidget(new QWidget()); + ui->setupUi(this); FlowLayout* flowLayout = new FlowLayout(0, 4, 4); ui->usersWidget->setLayout(flowLayout); - ui->messagesGridLayout->setColumnStretch(0, 1); - ui->messagesGridLayout->setColumnStretch(1, 3); - ui->messagePlainTextEdit->installEventFilter(this); + ui->messagePlainTextEdit->setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); + + QTextCursor cursor(ui->messagePlainTextEdit->textCursor()); + + cursor.movePosition(QTextCursor::Start); + + QTextBlockFormat format = cursor.blockFormat(); + format.setLineHeight(130, QTextBlockFormat::ProportionalHeight); + + cursor.setBlockFormat(format); + + ui->messagePlainTextEdit->setTextCursor(cursor); 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(); if (xmppClient.isConnected()) { @@ -89,36 +95,17 @@ ChatWindow::~ChatWindow() { delete ui; } -void ChatWindow::mousePressEvent(QMouseEvent *e) { - if (e->button() == Qt::LeftButton && isFloating()) { - _mousePressed = true; - _mouseStartPosition = e->pos(); - } -} - -void ChatWindow::mouseMoveEvent(QMouseEvent *e) { - if (_mousePressed) { - move(mapToParent(e->pos() - _mouseStartPosition)); - } -} - -void ChatWindow::mouseReleaseEvent( QMouseEvent *e ) { - if ( e->button() == Qt::LeftButton ) { - _mousePressed = false; - } -} - void ChatWindow::keyPressEvent(QKeyEvent* event) { - QDockWidget::keyPressEvent(event); if (event->key() == Qt::Key_Escape) { hide(); + } else { + FramelessDialog::keyPressEvent(event); } } void ChatWindow::showEvent(QShowEvent* event) { - QDockWidget::showEvent(event); + FramelessDialog::showEvent(event); if (!event->spontaneous()) { - activateWindow(); ui->messagePlainTextEdit->setFocus(); } } @@ -141,18 +128,20 @@ bool ChatWindow::eventFilter(QObject* sender, QEvent* event) { message.setBody(messageText); XmppClient::getInstance().getXMPPClient().sendPacket(message); #endif - ui->messagePlainTextEdit->document()->clear(); + QTextCursor cursor = ui->messagePlainTextEdit->textCursor(); + cursor.select(QTextCursor::Document); + cursor.removeSelectedText(); } return true; } - } else { - if (event->type() != QEvent::MouseButtonRelease) { - return false; + } else if (event->type() == QEvent::MouseButtonRelease) { + QVariant userVar = sender->property("user"); + if (userVar.isValid()) { + Menu::getInstance()->goToUser("@" + userVar.toString()); + return true; } - QString user = sender->property("user").toString(); - Menu::getInstance()->goToUser(user); } - return false; + return FramelessDialog::eventFilter(sender, event); } #ifdef HAVE_QXMPP @@ -175,16 +164,17 @@ void ChatWindow::addTimeStamp() { timeString.chop(1); if (!timeString.isEmpty()) { QLabel* timeLabel = new QLabel(timeString); - timeLabel->setStyleSheet("color: palette(shadow);" - "background-color: palette(highlight);" + timeLabel->setStyleSheet("color: #333333;" + "background-color: white;" + "font-size: 14pt;" "padding: 4px;"); timeLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); - timeLabel->setAlignment(Qt::AlignHCenter); + timeLabel->setAlignment(Qt::AlignLeft); - bool atBottom = isAtBottom(); + bool atBottom = isNearBottom(); - ui->messagesGridLayout->addWidget(timeLabel, ui->messagesGridLayout->rowCount(), 0, 1, 2); - ui->messagesGridLayout->parentWidget()->updateGeometry(); + ui->messagesVBoxLayout->addWidget(timeLabel); + ui->messagesVBoxLayout->parentWidget()->updateGeometry(); Application::processEvents(); numMessagesAfterLastTimeStamp = 0; @@ -249,6 +239,7 @@ void ChatWindow::participantsChanged() { "padding-bottom: 2px;" "padding-left: 2px;" "border: 1px solid palette(shadow);" + "font-size: 14pt;" "font-weight: bold"); userLabel->setProperty("user", participantName); userLabel->setCursor(Qt::PointingHandCursor); @@ -262,15 +253,11 @@ void ChatWindow::messageReceived(const QXmppMessage& message) { return; } - QLabel* userLabel = new QLabel(getParticipantName(message.from())); - userLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - userLabel->setStyleSheet("padding: 2px; font-weight: bold"); - userLabel->setAlignment(Qt::AlignTop | Qt::AlignRight); + // Update background if this is a message from the current user + bool fromSelf = getParticipantName(message.from()) == AccountManager::getInstance().getUsername(); - ChatMessageArea* messageArea = new ChatMessageArea(); - - messageArea->setOpenLinks(true); - messageArea->setOpenExternalLinks(true); + // Create message area + ChatMessageArea* messageArea = new ChatMessageArea(true); messageArea->setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); messageArea->setTextInteractionFlags(Qt::TextBrowserInteraction); messageArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); @@ -281,22 +268,30 @@ void ChatWindow::messageReceived(const QXmppMessage& message) { "padding-left: 2px;" "padding-top: 2px;" "padding-right: 20px;" + "margin: 0px;" + "color: #333333;" + "font-size: 14pt;" "background-color: rgba(0, 0, 0, 0%);" "border: 0;"); - bool fromSelf = getParticipantName(message.from()) == AccountManager::getInstance().getUsername(); + QString userLabel = getParticipantName(message.from()); if (fromSelf) { - userLabel->setStyleSheet(userLabel->styleSheet() + "; background-color: #e1e8ea"); - messageArea->setStyleSheet(messageArea->styleSheet() + "; background-color: #e1e8ea"); + userLabel = "" + userLabel + ": "; + messageArea->setStyleSheet(messageArea->styleSheet() + "background-color: #e1e8ea"); + } else { + userLabel = "" + userLabel + ": "; } - messageArea->setHtml(message.body().replace(regexLinks, "\\1")); + messageArea->document()->setDefaultStyleSheet("a { text-decoration: none; font-weight: bold; color: #267077;}"); + QString messageText = message.body().toHtmlEscaped(); + messageText = messageText.replace(regexLinks, "\\1"); + messageText = messageText.replace(regexHifiLinks, "\\1"); + messageArea->setHtml(userLabel + messageText); - bool atBottom = isAtBottom(); - ui->messagesGridLayout->addWidget(userLabel, ui->messagesGridLayout->rowCount(), 0); - ui->messagesGridLayout->addWidget(messageArea, ui->messagesGridLayout->rowCount() - 1, 1); + bool atBottom = isNearBottom(); - ui->messagesGridLayout->parentWidget()->updateGeometry(); + ui->messagesVBoxLayout->addWidget(messageArea); + ui->messagesVBoxLayout->parentWidget()->updateGeometry(); Application::processEvents(); if (atBottom || fromSelf) { @@ -313,26 +308,13 @@ void ChatWindow::messageReceived(const QXmppMessage& message) { #endif -bool ChatWindow::isAtBottom() { +bool ChatWindow::isNearBottom() { QScrollBar* verticalScrollBar = ui->messagesScrollArea->verticalScrollBar(); - return verticalScrollBar->sliderPosition() == verticalScrollBar->maximum(); + return verticalScrollBar->value() >= verticalScrollBar->maximum() - Ui::AUTO_SCROLL_THRESHOLD; } // Scroll chat message area to bottom. void ChatWindow::scrollToBottom() { QScrollBar* verticalScrollBar = ui->messagesScrollArea->verticalScrollBar(); - verticalScrollBar->setSliderPosition(verticalScrollBar->maximum()); -} - -void ChatWindow::togglePinned() { - QMainWindow* mainWindow = Application::getInstance()->getWindow(); - mainWindow->removeDockWidget(this); - if (ui->togglePinnedButton->isChecked()) { - mainWindow->addDockWidget(ui->togglePinnedButton->isChecked() ? Qt::RightDockWidgetArea : Qt::NoDockWidgetArea, this); - } - if (!this->toggleViewAction()->isChecked()) { - this->toggleViewAction()->trigger(); - } - this->setFloating(!ui->togglePinnedButton->isChecked()); - setTitleBarWidget(ui->togglePinnedButton->isChecked()?new QWidget():titleBar); + verticalScrollBar->setValue(verticalScrollBar->maximum()); } diff --git a/interface/src/ui/ChatWindow.h b/interface/src/ui/ChatWindow.h index 6a807f9b81..104fbe1746 100644 --- a/interface/src/ui/ChatWindow.h +++ b/interface/src/ui/ChatWindow.h @@ -17,6 +17,7 @@ #include #include +#include "FramelessDialog.h" #ifdef HAVE_QXMPP @@ -26,37 +27,38 @@ #endif namespace Ui { + + +// Maximum amount the chat can be scrolled up in order to auto scroll. +const int AUTO_SCROLL_THRESHOLD = 20; + + class ChatWindow; } -class ChatWindow : public QDockWidget { +class ChatWindow : public FramelessDialog { Q_OBJECT public: - ChatWindow(); + ChatWindow(QWidget* parent); ~ChatWindow(); - virtual void keyPressEvent(QKeyEvent *event); - virtual void showEvent(QShowEvent* event); - - virtual void mousePressEvent(QMouseEvent *e); - virtual void mouseMoveEvent(QMouseEvent *e); - virtual void mouseReleaseEvent(QMouseEvent *e); - protected: bool eventFilter(QObject* sender, QEvent* event); + virtual void keyPressEvent(QKeyEvent *event); + virtual void showEvent(QShowEvent* event); + private: #ifdef HAVE_QXMPP QString getParticipantName(const QString& participant); #endif void startTimerForTimeStamps(); void addTimeStamp(); - bool isAtBottom(); + bool isNearBottom(); void scrollToBottom(); Ui::ChatWindow* ui; - QWidget* titleBar; int numMessagesAfterLastTimeStamp; QDateTime lastMessageStamp; bool _mousePressed; @@ -65,7 +67,6 @@ private: private slots: void connected(); void timeout(); - void togglePinned(); #ifdef HAVE_QXMPP void error(QXmppClient::Error error); void participantsChanged(); diff --git a/interface/src/ui/FramelessDialog.cpp b/interface/src/ui/FramelessDialog.cpp index 18e3bca89a..4919e99db6 100644 --- a/interface/src/ui/FramelessDialog.cpp +++ b/interface/src/ui/FramelessDialog.cpp @@ -14,8 +14,13 @@ const int RESIZE_HANDLE_WIDTH = 7; -FramelessDialog::FramelessDialog(QWidget *parent, Qt::WindowFlags flags) : -QDialog(parent, flags | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint) { +FramelessDialog::FramelessDialog(QWidget *parent, Qt::WindowFlags flags, Position position) : + QDialog(parent, flags | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint), + _isResizing(false), + _resizeInitialWidth(0), + _selfHidden(false), + _position(position) { + setAttribute(Qt::WA_DeleteOnClose); // handle rezize and move events @@ -29,29 +34,37 @@ bool FramelessDialog::eventFilter(QObject* sender, QEvent* event) { switch (event->type()) { case QEvent::Move: if (sender == parentWidget()) { - // move to upper left corner on app move - move(parentWidget()->geometry().topLeft()); + resizeAndPosition(false); } break; case QEvent::Resize: if (sender == parentWidget()) { - // keep full app height on resizing the app - setFixedHeight(parentWidget()->size().height()); + resizeAndPosition(false); } break; case QEvent::WindowStateChange: if (parentWidget()->isMinimized()) { - setHidden(true); - } else { + if (isVisible()) { + _selfHidden = true; + setHidden(true); + } + } else if (_selfHidden) { + _selfHidden = false; setHidden(false); } break; case QEvent::ApplicationDeactivate: // hide on minimize and focus lost - setHidden(true); + if (isVisible()) { + _selfHidden = true; + setHidden(true); + } break; case QEvent::ApplicationActivate: - setHidden(false); + if (_selfHidden) { + _selfHidden = false; + setHidden(false); + } break; default: break; @@ -70,21 +83,38 @@ void FramelessDialog::setStyleSheetFile(const QString& fileName) { } void FramelessDialog::showEvent(QShowEvent* event) { - // move to upper left corner - move(parentWidget()->geometry().topLeft()); + resizeAndPosition(); +} +void FramelessDialog::resizeAndPosition(bool resizeParent) { // keep full app height setFixedHeight(parentWidget()->size().height()); // resize parrent if width is smaller than this dialog - if (parentWidget()->size().width() < size().width()) { + if (resizeParent && parentWidget()->size().width() < size().width()) { parentWidget()->resize(size().width(), parentWidget()->size().height()); } + + if (_position == POSITION_LEFT) { + // move to upper left corner + move(parentWidget()->geometry().topLeft()); + } else if (_position == POSITION_RIGHT) { + // move to upper right corner + QPoint pos = parentWidget()->geometry().topRight(); + pos.setX(pos.x() - size().width()); + move(pos); + } } + void FramelessDialog::mousePressEvent(QMouseEvent* mouseEvent) { - if (abs(mouseEvent->pos().x() - size().width()) < RESIZE_HANDLE_WIDTH && mouseEvent->button() == Qt::LeftButton) { - _isResizing = true; - QApplication::setOverrideCursor(Qt::SizeHorCursor); + if (mouseEvent->button() == Qt::LeftButton) { + bool hitLeft = _position == POSITION_LEFT && abs(mouseEvent->pos().x() - size().width()) < RESIZE_HANDLE_WIDTH; + bool hitRight = _position == POSITION_RIGHT && mouseEvent->pos().x() < RESIZE_HANDLE_WIDTH; + if (hitLeft || hitRight) { + _isResizing = true; + _resizeInitialWidth = size().width(); + QApplication::setOverrideCursor(Qt::SizeHorCursor); + } } } @@ -95,6 +125,14 @@ void FramelessDialog::mouseReleaseEvent(QMouseEvent* mouseEvent) { void FramelessDialog::mouseMoveEvent(QMouseEvent* mouseEvent) { if (_isResizing) { - resize(mouseEvent->pos().x(), size().height()); + if (_position == POSITION_LEFT) { + resize(mouseEvent->pos().x(), size().height()); + } else if (_position == POSITION_RIGHT) { + setUpdatesEnabled(false); + resize(_resizeInitialWidth - mouseEvent->pos().x(), size().height()); + resizeAndPosition(); + _resizeInitialWidth = size().width(); + setUpdatesEnabled(true); + } } } diff --git a/interface/src/ui/FramelessDialog.h b/interface/src/ui/FramelessDialog.h index db9f6dfd6c..828602a5db 100644 --- a/interface/src/ui/FramelessDialog.h +++ b/interface/src/ui/FramelessDialog.h @@ -19,7 +19,9 @@ class FramelessDialog : public QDialog { Q_OBJECT public: - FramelessDialog(QWidget* parent = 0, Qt::WindowFlags flags = 0); + enum Position { POSITION_LEFT, POSITION_RIGHT }; + + FramelessDialog(QWidget* parent = 0, Qt::WindowFlags flags = 0, Position position = POSITION_LEFT); void setStyleSheetFile(const QString& fileName); protected: @@ -31,7 +33,12 @@ protected: bool eventFilter(QObject* sender, QEvent* event); private: + void resizeAndPosition(bool resizeParent = true); + bool _isResizing; + int _resizeInitialWidth; + bool _selfHidden; ///< true when the dialog itself because of a window event (deactivation or minimization) + Position _position; }; diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 36508e94d1..7a70b743bd 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -19,7 +19,7 @@ const int SCROLL_PANEL_BOTTOM_MARGIN = 30; const int OK_BUTTON_RIGHT_MARGIN = 30; const int BUTTONS_TOP_MARGIN = 24; -PreferencesDialog::PreferencesDialog(QWidget* parent, Qt::WindowFlags flags) : FramelessDialog(parent, flags) { +PreferencesDialog::PreferencesDialog(QWidget* parent, Qt::WindowFlags flags) : FramelessDialog(parent, flags, POSITION_LEFT) { ui.setupUi(this); setStyleSheetFile("styles/preferences.qss"); @@ -38,26 +38,34 @@ void PreferencesDialog::accept() { void PreferencesDialog::setHeadUrl(QString modelUrl) { ui.faceURLEdit->setText(modelUrl); - setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint); } void PreferencesDialog::setSkeletonUrl(QString modelUrl) { ui.skeletonURLEdit->setText(modelUrl); - setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint); } void PreferencesDialog::openHeadModelBrowser() { setWindowFlags(windowFlags() & ~Qt::WindowStaysOnTopHint); + show(); + ModelsBrowser modelBrowser(Head); connect(&modelBrowser, &ModelsBrowser::selected, this, &PreferencesDialog::setHeadUrl); modelBrowser.browse(); + + setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint); + show(); } void PreferencesDialog::openBodyModelBrowser() { setWindowFlags(windowFlags() & ~Qt::WindowStaysOnTopHint); + show(); + ModelsBrowser modelBrowser(Skeleton); connect(&modelBrowser, &ModelsBrowser::selected, this, &PreferencesDialog::setSkeletonUrl); modelBrowser.browse(); + + setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint); + show(); } void PreferencesDialog::openSnapshotLocationBrowser() { @@ -176,6 +184,7 @@ void PreferencesDialog::savePreferences() { Menu::getInstance()->setMaxVoxelPacketsPerSecond(ui.maxVoxelsPPSSpin->value()); Menu::getInstance()->setAudioJitterBufferSamples(ui.audioJitterSpin->value()); + Application::getInstance()->getAudio()->setJitterBufferSamples(ui.audioJitterSpin->value()); Application::getInstance()->resizeGL(Application::getInstance()->getGLWidget()->width(), Application::getInstance()->getGLWidget()->height()); diff --git a/interface/ui/chatWindow.ui b/interface/ui/chatWindow.ui index 0372e00c09..4d223b2665 100644 --- a/interface/ui/chatWindow.ui +++ b/interface/ui/chatWindow.ui @@ -1,13 +1,13 @@ ChatWindow - + 0 0 400 - 608 + 440 @@ -16,127 +16,95 @@ 238 - - font-family: Helvetica, Arial, sans-serif; - - - QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable - - - Qt::NoDockWidgetArea - Chat - - - - 0 - - - 8 - - - 8 - - - 8 - - - 8 - - - - - - 0 - 0 - - - - Connecting to XMPP... - - - Qt::AlignCenter - - - - - - - - - - 0 - 0 - - - - font-weight: bold; color: palette(shadow); margin-bottom: 4px; - - - online now: - - - - - - - - 0 - 0 - - - - - 16 - 16 - - - - Qt::NoFocus - - - - - - - ../resources/images/pin.svg - ../resources/images/pinned.svg../resources/images/pin.svg - - - true - - - true - - - false - - - true - - - - - - - - 0 - 0 - - - - - 16 - 16 - - - - Qt::NoFocus - - - QPushButton { + + font-family: Helvetica, Arial, sans-serif; + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + 8 + + + 8 + + + 8 + + + 8 + + + + + + 0 + 0 + + + + Connecting to XMPP... + + + Qt::AlignCenter + + + + + + + + + + 0 + 0 + + + + font-weight: bold; color: palette(shadow); margin-bottom: 4px; + + + online now: + + + + + + + + 0 + 0 + + + + + 16 + 16 + + + + Qt::NoFocus + + + QPushButton { background-color: rgba( 0, 0, 0, 0% ); border: none; image: url(../resources/images/close.svg) @@ -148,50 +116,104 @@ QPushButton:pressed { border: none; image: url(../resources/images/close_down.svg) } - - - - - - true - - - - - - - - - - - - margin-top: 12px; - - - Qt::ScrollBarAlwaysOff - - - true - - - - - 0 - 0 - 382 - 16 - + + + + + + true + + + + + + + + + #usersWidget { + margin-right: 20px; +} + + + + + + margin-top: 12px; + + + Qt::ScrollBarAlwaysOff + + + true + + + + + 0 + 0 + 382 + 16 + + + + + 0 + 0 + + + + margin-top: 0px; + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + - + 0 0 - - margin-top: 0px; + + + 0 + 78 + - + + #chatFrame { +border-color: palette(dark); border-style: solid; border-left-width: 1px; border-right-width: 1px; border-bottom-width: 1px; +} + + + QFrame::NoFrame + + + QFrame::Raised + + + 0 + + 0 @@ -204,69 +226,65 @@ QPushButton:pressed { 0 - - 0 - + + + + + 0 + 0 + + + + + 0 + 60 + + + + + Helvetica,Arial,sans-serif + 14 + + + + + + + QFrame::NoFrame + + + Qt::ScrollBarAlwaysOff + + + QAbstractScrollArea::AdjustToContents + + + true + + + false + + + - - - - - - - 0 - 0 - - - - - 0 - 60 - - - - border-color: palette(dark); border-style: solid; border-left-width: 1px; border-right-width: 1px; border-bottom-width: 1px; - - - QFrame::NoFrame - - - Qt::ScrollBarAlwaysOff - - - QAbstractScrollArea::AdjustToContents - - - true - - - - - + + + + + + + ChatInputArea + QTextEdit +
ui/ChatInputArea.h
+
+
- messagePlainTextEdit messagesScrollArea - - togglePinnedButton - clicked() - ChatWindow - togglePinned() - - - 390 - 42 - - - 550 - 42 - - - closeButton clicked() diff --git a/libraries/audio/src/AudioInjector.cpp b/libraries/audio/src/AudioInjector.cpp index 6370e51826..eed41ac849 100644 --- a/libraries/audio/src/AudioInjector.cpp +++ b/libraries/audio/src/AudioInjector.cpp @@ -71,8 +71,8 @@ void AudioInjector::injectAudio() { quint8 volume = MAX_INJECTOR_VOLUME * _options.getVolume(); packetStream << volume; - timeval startTime = {}; - gettimeofday(&startTime, NULL); + QElapsedTimer timer; + timer.start(); int nextFrame = 0; int currentSendPosition = 0; @@ -104,7 +104,7 @@ void AudioInjector::injectAudio() { if (currentSendPosition != bytesToCopy && currentSendPosition < soundByteArray.size()) { // not the first packet and not done // sleep for the appropriate time - int usecToSleep = usecTimestamp(&startTime) + (++nextFrame * BUFFER_SEND_INTERVAL_USECS) - usecTimestampNow(); + int usecToSleep = (++nextFrame * BUFFER_SEND_INTERVAL_USECS) - timer.nsecsElapsed() / 1000; if (usecToSleep > 0) { usleep(usecToSleep); diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index d1e4edf2ea..278923026d 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -282,10 +282,13 @@ void AccountManager::requestAccessToken(const QString& login, const QString& pas QUrl grantURL = _authURL; grantURL.setPath("/oauth/token"); + const QString ACCOUNT_MANAGER_REQUESTED_SCOPE = "owner"; + QByteArray postData; postData.append("grant_type=password&"); postData.append("username=" + login + "&"); - postData.append("password=" + password); + postData.append("password=" + password + "&"); + postData.append("scope=" + ACCOUNT_MANAGER_REQUESTED_SCOPE); request.setUrl(grantURL); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); diff --git a/libraries/networking/src/Assignment.h b/libraries/networking/src/Assignment.h index 041121f760..f0f7e8db1a 100644 --- a/libraries/networking/src/Assignment.h +++ b/libraries/networking/src/Assignment.h @@ -12,12 +12,6 @@ #ifndef hifi_Assignment_h #define hifi_Assignment_h -#ifdef _WIN32 -#include "Systime.h" -#else -#include -#endif - #include #include "NodeList.h" diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index ebfb954bd8..5e812c06c3 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -542,6 +542,19 @@ OctreeElement* Octree::getOctreeElementAt(float x, float y, float z, float s) co return node; } +OctreeElement* Octree::getOctreeEnclosingElementAt(float x, float y, float z, float s) const { + unsigned char* octalCode = pointToOctalCode(x,y,z,s); + OctreeElement* node = nodeForOctalCode(_rootNode, octalCode, NULL); + + delete[] octalCode; // cleanup memory +#ifdef HAS_AUDIT_CHILDREN + if (node) { + node->auditChildren("Octree::getOctreeElementAt()"); + } +#endif // def HAS_AUDIT_CHILDREN + return node; +} + OctreeElement* Octree::getOrCreateChildElementAt(float x, float y, float z, float s) { return getRoot()->getOrCreateChildElementAt(x, y, z, s); diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index 0b9f1657ee..a11e73ab04 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -210,7 +210,15 @@ public: void reaverageOctreeElements(OctreeElement* startNode = NULL); void deleteOctreeElementAt(float x, float y, float z, float s); + + /// Find the voxel at position x,y,z,s + /// \return pointer to the OctreeElement or NULL if none at x,y,z,s. OctreeElement* getOctreeElementAt(float x, float y, float z, float s) const; + + /// Find the voxel at position x,y,z,s + /// \return pointer to the OctreeElement or to the smallest enclosing parent if none at x,y,z,s. + OctreeElement* getOctreeEnclosingElementAt(float x, float y, float z, float s) const; + OctreeElement* getOrCreateChildElementAt(float x, float y, float z, float s); void recurseTreeWithOperation(RecurseOctreeOperation operation, void* extraData = NULL); diff --git a/libraries/octree/src/OctreeSceneStats.cpp b/libraries/octree/src/OctreeSceneStats.cpp index ad8702adc2..e7d34680a7 100644 --- a/libraries/octree/src/OctreeSceneStats.cpp +++ b/libraries/octree/src/OctreeSceneStats.cpp @@ -820,11 +820,7 @@ const char* OctreeSceneStats::getItemValue(Item item) { void OctreeSceneStats::trackIncomingOctreePacket(const QByteArray& packet, bool wasStatsPacket, int nodeClockSkewUsec) { - _incomingPacket++; - _incomingBytes += packet.size(); - if (!wasStatsPacket) { - _incomingWastedBytes += (MAX_PACKET_SIZE - packet.size()); - } + const bool wantExtraDebugging = false; int numBytesPacketHeader = numBytesForPacketHeader(packet); const unsigned char* dataAt = reinterpret_cast(packet.data()) + numBytesPacketHeader; @@ -842,12 +838,43 @@ void OctreeSceneStats::trackIncomingOctreePacket(const QByteArray& packet, OCTREE_PACKET_SENT_TIME arrivedAt = usecTimestampNow(); int flightTime = arrivedAt - sentAt + nodeClockSkewUsec; + + if (wantExtraDebugging) { + qDebug() << "sentAt:" << sentAt << " usecs"; + qDebug() << "arrivedAt:" << arrivedAt << " usecs"; + qDebug() << "nodeClockSkewUsec:" << nodeClockSkewUsec << " usecs"; + qDebug() << "flightTime:" << flightTime << " usecs"; + } + + // Guard against possible corrupted packets... with bad timestamps + const int MAX_RESONABLE_FLIGHT_TIME = 200 * USECS_PER_SECOND; // 200 seconds is more than enough time for a packet to arrive + const int MIN_RESONABLE_FLIGHT_TIME = 0; + if (flightTime > MAX_RESONABLE_FLIGHT_TIME || flightTime < MIN_RESONABLE_FLIGHT_TIME) { + qDebug() << "ignoring unreasonable packet... flightTime:" << flightTime; + return; // ignore any packets that are unreasonable + } + + // Guard against possible corrupted packets... with bad sequence numbers + const int MAX_RESONABLE_SEQUENCE_OFFSET = 2000; + const int MIN_RESONABLE_SEQUENCE_OFFSET = -2000; + int sequenceOffset = (sequence - _incomingLastSequence); + if (sequenceOffset > MAX_RESONABLE_SEQUENCE_OFFSET || sequenceOffset < MIN_RESONABLE_SEQUENCE_OFFSET) { + qDebug() << "ignoring unreasonable packet... sequence:" << sequence << "_incomingLastSequence:" << _incomingLastSequence; + return; // ignore any packets that are unreasonable + } + + // track packets here... + _incomingPacket++; + _incomingBytes += packet.size(); + if (!wasStatsPacket) { + _incomingWastedBytes += (MAX_PACKET_SIZE - packet.size()); + } + const int USECS_PER_MSEC = 1000; float flightTimeMsecs = flightTime / USECS_PER_MSEC; _incomingFlightTimeAverage.updateAverage(flightTimeMsecs); // track out of order and possibly lost packets... - const bool wantExtraDebugging = false; if (sequence == _incomingLastSequence) { if (wantExtraDebugging) { qDebug() << "last packet duplicate got:" << sequence << "_incomingLastSequence:" << _incomingLastSequence; diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index b923427307..ed05658538 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -292,8 +292,8 @@ void ScriptEngine::run() { emit errorMessage("Uncaught exception at line" + QString::number(line) + ":" + result.toString()); } - timeval startTime; - gettimeofday(&startTime, NULL); + QElapsedTimer startTime; + startTime.start(); int thisFrame = 0; @@ -302,7 +302,7 @@ void ScriptEngine::run() { qint64 lastUpdate = usecTimestampNow(); while (!_isFinished) { - int usecToSleep = usecTimestamp(&startTime) + (thisFrame++ * SCRIPT_DATA_CALLBACK_USECS) - usecTimestampNow(); + int usecToSleep = (thisFrame++ * SCRIPT_DATA_CALLBACK_USECS) - startTime.nsecsElapsed() / 1000; // nsec to usec if (usecToSleep > 0) { usleep(usecToSleep); } diff --git a/libraries/shared/src/PerfStat.h b/libraries/shared/src/PerfStat.h index 478c9afead..22cf14f207 100644 --- a/libraries/shared/src/PerfStat.h +++ b/libraries/shared/src/PerfStat.h @@ -18,12 +18,6 @@ #include #include "SharedUtil.h" -#ifdef _WIN32 -#include "Systime.h" -#else -#include -#endif - #include #include #include diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index f4e4b28f93..c65b7505ee 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -15,28 +15,39 @@ #include #include +#ifdef _WIN32 +#include +#endif + #ifdef __APPLE__ #include #endif #include +#include +#include #include "OctalCode.h" #include "SharedUtil.h" -quint64 usecTimestamp(const timeval *time) { - return (time->tv_sec * 1000000 + time->tv_usec); -} - -int usecTimestampNowAdjust = 0; +static int usecTimestampNowAdjust = 0; // in usec void usecTimestampNowForceClockSkew(int clockSkew) { ::usecTimestampNowAdjust = clockSkew; } quint64 usecTimestampNow() { - timeval now; - gettimeofday(&now, NULL); - return (now.tv_sec * 1000000 + now.tv_usec) + ::usecTimestampNowAdjust; + static bool usecTimestampNowIsInitialized = false; + static qint64 TIME_REFERENCE = 0; // in usec + static QElapsedTimer timestampTimer; + + if (!usecTimestampNowIsInitialized) { + TIME_REFERENCE = QDateTime::currentMSecsSinceEpoch() * 1000; // ms to usec + timestampTimer.start(); + usecTimestampNowIsInitialized = true; + } + + // usec nsec to usec usec + return TIME_REFERENCE + timestampTimer.nsecsElapsed() / 1000 + ::usecTimestampNowAdjust; } float randFloat() { @@ -640,27 +651,59 @@ void debug::checkDeadBeef(void* memoryVoid, int size) { // https://github.com/threerings/clyde/blob/master/src/main/java/com/threerings/math/Quaternion.java) glm::vec3 safeEulerAngles(const glm::quat& q) { float sy = 2.0f * (q.y * q.w - q.x * q.z); + glm::vec3 eulers; if (sy < 1.0f - EPSILON) { if (sy > -1.0f + EPSILON) { - return glm::vec3( + eulers = glm::vec3( atan2f(q.y * q.z + q.x * q.w, 0.5f - (q.x * q.x + q.y * q.y)), asinf(sy), atan2f(q.x * q.y + q.z * q.w, 0.5f - (q.y * q.y + q.z * q.z))); } else { // not a unique solution; x + z = atan2(-m21, m11) - return glm::vec3( + eulers = glm::vec3( 0.0f, - PI_OVER_TWO, atan2f(q.x * q.w - q.y * q.z, 0.5f - (q.x * q.x + q.z * q.z))); } } else { // not a unique solution; x - z = atan2(-m21, m11) - return glm::vec3( + eulers = glm::vec3( 0.0f, PI_OVER_TWO, -atan2f(q.x * q.w - q.y * q.z, 0.5f - (q.x * q.x + q.z * q.z))); } + + // adjust so that z, rather than y, is in [-pi/2, pi/2] + if (eulers.z < -PI_OVER_TWO) { + if (eulers.x < 0.0f) { + eulers.x += PI; + } else { + eulers.x -= PI; + } + eulers.y = -eulers.y; + if (eulers.y < 0.0f) { + eulers.y += PI; + } else { + eulers.y -= PI; + } + eulers.z += PI; + + } else if (eulers.z > PI_OVER_TWO) { + if (eulers.x < 0.0f) { + eulers.x += PI; + } else { + eulers.x -= PI; + } + eulers.y = -eulers.y; + if (eulers.y < 0.0f) { + eulers.y += PI; + } else { + eulers.y -= PI; + } + eulers.z -= PI; + } + return eulers; } // Helper function returns the positive angle (in radians) between two 3D vectors diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index a8403d617c..4a3fe2a129 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -24,12 +24,6 @@ #include -#ifdef _WIN32 -#include "Systime.h" -#else -#include -#endif - const int BYTES_PER_COLOR = 3; const int BYTES_PER_FLAGS = 1; typedef unsigned char rgbColor[BYTES_PER_COLOR]; @@ -66,7 +60,6 @@ static const quint64 USECS_PER_SECOND = USECS_PER_MSEC * MSECS_PER_SECOND; const int BITS_IN_BYTE = 8; -quint64 usecTimestamp(const timeval *time); quint64 usecTimestampNow(); void usecTimestampNowForceClockSkew(int clockSkew); diff --git a/libraries/shared/src/Systime.cpp b/libraries/shared/src/Systime.cpp deleted file mode 100644 index ab32821a0f..0000000000 --- a/libraries/shared/src/Systime.cpp +++ /dev/null @@ -1,61 +0,0 @@ -// -// Systime.cpp -// libraries/shared/src -// -// Copyright 2013 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 -// - -#ifdef WIN32 - -#include "Systime.h" - -/** - * gettimeofday - * Implementation according to: - * The Open Group Base Specifications Issue 6 - * IEEE Std 1003.1, 2004 Edition - */ - -/** - * THIS SOFTWARE IS NOT COPYRIGHTED - * - * This source code is offered for use in the public domain. You may - * use, modify or distribute it freely. - * - * This code is distributed in the hope that it will be useful but - * WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESS OR IMPLIED ARE HEREBY - * DISCLAIMED. This includes but is not limited to warranties of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * Contributed by: - * Danny Smith - */ - -#define WIN32_LEAN_AND_MEAN -#include - -/** Offset between 1/1/1601 and 1/1/1970 in 100 nanosec units */ -#define _W32_FT_OFFSET (116444736000000000ULL) - -int gettimeofday(timeval* p_tv, timezone* p_tz) { - - union { - unsigned long long ns100; /**time since 1 Jan 1601 in 100ns units */ - FILETIME ft; - } _now; - - if (p_tv) { - GetSystemTimeAsFileTime (&_now.ft); - p_tv->tv_usec=(long)((_now.ns100 / 10ULL) % 1000000ULL ); - p_tv->tv_sec= (long)((_now.ns100 - _W32_FT_OFFSET) / 10000000ULL); - } - - /** Always return 0 as per Open Group Base Specifications Issue 6. - Do not set errno on error. */ - return 0; -} - -#endif \ No newline at end of file diff --git a/libraries/shared/src/Systime.h b/libraries/shared/src/Systime.h deleted file mode 100644 index 3098f09ecd..0000000000 --- a/libraries/shared/src/Systime.h +++ /dev/null @@ -1,27 +0,0 @@ -// -// Systime.h -// libraries/shared/src -// -// Copyright 2013 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_Systime_h -#define hifi_Systime_h - -#ifdef WIN32 - -#include - -struct timezone { - int tz_minuteswest; /* minutes west of Greenwich */ - int tz_dsttime; /* type of dst correction */ -}; - -int gettimeofday(struct timeval* p_tv, struct timezone* p_tz); - -#endif - -#endif // hifi_Systime_h \ No newline at end of file diff --git a/libraries/voxels/src/VoxelTree.cpp b/libraries/voxels/src/VoxelTree.cpp index bb9de72e9d..b1ddf2e5b0 100644 --- a/libraries/voxels/src/VoxelTree.cpp +++ b/libraries/voxels/src/VoxelTree.cpp @@ -42,7 +42,11 @@ void VoxelTree::deleteVoxelAt(float x, float y, float z, float s) { } VoxelTreeElement* VoxelTree::getVoxelAt(float x, float y, float z, float s) const { - return (VoxelTreeElement*)getOctreeElementAt(x, y, z, s); + return static_cast(getOctreeElementAt(x, y, z, s)); +} + +VoxelTreeElement* VoxelTree::getEnclosingVoxelAt(float x, float y, float z, float s) const { + return static_cast(getOctreeEnclosingElementAt(x, y, z, s)); } void VoxelTree::createVoxel(float x, float y, float z, float s, diff --git a/libraries/voxels/src/VoxelTree.h b/libraries/voxels/src/VoxelTree.h index e0bc1d9a47..eb24c182b2 100644 --- a/libraries/voxels/src/VoxelTree.h +++ b/libraries/voxels/src/VoxelTree.h @@ -29,7 +29,15 @@ public: VoxelTreeElement* getRoot() { return (VoxelTreeElement*)_rootNode; } void deleteVoxelAt(float x, float y, float z, float s); + + /// Find the voxel at position x,y,z,s + /// \return pointer to the VoxelTreeElement or NULL if none at x,y,z,s. VoxelTreeElement* getVoxelAt(float x, float y, float z, float s) const; + + /// Find the voxel at position x,y,z,s + /// \return pointer to the VoxelTreeElement or to the smallest enclosing parent if none at x,y,z,s. + VoxelTreeElement* getEnclosingVoxelAt(float x, float y, float z, float s) const; + void createVoxel(float x, float y, float z, float s, unsigned char red, unsigned char green, unsigned char blue, bool destructive = false); diff --git a/libraries/voxels/src/VoxelTreeCommands.cpp b/libraries/voxels/src/VoxelTreeCommands.cpp index f0f092fd04..39e08d3bc2 100644 --- a/libraries/voxels/src/VoxelTreeCommands.cpp +++ b/libraries/voxels/src/VoxelTreeCommands.cpp @@ -13,6 +13,50 @@ #include "VoxelTreeCommands.h" + + +struct SendVoxelsOperationArgs { + const unsigned char* newBaseOctCode; + VoxelEditPacketSender* packetSender; +}; + +bool sendVoxelsOperation(OctreeElement* element, void* extraData) { + VoxelTreeElement* voxel = static_cast(element); + SendVoxelsOperationArgs* args = static_cast(extraData); + if (voxel->isColored()) { + const unsigned char* nodeOctalCode = voxel->getOctalCode(); + unsigned char* codeColorBuffer = NULL; + int codeLength = 0; + int bytesInCode = 0; + int codeAndColorLength; + + // If the newBase is NULL, then don't rebase + if (args->newBaseOctCode) { + codeColorBuffer = rebaseOctalCode(nodeOctalCode, args->newBaseOctCode, true); + codeLength = numberOfThreeBitSectionsInCode(codeColorBuffer); + bytesInCode = bytesRequiredForCodeLength(codeLength); + codeAndColorLength = bytesInCode + SIZE_OF_COLOR_DATA; + } else { + codeLength = numberOfThreeBitSectionsInCode(nodeOctalCode); + bytesInCode = bytesRequiredForCodeLength(codeLength); + codeAndColorLength = bytesInCode + SIZE_OF_COLOR_DATA; + codeColorBuffer = new unsigned char[codeAndColorLength]; + memcpy(codeColorBuffer, nodeOctalCode, bytesInCode); + } + + // copy the colors over + codeColorBuffer[bytesInCode + RED_INDEX] = voxel->getColor()[RED_INDEX]; + codeColorBuffer[bytesInCode + GREEN_INDEX] = voxel->getColor()[GREEN_INDEX]; + codeColorBuffer[bytesInCode + BLUE_INDEX] = voxel->getColor()[BLUE_INDEX]; + args->packetSender->queueVoxelEditMessage(PacketTypeVoxelSetDestructive, + codeColorBuffer, codeAndColorLength); + + delete[] codeColorBuffer; + } + return true; // keep going +} + + AddVoxelCommand::AddVoxelCommand(VoxelTree* tree, VoxelDetail& voxel, VoxelEditPacketSender* packetSender, QUndoCommand* parent) : QUndoCommand("Add Voxel", parent), _tree(tree), @@ -43,11 +87,40 @@ DeleteVoxelCommand::DeleteVoxelCommand(VoxelTree* tree, VoxelDetail& voxel, Voxe QUndoCommand("Delete Voxel", parent), _tree(tree), _packetSender(packetSender), - _voxel(voxel) + _voxel(voxel), + _oldTree(NULL) { + _tree->lockForRead(); + VoxelTreeElement* element = _tree->getEnclosingVoxelAt(_voxel.x, _voxel.y, _voxel.z, _voxel.s); + if (element->getScale() == _voxel.s) { + if (!element->hasContent() && !element->isLeaf()) { + _oldTree = new VoxelTree(); + _tree->copySubTreeIntoNewTree(element, _oldTree, false); + } else { + _voxel.red = element->getColor()[0]; + _voxel.green = element->getColor()[1]; + _voxel.blue = element->getColor()[2]; + } + } else if (element->hasContent() && element->isLeaf()) { + _voxel.red = element->getColor()[0]; + _voxel.green = element->getColor()[1]; + _voxel.blue = element->getColor()[2]; + } else { + _voxel.s = 0.0f; + qDebug() << "No element for delete."; + } + _tree->unlock(); +} + +DeleteVoxelCommand::~DeleteVoxelCommand() { + delete _oldTree; } void DeleteVoxelCommand::redo() { + if (_voxel.s == 0.0f) { + return; + } + if (_tree) { _tree->deleteVoxelAt(_voxel.x, _voxel.y, _voxel.z, _voxel.s); } @@ -57,10 +130,32 @@ void DeleteVoxelCommand::redo() { } void DeleteVoxelCommand::undo() { - if (_tree) { - _tree->createVoxel(_voxel.x, _voxel.y, _voxel.z, _voxel.s, _voxel.red, _voxel.green, _voxel.blue); + if (_voxel.s == 0.0f) { + return; } - if (_packetSender) { - _packetSender->queueVoxelEditMessages(PacketTypeVoxelSet, 1, &_voxel); + + if (_oldTree) { + VoxelTreeElement* element = _oldTree->getVoxelAt(_voxel.x, _voxel.y, _voxel.z, _voxel.s); + if (element) { + if (_tree) { + _tree->lockForWrite(); + _oldTree->copySubTreeIntoNewTree(element, _tree, false); + _tree->unlock(); + } + if (_packetSender) { + SendVoxelsOperationArgs args; + args.newBaseOctCode = NULL; + args.packetSender = _packetSender; + _oldTree->recurseTreeWithOperation(sendVoxelsOperation, &args); + _packetSender->releaseQueuedMessages(); + } + } + } else { + if (_tree) { + _tree->createVoxel(_voxel.x, _voxel.y, _voxel.z, _voxel.s, _voxel.red, _voxel.green, _voxel.blue); + } + if (_packetSender) { + _packetSender->queueVoxelEditMessages(PacketTypeVoxelSet, 1, &_voxel); + } } } diff --git a/libraries/voxels/src/VoxelTreeCommands.h b/libraries/voxels/src/VoxelTreeCommands.h index 4f2610577e..8df1f0dc14 100644 --- a/libraries/voxels/src/VoxelTreeCommands.h +++ b/libraries/voxels/src/VoxelTreeCommands.h @@ -36,6 +36,7 @@ private: class DeleteVoxelCommand : public QUndoCommand { public: DeleteVoxelCommand(VoxelTree* tree, VoxelDetail& voxel, VoxelEditPacketSender* packetSender = NULL, QUndoCommand* parent = NULL); + ~DeleteVoxelCommand(); virtual void redo(); virtual void undo(); @@ -44,6 +45,7 @@ private: VoxelTree* _tree; VoxelEditPacketSender* _packetSender; VoxelDetail _voxel; + VoxelTree* _oldTree; }; #endif // hifi_VoxelTreeCommands_h diff --git a/libraries/voxels/src/VoxelsScriptingInterface.cpp b/libraries/voxels/src/VoxelsScriptingInterface.cpp index 08dcfd3f47..15503db454 100644 --- a/libraries/voxels/src/VoxelsScriptingInterface.cpp +++ b/libraries/voxels/src/VoxelsScriptingInterface.cpp @@ -76,32 +76,16 @@ void VoxelsScriptingInterface::setVoxel(float x, float y, float z, float scale, if (_tree) { if (_undoStack) { AddVoxelCommand* addCommand = new AddVoxelCommand(_tree, - addVoxelDetail, - getVoxelPacketSender()); - - VoxelTreeElement* deleteVoxelElement = _tree->getVoxelAt(addVoxelDetail.x, addVoxelDetail.y, addVoxelDetail.z, addVoxelDetail.s); - if (deleteVoxelElement) { - nodeColor color; - memcpy(&color, &deleteVoxelElement->getColor(), sizeof(nodeColor)); - VoxelDetail deleteVoxelDetail = {addVoxelDetail.x, - addVoxelDetail.y, - addVoxelDetail.z, - addVoxelDetail.s, - color[0], - color[1], - color[2]}; - DeleteVoxelCommand* delCommand = new DeleteVoxelCommand(_tree, - deleteVoxelDetail, - getVoxelPacketSender()); - _undoStack->beginMacro(addCommand->text()); - // As QUndoStack automatically executes redo() on push, we don't need to execute the command ourselves. - _undoStack->push(delCommand); - _undoStack->push(addCommand); - _undoStack->endMacro(); - } else { - // As QUndoStack automatically executes redo() on push, we don't need to execute the command ourselves. - _undoStack->push(addCommand); - } + addVoxelDetail, + getVoxelPacketSender()); + DeleteVoxelCommand* deleteCommand = new DeleteVoxelCommand(_tree, + addVoxelDetail, + getVoxelPacketSender()); + _undoStack->beginMacro(addCommand->text()); + // As QUndoStack automatically executes redo() on push, we don't need to execute the command ourselves. + _undoStack->push(deleteCommand); + _undoStack->push(addCommand); + _undoStack->endMacro(); } else { // queue the destructive add queueVoxelAdd(PacketTypeVoxelSetDestructive, addVoxelDetail);