diff --git a/BUILD.md b/BUILD.md index 9c56574cbb..547b79cb08 100644 --- a/BUILD.md +++ b/BUILD.md @@ -1,7 +1,7 @@ ###Dependencies -* [cmake](http://www.cmake.org/cmake/resources/software.html) ~> 3.3.2 -* [Qt](http://www.qt.io/download-open-source) ~> 5.6.1 +* [cmake](https://cmake.org/download/) ~> 3.3.2 +* [Qt](https://www.qt.io/download-open-source) ~> 5.6.1 * [OpenSSL](https://www.openssl.org/community/binaries.html) * IMPORTANT: Use the latest available version of OpenSSL to avoid security vulnerabilities. * [VHACD](https://github.com/virneo/v-hacd)(clone this repository)(Optional) @@ -9,18 +9,17 @@ ####CMake External Project Dependencies * [boostconfig](https://github.com/boostorg/config) ~> 1.58 -* [Bullet Physics Engine](https://code.google.com/p/bullet/downloads/list) ~> 2.82 -* [Faceshift](http://www.faceshift.com/) ~> 4.3 +* [Bullet Physics Engine](https://github.com/bulletphysics/bullet3/releases) ~> 2.83 * [GLEW](http://glew.sourceforge.net/) -* [glm](http://glm.g-truc.net/0.9.5/index.html) ~> 0.9.5.4 +* [glm](https://glm.g-truc.net/0.9.5/index.html) ~> 0.9.5.4 * [gverb](https://github.com/highfidelity/gverb) * [Oculus SDK](https://developer.oculus.com/downloads/) ~> 0.6 (Win32) / 0.5 (Mac / Linux) * [oglplus](http://oglplus.org/) ~> 0.63 * [OpenVR](https://github.com/ValveSoftware/openvr) ~> 0.91 (Win32 only) * [Polyvox](http://www.volumesoffun.com/) ~> 0.2.1 -* [QuaZip](http://sourceforge.net/projects/quazip/files/quazip/) ~> 0.7.1 +* [QuaZip](https://sourceforge.net/projects/quazip/files/quazip/) ~> 0.7.1 * [SDL2](https://www.libsdl.org/download-2.0.php) ~> 2.0.3 -* [soxr](http://soxr.sourceforge.net) ~> 0.1.1 +* [soxr](https://sourceforge.net/p/soxr/wiki/Home/) ~> 0.1.1 * [Intel Threading Building Blocks](https://www.threadingbuildingblocks.org/) ~> 4.3 * [Sixense](http://sixense.com/) ~> 071615 * [zlib](http://www.zlib.net/) ~> 1.28 (Win32 only) diff --git a/BUILD_OSX.md b/BUILD_OSX.md index 55d4276aa0..980263cbbc 100644 --- a/BUILD_OSX.md +++ b/BUILD_OSX.md @@ -1,7 +1,7 @@ Please read the [general build guide](BUILD.md) for information on dependencies required for all platforms. Only OS X specific instructions are found in this file. ###Homebrew -[Homebrew](http://brew.sh/) is an excellent package manager for OS X. It makes install of some High Fidelity dependencies very simple. +[Homebrew](https://brew.sh/) is an excellent package manager for OS X. It makes install of some High Fidelity dependencies very simple. brew tap homebrew/versions brew install cmake openssl @@ -18,11 +18,11 @@ Note that this uses the version from the homebrew formula at the time of this wr ###Qt You can use the online installer or the offline installer. -* [Download the online installer](http://www.qt.io/download-open-source/#section-2) +* [Download the online installer](https://www.qt.io/download-open-source/#section-2) * When it asks you to select components, select the following: * Qt > Qt 5.6 -* [Download the offline installer](http://download.qt.io/official_releases/qt/5.6/5.6.1-1/qt-opensource-mac-x64-clang-5.6.1-1.dmg) +* [Download the offline installer](https://download.qt.io/official_releases/qt/5.6/5.6.1-1/qt-opensource-mac-x64-clang-5.6.1-1.dmg) Once Qt is installed, you need to manually configure the following: * Set the QT_CMAKE_PREFIX_PATH environment variable to your `Qt5.6.1/5.6/clang_64/lib/cmake/` directory. diff --git a/BUILD_WIN.md b/BUILD_WIN.md index b8adaad8d1..45373d3093 100644 --- a/BUILD_WIN.md +++ b/BUILD_WIN.md @@ -33,8 +33,8 @@ You can use the online installer or the offline installer. If you use the offlin * Qt > Qt 5.6.1 > **msvc2013 64-bit** * Download the offline installer, 32- or 64-bit to match your build preference: - * [32-bit](http://download.qt.io/official_releases/qt/5.6/5.6.1-1/qt-opensource-windows-x86-msvc2013-5.6.1-1.exe) - * [64-bit](http://download.qt.io/official_releases/qt/5.6/5.6.1-1/qt-opensource-windows-x86-msvc2013_64-5.6.1-1.exe) + * [32-bit](https://download.qt.io/official_releases/qt/5.6/5.6.1-1/qt-opensource-windows-x86-msvc2013-5.6.1-1.exe) + * [64-bit](https://download.qt.io/official_releases/qt/5.6/5.6.1-1/qt-opensource-windows-x86-msvc2013_64-5.6.1-1.exe) Once Qt is installed, you need to manually configure the following: * Set the QT_CMAKE_PREFIX_PATH environment variable to your `Qt\5.6.1\msvc2013\lib\cmake` or `Qt\5.6.1\msvc2013_64\lib\cmake` directory. @@ -72,7 +72,7 @@ Your system may already have several versions of the OpenSSL DLL's (ssleay32.dll QSslSocket: cannot resolve SSL_CTX_set_next_proto_select_cb QSslSocket: cannot resolve SSL_get0_next_proto_negotiated -To prevent these problems, install OpenSSL yourself. Download one of the following binary packages [from this website](http://slproweb.com/products/Win32OpenSSL.html): +To prevent these problems, install OpenSSL yourself. Download one of the following binary packages [from this website](https://slproweb.com/products/Win32OpenSSL.html): * Win32 OpenSSL v1.0.1q * Win64 OpenSSL v1.0.1q diff --git a/README.md b/README.md index 44bfb94634..00e7cbc45b 100644 --- a/README.md +++ b/README.md @@ -11,11 +11,11 @@ We're hiring! We're looking for skilled developers; send your resume to hiring@highfidelity.com ##### Chat with us -Come chat with us in [our Gitter](http://gitter.im/highfidelity/hifi) if you have any questions or just want to say hi! +Come chat with us in [our Gitter](https://gitter.im/highfidelity/hifi) if you have any questions or just want to say hi! Documentation ========= -Documentation is available at [docs.highfidelity.com](http://docs.highfidelity.com), if something is missing, please suggest it via a new job on Worklist (add to the hifi-docs project). +Documentation is available at [docs.highfidelity.com](https://docs.highfidelity.com), if something is missing, please suggest it via a new job on Worklist (add to the hifi-docs project). Build Instructions ========= diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index 3bad7ee647..28384f9c1c 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -300,11 +300,14 @@ Rectangle { anchors.verticalCenter: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter onClicked: { - var newValue = !model["personalMute"]; - userModel.setProperty(model.userIndex, "personalMute", newValue) - userModelData[model.userIndex]["personalMute"] = newValue // Defensive programming - Users["personalMute"](model.sessionId, newValue) - UserActivityLogger["palAction"](newValue ? "personalMute" : "un-personalMute", model.sessionId) + // cannot change mute status when ignoring + if (!model["ignore"]) { + var newValue = !model["personalMute"]; + userModel.setProperty(model.userIndex, "personalMute", newValue) + userModelData[model.userIndex]["personalMute"] = newValue // Defensive programming + Users["personalMute"](model.sessionId, newValue) + UserActivityLogger["palAction"](newValue ? "personalMute" : "un-personalMute", model.sessionId) + } } } @@ -336,6 +339,7 @@ Rectangle { } else { delete ignored[model.sessionId] } + avgAudioVolume.glyph = avgAudioVolume.getGlyph() } // http://doc.qt.io/qt-5/qtqml-syntax-propertybinding.html#creating-property-bindings-from-javascript // I'm using an explicit binding here because clicking a checkbox breaks the implicit binding as set by diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 8bb2092f94..644dbeabba 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2926,10 +2926,12 @@ void Application::keyPressEvent(QKeyEvent* event) { } break; case Qt::Key_P: { - bool isFirstPersonChecked = Menu::getInstance()->isOptionChecked(MenuOption::FirstPerson); - Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, !isFirstPersonChecked); - Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, isFirstPersonChecked); - cameraMenuChanged(); + if (!(isShifted || isMeta || isOption)) { + bool isFirstPersonChecked = Menu::getInstance()->isOptionChecked(MenuOption::FirstPerson); + Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, !isFirstPersonChecked); + Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, isFirstPersonChecked); + cameraMenuChanged(); + } break; } diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index acf97ad5f7..c131367aee 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -577,7 +577,7 @@ Menu::Menu() { nodeList.data(), SLOT(toggleSendNewerDSConnectVersion(bool))); #endif - + // Developer >> Tests >>> MenuWrapper* testMenu = developerMenu->addMenu("Tests"); addActionToQMenuAndActionHash(testMenu, MenuOption::RunClientScriptTests, 0, dialogsManager.data(), SLOT(showTestingResults())); @@ -628,9 +628,9 @@ Menu::Menu() { auto scope = DependencyManager::get(); MenuWrapper* audioScopeMenu = audioDebugMenu->addMenu("Audio Scope"); - addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScope, Qt::CTRL | Qt::Key_P, false, + addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScope, Qt::CTRL | Qt::Key_F2, false, scope.data(), SLOT(toggle())); - addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScopePause, Qt::CTRL | Qt::SHIFT | Qt::Key_P, false, + addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScopePause, Qt::CTRL | Qt::SHIFT | Qt::Key_F2, false, scope.data(), SLOT(togglePause())); addDisabledActionAndSeparator(audioScopeMenu, "Display Frames"); diff --git a/interface/src/ui/overlays/Line3DOverlay.cpp b/interface/src/ui/overlays/Line3DOverlay.cpp index 23668bcc25..e1ea06c599 100644 --- a/interface/src/ui/overlays/Line3DOverlay.cpp +++ b/interface/src/ui/overlays/Line3DOverlay.cpp @@ -23,10 +23,15 @@ Line3DOverlay::Line3DOverlay() : Line3DOverlay::Line3DOverlay(const Line3DOverlay* line3DOverlay) : Base3DOverlay(line3DOverlay), - _start(line3DOverlay->_start), - _end(line3DOverlay->_end), _geometryCacheID(DependencyManager::get()->allocateID()) { + setParentID(line3DOverlay->getParentID()); + setParentJointIndex(line3DOverlay->getParentJointIndex()); + setLocalTransform(line3DOverlay->getLocalTransform()); + _direction = line3DOverlay->getDirection(); + _length = line3DOverlay->getLength(); + _endParentID = line3DOverlay->getEndParentID(); + _endParentJointIndex = line3DOverlay->getEndJointIndex(); } Line3DOverlay::~Line3DOverlay() { @@ -37,17 +42,23 @@ Line3DOverlay::~Line3DOverlay() { } glm::vec3 Line3DOverlay::getStart() const { - bool success; - glm::vec3 worldStart = localToWorld(_start, getParentID(), getParentJointIndex(), success); - if (!success) { - qDebug() << "Line3DOverlay::getStart failed"; - } - return worldStart; + return getPosition(); } glm::vec3 Line3DOverlay::getEnd() const { bool success; - glm::vec3 worldEnd = localToWorld(_end, getParentID(), getParentJointIndex(), success); + glm::vec3 localEnd; + glm::vec3 worldEnd; + + if (_endParentID != QUuid()) { + glm::vec3 localOffset = _direction * _length; + bool success; + worldEnd = localToWorld(localOffset, _endParentID, _endParentJointIndex, success); + return worldEnd; + } + + localEnd = getLocalEnd(); + worldEnd = localToWorld(localEnd, getParentID(), getParentJointIndex(), success); if (!success) { qDebug() << "Line3DOverlay::getEnd failed"; } @@ -55,27 +66,55 @@ glm::vec3 Line3DOverlay::getEnd() const { } void Line3DOverlay::setStart(const glm::vec3& start) { - bool success; - _start = worldToLocal(start, getParentID(), getParentJointIndex(), success); - if (!success) { - qDebug() << "Line3DOverlay::setStart failed"; - } + setPosition(start); } void Line3DOverlay::setEnd(const glm::vec3& end) { bool success; - _end = worldToLocal(end, getParentID(), getParentJointIndex(), success); + glm::vec3 localStart; + glm::vec3 localEnd; + glm::vec3 offset; + + if (_endParentID != QUuid()) { + offset = worldToLocal(end, _endParentID, _endParentJointIndex, success); + } else { + localStart = getLocalStart(); + localEnd = worldToLocal(end, getParentID(), getParentJointIndex(), success); + offset = localEnd - localStart; + } if (!success) { qDebug() << "Line3DOverlay::setEnd failed"; + return; + } + + _length = glm::length(offset); + if (_length > 0.0f) { + _direction = glm::normalize(offset); + } else { + _direction = glm::vec3(0.0f); + } +} + +void Line3DOverlay::setLocalEnd(const glm::vec3& localEnd) { + glm::vec3 offset; + if (_endParentID != QUuid()) { + offset = localEnd; + } else { + glm::vec3 localStart = getLocalStart(); + offset = localEnd - localStart; + } + _length = glm::length(offset); + if (_length > 0.0f) { + _direction = glm::normalize(offset); + } else { + _direction = glm::vec3(0.0f); } } AABox Line3DOverlay::getBounds() const { auto extents = Extents{}; - extents.addPoint(_start); - extents.addPoint(_end); - extents.transform(getTransform()); - + extents.addPoint(getStart()); + extents.addPoint(getEnd()); return AABox(extents); } @@ -90,18 +129,20 @@ void Line3DOverlay::render(RenderArgs* args) { glm::vec4 colorv4(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha); auto batch = args->_batch; if (batch) { - batch->setModelTransform(getTransform()); + batch->setModelTransform(Transform()); + glm::vec3 start = getStart(); + glm::vec3 end = getEnd(); auto geometryCache = DependencyManager::get(); if (getIsDashedLine()) { // TODO: add support for color to renderDashedLine() geometryCache->bindSimpleProgram(*batch, false, false, false, true, true); - geometryCache->renderDashedLine(*batch, _start, _end, colorv4, _geometryCacheID); + geometryCache->renderDashedLine(*batch, start, end, colorv4, _geometryCacheID); } else if (_glow > 0.0f) { - geometryCache->renderGlowLine(*batch, _start, _end, colorv4, _glow, _glowWidth, _geometryCacheID); + geometryCache->renderGlowLine(*batch, start, end, colorv4, _glow, _glowWidth, _geometryCacheID); } else { geometryCache->bindSimpleProgram(*batch, false, false, false, true, true); - geometryCache->renderLine(*batch, _start, _end, colorv4, _geometryCacheID); + geometryCache->renderLine(*batch, start, end, colorv4, _geometryCacheID); } } } @@ -116,6 +157,10 @@ const render::ShapeKey Line3DOverlay::getShapeKey() { void Line3DOverlay::setProperties(const QVariantMap& originalProperties) { QVariantMap properties = originalProperties; + glm::vec3 newStart(0.0f); + bool newStartSet { false }; + glm::vec3 newEnd(0.0f); + bool newEndSet { false }; auto start = properties["start"]; // if "start" property was not there, check to see if they included aliases: startPoint @@ -123,30 +168,57 @@ void Line3DOverlay::setProperties(const QVariantMap& originalProperties) { start = properties["startPoint"]; } if (start.isValid()) { - setStart(vec3FromVariant(start)); + newStart = vec3FromVariant(start); + newStartSet = true; } properties.remove("start"); // so that Base3DOverlay doesn't respond to it - auto localStart = properties["localStart"]; - if (localStart.isValid()) { - _start = vec3FromVariant(localStart); - } - properties.remove("localStart"); // so that Base3DOverlay doesn't respond to it - auto end = properties["end"]; // if "end" property was not there, check to see if they included aliases: endPoint if (!end.isValid()) { end = properties["endPoint"]; } if (end.isValid()) { - setEnd(vec3FromVariant(end)); + newEnd = vec3FromVariant(end); + newEndSet = true; + } + properties.remove("end"); // so that Base3DOverlay doesn't respond to it + + auto length = properties["length"]; + if (length.isValid()) { + _length = length.toFloat(); + } + + Base3DOverlay::setProperties(properties); + + auto endParentIDProp = properties["endParentID"]; + if (endParentIDProp.isValid()) { + _endParentID = QUuid(endParentIDProp.toString()); + } + auto endParentJointIndexProp = properties["endParentJointIndex"]; + if (endParentJointIndexProp.isValid()) { + _endParentJointIndex = endParentJointIndexProp.toInt(); + } + + auto localStart = properties["localStart"]; + if (localStart.isValid()) { + glm::vec3 tmpLocalEnd = getLocalEnd(); + setLocalStart(vec3FromVariant(localStart)); + setLocalEnd(tmpLocalEnd); } auto localEnd = properties["localEnd"]; if (localEnd.isValid()) { - _end = vec3FromVariant(localEnd); + setLocalEnd(vec3FromVariant(localEnd)); + } + + // these are saved until after Base3DOverlay::setProperties so parenting infomation can be set, first + if (newStartSet) { + setStart(newStart); + } + if (newEndSet) { + setEnd(newEnd); } - properties.remove("localEnd"); // so that Base3DOverlay doesn't respond to it auto glow = properties["glow"]; if (glow.isValid()) { @@ -161,7 +233,6 @@ void Line3DOverlay::setProperties(const QVariantMap& originalProperties) { setGlow(glowWidth.toFloat()); } - Base3DOverlay::setProperties(properties); } QVariant Line3DOverlay::getProperty(const QString& property) { @@ -171,6 +242,15 @@ QVariant Line3DOverlay::getProperty(const QString& property) { if (property == "end" || property == "endPoint" || property == "p2") { return vec3toVariant(getEnd()); } + if (property == "localStart") { + return vec3toVariant(getLocalStart()); + } + if (property == "localEnd") { + return vec3toVariant(getLocalEnd()); + } + if (property == "length") { + return QVariant(getLength()); + } return Base3DOverlay::getProperty(property); } diff --git a/interface/src/ui/overlays/Line3DOverlay.h b/interface/src/ui/overlays/Line3DOverlay.h index b4e2ba8168..aceecff6b2 100644 --- a/interface/src/ui/overlays/Line3DOverlay.h +++ b/interface/src/ui/overlays/Line3DOverlay.h @@ -15,7 +15,7 @@ class Line3DOverlay : public Base3DOverlay { Q_OBJECT - + public: static QString const TYPE; virtual QString getType() const override { return TYPE; } @@ -37,6 +37,9 @@ public: void setStart(const glm::vec3& start); void setEnd(const glm::vec3& end); + void setLocalStart(const glm::vec3& localStart) { setLocalPosition(localStart); } + void setLocalEnd(const glm::vec3& localEnd); + void setGlow(const float& glow) { _glow = glow; } void setGlowWidth(const float& glowWidth) { _glowWidth = glowWidth; } @@ -47,13 +50,26 @@ public: virtual void locationChanged(bool tellPhysics = true) override; -protected: - glm::vec3 _start; - glm::vec3 _end; + glm::vec3 getDirection() const { return _direction; } + float getLength() const { return _length; } + glm::vec3 getLocalStart() const { return getLocalPosition(); } + glm::vec3 getLocalEnd() const { return getLocalStart() + _direction * _length; } + QUuid getEndParentID() const { return _endParentID; } + quint16 getEndJointIndex() const { return _endParentJointIndex; } + +private: + QUuid _endParentID; + quint16 _endParentJointIndex { INVALID_JOINT_INDEX }; + + // _direction and _length are in the parent's frame. If _endParentID is set, they are + // relative to that. Otherwise, they are relative to the local-start-position (which is the + // same as localPosition) + glm::vec3 _direction; // in parent frame + float _length { 1.0 }; // in parent frame + float _glow { 0.0 }; float _glowWidth { 0.0 }; int _geometryCacheID; }; - #endif // hifi_Line3DOverlay_h diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index 55a9c8b9e4..8ab65efd24 100644 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -160,13 +160,14 @@ RenderDeferredTask::RenderDeferredTask(RenderFetchCullSortTask::Output items) { addJob("DrawOverlay3DOpaque", overlayOpaquesInputs, true); addJob("DrawOverlay3DTransparent", overlayTransparentsInputs, false); - // Debugging stages { // Bounds do not draw on stencil buffer, so they must come last addJob("DrawMetaBounds", metas); + addJob("DrawOverlayOpaqueBounds", overlayOpaques); + addJob("DrawOverlayTransparentBounds", overlayTransparents); // Debugging Deferred buffer job const auto debugFramebuffers = render::Varying(DebugDeferredBuffer::Inputs(deferredFramebuffer, linearDepthTarget, surfaceGeometryFramebuffer, ambientOcclusionFramebuffer)); diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 701bc35ce3..422f560e7e 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -266,6 +266,27 @@ CONTROLLER_STATE_MACHINE[STATE_OVERLAY_STYLUS_TOUCHING] = { }; CONTROLLER_STATE_MACHINE[STATE_OVERLAY_LASER_TOUCHING] = CONTROLLER_STATE_MACHINE[STATE_OVERLAY_STYLUS_TOUCHING]; +// Object assign polyfill +if (typeof Object.assign != 'function') { + Object.assign = function(target, varArgs) { + 'use strict'; + if (target == null) { + throw new TypeError('Cannot convert undefined or null to object'); + } + var to = Object(target); + for (var index = 1; index < arguments.length; index++) { + var nextSource = arguments[index]; + if (nextSource != null) { + for (var nextKey in nextSource) { + if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { + to[nextKey] = nextSource[nextKey]; + } + } + } + } + return to; + }; +} function distanceBetweenPointAndEntityBoundingBox(point, entityProps) { var entityXform = new Xform(entityProps.rotation, entityProps.position); @@ -741,6 +762,10 @@ function MyController(hand) { this.homeButtonTouched = false; this.editTriggered = false; + this.controllerJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? + "_CONTROLLER_RIGHTHAND" : + "_CONTROLLER_LEFTHAND"); + // Until there is some reliable way to keep track of a "stack" of parentIDs, we'll have problems // when more than one avatar does parenting grabs on things. This script tries to work // around this with two associative arrays: previousParentID and previousParentJointIndex. If @@ -792,9 +817,6 @@ function MyController(hand) { // for visualizations this.overlayLine = null; - - // for lights - this.overlayLine = null; this.searchSphere = null; this.waitForTriggerRelease = false; @@ -921,9 +943,7 @@ function MyController(hand) { ignoreRayIntersection: true, drawInFront: false, parentID: AVATAR_SELF_ID, - parentJointIndex: MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? - "_CONTROLLER_RIGHTHAND" : - "_CONTROLLER_LEFTHAND") + parentJointIndex: this.controllerJointIndex }); } }; @@ -1008,32 +1028,38 @@ function MyController(hand) { } }; - this.overlayLineOn = function(closePoint, farPoint, color) { + this.overlayLineOn = function(closePoint, farPoint, color, farParentID) { if (this.overlayLine === null) { var lineProperties = { name: "line", glow: 1.0, - start: closePoint, - end: farPoint, - color: color, - ignoreRayIntersection: true, // always ignore this - drawInFront: true, // Even when burried inside of something, show it. - visible: true, - alpha: 1 - }; - this.overlayLine = Overlays.addOverlay("line3d", lineProperties); - - } else { - Overlays.editOverlay(this.overlayLine, { lineWidth: 5, start: closePoint, end: farPoint, color: color, - visible: true, ignoreRayIntersection: true, // always ignore this drawInFront: true, // Even when burried inside of something, show it. - alpha: 1 - }); + visible: true, + alpha: 1, + parentID: AVATAR_SELF_ID, + parentJointIndex: this.controllerJointIndex, + endParentID: farParentID + }; + this.overlayLine = Overlays.addOverlay("line3d", lineProperties); + + } else { + if (farParentID && farParentID != NULL_UUID) { + Overlays.editOverlay(this.overlayLine, { + color: color, + endParentID: farParentID + }); + } else { + Overlays.editOverlay(this.overlayLine, { + length: Vec3.distance(farPoint, closePoint), + color: color, + endParentID: farParentID + }); + } } }; @@ -1460,7 +1486,18 @@ function MyController(hand) { return true; }; + this.entityIsCloneable = function(entityID) { + var entityProps = entityPropertiesCache.getGrabbableProps(entityID); + var props = entityPropertiesCache.getProps(entityID); + if (!props) { + return false; + } + if (entityProps.hasOwnProperty("cloneable")) { + return entityProps.cloneable; + } + return false; + } this.entityIsGrabbable = function(entityID) { var grabbableProps = entityPropertiesCache.getGrabbableProps(entityID); var props = entityPropertiesCache.getProps(entityID); @@ -1540,7 +1577,7 @@ function MyController(hand) { this.entityIsNearGrabbable = function(entityID, handPosition, maxDistance) { - if (!this.entityIsGrabbable(entityID)) { + if (!this.entityIsCloneable(entityID) && !this.entityIsGrabbable(entityID)) { return false; } @@ -2231,7 +2268,10 @@ function MyController(hand) { var rayPickInfo = this.calcRayPickInfo(this.hand); - this.overlayLineOn(rayPickInfo.searchRay.origin, Vec3.subtract(grabbedProperties.position, this.offsetPosition), COLORS_GRAB_DISTANCE_HOLD); + this.overlayLineOn(rayPickInfo.searchRay.origin, + Vec3.subtract(grabbedProperties.position, this.offsetPosition), + COLORS_GRAB_DISTANCE_HOLD, + this.grabbedThingID); var distanceToObject = Vec3.length(Vec3.subtract(MyAvatar.position, this.currentObjectPosition)); var success = Entities.updateAction(this.grabbedThingID, this.actionID, { @@ -2403,6 +2443,9 @@ function MyController(hand) { this.offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, this.offsetRotation)), offset); } + // This boolean is used to check if the object that is grabbed has just been cloned + // It is only set true, if the object that is grabbed creates a new clone. + var isClone = false; var isPhysical = propsArePhysical(grabbedProperties) || (!this.grabbedIsOverlay && entityHasActions(this.grabbedThingID)); if (isPhysical && this.state == STATE_NEAR_GRABBING && grabbedProperties.parentID === NULL_UUID) { @@ -2420,9 +2463,7 @@ function MyController(hand) { this.actionID = null; var handJointIndex; if (this.ignoreIK) { - handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? - "_CONTROLLER_RIGHTHAND" : - "_CONTROLLER_LEFTHAND"); + handJointIndex = this.controllerJointIndex; } else { handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); } @@ -2441,6 +2482,54 @@ function MyController(hand) { if (this.grabbedIsOverlay) { Overlays.editOverlay(this.grabbedThingID, reparentProps); } else { + if (grabbedProperties.userData.length > 0) { + try{ + var userData = JSON.parse(grabbedProperties.userData); + var grabInfo = userData.grabbableKey; + if (grabInfo && grabInfo.cloneable) { + // Check if + var worldEntities = Entities.findEntitiesInBox(Vec3.subtract(MyAvatar.position, {x:25,y:25, z:25}), {x:50, y: 50, z: 50}) + var count = 0; + worldEntities.forEach(function(item) { + var item = Entities.getEntityProperties(item, ["name"]); + if (item.name === grabbedProperties.name) { + count++; + } + }) + var cloneableProps = Entities.getEntityProperties(grabbedProperties.id); + var lifetime = grabInfo.cloneLifetime ? grabInfo.cloneLifetime : 300; + var limit = grabInfo.cloneLimit ? grabInfo.cloneLimit : 10; + var dynamic = grabInfo.cloneDynamic ? grabInfo.cloneDynamic : false; + var cUserData = Object.assign({}, userData); + var cProperties = Object.assign({}, cloneableProps); + isClone = true; + + if (count > limit) { + delete cloneableProps; + delete lifetime; + delete cUserData; + delete cProperties; + return; + } + + delete cUserData.grabbableKey.cloneLifetime; + delete cUserData.grabbableKey.cloneable; + delete cUserData.grabbableKey.cloneDynamic; + delete cUserData.grabbableKey.cloneLimit; + delete cProperties.id + + cProperties.dynamic = dynamic; + cProperties.locked = false; + cUserData.grabbableKey.triggerable = true; + cUserData.grabbableKey.grabbable = true; + cProperties.lifetime = lifetime; + cProperties.userData = JSON.stringify(cUserData); + var cloneID = Entities.addEntity(cProperties); + this.grabbedThingID = cloneID; + grabbedProperties = Entities.getEntityProperties(cloneID); + } + }catch(e) {} + } Entities.editEntity(this.grabbedThingID, reparentProps); } @@ -2452,7 +2541,6 @@ function MyController(hand) { this.previousParentID[this.grabbedThingID] = grabbedProperties.parentID; this.previousParentJointIndex[this.grabbedThingID] = grabbedProperties.parentJointIndex; } - Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ action: 'equip', grabbedEntity: this.grabbedThingID, @@ -2468,22 +2556,37 @@ function MyController(hand) { }); } - if (this.state == STATE_NEAR_GRABBING) { - this.callEntityMethodOnGrabbed("startNearGrab"); - } else { // this.state == STATE_HOLD - this.callEntityMethodOnGrabbed("startEquip"); + var _this = this; + /* + * Setting context for function that is either called via timer or directly, depending if + * if the object in question is a clone. If it is a clone, we need to make sure that the intial equipment event + * is called correctly, as these just freshly created entity may not have completely initialized. + */ + var grabEquipCheck = function () { + if (_this.state == STATE_NEAR_GRABBING) { + _this.callEntityMethodOnGrabbed("startNearGrab"); + } else { // this.state == STATE_HOLD + _this.callEntityMethodOnGrabbed("startEquip"); + } + + _this.currentHandControllerTipPosition = + (_this.hand === RIGHT_HAND) ? MyAvatar.rightHandTipPosition : MyAvatar.leftHandTipPosition; + _this.currentObjectTime = Date.now(); + + _this.currentObjectPosition = grabbedProperties.position; + _this.currentObjectRotation = grabbedProperties.rotation; + _this.currentVelocity = ZERO_VEC; + _this.currentAngularVelocity = ZERO_VEC; + + _this.prevDropDetected = false; } - this.currentHandControllerTipPosition = - (this.hand === RIGHT_HAND) ? MyAvatar.rightHandTipPosition : MyAvatar.leftHandTipPosition; - this.currentObjectTime = Date.now(); - - this.currentObjectPosition = grabbedProperties.position; - this.currentObjectRotation = grabbedProperties.rotation; - this.currentVelocity = ZERO_VEC; - this.currentAngularVelocity = ZERO_VEC; - - this.prevDropDetected = false; + if (isClone) { + // 100 ms seems to be sufficient time to force the check even occur after the object has been initialized. + Script.setTimeout(grabEquipCheck, 100); + } else { + grabEquipCheck(); + } }; this.nearGrabbing = function(deltaTime, timestamp) { @@ -3167,9 +3270,7 @@ function MyController(hand) { return true; } - var controllerJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? - "_CONTROLLER_RIGHTHAND" : - "_CONTROLLER_LEFTHAND"); + var controllerJointIndex = this.controllerJointIndex; if (props.parentJointIndex == controllerJointIndex) { return true; } @@ -3195,9 +3296,7 @@ function MyController(hand) { children = children.concat(Entities.getChildrenIDsOfJoint(AVATAR_SELF_ID, handJointIndex)); // find children of faux controller joint - var controllerJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? - "_CONTROLLER_RIGHTHAND" : - "_CONTROLLER_LEFTHAND"); + var controllerJointIndex = this.controllerJointIndex; children = children.concat(Entities.getChildrenIDsOfJoint(MyAvatar.sessionUUID, controllerJointIndex)); children = children.concat(Entities.getChildrenIDsOfJoint(AVATAR_SELF_ID, controllerJointIndex)); @@ -3209,7 +3308,8 @@ function MyController(hand) { children = children.concat(Entities.getChildrenIDsOfJoint(AVATAR_SELF_ID, controllerCRJointIndex)); children.forEach(function(childID) { - if (childID !== _this.stylus) { + if (childID !== _this.stylus && + childID !== _this.overlayLine) { // we appear to be holding something and this script isn't in a state that would be holding something. // unhook it. if we previously took note of this entity's parent, put it back where it was. This // works around some problems that happen when more than one hand or avatar is passing something around. @@ -3305,6 +3405,7 @@ Messages.subscribe('Hifi-Hand-Disabler'); Messages.subscribe('Hifi-Hand-Grab'); Messages.subscribe('Hifi-Hand-RayPick-Blacklist'); Messages.subscribe('Hifi-Object-Manipulation'); +Messages.subscribe('Hifi-Hand-Drop'); var handleHandMessages = function(channel, message, sender) { var data; @@ -3390,6 +3491,15 @@ var handleHandMessages = function(channel, message, sender) { } catch (e) { print("WARNING: handControllerGrab.js -- error parsing Hifi-Hand-RayPick-Blacklist message: " + message); } + } else if (channel === 'Hifi-Hand-Drop') { + if (message === 'left') { + leftController.release(); + } else if (message === 'right') { + rightController.release(); + } else if (message === 'both') { + leftController.release(); + rightController.release(); + } } } }; diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 404cc26fcb..74080dbe09 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -57,6 +57,7 @@ selectionManager.addEventListener(function () { lightOverlayManager.updatePositions(); }); +const KEY_P = 80; //Key code for letter p used for Parenting hotkey. var DEGREES_TO_RADIANS = Math.PI / 180.0; var RADIANS_TO_DEGREES = 180.0 / Math.PI; @@ -170,8 +171,6 @@ var toolBar = (function () { tablet = null; function createNewEntity(properties) { - Settings.setValue(EDIT_SETTING, false); - var dimensions = properties.dimensions ? properties.dimensions : DEFAULT_DIMENSIONS; var position = getPositionToCreateEntity(); var entityID = null; @@ -822,7 +821,6 @@ function setupModelMenus() { }); modelMenuAddedDelete = true; } - Menu.addMenuItem({ menuName: "Edit", menuItemName: "Entity List...", @@ -830,11 +828,25 @@ function setupModelMenus() { afterItem: "Entities", grouping: "Advanced" }); + + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Parent Entity to Last", + afterItem: "Entity List...", + grouping: "Advanced" + }); + + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Unparent Entity", + afterItem: "Parent Entity to Last", + grouping: "Advanced" + }); Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Large Models", shortcutKey: "CTRL+META+L", - afterItem: "Entity List...", + afterItem: "Unparent Entity", isCheckable: true, isChecked: true, grouping: "Advanced" @@ -937,6 +949,8 @@ function cleanupModelMenus() { Menu.removeMenuItem("Edit", "Delete"); } + Menu.removeMenuItem("Edit", "Parent Entity to Last"); + Menu.removeMenuItem("Edit", "Unparent Entity"); Menu.removeMenuItem("Edit", "Entity List..."); Menu.removeMenuItem("Edit", "Allow Selecting of Large Models"); Menu.removeMenuItem("Edit", "Allow Selecting of Small Models"); @@ -969,6 +983,9 @@ Script.scriptEnding.connect(function () { Overlays.deleteOverlay(importingSVOImageOverlay); Overlays.deleteOverlay(importingSVOTextOverlay); + + Controller.keyReleaseEvent.disconnect(keyReleaseEvent); + Controller.keyPressEvent.disconnect(keyPressEvent); }); var lastOrientation = null; @@ -1080,7 +1097,68 @@ function recursiveDelete(entities, childrenList) { Entities.deleteEntity(entityID); } } +function unparentSelectedEntities() { + if (SelectionManager.hasSelection()) { + var selectedEntities = selectionManager.selections; + var parentCheck = false; + if (selectedEntities.length < 1) { + Window.notifyEditError("You must have an entity selected inorder to unparent it."); + return; + } + selectedEntities.forEach(function (id, index) { + var parentId = Entities.getEntityProperties(id, ["parentID"]).parentID; + if (parentId !== null && parentId.length > 0 && parentId !== "{00000000-0000-0000-0000-000000000000}") { + parentCheck = true; + } + Entities.editEntity(id, {parentID: null}) + return true; + }); + if (parentCheck) { + if (selectedEntities.length > 1) { + Window.notify("Entities unparented"); + } else { + Window.notify("Entity unparented"); + } + } else { + if (selectedEntities.length > 1) { + Window.notify("Selected Entities have no parents"); + } else { + Window.notify("Selected Entity does not have a parent"); + } + } + } else { + Window.notifyEditError("You have nothing selected to unparent"); + } +} +function parentSelectedEntities() { + if (SelectionManager.hasSelection()) { + var selectedEntities = selectionManager.selections; + if (selectedEntities.length <= 1) { + Window.notifyEditError("You must have multiple entities selected in order to parent them"); + return; + } + var parentCheck = false; + var lastEntityId = selectedEntities[selectedEntities.length-1]; + selectedEntities.forEach(function (id, index) { + if (lastEntityId !== id) { + var parentId = Entities.getEntityProperties(id, ["parentID"]).parentID; + if (parentId !== lastEntityId) { + parentCheck = true; + } + Entities.editEntity(id, {parentID: lastEntityId}) + } + }); + + if(parentCheck) { + Window.notify("Entities parented"); + }else { + Window.notify("Entities are already parented to last"); + } + } else { + Window.notifyEditError("You have nothing selected to parent"); + } +} function deleteSelectedEntities() { if (SelectionManager.hasSelection()) { selectedParticleEntity = 0; @@ -1143,6 +1221,10 @@ function handeMenuEvent(menuItem) { Entities.setLightsArePickable(Menu.isOptionChecked("Allow Selecting of Lights")); } else if (menuItem === "Delete") { deleteSelectedEntities(); + } else if (menuItem === "Parent Entity to Last") { + parentSelectedEntities(); + } else if (menuItem === "Unparent Entity") { + unparentSelectedEntities(); } else if (menuItem === "Export Entities") { if (!selectionManager.hasSelection()) { Window.notifyEditError("No entities have been selected."); @@ -1268,13 +1350,12 @@ Window.svoImportRequested.connect(importSVO); Menu.menuItemEvent.connect(handeMenuEvent); -Controller.keyPressEvent.connect(function (event) { +var keyPressEvent = function (event) { if (isActive) { cameraManager.keyPressEvent(event); } -}); - -Controller.keyReleaseEvent.connect(function (event) { +}; +var keyReleaseEvent = function (event) { if (isActive) { cameraManager.keyReleaseEvent(event); } @@ -1308,8 +1389,16 @@ Controller.keyReleaseEvent.connect(function (event) { }); grid.setPosition(newPosition); } + } else if (event.key === KEY_P && event.isControl && !event.isAutoRepeat ) { + if (event.isShifted) { + unparentSelectedEntities(); + } else { + parentSelectedEntities(); + } } -}); +}; +Controller.keyReleaseEvent.connect(keyReleaseEvent); +Controller.keyPressEvent.connect(keyPressEvent); function recursiveAdd(newParentID, parentData) { var children = parentData.children; @@ -1557,6 +1646,10 @@ var PropertiesTool = function (opts) { } pushCommandForSelections(); selectionManager._update(); + } else if(data.type === 'parent') { + parentSelectedEntities(); + } else if(data.type === 'unparent') { + unparentSelectedEntities(); } else if(data.type === 'saveUserData'){ //the event bridge and json parsing handle our avatar id string differently. var actualID = data.id.split('"')[1]; @@ -1814,6 +1907,9 @@ var PopupMenu = function () { for (var i = 0; i < overlays.length; i++) { Overlays.deleteOverlay(overlays[i]); } + Controller.mousePressEvent.disconnect(self.mousePressEvent); + Controller.mouseMoveEvent.disconnect(self.mouseMoveEvent); + Controller.mouseReleaseEvent.disconnect(self.mouseReleaseEvent); } Controller.mousePressEvent.connect(self.mousePressEvent); @@ -1856,7 +1952,11 @@ function selectParticleEntity(entityID) { entityListTool.webView.webEventReceived.connect(function (data) { data = JSON.parse(data); - if (data.type === "selectionUpdate") { + if(data.type === 'parent') { + parentSelectedEntities(); + } else if(data.type === 'unparent') { + unparentSelectedEntities(); + } else if (data.type === "selectionUpdate") { var ids = data.entityIds; if (ids.length === 1) { if (Entities.getEntityProperties(ids[0], "type").type === "ParticleEffect") { diff --git a/scripts/system/html/entityList.html b/scripts/system/html/entityList.html index 197d8f550a..3cb79353f9 100644 --- a/scripts/system/html/entityList.html +++ b/scripts/system/html/entityList.html @@ -89,6 +89,7 @@ +
No entities found in view within a 100 meter radius. Try moving to a different location and refreshing.
diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index 59c420b547..35accdd0df 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -61,7 +61,7 @@ - +

