diff --git a/assignment-client/src/main.cpp b/assignment-client/src/main.cpp index 7132b5c38a..5e103cf767 100644 --- a/assignment-client/src/main.cpp +++ b/assignment-client/src/main.cpp @@ -17,8 +17,6 @@ #include "AssignmentClientMonitor.h" int main(int argc, char* argv[]) { - initialiseUsecTimestampNow(); - #ifndef WIN32 setvbuf(stdout, NULL, _IOLBF, 0); #endif diff --git a/domain-server/src/main.cpp b/domain-server/src/main.cpp index 871c16a215..1d9e837f0a 100644 --- a/domain-server/src/main.cpp +++ b/domain-server/src/main.cpp @@ -23,8 +23,6 @@ #include "DomainServer.h" int main(int argc, char* argv[]) { - initialiseUsecTimestampNow(); - #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/examples/swissArmyJetpack.js b/examples/swissArmyJetpack.js new file mode 100644 index 0000000000..9bb5bea267 --- /dev/null +++ b/examples/swissArmyJetpack.js @@ -0,0 +1,142 @@ +// +// swissArmyJetpack.js +// examples +// +// Created by Andrew Meadows 2014.04.24 +// Copyright 2014 High Fidelity, Inc. +// +// This is a work in progress. It will eventually be able to move the avatar around, +// toggle collision groups, modify avatar movement options, and other stuff (maybe trigger animations). +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var numberOfButtons = 3; + +var enabledColors = new Array(); +enabledColors[0] = { red: 255, green: 0, blue: 0}; +enabledColors[1] = { red: 0, green: 255, blue: 0}; +enabledColors[2] = { red: 0, green: 0, blue: 255}; + +var disabledColors = new Array(); +disabledColors[0] = { red: 90, green: 75, blue: 75}; +disabledColors[1] = { red: 75, green: 90, blue: 75}; +disabledColors[2] = { red: 75, green: 90, blue: 90}; + +var buttons = new Array(); +var labels = new Array(); + +var labelContents = new Array(); +labelContents[0] = "Collide with Avatars"; +labelContents[1] = "Collide with Voxels"; +labelContents[2] = "Collide with Particles"; +var groupBits = 0; + +var buttonStates = new Array(); + +var disabledOffsetT = 0; +var enabledOffsetT = 55; + +var buttonX = 50; +var buttonY = 200; +var buttonWidth = 30; +var buttonHeight = 54; +var textX = buttonX + buttonWidth + 10; + +for (i = 0; i < numberOfButtons; i++) { + var offsetS = 12 + var offsetT = disabledOffsetT; + + buttons[i] = Overlays.addOverlay("image", { + //x: buttonX + (buttonWidth * i), + x: buttonX, + y: buttonY + (buttonHeight * i), + width: buttonWidth, + height: buttonHeight, + subImage: { x: offsetS, y: offsetT, width: buttonWidth, height: buttonHeight }, + imageURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/images/testing-swatches.svg", + color: disabledColors[i], + alpha: 1, + }); + + labels[i] = Overlays.addOverlay("text", { + x: textX, + y: buttonY + (buttonHeight * i) + 12, + width: 150, + height: 50, + color: { red: 0, green: 0, blue: 0}, + textColor: { red: 255, green: 0, blue: 0}, + topMargin: 4, + leftMargin: 4, + text: labelContents[i] + }); + + buttonStates[i] = false; +} + +function updateButton(i, enabled) { + var offsetY = disabledOffsetT; + var buttonColor = disabledColors[i]; + groupBits + if (enabled) { + offsetY = enabledOffsetT; + buttonColor = enabledColors[i]; + if (i == 0) { + groupBits |= COLLISION_GROUP_AVATARS; + } else if (i == 1) { + groupBits |= COLLISION_GROUP_VOXELS; + } else if (i == 2) { + groupBits |= COLLISION_GROUP_PARTICLES; + } + } else { + if (i == 0) { + groupBits &= ~COLLISION_GROUP_AVATARS; + } else if (i == 1) { + groupBits &= ~COLLISION_GROUP_VOXELS; + } else if (i == 2) { + groupBits &= ~COLLISION_GROUP_PARTICLES; + } + } + MyAvatar.collisionGroups = groupBits; + + Overlays.editOverlay(buttons[i], { subImage: { y: offsetY } } ); + Overlays.editOverlay(buttons[i], { color: buttonColor } ); + buttonStates[i] = enabled; +} + +// When our script shuts down, we should clean up all of our overlays +function scriptEnding() { + for (i = 0; i < numberOfButtons; i++) { + print("adebug deleting overlay " + i); + Overlays.deleteOverlay(buttons[i]); + Overlays.deleteOverlay(labels[i]); + } +} +Script.scriptEnding.connect(scriptEnding); + + +// Our update() function is called at approximately 60fps, and we will use it to animate our various overlays +function update(deltaTime) { + if (groupBits != MyAvatar.collisionGroups) { + groupBits = MyAvatar.collisionGroups; + updateButton(0, groupBits & COLLISION_GROUP_AVATARS); + updateButton(1, groupBits & COLLISION_GROUP_VOXELS); + updateButton(2, groupBits & COLLISION_GROUP_PARTICLES); + } +} +Script.update.connect(update); + + +// we also handle click detection in our mousePressEvent() +function mousePressEvent(event) { + var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y}); + for (i = 0; i < numberOfButtons; i++) { + if (clickedOverlay == buttons[i]) { + var enabled = !(buttonStates[i]); + updateButton(i, enabled); + } + } +} +Controller.mousePressEvent.connect(mousePressEvent); + 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 4adb2f772a..97b5c05f25 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -857,7 +857,7 @@ void Application::keyPressEvent(QKeyEvent* event) { case Qt::Key_G: if (isShifted) { - Menu::getInstance()->triggerOption(MenuOption::Gravity); + Menu::getInstance()->triggerOption(MenuOption::ObeyGravity); } break; @@ -3520,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 5983b0d1a9..325770a8df 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -16,6 +16,7 @@ #include #include +#include #include #include #include diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index c182f6e842..68e38615bf 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -644,10 +644,10 @@ void Audio::addReceivedAudioToBuffer(const QByteArray& audioByteArray) { const int NUM_INITIAL_PACKETS_DISCARD = 3; const int STANDARD_DEVIATION_SAMPLE_COUNT = 500; - _timeSinceLastRecieved.start(); _totalPacketsReceived++; - double timeDiff = (double)_timeSinceLastRecieved.nsecsElapsed() / 1000000.0; // ns to ms + 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) { @@ -1265,7 +1265,7 @@ bool Audio::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDeviceInfo) // setup a procedural audio output device _proceduralAudioOutput = new QAudioOutput(outputDeviceInfo, _outputFormat, this); - _timeSinceLastRecieved.start(); + _timeSinceLastReceived.start(); // setup spatial audio ringbuffer int numFrameSamples = _outputFormat.sampleRate() * _desiredOutputFormat.channelCount(); diff --git a/interface/src/Audio.h b/interface/src/Audio.h index e1b8a7dddc..277c606d4b 100644 --- a/interface/src/Audio.h +++ b/interface/src/Audio.h @@ -129,7 +129,7 @@ private: QString _outputAudioDeviceName; StDev _stdev; - QElapsedTimer _timeSinceLastRecieved; + QElapsedTimer _timeSinceLastReceived; float _averagedLatency; float _measuredJitter; int16_t _jitterBufferSamples; diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index a7012d838d..44117df55c 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -186,9 +187,9 @@ Menu::Menu() : QAction::PreferencesRole); addDisabledActionAndSeparator(editMenu, "Physics"); - addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::Gravity, Qt::SHIFT | Qt::Key_G, false); - - + QObject* avatar = appInstance->getAvatar(); + addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::ObeyGravity, Qt::SHIFT | Qt::Key_G, true, + avatar, SLOT(updateMotionBehaviorFlags())); addAvatarCollisionSubMenu(editMenu); @@ -507,7 +508,7 @@ void Menu::loadSettings(QSettings* settings) { // MyAvatar caches some menu options, so we have to update them whenever we load settings. // TODO: cache more settings in MyAvatar that are checked with very high frequency. MyAvatar* myAvatar = Application::getInstance()->getAvatar(); - myAvatar->updateCollisionFlags(); + myAvatar->updateCollisionGroups(); if (lockedSettings) { Application::getInstance()->unlockSettings(); @@ -970,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"); @@ -1145,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 } @@ -1370,13 +1381,13 @@ void Menu::addAvatarCollisionSubMenu(QMenu* overMenu) { Application* appInstance = Application::getInstance(); QObject* avatar = appInstance->getAvatar(); addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithEnvironment, - 0, false, avatar, SLOT(updateCollisionFlags())); + 0, false, avatar, SLOT(updateCollisionGroups())); addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithAvatars, - 0, true, avatar, SLOT(updateCollisionFlags())); + 0, true, avatar, SLOT(updateCollisionGroups())); addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithVoxels, - 0, false, avatar, SLOT(updateCollisionFlags())); + 0, false, avatar, SLOT(updateCollisionGroups())); addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithParticles, - 0, true, avatar, SLOT(updateCollisionFlags())); + 0, true, avatar, SLOT(updateCollisionGroups())); } QAction* Menu::getActionFromName(const QString& menuName, QMenu* menu) { diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 09b5fabfc8..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(); @@ -314,7 +315,7 @@ namespace MenuOption { const QString GoTo = "Go To..."; const QString GoToDomain = "Go To Domain..."; const QString GoToLocation = "Go To Location..."; - const QString Gravity = "Use Gravity"; + const QString ObeyGravity = "Obey Gravity"; const QString HandsCollideWithSelf = "Collide With Self"; const QString HeadMouse = "Head Mouse"; const QString IncreaseAvatarSize = "Increase Avatar Size"; diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index fe685b89f8..b060c3421a 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -47,16 +47,14 @@ Avatar::Avatar() : AvatarData(), _skeletonModel(this), _bodyYawDelta(0.0f), - _mode(AVATAR_MODE_STANDING), _velocity(0.0f, 0.0f, 0.0f), - _thrust(0.0f, 0.0f, 0.0f), _leanScale(0.5f), _scale(1.0f), _worldUpDirection(DEFAULT_UP_DIRECTION), _mouseRayOrigin(0.0f, 0.0f, 0.0f), _mouseRayDirection(0.0f, 0.0f, 0.0f), _moving(false), - _collisionFlags(0), + _collisionGroups(0), _initialized(false), _shouldRenderBillboard(true) { @@ -138,20 +136,9 @@ void Avatar::simulate(float deltaTime) { head->simulate(deltaTime, false, _shouldRenderBillboard); } - // use speed and angular velocity to determine walking vs. standing - float speed = glm::length(_velocity); - if (speed + fabs(_bodyYawDelta) > 0.2) { - _mode = AVATAR_MODE_WALKING; - } else { - _mode = AVATAR_MODE_INTERACTING; - } - // update position by velocity, and subtract the change added earlier for gravity _position += _velocity * deltaTime; - // Zero thrust out now that we've added it to velocity in this frame - _thrust = glm::vec3(0, 0, 0); - // update animation for display name fade in/out if ( _displayNameTargetAlpha != _displayNameAlpha) { // the alpha function is @@ -166,7 +153,7 @@ void Avatar::simulate(float deltaTime) { // Fading in _displayNameAlpha = 1 - (1 - _displayNameAlpha) * coef; } - _displayNameAlpha = abs(_displayNameAlpha - _displayNameTargetAlpha) < 0.01? _displayNameTargetAlpha : _displayNameAlpha; + _displayNameAlpha = abs(_displayNameAlpha - _displayNameTargetAlpha) < 0.01f ? _displayNameTargetAlpha : _displayNameAlpha; } } @@ -563,7 +550,7 @@ bool Avatar::findCollisions(const QVector& shapes, CollisionList& } bool Avatar::findParticleCollisions(const glm::vec3& particleCenter, float particleRadius, CollisionList& collisions) { - if (_collisionFlags & COLLISION_GROUP_PARTICLES) { + if (_collisionGroups & COLLISION_GROUP_PARTICLES) { return false; } bool collided = false; @@ -753,19 +740,19 @@ void Avatar::renderJointConnectingCone(glm::vec3 position1, glm::vec3 position2, glEnd(); } -void Avatar::updateCollisionFlags() { - _collisionFlags = 0; +void Avatar::updateCollisionGroups() { + _collisionGroups = 0; if (Menu::getInstance()->isOptionChecked(MenuOption::CollideWithEnvironment)) { - _collisionFlags |= COLLISION_GROUP_ENVIRONMENT; + _collisionGroups |= COLLISION_GROUP_ENVIRONMENT; } if (Menu::getInstance()->isOptionChecked(MenuOption::CollideWithAvatars)) { - _collisionFlags |= COLLISION_GROUP_AVATARS; + _collisionGroups |= COLLISION_GROUP_AVATARS; } if (Menu::getInstance()->isOptionChecked(MenuOption::CollideWithVoxels)) { - _collisionFlags |= COLLISION_GROUP_VOXELS; + _collisionGroups |= COLLISION_GROUP_VOXELS; } if (Menu::getInstance()->isOptionChecked(MenuOption::CollideWithParticles)) { - _collisionFlags |= COLLISION_GROUP_PARTICLES; + _collisionGroups |= COLLISION_GROUP_PARTICLES; } } diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index ecf1be4899..bcf3487e09 100755 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -46,13 +46,6 @@ enum DriveKeys { MAX_DRIVE_KEYS }; -enum AvatarMode { - AVATAR_MODE_STANDING = 0, - AVATAR_MODE_WALKING, - AVATAR_MODE_INTERACTING, - NUM_AVATAR_MODES -}; - enum ScreenTintLayer { SCREEN_TINT_BEFORE_LANDSCAPE = 0, SCREEN_TINT_BEFORE_AVATARS, @@ -70,6 +63,7 @@ class Texture; class Avatar : public AvatarData { Q_OBJECT + Q_PROPERTY(quint32 collisionGroups READ getCollisionGroups WRITE setCollisionGroups) public: Avatar(); @@ -155,8 +149,11 @@ public: virtual float getBoundingRadius() const; void updateShapePositions(); + quint32 getCollisionGroups() const { return _collisionGroups; } + virtual void setCollisionGroups(quint32 collisionGroups) { _collisionGroups = (collisionGroups & VALID_COLLISION_GROUPS); } + public slots: - void updateCollisionFlags(); + void updateCollisionGroups(); signals: void collisionWithAvatar(const QUuid& myUUID, const QUuid& theirUUID, const CollisionInfo& collision); @@ -164,9 +161,7 @@ signals: protected: SkeletonModel _skeletonModel; float _bodyYawDelta; - AvatarMode _mode; glm::vec3 _velocity; - glm::vec3 _thrust; float _leanScale; float _scale; glm::vec3 _worldUpDirection; @@ -175,7 +170,7 @@ protected: float _stringLength; bool _moving; ///< set when position is changing - uint32_t _collisionFlags; + quint32 _collisionGroups; // protected methods... glm::vec3 getBodyRightDirection() const { return getOrientation() * IDENTITY_RIGHT; } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index fb547da13e..26dbc09d5b 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include @@ -54,16 +55,13 @@ MyAvatar::MyAvatar() : _shouldJump(false), _gravity(0.0f, -1.0f, 0.0f), _distanceToNearestAvatar(std::numeric_limits::max()), - _elapsedTimeMoving(0.0f), - _elapsedTimeStopped(0.0f), - _elapsedTimeSinceCollision(0.0f), _lastCollisionPosition(0, 0, 0), _speedBrakes(false), + _thrust(0.0f), _isThrustOn(false), _thrustMultiplier(1.0f), - _moveTarget(0,0,0), + _motionBehaviors(0), _lastBodyPenetration(0.0f), - _moveTargetStepCounter(0), _lookAtTargetAvatar(), _shouldRender(true), _billboardValid(false), @@ -97,11 +95,6 @@ void MyAvatar::reset() { setOrientation(glm::quat(glm::vec3(0.0f))); } -void MyAvatar::setMoveTarget(const glm::vec3 moveTarget) { - _moveTarget = moveTarget; - _moveTargetStepCounter = 0; -} - void MyAvatar::update(float deltaTime) { Head* head = getHead(); head->relaxLean(deltaTime); @@ -132,7 +125,7 @@ void MyAvatar::update(float deltaTime) { head->setAudioLoudness(audio->getLastInputLoudness()); head->setAudioAverageLoudness(audio->getAudioAverageInputLoudness()); - if (Menu::getInstance()->isOptionChecked(MenuOption::Gravity)) { + if (_motionBehaviors & AVATAR_MOTION_OBEY_GRAVITY) { setGravity(Application::getInstance()->getEnvironment()->getGravity(getPosition())); } else { setGravity(glm::vec3(0.0f, 0.0f, 0.0f)); @@ -145,17 +138,6 @@ void MyAvatar::simulate(float deltaTime) { glm::quat orientation = getOrientation(); - // Update movement timers - _elapsedTimeSinceCollision += deltaTime; - const float VELOCITY_MOVEMENT_TIMER_THRESHOLD = 0.2f; - if (glm::length(_velocity) < VELOCITY_MOVEMENT_TIMER_THRESHOLD) { - _elapsedTimeMoving = 0.0f; - _elapsedTimeStopped += deltaTime; - } else { - _elapsedTimeStopped = 0.0f; - _elapsedTimeMoving += deltaTime; - } - if (_scale != _targetScale) { float scale = (1.0f - SMOOTHING_RATIO) * _scale + SMOOTHING_RATIO * _targetScale; setScale(scale); @@ -269,34 +251,11 @@ void MyAvatar::simulate(float deltaTime) { // update the euler angles setOrientation(orientation); - const float WALKING_SPEED_THRESHOLD = 0.2f; - // use speed and angular velocity to determine walking vs. standing - float speed = glm::length(_velocity); - if (speed + fabs(_bodyYawDelta) > WALKING_SPEED_THRESHOLD) { - _mode = AVATAR_MODE_WALKING; - } else { - _mode = AVATAR_MODE_INTERACTING; - } - // update moving flag based on speed const float MOVING_SPEED_THRESHOLD = 0.01f; + float speed = glm::length(_velocity); _moving = speed > MOVING_SPEED_THRESHOLD; - // If a move target is set, update position explicitly - const float MOVE_FINISHED_TOLERANCE = 0.1f; - const float MOVE_SPEED_FACTOR = 2.0f; - const int MOVE_TARGET_MAX_STEPS = 250; - if ((glm::length(_moveTarget) > EPSILON) && (_moveTargetStepCounter < MOVE_TARGET_MAX_STEPS)) { - if (glm::length(_position - _moveTarget) > MOVE_FINISHED_TOLERANCE) { - _position += (_moveTarget - _position) * (deltaTime * MOVE_SPEED_FACTOR); - _moveTargetStepCounter++; - } else { - // Move completed - _moveTarget = glm::vec3(0,0,0); - _moveTargetStepCounter = 0; - } - } - updateChatCircle(deltaTime); _position += _velocity * deltaTime; @@ -324,10 +283,10 @@ void MyAvatar::simulate(float deltaTime) { head->simulate(deltaTime, true); // Zero thrust out now that we've added it to velocity in this frame - _thrust = glm::vec3(0.0f); + _thrust *= glm::vec3(0.0f); // now that we're done stepping the avatar forward in time, compute new collisions - if (_collisionFlags != 0) { + if (_collisionGroups != 0) { Camera* myCamera = Application::getInstance()->getCamera(); float radius = getSkeletonHeight() * COLLISION_RADIUS_SCALE; @@ -335,15 +294,15 @@ void MyAvatar::simulate(float deltaTime) { radius = myCamera->getAspectRatio() * (myCamera->getNearClip() / cos(myCamera->getFieldOfView() / 2.0f)); radius *= COLLISION_RADIUS_SCALAR; } - if (_collisionFlags) { + if (_collisionGroups) { updateShapePositions(); - if (_collisionFlags & COLLISION_GROUP_ENVIRONMENT) { + if (_collisionGroups & COLLISION_GROUP_ENVIRONMENT) { updateCollisionWithEnvironment(deltaTime, radius); } - if (_collisionFlags & COLLISION_GROUP_VOXELS) { + if (_collisionGroups & COLLISION_GROUP_VOXELS) { updateCollisionWithVoxels(deltaTime, radius); } - if (_collisionFlags & COLLISION_GROUP_AVATARS) { + if (_collisionGroups & COLLISION_GROUP_AVATARS) { updateCollisionWithAvatars(deltaTime); } } @@ -451,8 +410,6 @@ void MyAvatar::renderDebugBodyPoints() { glTranslatef(position.x, position.y, position.z); glutSolidSphere(0.15, 10, 10); glPopMatrix(); - - } // virtual @@ -830,7 +787,6 @@ void MyAvatar::applyHardCollision(const glm::vec3& penetration, float elasticity // cancel out the velocity component in the direction of penetration float penetrationLength = glm::length(penetration); if (penetrationLength > EPSILON) { - _elapsedTimeSinceCollision = 0.0f; glm::vec3 direction = penetration / penetrationLength; _velocity -= glm::dot(_velocity, direction) * direction * (1.0f + elasticity); _velocity *= glm::clamp(1.0f - damping, 0.0f, 1.0f); @@ -867,7 +823,7 @@ void MyAvatar::updateCollisionSound(const glm::vec3 &penetration, float deltaTim std::min(COLLISION_LOUDNESS * velocityTowardCollision, 1.0f), frequency * (1.0f + velocityTangentToCollision / velocityTowardCollision), std::min(velocityTangentToCollision / velocityTowardCollision * NOISE_SCALING, 1.0f), - 1.0f - DURATION_SCALING * powf(frequency, 0.5f) / velocityTowardCollision, true); + 1.0f - DURATION_SCALING * powf(frequency, 0.5f) / velocityTowardCollision, false); } } @@ -1184,8 +1140,31 @@ 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."); } - +} + +void MyAvatar::updateMotionBehaviors() { + _motionBehaviors = 0; + if (Menu::getInstance()->isOptionChecked(MenuOption::ObeyGravity)) { + _motionBehaviors |= AVATAR_MOTION_OBEY_GRAVITY; + } +} + +void MyAvatar::setCollisionGroups(quint32 collisionGroups) { + Avatar::setCollisionGroups(collisionGroups & VALID_COLLISION_GROUPS); + Menu* menu = Menu::getInstance(); + menu->setIsOptionChecked(MenuOption::CollideWithEnvironment, (bool)(_collisionGroups & COLLISION_GROUP_ENVIRONMENT)); + menu->setIsOptionChecked(MenuOption::CollideWithAvatars, (bool)(_collisionGroups & COLLISION_GROUP_AVATARS)); + menu->setIsOptionChecked(MenuOption::CollideWithVoxels, (bool)(_collisionGroups & COLLISION_GROUP_VOXELS)); + menu->setIsOptionChecked(MenuOption::CollideWithParticles, (bool)(_collisionGroups & COLLISION_GROUP_PARTICLES)); +} + +void MyAvatar::setMotionBehaviors(quint32 flags) { + _motionBehaviors = flags; + Menu* menu = Menu::getInstance(); + menu->setIsOptionChecked(MenuOption::ObeyGravity, (bool)(_motionBehaviors & AVATAR_MOTION_OBEY_GRAVITY)); } void MyAvatar::applyCollision(const glm::vec3& contactPoint, const glm::vec3& penetration) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 71c74f7c91..9b6b13568f 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -25,9 +25,12 @@ enum AvatarHandState NUM_HAND_STATES }; +const quint32 AVATAR_MOTION_OBEY_GRAVITY = 1U << 0; + class MyAvatar : public Avatar { Q_OBJECT Q_PROPERTY(bool shouldRenderLocally READ getShouldRenderLocally WRITE setShouldRenderLocally) + Q_PROPERTY(quint32 motionBehaviors READ getMotionBehaviors WRITE setMotionBehaviors) public: MyAvatar(); @@ -54,10 +57,7 @@ public: void setShouldRenderLocally(bool shouldRender) { _shouldRender = shouldRender; } // getters - AvatarMode getMode() const { return _mode; } float getLeanScale() const { return _leanScale; } - float getElapsedTimeStopped() const { return _elapsedTimeStopped; } - float getElapsedTimeMoving() const { return _elapsedTimeMoving; } const glm::vec3& getMouseRayOrigin() const { return _mouseRayOrigin; } const glm::vec3& getMouseRayDirection() const { return _mouseRayDirection; } glm::vec3 getGravity() const { return _gravity; } @@ -91,6 +91,10 @@ public: virtual void setFaceModelURL(const QUrl& faceModelURL); virtual void setSkeletonModelURL(const QUrl& skeletonModelURL); + virtual void setCollisionGroups(quint32 collisionGroups); + void setMotionBehaviors(quint32 flags); + quint32 getMotionBehaviors() const { return _motionBehaviors; } + void applyCollision(const glm::vec3& contactPoint, const glm::vec3& penetration); public slots: @@ -107,6 +111,8 @@ public slots: glm::vec3 getThrust() { return _thrust; }; void setThrust(glm::vec3 newThrust) { _thrust = newThrust; } + void updateMotionBehaviors(); + signals: void transformChanged(); @@ -118,16 +124,17 @@ private: float _driveKeys[MAX_DRIVE_KEYS]; glm::vec3 _gravity; float _distanceToNearestAvatar; // How close is the nearest avatar? - float _elapsedTimeMoving; // Timers to drive camera transitions when moving - float _elapsedTimeStopped; - float _elapsedTimeSinceCollision; + + // motion stuff glm::vec3 _lastCollisionPosition; bool _speedBrakes; + glm::vec3 _thrust; // final acceleration for the current frame bool _isThrustOn; float _thrustMultiplier; - glm::vec3 _moveTarget; + + quint32 _motionBehaviors; + glm::vec3 _lastBodyPenetration; - int _moveTargetStepCounter; QWeakPointer _lookAtTargetAvatar; glm::vec3 _targetAvatarPosition; bool _shouldRender; 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 6f9dc5e3bd..2bb0633f24 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -16,7 +16,6 @@ #include int main(int argc, const char * argv[]) { - initialiseUsecTimestampNow(); QElapsedTimer startupTime; startupTime.start(); 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/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/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 09c3c63ebd..ed05658538 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -230,8 +231,13 @@ void ScriptEngine::init() { registerGlobalObject("Voxels", &_voxelsScriptingInterface); - QScriptValue treeScaleValue = _engine.newVariant(QVariant(TREE_SCALE)); - _engine.globalObject().setProperty("TREE_SCALE", treeScaleValue); + // constants + QScriptValue globalObject = _engine.globalObject(); + globalObject.setProperty("TREE_SCALE", _engine.newVariant(QVariant(TREE_SCALE))); + globalObject.setProperty("COLLISION_GROUP_ENVIRONMENT", _engine.newVariant(QVariant(COLLISION_GROUP_ENVIRONMENT))); + globalObject.setProperty("COLLISION_GROUP_AVATARS", _engine.newVariant(QVariant(COLLISION_GROUP_AVATARS))); + globalObject.setProperty("COLLISION_GROUP_VOXELS", _engine.newVariant(QVariant(COLLISION_GROUP_VOXELS))); + globalObject.setProperty("COLLISION_GROUP_PARTICLES", _engine.newVariant(QVariant(COLLISION_GROUP_PARTICLES))); // let the VoxelPacketSender know how frequently we plan to call it _voxelsScriptingInterface.getVoxelPacketSender()->setProcessCallIntervalHint(SCRIPT_DATA_CALLBACK_USECS); diff --git a/libraries/shared/src/CollisionInfo.h b/libraries/shared/src/CollisionInfo.h index 7db965fe64..510728daa6 100644 --- a/libraries/shared/src/CollisionInfo.h +++ b/libraries/shared/src/CollisionInfo.h @@ -27,6 +27,7 @@ const quint32 COLLISION_GROUP_ENVIRONMENT = 1U << 0; const quint32 COLLISION_GROUP_AVATARS = 1U << 1; const quint32 COLLISION_GROUP_VOXELS = 1U << 2; const quint32 COLLISION_GROUP_PARTICLES = 1U << 3; +const quint32 VALID_COLLISION_GROUPS = 0x0f; // CollisionInfo contains details about the collision between two things: BodyA and BodyB. // The assumption is that the context that analyzes the collision knows about BodyA but diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index bbb73cae4c..c65b7505ee 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -30,27 +30,22 @@ #include "OctalCode.h" #include "SharedUtil.h" - -static qint64 TIME_REFERENCE = 0; // in usec -static QElapsedTimer timestampTimer; static int usecTimestampNowAdjust = 0; // in usec - -void initialiseUsecTimestampNow() { - static bool initialised = false; - if (initialised) { - qDebug() << "[WARNING] Double initialisation of usecTimestampNow()."; - return; - } - - TIME_REFERENCE = QDateTime::currentMSecsSinceEpoch() * 1000; // ms to usec - initialised = true; -} - void usecTimestampNowForceClockSkew(int clockSkew) { ::usecTimestampNowAdjust = clockSkew; } quint64 usecTimestampNow() { + 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; } diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index 54d599070d..4a3fe2a129 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -60,7 +60,6 @@ static const quint64 USECS_PER_SECOND = USECS_PER_MSEC * MSECS_PER_SECOND; const int BITS_IN_BYTE = 8; -void initialiseUsecTimestampNow(); quint64 usecTimestampNow(); void usecTimestampNowForceClockSkew(int clockSkew); 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);