@@ -295,12 +295,29 @@
+
+ + +
+
diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 048d5561df..dd8e8199b4 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -19,6 +19,7 @@ const VISIBLE_GLYPH = ""; const TRANSPARENCY_GLYPH = ""; const SCRIPT_GLYPH = "k"; const DELETE = 46; // Key code for the delete key. +const KEY_P = 80; // Key code for letter p used for Parenting hotkey. const MAX_ITEMS = Number.MAX_VALUE; // Used to set the max length of the list of discovered entities. debugPrint = function (message) { @@ -26,7 +27,7 @@ debugPrint = function (message) { }; function loaded() { - openEventBridge(function() { + openEventBridge(function() { entityList = new List('entity-list', { valueNames: ['name', 'type', 'url', 'locked', 'visible'], page: MAX_ITEMS}); entityList.clear(); elEntityTable = document.getElementById("entity-table"); @@ -48,7 +49,7 @@ function loaded() { elNoEntitiesInView = document.getElementById("no-entities-in-view"); elNoEntitiesRadius = document.getElementById("no-entities-radius"); elEntityTableScroll = document.getElementById("entity-table-scroll"); - + document.getElementById("entity-name").onclick = function() { setSortColumn('name'); }; @@ -90,7 +91,7 @@ function loaded() { selection = selection.concat(selectedEntities); } else if (clickEvent.shiftKey && selectedEntities.length > 0) { var previousItemFound = -1; - var clickedItemFound = -1; + var clickedItemFound = -1; for (var entity in entityList.visibleItems) { if (clickedItemFound === -1 && this.dataset.entityId == entityList.visibleItems[entity].values().id) { clickedItemFound = entity; @@ -113,11 +114,11 @@ function loaded() { selection = selection.concat(betweenItems, selectedEntities); } } - + selectedEntities = selection; - + this.className = 'selected'; - + EventBridge.emitWebEvent(JSON.stringify({ type: "selectionUpdate", focus: false, @@ -126,7 +127,7 @@ function loaded() { refreshFooter(); } - + function onRowDoubleClicked() { EventBridge.emitWebEvent(JSON.stringify({ type: "selectionUpdate", @@ -134,7 +135,7 @@ function loaded() { entityIds: [this.dataset.entityId], })); } - + const BYTES_PER_MEGABYTE = 1024 * 1024; function decimalMegabytes(number) { @@ -173,7 +174,7 @@ function loaded() { currentElement.onclick = onRowClicked; currentElement.ondblclick = onRowDoubleClicked; }); - + if (refreshEntityListTimer) { clearTimeout(refreshEntityListTimer); } @@ -183,13 +184,13 @@ function loaded() { item.values({ name: name, url: filename, locked: locked, visible: visible }); } } - + function clearEntities() { entities = {}; entityList.clear(); refreshFooter(); } - + var elSortOrder = { name: document.querySelector('#entity-name .sort-order'), type: document.querySelector('#entity-type .sort-order'), @@ -215,12 +216,12 @@ function loaded() { entityList.sort(currentSortColumn, { order: currentSortOrder }); } setSortColumn('type'); - + function refreshEntities() { clearEntities(); EventBridge.emitWebEvent(JSON.stringify({ type: 'refresh' })); } - + function refreshFooter() { if (selectedEntities.length > 1) { elFooter.firstChild.nodeValue = selectedEntities.length + " entities selected"; @@ -239,7 +240,7 @@ function loaded() { entityList.search(elFilter.value); refreshFooter(); } - + function updateSelectedEntities(selectedIDs) { var notFound = false; for (var id in entities) { @@ -262,7 +263,7 @@ function loaded() { return notFound; } - + elRefresh.onclick = function() { refreshEntities(); } @@ -282,7 +283,7 @@ function loaded() { EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' })); refreshEntities(); } - + document.addEventListener("keydown", function (keyDownEvent) { if (keyDownEvent.target.nodeName === "INPUT") { return; @@ -292,8 +293,15 @@ function loaded() { EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' })); refreshEntities(); } + if (keyDownEvent.keyCode === KEY_P && keyDownEvent.ctrlKey) { + if (keyDownEvent.shiftKey) { + EventBridge.emitWebEvent(JSON.stringify({ type: 'unparent' })); + } else { + EventBridge.emitWebEvent(JSON.stringify({ type: 'parent' })); + } + } }, false); - + var isFilterInView = false; var FILTER_IN_VIEW_ATTRIBUTE = "pressed"; elNoEntitiesInView.style.display = "none"; @@ -320,7 +328,7 @@ function loaded() { if (window.EventBridge !== undefined) { EventBridge.scriptEventReceived.connect(function(data) { data = JSON.parse(data); - + if (data.type === "clearEntityList") { clearEntities(); } else if (data.type == "selectionUpdate") { @@ -426,4 +434,3 @@ function loaded() { event.preventDefault(); }, false); } - diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 35063a254f..71a868b292 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -24,9 +24,10 @@ var ICON_FOR_TYPE = { } var EDITOR_TIMEOUT_DURATION = 1500; - +const KEY_P = 80; //Key code for letter p used for Parenting hotkey. var colorPickers = []; var lastEntityID = null; + debugPrint = function(message) { EventBridge.emitWebEvent( JSON.stringify({ @@ -273,7 +274,7 @@ function updateCheckedSubProperty(propertyName, propertyValue, subPropertyElemen propertyValue += subPropertyString + ','; } } else { - // We've unchecked, so remove + // We've unchecked, so remove propertyValue = propertyValue.replace(subPropertyString + ",", ""); } @@ -323,13 +324,9 @@ function setUserDataFromEditor(noUpdate) { }) ); } - } - - } - -function userDataChanger(groupName, keyName, checkBoxElement, userDataElement, defaultValue) { +function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults) { var properties = {}; var parsedData = {}; try { @@ -339,17 +336,31 @@ function userDataChanger(groupName, keyName, checkBoxElement, userDataElement, d } else { parsedData = JSON.parse(userDataElement.value); } - } catch (e) {} if (!(groupName in parsedData)) { parsedData[groupName] = {} } - delete parsedData[groupName][keyName]; - if (checkBoxElement.checked !== defaultValue) { - parsedData[groupName][keyName] = checkBoxElement.checked; - } - + var keys = Object.keys(updateKeyPair); + keys.forEach(function (key) { + delete parsedData[groupName][key]; + if (updateKeyPair[key] !== null && updateKeyPair[key] !== "null") { + if (updateKeyPair[key] instanceof Element) { + if(updateKeyPair[key].type === "checkbox") { + if (updateKeyPair[key].checked !== defaults[key]) { + parsedData[groupName][key] = updateKeyPair[key].checked; + } + } else { + var val = isNaN(updateKeyPair[key].value) ? updateKeyPair[key].value : parseInt(updateKeyPair[key].value); + if (val !== defaults[key]) { + parsedData[groupName][key] = val; + } + } + } else { + parsedData[groupName][key] = updateKeyPair[key]; + } + } + }); if (Object.keys(parsedData[groupName]).length == 0) { delete parsedData[groupName]; } @@ -368,6 +379,12 @@ function userDataChanger(groupName, keyName, checkBoxElement, userDataElement, d properties: properties, }) ); +} +function userDataChanger(groupName, keyName, values, userDataElement, defaultValue) { + var val = {}, def = {}; + val[keyName] = values; + def[keyName] = defaultValue; + multiDataUpdater(groupName, val, userDataElement, def); }; function setTextareaScrolling(element) { @@ -521,6 +538,7 @@ function unbindAllInputs() { function loaded() { openEventBridge(function() { + var allSections = []; var elID = document.getElementById("property-id"); var elType = document.getElementById("property-type"); @@ -584,6 +602,13 @@ function loaded() { var elCollisionSoundURL = document.getElementById("property-collision-sound-url"); var elGrabbable = document.getElementById("property-grabbable"); + + var elCloneable = document.getElementById("property-cloneable"); + var elCloneableDynamic = document.getElementById("property-cloneable-dynamic"); + var elCloneableGroup = document.getElementById("group-cloneable-group"); + var elCloneableLifetime = document.getElementById("property-cloneable-lifetime"); + var elCloneableLimit = document.getElementById("property-cloneable-limit"); + var elWantsTrigger = document.getElementById("property-wants-trigger"); var elIgnoreIK = document.getElementById("property-ignore-ik"); @@ -780,7 +805,7 @@ function loaded() { if (lastEntityID !== '"' + properties.id + '"' && lastEntityID !== null && editor !== null) { saveJSONUserData(true); } - //the event bridge and json parsing handle our avatar id string differently. + //the event bridge and json parsing handle our avatar id string differently. lastEntityID = '"' + properties.id + '"'; elID.value = properties.id; @@ -847,8 +872,16 @@ function loaded() { elCollideOtherAvatar.checked = properties.collidesWith.indexOf("otherAvatar") > -1; elGrabbable.checked = properties.dynamic; + elWantsTrigger.checked = false; elIgnoreIK.checked = true; + + elCloneable.checked = false; + elCloneableDynamic.checked = false; + elCloneableGroup.style.display = elCloneable.checked ? "block": "none"; + elCloneableLimit.value = 10; + elCloneableLifetime.value = 300; + var parsedUserData = {} try { parsedUserData = JSON.parse(properties.userData); @@ -863,8 +896,25 @@ function loaded() { if ("ignoreIK" in parsedUserData["grabbableKey"]) { elIgnoreIK.checked = parsedUserData["grabbableKey"].ignoreIK; } + if ("cloneable" in parsedUserData["grabbableKey"]) { + elCloneable.checked = parsedUserData["grabbableKey"].cloneable; + elCloneableGroup.style.display = elCloneable.checked ? "block": "none"; + elCloneableLimit.value = elCloneable.checked ? 10: 0; + elCloneableLifetime.value = elCloneable.checked ? 300: 0; + elCloneableDynamic.checked = parsedUserData["grabbableKey"].cloneDynamic ? parsedUserData["grabbableKey"].cloneDynamic : properties.dynamic; + elDynamic.checked = elCloneable.checked ? false: properties.dynamic; + if (elCloneable.checked) { + if ("cloneLifetime" in parsedUserData["grabbableKey"]) { + elCloneableLifetime.value = parsedUserData["grabbableKey"].cloneLifetime ? parsedUserData["grabbableKey"].cloneLifetime : 300; + } + if ("cloneLimit" in parsedUserData["grabbableKey"]) { + elCloneableLimit.value = parsedUserData["grabbableKey"].cloneLimit ? parsedUserData["grabbableKey"].cloneLimit : 10; + } + } + } } - } catch (e) {} + } catch (e) { + } elCollisionSoundURL.value = properties.collisionSoundURL; elLifetime.value = properties.lifetime; @@ -1154,8 +1204,38 @@ function loaded() { }); elGrabbable.addEventListener('change', function() { + if(elCloneable.checked) { + elGrabbable.checked = false; + } userDataChanger("grabbableKey", "grabbable", elGrabbable, elUserData, properties.dynamic); }); + elCloneableDynamic.addEventListener('change', function (event){ + userDataChanger("grabbableKey", "cloneDynamic", event.target, elUserData, -1); + }); + elCloneable.addEventListener('change', function (event) { + var checked = event.target.checked; + if (checked) { + multiDataUpdater("grabbableKey", + {cloneLifetime: elCloneableLifetime, cloneLimit: elCloneableLimit, cloneDynamic: elCloneableDynamic, cloneable: event.target}, + elUserData, {}); + elCloneableGroup.style.display = "block"; + EventBridge.emitWebEvent( + '{"id":' + lastEntityID + ', "type":"update", "properties":{"dynamic":false, "grabbable": false}}' + ); + } else { + multiDataUpdater("grabbableKey", + {cloneLifetime: null, cloneLimit: null, cloneDynamic: null, cloneable: false}, + elUserData, {}); + elCloneableGroup.style.display = "none"; + } + }); + + var numberListener = function (event) { + userDataChanger("grabbableKey", event.target.getAttribute("data-user-data-type"), parseInt(event.target.value), elUserData, false); + }; + elCloneableLifetime.addEventListener('change', numberListener); + elCloneableLimit.addEventListener('change', numberListener); + elWantsTrigger.addEventListener('change', function() { userDataChanger("grabbableKey", "wantsTrigger", elWantsTrigger, elUserData, false); }); @@ -1390,7 +1470,7 @@ function loaded() { elZoneFlyingAllowed.addEventListener('change', createEmitCheckedPropertyUpdateFunction('flyingAllowed')); elZoneGhostingAllowed.addEventListener('change', createEmitCheckedPropertyUpdateFunction('ghostingAllowed')); elZoneFilterURL.addEventListener('change', createEmitTextPropertyUpdateFunction('filterURL')); - + var voxelVolumeSizeChangeFunction = createEmitVec3PropertyUpdateFunction( 'voxelVolumeSize', elVoxelVolumeSizeX, elVoxelVolumeSizeY, elVoxelVolumeSizeZ); elVoxelVolumeSizeX.addEventListener('change', voxelVolumeSizeChangeFunction); @@ -1441,7 +1521,15 @@ function loaded() { })); }); - + document.addEventListener("keydown", function (keyDown) { + if (keyDown.keyCode === KEY_P && keyDown.ctrlKey) { + if (keyDown.shiftKey) { + EventBridge.emitWebEvent(JSON.stringify({ type: 'unparent' })); + } else { + EventBridge.emitWebEvent(JSON.stringify({ type: 'parent' })); + } + } + }); window.onblur = function() { // Fake a change event var ev = document.createEvent("HTMLEvents"); diff --git a/scripts/system/html/js/gridControls.js b/scripts/system/html/js/gridControls.js index a245ed4cda..be4271788e 100644 --- a/scripts/system/html/js/gridControls.js +++ b/scripts/system/html/js/gridControls.js @@ -6,6 +6,8 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +const KEY_P = 80; //Key code for letter p used for Parenting hotkey. + function loaded() { openEventBridge(function() { elPosY = document.getElementById("horiz-y"); @@ -131,10 +133,17 @@ function loaded() { EventBridge.emitWebEvent(JSON.stringify({ type: 'init' })); }); - + document.addEventListener("keydown", function (keyDown) { + if (keyDown.keyCode === KEY_P && keyDown.ctrlKey) { + if (keyDown.shiftKey) { + EventBridge.emitWebEvent(JSON.stringify({ type: 'unparent' })); + } else { + EventBridge.emitWebEvent(JSON.stringify({ type: 'parent' })); + } + } + }) // Disable right-click context menu which is not visible in the HMD and makes it seem like the app has locked document.addEventListener("contextmenu", function (event) { event.preventDefault(); }, false); } - diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index cacb8b0872..c0670a4d46 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -1167,14 +1167,14 @@ SelectionDisplay = (function() { // determine which bottom corner we are closest to /*------------------------------ example: - + BRF +--------+ BLF | | | | BRN +--------+ BLN - + * - + ------------------------------*/ var cameraPosition = Camera.getPosition(); @@ -2187,8 +2187,12 @@ SelectionDisplay = (function() { offset = Vec3.multiplyQbyV(props.rotation, offset); var boxPosition = Vec3.sum(props.position, offset); + var color = {red: 255, green: 128, blue: 0}; + if (i >= selectionManager.selections.length - 1) color = {red: 255, green: 255, blue: 64}; + Overlays.editOverlay(selectionBoxes[i], { position: boxPosition, + color: color, rotation: props.rotation, dimensions: props.dimensions, visible: true, @@ -2393,7 +2397,7 @@ SelectionDisplay = (function() { if (wantDebug) { print("Start Elevation: " + translateXZTool.startingElevation + ", elevation: " + elevation); } - if ((translateXZTool.startingElevation > 0.0 && elevation < MIN_ELEVATION) || + if ((translateXZTool.startingElevation > 0.0 && elevation < MIN_ELEVATION) || (translateXZTool.startingElevation < 0.0 && elevation > -MIN_ELEVATION)) { if (wantDebug) { print("too close to horizon!"); @@ -3857,7 +3861,7 @@ SelectionDisplay = (function() { }; that.mousePressEvent = function(event) { - var wantDebug = false; + var wantDebug = false; if (!event.isLeftButton && !that.triggered) { // if another mouse button than left is pressed ignore it return false; @@ -3888,7 +3892,6 @@ SelectionDisplay = (function() { result = Overlays.findRayIntersection(pickRay); if (result.intersects) { - if (wantDebug) { print("something intersects... "); print(" result.overlayID:" + result.overlayID + "[" + overlayNames[result.overlayID] + "]"); @@ -3988,7 +3991,7 @@ SelectionDisplay = (function() { if (wantDebug) { print("rotate handle case..."); } - + // After testing our stretch handles, then check out rotate handles Overlays.editOverlay(yawHandle, { @@ -4210,7 +4213,7 @@ SelectionDisplay = (function() { case selectionBox: activeTool = translateXZTool; translateXZTool.pickPlanePosition = result.intersection; - translateXZTool.greatestDimension = Math.max(Math.max(SelectionManager.worldDimensions.x, SelectionManager.worldDimensions.y), + translateXZTool.greatestDimension = Math.max(Math.max(SelectionManager.worldDimensions.x, SelectionManager.worldDimensions.y), SelectionManager.worldDimensions.z); if (wantDebug) { print("longest dimension: " + translateXZTool.greatestDimension); @@ -4219,7 +4222,7 @@ SelectionDisplay = (function() { translateXZTool.startingElevation = translateXZTool.elevation(pickRay.origin, translateXZTool.pickPlanePosition); print(" starting elevation: " + translateXZTool.startingElevation); } - + mode = translateXZTool.mode; activeTool.onBegin(event); somethingClicked = 'selectionBox'; diff --git a/scripts/system/notifications.js b/scripts/system/notifications.js index 3ae071c7e3..b2ebb1fd46 100644 --- a/scripts/system/notifications.js +++ b/scripts/system/notifications.js @@ -521,6 +521,9 @@ function onEditError(msg) { createNotification(wordWrap(msg), NotificationType.EDIT_ERROR); } +function onNotify(msg) { + createNotification(wordWrap(msg), NotificationType.UNKNOWN); // Needs a generic notification system for user feedback, thus using this +} function onSnapshotTaken(pathStillSnapshot, pathAnimatedSnapshot, notify) { if (notify) { @@ -637,6 +640,7 @@ Window.domainConnectionRefused.connect(onDomainConnectionRefused); Window.snapshotTaken.connect(onSnapshotTaken); Window.processingGif.connect(processingGif); Window.notifyEditError = onEditError; +Window.notify = onNotify; setup();