diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index c67e998dd4..3ca924b007 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -870,8 +870,8 @@ AvatarMixerClientData* AvatarMixer::getOrCreateClientData(SharedNodePointer node node->setLinkedData(std::unique_ptr { new AvatarMixerClientData(node->getUUID()) }); clientData = dynamic_cast(node->getLinkedData()); auto& avatar = clientData->getAvatar(); - avatar.setDomainMinimumScale(_domainMinimumScale); - avatar.setDomainMaximumScale(_domainMaximumScale); + avatar.setDomainMinimumHeight(_domainMinimumHeight); + avatar.setDomainMaximumHeight(_domainMaximumHeight); } return clientData; @@ -939,21 +939,21 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) { const QString AVATARS_SETTINGS_KEY = "avatars"; - static const QString MIN_SCALE_OPTION = "min_avatar_scale"; - float settingMinScale = domainSettings[AVATARS_SETTINGS_KEY].toObject()[MIN_SCALE_OPTION].toDouble(MIN_AVATAR_SCALE); - _domainMinimumScale = glm::clamp(settingMinScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE); + static const QString MIN_HEIGHT_OPTION = "min_avatar_height"; + float settingMinHeight = domainSettings[AVATARS_SETTINGS_KEY].toObject()[MIN_HEIGHT_OPTION].toDouble(MIN_AVATAR_HEIGHT); + _domainMinimumHeight = glm::clamp(settingMinHeight, MIN_AVATAR_HEIGHT, MAX_AVATAR_HEIGHT); - static const QString MAX_SCALE_OPTION = "max_avatar_scale"; - float settingMaxScale = domainSettings[AVATARS_SETTINGS_KEY].toObject()[MAX_SCALE_OPTION].toDouble(MAX_AVATAR_SCALE); - _domainMaximumScale = glm::clamp(settingMaxScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE); + static const QString MAX_HEIGHT_OPTION = "max_avatar_height"; + float settingMaxHeight = domainSettings[AVATARS_SETTINGS_KEY].toObject()[MAX_HEIGHT_OPTION].toDouble(MAX_AVATAR_HEIGHT); + _domainMaximumHeight = glm::clamp(settingMaxHeight, MIN_AVATAR_HEIGHT, MAX_AVATAR_HEIGHT); // make sure that the domain owner didn't flip min and max - if (_domainMinimumScale > _domainMaximumScale) { - std::swap(_domainMinimumScale, _domainMaximumScale); + if (_domainMinimumHeight > _domainMaximumHeight) { + std::swap(_domainMinimumHeight, _domainMaximumHeight); } - qCDebug(avatars) << "This domain requires a minimum avatar scale of" << _domainMinimumScale - << "and a maximum avatar scale of" << _domainMaximumScale; + qCDebug(avatars) << "This domain requires a minimum avatar height of" << _domainMinimumHeight + << "and a maximum avatar height of" << _domainMaximumHeight; const QString AVATAR_WHITELIST_DEFAULT{ "" }; static const QString AVATAR_WHITELIST_OPTION = "avatar_whitelist"; diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index 610da8ad57..cb5f536faa 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -90,8 +90,8 @@ private: float _maxKbpsPerNode = 0.0f; - float _domainMinimumScale { MIN_AVATAR_SCALE }; - float _domainMaximumScale { MAX_AVATAR_SCALE }; + float _domainMinimumHeight { MIN_AVATAR_HEIGHT }; + float _domainMaximumHeight { MAX_AVATAR_HEIGHT }; RateCounter<> _broadcastRate; p_high_resolution_clock::time_point _lastDebugMessage; diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index a4bf8fa253..288652715a 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -25,6 +25,23 @@ AvatarMixerClientData::AvatarMixerClientData(const QUuid& nodeID) : _avatar->setID(nodeID); } +uint64_t AvatarMixerClientData::getLastOtherAvatarEncodeTime(QUuid otherAvatar) const { + std::unordered_map::const_iterator itr = _lastOtherAvatarEncodeTime.find(otherAvatar); + if (itr != _lastOtherAvatarEncodeTime.end()) { + return itr->second; + } + return 0; +} + +void AvatarMixerClientData::setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, const uint64_t& time) { + std::unordered_map::iterator itr = _lastOtherAvatarEncodeTime.find(otherAvatar); + if (itr != _lastOtherAvatarEncodeTime.end()) { + itr->second = time; + } else { + _lastOtherAvatarEncodeTime.emplace(std::pair(otherAvatar, time)); + } +} + void AvatarMixerClientData::queuePacket(QSharedPointer message, SharedNodePointer node) { if (!_packetQueue.node) { _packetQueue.node = node; diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index d5c7784da7..acd9be0702 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -110,16 +110,10 @@ public: bool getRequestsDomainListData() { return _requestsDomainListData; } void setRequestsDomainListData(bool requesting) { _requestsDomainListData = requesting; } - ViewFrustum getViewFrustom() const { return _currentViewFrustum; } + ViewFrustum getViewFrustum() const { return _currentViewFrustum; } - quint64 getLastOtherAvatarEncodeTime(QUuid otherAvatar) { - quint64 result = 0; - if (_lastOtherAvatarEncodeTime.find(otherAvatar) != _lastOtherAvatarEncodeTime.end()) { - result = _lastOtherAvatarEncodeTime[otherAvatar]; - } - _lastOtherAvatarEncodeTime[otherAvatar] = usecTimestampNow(); - return result; - } + uint64_t getLastOtherAvatarEncodeTime(QUuid otherAvatar) const; + void setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, const uint64_t& time); QVector& getLastOtherAvatarSentJoints(QUuid otherAvatar) { _lastOtherAvatarSentJoints[otherAvatar].resize(_avatar->getJointCount()); @@ -143,7 +137,7 @@ private: // this is a map of the last time we encoded an "other" avatar for // sending to "this" node - std::unordered_map _lastOtherAvatarEncodeTime; + std::unordered_map _lastOtherAvatarEncodeTime; std::unordered_map> _lastOtherAvatarSentJoints; uint64_t _identityChangeTimestamp; diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index dd045c24ea..9ea1ed3637 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -32,6 +33,10 @@ #include "AvatarMixerClientData.h" #include "AvatarMixerSlave.h" +namespace PrioritySortUtil { + // we declare this callback here but override it later + std::function getAvatarAgeCallback = [] (const AvatarSharedPointer& avatar) { return 0; }; +} void AvatarMixerSlave::configure(ConstIter begin, ConstIter end) { _begin = begin; @@ -184,10 +189,8 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // setup list of AvatarData as well as maps to map betweeen the AvatarData and the original nodes - // for calling the AvatarData::sortAvatars() function and getting our sorted list of client nodes - QList avatarList; + std::vector avatarsToSort; std::unordered_map avatarDataToNodes; - std::for_each(_begin, _end, [&](const SharedNodePointer& otherNode) { // make sure this is an agent that we have avatar data for before considering it for inclusion if (otherNode->getType() == NodeType::Agent @@ -195,36 +198,61 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) const AvatarMixerClientData* otherNodeData = reinterpret_cast(otherNode->getLinkedData()); AvatarSharedPointer otherAvatar = otherNodeData->getAvatarSharedPointer(); - avatarList << otherAvatar; + avatarsToSort.push_back(otherAvatar); avatarDataToNodes[otherAvatar] = otherNode; } }); - AvatarSharedPointer thisAvatar = nodeData->getAvatarSharedPointer(); - ViewFrustum cameraView = nodeData->getViewFrustom(); - std::priority_queue sortedAvatars; - AvatarData::sortAvatars(avatarList, cameraView, sortedAvatars, - [&](AvatarSharedPointer avatar)->uint64_t { - auto avatarNode = avatarDataToNodes[avatar]; - assert(avatarNode); // we can't have gotten here without the avatarData being a valid key in the map - return nodeData->getLastBroadcastTime(avatarNode->getUUID()); - }, [&](AvatarSharedPointer avatar)->float{ - glm::vec3 nodeBoxHalfScale = (avatar->getWorldPosition() - avatar->getGlobalBoundingBoxCorner() * avatar->getSensorToWorldScale()); - return glm::max(nodeBoxHalfScale.x, glm::max(nodeBoxHalfScale.y, nodeBoxHalfScale.z)); - }, [&](AvatarSharedPointer avatar)->bool { + // now that we've assembled the avatarDataToNodes map we can replace PrioritySortUtil::getAvatarAgeCallback + // with the true implementation + PrioritySortUtil::getAvatarAgeCallback = [&] (const AvatarSharedPointer& avatar) { + auto avatarNode = avatarDataToNodes[avatar]; + assert(avatarNode); // we can't have gotten here without the avatarData being a valid key in the map + return nodeData->getLastOtherAvatarEncodeTime(avatarNode->getUUID()); + }; + + class SortableAvatar: public PrioritySortUtil::Sortable { + public: + SortableAvatar() = delete; + SortableAvatar(const AvatarSharedPointer& avatar) : _avatar(avatar) {} + glm::vec3 getPosition() const override { return _avatar->getWorldPosition(); } + float getRadius() const override { + glm::vec3 nodeBoxHalfScale = (_avatar->getWorldPosition() - _avatar->getGlobalBoundingBoxCorner() * _avatar->getSensorToWorldScale()); + return glm::max(nodeBoxHalfScale.x, glm::max(nodeBoxHalfScale.y, nodeBoxHalfScale.z)); + } + uint64_t getTimestamp() const override { + // use the callback implemented above + return PrioritySortUtil::getAvatarAgeCallback(_avatar); + } + const AvatarSharedPointer& getAvatar() const { return _avatar; } + + private: + AvatarSharedPointer _avatar; + }; + + // prepare to sort + ViewFrustum cameraView = nodeData->getViewFrustum(); + PrioritySortUtil::PriorityQueue sortedAvatars(cameraView, + AvatarData::_avatarSortCoefficientSize, + AvatarData::_avatarSortCoefficientCenter, + AvatarData::_avatarSortCoefficientAge); + + // ignore or sort + const AvatarSharedPointer& thisAvatar = nodeData->getAvatarSharedPointer(); + for (const auto& avatar : avatarsToSort) { if (avatar == thisAvatar) { - return true; // ignore ourselves... + // don't echo updates to self + continue; } bool shouldIgnore = false; - - // We will also ignore other nodes for a couple of different reasons: + // We ignore other nodes for a couple of reasons: // 1) ignore bubbles and ignore specific node // 2) the node hasn't really updated it's frame data recently, this can // happen if for example the avatar is connected on a desktop and sending // updates at ~30hz. So every 3 frames we skip a frame. - auto avatarNode = avatarDataToNodes[avatar]; + auto avatarNode = avatarDataToNodes[avatar]; assert(avatarNode); // we can't have gotten here without the avatarData being a valid key in the map const AvatarMixerClientData* avatarNodeData = reinterpret_cast(avatarNode->getLinkedData()); @@ -240,7 +268,6 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) || (avatarNode->isIgnoringNodeWithID(node->getUUID()) && !getsAnyIgnored)) { shouldIgnore = true; } else { - // Check to see if the space bubble is enabled // Don't bother with these checks if the other avatar has their bubble enabled and we're gettingAnyIgnored if (node->isIgnoreRadiusEnabled() || (avatarNode->isIgnoreRadiusEnabled() && !getsAnyIgnored)) { @@ -267,8 +294,6 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) nodeData->removeFromRadiusIgnoringSet(node, avatarNode->getUUID()); } } - quint64 endIgnoreCalculation = usecTimestampNow(); - _stats.ignoreCalculationElapsedTime += (endIgnoreCalculation - startIgnoreCalculation); if (!shouldIgnore) { AvatarDataSequenceNumber lastSeqToReceiver = nodeData->getLastBroadcastSequenceNumber(avatarNode->getUUID()); @@ -292,20 +317,21 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) ++numAvatarsWithSkippedFrames; } } - return shouldIgnore; - }); + quint64 endIgnoreCalculation = usecTimestampNow(); + _stats.ignoreCalculationElapsedTime += (endIgnoreCalculation - startIgnoreCalculation); + + if (!shouldIgnore) { + // sort this one for later + sortedAvatars.push(SortableAvatar(avatar)); + } + } // loop through our sorted avatars and allocate our bandwidth to them accordingly - int avatarRank = 0; - // this is overly conservative, because it includes some avatars we might not consider int remainingAvatars = (int)sortedAvatars.size(); - while (!sortedAvatars.empty()) { - AvatarPriority sortData = sortedAvatars.top(); + const auto& avatarData = sortedAvatars.top().getAvatar(); sortedAvatars.pop(); - const auto& avatarData = sortData.avatar; - avatarRank++; remainingAvatars--; auto otherNode = avatarDataToNodes[avatarData]; @@ -332,10 +358,8 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) nodeData->setLastBroadcastTime(otherNode->getUUID(), usecTimestampNow()); } + // determine if avatar is in view which determines how much data to send glm::vec3 otherPosition = otherAvatar->getClientGlobalPosition(); - - - // determine if avatar is in view, to determine how much data to include... glm::vec3 otherNodeBoxScale = (otherPosition - otherNodeData->getGlobalBoundingBoxCorner()) * 2.0f * otherAvatar->getSensorToWorldScale(); AABox otherNodeBox(otherNodeData->getGlobalBoundingBoxCorner(), otherNodeBoxScale); bool isInView = nodeData->otherAvatarInView(otherNodeBox); @@ -405,14 +429,18 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // set the last sent sequence number for this sender on the receiver nodeData->setLastBroadcastSequenceNumber(otherNode->getUUID(), otherNodeData->getLastReceivedSequenceNumber()); + nodeData->setLastOtherAvatarEncodeTime(otherNode->getUUID(), usecTimestampNow()); } + } else { + // TODO? this avatar is not included now, and will probably not be included next frame. + // It would be nice if we could tweak its future sort priority to put it at the back of the list. } avatarPacketList->endSegment(); quint64 endAvatarDataPacking = usecTimestampNow(); _stats.avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); - }; + } quint64 startPacketSending = usecTimestampNow(); diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 31f224667c..c6d118a87b 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -1,5 +1,5 @@ { - "version": 2.0, + "version": 2.1, "settings": [ { "name": "label", @@ -1015,20 +1015,20 @@ "assignment-types": [ 1, 2 ], "settings": [ { - "name": "min_avatar_scale", + "name": "min_avatar_height", "type": "double", - "label": "Minimum Avatar Scale", - "help": "Limits the scale of avatars in your domain. Must be at least 0.005.", - "placeholder": 0.25, - "default": 0.25 + "label": "Minimum Avatar Height (meters)", + "help": "Limits the height of avatars in your domain. Must be at least 0.009.", + "placeholder": 0.4, + "default": 0.4 }, { - "name": "max_avatar_scale", + "name": "max_avatar_height", "type": "double", - "label": "Maximum Avatar Scale", - "help": "Limits the scale of avatars in your domain. Cannot be greater than 1000.", - "placeholder": 3.0, - "default": 3.0 + "label": "Maximum Avatar Height (meters)", + "help": "Limits the scale of avatars in your domain. Cannot be greater than 1755.", + "placeholder": 5.2, + "default": 5.2 }, { "name": "avatar_whitelist", diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index a4d3e675aa..05227d35b7 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -304,6 +304,26 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList *wizardCompletedOnce = QVariant(true); } + if (oldVersion < 2.1) { + // convert old avatar scale settings into avatar height. + + const QString AVATAR_MIN_SCALE_KEYPATH = "avatars.min_avatar_scale"; + const QString AVATAR_MAX_SCALE_KEYPATH = "avatars.max_avatar_scale"; + const QString AVATAR_MIN_HEIGHT_KEYPATH = "avatars.min_avatar_height"; + const QString AVATAR_MAX_HEIGHT_KEYPATH = "avatars.max_avatar_height"; + + QVariant* avatarMinScale = _configMap.valueForKeyPath(AVATAR_MIN_SCALE_KEYPATH); + if (avatarMinScale) { + float scale = avatarMinScale->toFloat(); + _configMap.valueForKeyPath(AVATAR_MIN_HEIGHT_KEYPATH, scale * DEFAULT_AVATAR_HEIGHT); + } + + QVariant* avatarMaxScale = _configMap.valueForKeyPath(AVATAR_MAX_SCALE_KEYPATH); + if (avatarMaxScale) { + float scale = avatarMaxScale->toFloat(); + _configMap.valueForKeyPath(AVATAR_MAX_HEIGHT_KEYPATH, scale * DEFAULT_AVATAR_HEIGHT); + } + } // write the current description version to our settings *versionVariant = _descriptionVersion; diff --git a/interface/resources/controllers/oculus_touch.json b/interface/resources/controllers/oculus_touch.json index 03fc1cbefb..b818d371e3 100644 --- a/interface/resources/controllers/oculus_touch.json +++ b/interface/resources/controllers/oculus_touch.json @@ -13,11 +13,11 @@ { "from": "OculusTouch.LY", "to": "Standard.LY", "filters": [ - { "type": "deadZone", "min": 0.3 }, + { "type": "deadZone", "min": 0.7 }, "invert" ] }, - { "from": "OculusTouch.LX", "filters": { "type": "deadZone", "min": 0.3 }, "to": "Standard.LX" }, + { "from": "OculusTouch.LX", "filters": { "type": "deadZone", "min": 0.7 }, "to": "Standard.LX" }, { "from": "OculusTouch.LT", "to": "Standard.LTClick", "peek": true, "filters": [ { "type": "hysteresis", "min": 0.85, "max": 0.9 } ] @@ -29,11 +29,11 @@ { "from": "OculusTouch.RY", "to": "Standard.RY", "filters": [ - { "type": "deadZone", "min": 0.3 }, + { "type": "deadZone", "min": 0.7 }, "invert" ] }, - { "from": "OculusTouch.RX", "filters": { "type": "deadZone", "min": 0.3 }, "to": "Standard.RX" }, + { "from": "OculusTouch.RX", "filters": { "type": "deadZone", "min": 0.7 }, "to": "Standard.RX" }, { "from": "OculusTouch.RT", "to": "Standard.RTClick", "peek": true, "filters": [ { "type": "hysteresis", "min": 0.85, "max": 0.9 } ] diff --git a/interface/resources/qml/hifi/overlays/ImageOverlay.qml b/interface/resources/qml/hifi/overlays/ImageOverlay.qml index 8c638679f9..6899c38e67 100644 --- a/interface/resources/qml/hifi/overlays/ImageOverlay.qml +++ b/interface/resources/qml/hifi/overlays/ImageOverlay.qml @@ -9,33 +9,30 @@ Overlay { Image { id: image - property bool scaleFix: true; - property real xOffset: 0 - property real yOffset: 0 + property bool scaleFix: true + property real xStart: 0 + property real yStart: 0 + property real xSize: 0 + property real ySize: 0 property real imageScale: 1.0 property var resizer: Timer { interval: 50 repeat: false running: false onTriggered: { - var targetAspect = root.width / root.height; - var sourceAspect = image.sourceSize.width / image.sourceSize.height; - if (sourceAspect <= targetAspect) { - if (root.width === image.sourceSize.width) { - return; - } - image.imageScale = root.width / image.sourceSize.width; - } else if (sourceAspect > targetAspect){ - if (root.height === image.sourceSize.height) { - return; - } - image.imageScale = root.height / image.sourceSize.height; + if (image.xSize === 0) { + image.xSize = image.sourceSize.width - image.xStart; } - image.sourceSize = Qt.size(image.sourceSize.width * image.imageScale, image.sourceSize.height * image.imageScale); + if (image.ySize === 0) { + image.ySize = image.sourceSize.height - image.yStart; + } + + image.anchors.leftMargin = -image.xStart * root.width / image.xSize; + image.anchors.topMargin = -image.yStart * root.height / image.ySize; + image.anchors.rightMargin = (image.xStart + image.xSize - image.sourceSize.width) * root.width / image.xSize; + image.anchors.bottomMargin = (image.yStart + image.ySize - image.sourceSize.height) * root.height / image.ySize; } } - x: -1 * xOffset * imageScale - y: -1 * yOffset * imageScale onSourceSizeChanged: { if (sourceSize.width !== 0 && sourceSize.height !== 0 && progress === 1.0 && scaleFix) { @@ -43,6 +40,8 @@ Overlay { resizer.start(); } } + + anchors.fill: parent } ColorOverlay { @@ -57,8 +56,10 @@ Overlay { var key = keys[i]; var value = subImage[key]; switch (key) { - case "x": image.xOffset = value; break; - case "y": image.yOffset = value; break; + case "x": image.xStart = value; break; + case "y": image.yStart = value; break; + case "width": image.xSize = value; break; + case "height": image.ySize = value; break; } } } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 7bb88b0445..0440d2af3a 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2812,10 +2812,10 @@ static int getEventQueueSize(QThread* thread) { static void dumpEventQueue(QThread* thread) { auto threadData = QThreadData::get2(thread); QMutexLocker locker(&threadData->postEventList.mutex); - qDebug() << "AJT: event list, size =" << threadData->postEventList.size(); + qDebug() << "Event list, size =" << threadData->postEventList.size(); for (auto& postEvent : threadData->postEventList) { QEvent::Type type = (postEvent.event ? postEvent.event->type() : QEvent::None); - qDebug() << "AJT: " << type; + qDebug() << " " << type; } } #endif // DEBUG_EVENT_QUEUE diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 9ffe74d470..8a294182bd 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -142,32 +143,39 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { PerformanceTimer perfTimer("otherAvatars"); - auto avatarMap = getHashCopy(); - QList avatarList = avatarMap.values(); + class SortableAvatar: public PrioritySortUtil::Sortable { + public: + SortableAvatar() = delete; + SortableAvatar(const AvatarSharedPointer& avatar) : _avatar(avatar) {} + glm::vec3 getPosition() const override { return _avatar->getWorldPosition(); } + float getRadius() const override { return std::static_pointer_cast(_avatar)->getBoundingRadius(); } + uint64_t getTimestamp() const override { return std::static_pointer_cast(_avatar)->getLastRenderUpdateTime(); } + const AvatarSharedPointer& getAvatar() const { return _avatar; } + private: + AvatarSharedPointer _avatar; + }; + ViewFrustum cameraView; qApp->copyDisplayViewFrustum(cameraView); + PrioritySortUtil::PriorityQueue sortedAvatars(cameraView, + AvatarData::_avatarSortCoefficientSize, + AvatarData::_avatarSortCoefficientCenter, + AvatarData::_avatarSortCoefficientAge); - std::priority_queue sortedAvatars; - AvatarData::sortAvatars(avatarList, cameraView, sortedAvatars, - - [](AvatarSharedPointer avatar)->uint64_t{ - return std::static_pointer_cast(avatar)->getLastRenderUpdateTime(); - }, - - [](AvatarSharedPointer avatar)->float{ - return std::static_pointer_cast(avatar)->getBoundingRadius(); - }, - - [this](AvatarSharedPointer avatar)->bool{ - const auto& castedAvatar = std::static_pointer_cast(avatar); - if (castedAvatar == _myAvatar || !castedAvatar->isInitialized()) { - // DO NOT update _myAvatar! Its update has already been done earlier in the main loop. - // DO NOT update or fade out uninitialized Avatars - return true; // ignore it - } - return false; - }); + // sort + auto avatarMap = getHashCopy(); + AvatarHash::iterator itr = avatarMap.begin(); + while (itr != avatarMap.end()) { + const auto& avatar = std::static_pointer_cast(*itr); + // DO NOT update _myAvatar! Its update has already been done earlier in the main loop. + // DO NOT update or fade out uninitialized Avatars + if (avatar != _myAvatar && avatar->isInitialized()) { + sortedAvatars.push(SortableAvatar(avatar)); + } + ++itr; + } + // process in sorted order uint64_t startTime = usecTimestampNow(); const uint64_t UPDATE_BUDGET = 2000; // usec uint64_t updateExpiry = startTime + UPDATE_BUDGET; @@ -176,8 +184,8 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { render::Transaction transaction; while (!sortedAvatars.empty()) { - const AvatarPriority& sortData = sortedAvatars.top(); - const auto& avatar = std::static_pointer_cast(sortData.avatar); + const SortableAvatar& sortData = sortedAvatars.top(); + const auto& avatar = std::static_pointer_cast(sortData.getAvatar()); bool ignoring = DependencyManager::get()->isPersonalMutingNode(avatar->getID()); if (ignoring) { @@ -207,7 +215,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { uint64_t now = usecTimestampNow(); if (now < updateExpiry) { // we're within budget - bool inView = sortData.priority > OUT_OF_VIEW_THRESHOLD; + bool inView = sortData.getPriority() > OUT_OF_VIEW_THRESHOLD; if (inView && avatar->hasNewJointData()) { numAvatarsUpdated++; } @@ -221,7 +229,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { // --> some avatar velocity measurements may be a little off // no time simulate, but we take the time to count how many were tragically missed - bool inView = sortData.priority > OUT_OF_VIEW_THRESHOLD; + bool inView = sortData.getPriority() > OUT_OF_VIEW_THRESHOLD; if (!inView) { break; } @@ -230,9 +238,9 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { } sortedAvatars.pop(); while (inView && !sortedAvatars.empty()) { - const AvatarPriority& newSortData = sortedAvatars.top(); - const auto& newAvatar = std::static_pointer_cast(newSortData.avatar); - inView = newSortData.priority > OUT_OF_VIEW_THRESHOLD; + const SortableAvatar& newSortData = sortedAvatars.top(); + const auto& newAvatar = std::static_pointer_cast(newSortData.getAvatar()); + inView = newSortData.getPriority() > OUT_OF_VIEW_THRESHOLD; if (inView && newAvatar->hasNewJointData()) { numAVatarsNotUpdated++; } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index df2089223b..1623fa3213 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1516,9 +1516,19 @@ void MyAvatar::updateMotors() { _characterController.clearMotors(); glm::quat motorRotation; if (_motionBehaviors & AVATAR_MOTION_ACTION_MOTOR_ENABLED) { + + const float FLYING_MOTOR_TIMESCALE = 0.05f; + const float WALKING_MOTOR_TIMESCALE = 0.2f; + const float INVALID_MOTOR_TIMESCALE = 1.0e6f; + + float horizontalMotorTimescale; + float verticalMotorTimescale; + if (_characterController.getState() == CharacterController::State::Hover || _characterController.computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) { motorRotation = getMyHead()->getHeadOrientation(); + horizontalMotorTimescale = FLYING_MOTOR_TIMESCALE; + verticalMotorTimescale = FLYING_MOTOR_TIMESCALE; } else { // non-hovering = walking: follow camera twist about vertical but not lift // we decompose camera's rotation and store the twist part in motorRotation @@ -1529,11 +1539,12 @@ void MyAvatar::updateMotors() { glm::quat liftRotation; swingTwistDecomposition(headOrientation, Vectors::UNIT_Y, liftRotation, motorRotation); motorRotation = orientation * motorRotation; + horizontalMotorTimescale = WALKING_MOTOR_TIMESCALE; + verticalMotorTimescale = INVALID_MOTOR_TIMESCALE; } - const float DEFAULT_MOTOR_TIMESCALE = 0.2f; - const float INVALID_MOTOR_TIMESCALE = 1.0e6f; + if (_isPushing || _isBraking || !_isBeingPushed) { - _characterController.addMotor(_actionMotorVelocity, motorRotation, DEFAULT_MOTOR_TIMESCALE, INVALID_MOTOR_TIMESCALE); + _characterController.addMotor(_actionMotorVelocity, motorRotation, horizontalMotorTimescale, verticalMotorTimescale); } else { // _isBeingPushed must be true --> disable action motor by giving it a long timescale, // otherwise it's attempt to "stand in in place" could defeat scripted motor/thrusts @@ -1799,6 +1810,7 @@ void MyAvatar::postUpdate(float deltaTime, const render::ScenePointer& scene) { _skeletonModel->setCauterizeBoneSet(_headBoneSet); _fstAnimGraphOverrideUrl = _skeletonModel->getGeometry()->getAnimGraphOverrideUrl(); initAnimGraph(); + _isAnimatingScale = true; } if (_enableDebugDrawDefaultPose || _enableDebugDrawAnimPose) { @@ -1956,27 +1968,33 @@ void MyAvatar::updateOrientation(float deltaTime) { // Use head/HMD roll to turn while flying, but not when standing still. if (qApp->isHMDMode() && getCharacterController()->getState() == CharacterController::State::Hover && _hmdRollControlEnabled && hasDriveInput()) { + // Turn with head roll. - const float MIN_CONTROL_SPEED = 0.01f; - float speed = glm::length(getWorldVelocity()); - if (speed >= MIN_CONTROL_SPEED) { - // Feather turn when stopping moving. - float speedFactor; - if (getDriveKey(TRANSLATE_Z) != 0.0f || _lastDrivenSpeed == 0.0f) { - _lastDrivenSpeed = speed; - speedFactor = 1.0f; - } else { - speedFactor = glm::min(speed / _lastDrivenSpeed, 1.0f); - } + const float MIN_CONTROL_SPEED = 2.0f * getSensorToWorldScale(); // meters / sec + const glm::vec3 characterForward = getWorldOrientation() * Vectors::UNIT_NEG_Z; + float forwardSpeed = glm::dot(characterForward, getWorldVelocity()); - float direction = glm::dot(getWorldVelocity(), getWorldOrientation() * Vectors::UNIT_NEG_Z) > 0.0f ? 1.0f : -1.0f; + // only enable roll-turns if we are moving forward or backward at greater then MIN_CONTROL_SPEED + if (fabsf(forwardSpeed) >= MIN_CONTROL_SPEED) { + float direction = forwardSpeed > 0.0f ? 1.0f : -1.0f; float rollAngle = glm::degrees(asinf(glm::dot(IDENTITY_UP, _hmdSensorOrientation * IDENTITY_RIGHT))); float rollSign = rollAngle < 0.0f ? -1.0f : 1.0f; rollAngle = fabsf(rollAngle); - rollAngle = rollAngle > _hmdRollControlDeadZone ? rollSign * (rollAngle - _hmdRollControlDeadZone) : 0.0f; - totalBodyYaw += speedFactor * direction * rollAngle * deltaTime * _hmdRollControlRate; + const float MIN_ROLL_ANGLE = _hmdRollControlDeadZone; + const float MAX_ROLL_ANGLE = 90.0f; // degrees + + if (rollAngle > MIN_ROLL_ANGLE) { + // rate of turning is linearly proportional to rollAngle + rollAngle = glm::clamp(rollAngle, MIN_ROLL_ANGLE, MAX_ROLL_ANGLE); + + // scale rollAngle into a value from zero to one. + float rollFactor = (rollAngle - MIN_ROLL_ANGLE) / (MAX_ROLL_ANGLE - MIN_ROLL_ANGLE); + + float angularSpeed = rollSign * rollFactor * _hmdRollControlRate; + totalBodyYaw += direction * angularSpeed * deltaTime; + } } } @@ -2022,12 +2040,13 @@ void MyAvatar::updateActionMotor(float deltaTime) { _isBraking = _wasPushing || (_isBraking && speed > MIN_ACTION_BRAKE_SPEED); } + CharacterController::State state = _characterController.getState(); + // compute action input glm::vec3 forward = (getDriveKey(TRANSLATE_Z)) * IDENTITY_FORWARD; glm::vec3 right = (getDriveKey(TRANSLATE_X)) * IDENTITY_RIGHT; glm::vec3 direction = forward + right; - CharacterController::State state = _characterController.getState(); if (state == CharacterController::State::Hover || _characterController.computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) { // we can fly --> support vertical motion @@ -2161,41 +2180,6 @@ bool findAvatarAvatarPenetration(const glm::vec3 positionA, float radiusA, float // target scale to match the new scale they have chosen. When they leave the domain they will not return to the scale they were // before they entered the limiting domain. -void MyAvatar::clampTargetScaleToDomainLimits() { - // when we're about to change the target scale because the user has asked to increase or decrease their scale, - // we first make sure that we're starting from a target scale that is allowed by the current domain - - auto clampedTargetScale = glm::clamp(_targetScale, _domainMinimumScale, _domainMaximumScale); - - if (clampedTargetScale != _targetScale) { - qCDebug(interfaceapp, "Clamped scale to %f since original target scale %f was not allowed by domain", - (double)clampedTargetScale, (double)_targetScale); - - setTargetScale(clampedTargetScale); - } -} - -void MyAvatar::clampScaleChangeToDomainLimits(float desiredScale) { - auto clampedTargetScale = glm::clamp(desiredScale, _domainMinimumScale, _domainMaximumScale); - - if (clampedTargetScale != desiredScale) { - qCDebug(interfaceapp, "Forcing scale to %f since %f is not allowed by domain", - clampedTargetScale, desiredScale); - } - - setTargetScale(clampedTargetScale); - qCDebug(interfaceapp, "Changed scale to %f", (double)_targetScale); - emit(scaleChanged()); -} - -float MyAvatar::getDomainMinScale() { - return _domainMinimumScale; -} - -float MyAvatar::getDomainMaxScale() { - return _domainMaximumScale; -} - void MyAvatar::setGravity(float gravity) { _characterController.setGravity(gravity); } @@ -2205,70 +2189,58 @@ float MyAvatar::getGravity() { } void MyAvatar::increaseSize() { - // make sure we're starting from an allowable scale - clampTargetScaleToDomainLimits(); + float minScale = getDomainMinScale(); + float maxScale = getDomainMaxScale(); - // calculate what our new scale should be - float updatedTargetScale = _targetScale * (1.0f + SCALING_RATIO); + float clampedTargetScale = glm::clamp(_targetScale, minScale, maxScale); + float newTargetScale = glm::clamp(clampedTargetScale * (1.0f + SCALING_RATIO), minScale, maxScale); - // attempt to change to desired scale (clamped to the domain limits) - clampScaleChangeToDomainLimits(updatedTargetScale); + setTargetScale(newTargetScale); } void MyAvatar::decreaseSize() { - // make sure we're starting from an allowable scale - clampTargetScaleToDomainLimits(); + float minScale = getDomainMinScale(); + float maxScale = getDomainMaxScale(); - // calculate what our new scale should be - float updatedTargetScale = _targetScale * (1.0f - SCALING_RATIO); + float clampedTargetScale = glm::clamp(_targetScale, minScale, maxScale); + float newTargetScale = glm::clamp(clampedTargetScale * (1.0f - SCALING_RATIO), minScale, maxScale); - // attempt to change to desired scale (clamped to the domain limits) - clampScaleChangeToDomainLimits(updatedTargetScale); + setTargetScale(newTargetScale); } void MyAvatar::resetSize() { // attempt to reset avatar size to the default (clamped to domain limits) const float DEFAULT_AVATAR_SCALE = 1.0f; - - clampScaleChangeToDomainLimits(DEFAULT_AVATAR_SCALE); + setTargetScale(DEFAULT_AVATAR_SCALE); } void MyAvatar::restrictScaleFromDomainSettings(const QJsonObject& domainSettingsObject) { - // pull out the minimum and maximum scale and set them to restrict our scale + // pull out the minimum and maximum height and set them to restrict our scale static const QString AVATAR_SETTINGS_KEY = "avatars"; auto avatarsObject = domainSettingsObject[AVATAR_SETTINGS_KEY].toObject(); - static const QString MIN_SCALE_OPTION = "min_avatar_scale"; - float settingMinScale = avatarsObject[MIN_SCALE_OPTION].toDouble(MIN_AVATAR_SCALE); - setDomainMinimumScale(settingMinScale); + static const QString MIN_HEIGHT_OPTION = "min_avatar_height"; + float settingMinHeight = avatarsObject[MIN_HEIGHT_OPTION].toDouble(MIN_AVATAR_HEIGHT); + setDomainMinimumHeight(settingMinHeight); - static const QString MAX_SCALE_OPTION = "max_avatar_scale"; - float settingMaxScale = avatarsObject[MAX_SCALE_OPTION].toDouble(MAX_AVATAR_SCALE); - setDomainMaximumScale(settingMaxScale); + static const QString MAX_HEIGHT_OPTION = "max_avatar_height"; + float settingMaxHeight = avatarsObject[MAX_HEIGHT_OPTION].toDouble(MAX_AVATAR_HEIGHT); + setDomainMaximumHeight(settingMaxHeight); // make sure that the domain owner didn't flip min and max - if (_domainMinimumScale > _domainMaximumScale) { - std::swap(_domainMinimumScale, _domainMaximumScale); + if (_domainMinimumHeight > _domainMaximumHeight) { + std::swap(_domainMinimumHeight, _domainMaximumHeight); } // Set avatar current scale Settings settings; settings.beginGroup("Avatar"); _targetScale = loadSetting(settings, "scale", 1.0f); - qCDebug(interfaceapp) << "This domain requires a minimum avatar scale of " << _domainMinimumScale - << " and a maximum avatar scale of " << _domainMaximumScale - << ". Current avatar scale is " << _targetScale; + qCDebug(interfaceapp) << "This domain requires a minimum avatar scale of " << _domainMinimumHeight + << " and a maximum avatar scale of " << _domainMaximumHeight; - // debug to log if this avatar's scale in this domain will be clamped - float clampedScale = glm::clamp(_targetScale, _domainMinimumScale, _domainMaximumScale); - - if (_targetScale != clampedScale) { - qCDebug(interfaceapp) << "Current avatar scale is clamped to " << clampedScale - << " because " << _targetScale << " is not allowed by current domain"; - // The current scale of avatar should not be more than domain's max_avatar_scale and not less than domain's min_avatar_scale . - _targetScale = clampedScale; - } + _isAnimatingScale = true; setModelScale(_targetScale); rebuildCollisionShape(); @@ -2288,8 +2260,8 @@ void MyAvatar::saveAvatarScale() { } void MyAvatar::clearScaleRestriction() { - _domainMinimumScale = MIN_AVATAR_SCALE; - _domainMaximumScale = MAX_AVATAR_SCALE; + _domainMinimumHeight = MIN_AVATAR_HEIGHT; + _domainMaximumHeight = MAX_AVATAR_HEIGHT; } void MyAvatar::goToLocation(const QVariant& propertiesVar) { @@ -3248,6 +3220,7 @@ void MyAvatar::setModelScale(float scale) { if (changed) { float sensorToWorldScale = getEyeHeight() / getUserEyeHeight(); emit sensorToWorldScaleChanged(sensorToWorldScale); + emit scaleChanged(); } } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 563fb7fccd..7c9513cb3e 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -110,6 +110,10 @@ class MyAvatar : public Avatar { * @property userEyeHeight {number} Estimated height of the users eyes in sensor space. (meters) * @property SELF_ID {string} READ-ONLY. UUID representing "my avatar". Only use for local-only entities and overlays in situations where MyAvatar.sessionUUID is not available (e.g., if not connected to a domain). * Note: Likely to be deprecated. + * @property hmdRollControlEnabled {bool} When enabled the roll angle of your HMD will turn your avatar while flying. + * @property hmdRollControlDeadZone {number} If hmdRollControlEnabled is true, this value can be used to tune what roll angle is required to begin turning. + * This angle is specified in degrees. + * @property hmdRollControlRate {number} If hmdRollControlEnabled is true, this value determines the maximum turn rate of your avatar when rolling your HMD in degrees per second. */ // FIXME: `glm::vec3 position` is not accessible from QML, so this exposes position in a QML-native type @@ -158,7 +162,7 @@ class MyAvatar : public Avatar { Q_PROPERTY(float userEyeHeight READ getUserEyeHeight) Q_PROPERTY(QUuid SELF_ID READ getSelfID CONSTANT) - + const QString DOMINANT_LEFT_HAND = "left"; const QString DOMINANT_RIGHT_HAND = "right"; @@ -558,8 +562,6 @@ public slots: void increaseSize(); void decreaseSize(); void resetSize(); - float getDomainMinScale(); - float getDomainMaxScale(); void setGravity(float gravity); float getGravity(); @@ -737,12 +739,12 @@ private: bool _clearOverlayWhenMoving { true }; QString _dominantHand { DOMINANT_RIGHT_HAND }; - const float ROLL_CONTROL_DEAD_ZONE_DEFAULT = 8.0f; // deg - const float ROLL_CONTROL_RATE_DEFAULT = 2.5f; // deg/sec/deg + const float ROLL_CONTROL_DEAD_ZONE_DEFAULT = 8.0f; // degrees + const float ROLL_CONTROL_RATE_DEFAULT = 114.0f; // degrees / sec + bool _hmdRollControlEnabled { true }; float _hmdRollControlDeadZone { ROLL_CONTROL_DEAD_ZONE_DEFAULT }; float _hmdRollControlRate { ROLL_CONTROL_RATE_DEFAULT }; - float _lastDrivenSpeed { 0.0f }; // working copy -- see AvatarData for thread-safe _sensorToWorldMatrixCache, used for outward facing access glm::mat4 _sensorToWorldMatrix { glm::mat4() }; diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 435d15d161..69089df9c2 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -83,19 +83,28 @@ void QmlCommerce::buy(const QString& assetId, int cost, const bool controlledFai void QmlCommerce::balance() { auto ledger = DependencyManager::get(); auto wallet = DependencyManager::get(); - ledger->balance(wallet->listPublicKeys()); + QStringList cachedPublicKeys = wallet->listPublicKeys(); + if (!cachedPublicKeys.isEmpty()) { + ledger->balance(cachedPublicKeys); + } } void QmlCommerce::inventory() { auto ledger = DependencyManager::get(); auto wallet = DependencyManager::get(); - ledger->inventory(wallet->listPublicKeys()); + QStringList cachedPublicKeys = wallet->listPublicKeys(); + if (!cachedPublicKeys.isEmpty()) { + ledger->inventory(cachedPublicKeys); + } } void QmlCommerce::history() { auto ledger = DependencyManager::get(); auto wallet = DependencyManager::get(); - ledger->history(wallet->listPublicKeys()); + QStringList cachedPublicKeys = wallet->listPublicKeys(); + if (!cachedPublicKeys.isEmpty()) { + ledger->history(cachedPublicKeys); + } } void QmlCommerce::changePassphrase(const QString& oldPassphrase, const QString& newPassphrase) { diff --git a/interface/src/raypick/RayPick.cpp b/interface/src/raypick/RayPick.cpp index 2f6e69bc7e..501e8d1e42 100644 --- a/interface/src/raypick/RayPick.cpp +++ b/interface/src/raypick/RayPick.cpp @@ -84,18 +84,7 @@ glm::vec2 RayPick::projectOntoXYPlane(const glm::vec3& worldPos, const glm::vec3 glm::vec2 RayPick::projectOntoOverlayXYPlane(const QUuid& overlayID, const glm::vec3& worldPos, bool unNormalized) { glm::vec3 position = vec3FromVariant(qApp->getOverlays().getProperty(overlayID, "position").value); glm::quat rotation = quatFromVariant(qApp->getOverlays().getProperty(overlayID, "rotation").value); - glm::vec3 dimensions; - - float dpi = qApp->getOverlays().getProperty(overlayID, "dpi").value.toFloat(); - if (dpi > 0) { - // Calculate physical dimensions for web3d overlay from resolution and dpi; "dimensions" property is used as a scale. - glm::vec3 resolution = glm::vec3(vec2FromVariant(qApp->getOverlays().getProperty(overlayID, "resolution").value), 1); - glm::vec3 scale = glm::vec3(vec2FromVariant(qApp->getOverlays().getProperty(overlayID, "dimensions").value), 0.01f); - const float INCHES_TO_METERS = 1.0f / 39.3701f; - dimensions = (resolution * INCHES_TO_METERS / dpi) * scale; - } else { - dimensions = glm::vec3(vec2FromVariant(qApp->getOverlays().getProperty(overlayID, "dimensions").value), 0.01); - } + glm::vec3 dimensions = glm::vec3(vec2FromVariant(qApp->getOverlays().getProperty(overlayID, "dimensions").value), 0.01f); return projectOntoXYPlane(worldPos, position, rotation, dimensions, ENTITY_ITEM_DEFAULT_REGISTRATION_POINT, unNormalized); } diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index 09d9ba574a..f27e26280a 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -35,7 +35,8 @@ ModelOverlay::ModelOverlay(const ModelOverlay* modelOverlay) : _modelTextures(QVariantMap()), _url(modelOverlay->_url), _updateModel(false), - _loadPriority(modelOverlay->getLoadPriority()) + _scaleToFit(modelOverlay->_scaleToFit), + _loadPriority(modelOverlay->_loadPriority) { _model->init(); _model->setLoadingPriority(_loadPriority); diff --git a/interface/src/ui/overlays/ModelOverlay.h b/interface/src/ui/overlays/ModelOverlay.h index ea0eff170c..c4506d9621 100644 --- a/interface/src/ui/overlays/ModelOverlay.h +++ b/interface/src/ui/overlays/ModelOverlay.h @@ -75,8 +75,8 @@ private: QVariantMap _modelTextures; QUrl _url; - bool _updateModel = { false }; - bool _scaleToFit = { false }; + bool _updateModel { false }; + bool _scaleToFit { false }; float _loadPriority { 0.0f }; AnimationPointer _animation; @@ -87,7 +87,7 @@ private: bool _animationRunning { false }; bool _animationLoop { false }; float _animationFirstFrame { 0.0f }; - float _animationLastFrame = { 0.0f }; + float _animationLastFrame { 0.0f }; bool _animationHold { false }; bool _animationAllowTranslation { false }; uint64_t _lastAnimated { 0 }; diff --git a/interface/src/ui/overlays/Overlays.h b/interface/src/ui/overlays/Overlays.h index a8f504bbc5..e1996e6bfc 100644 --- a/interface/src/ui/overlays/Overlays.h +++ b/interface/src/ui/overlays/Overlays.h @@ -305,13 +305,6 @@ public slots: OverlayID getKeyboardFocusOverlay(); void setKeyboardFocusOverlay(const OverlayID& id); - void mousePressPointerEvent(const OverlayID& overlayID, const PointerEvent& event); - void mouseMovePointerEvent(const OverlayID& overlayID, const PointerEvent& event); - void mouseReleasePointerEvent(const OverlayID& overlayID, const PointerEvent& event); - void hoverEnterPointerEvent(const OverlayID& overlayID, const PointerEvent& event); - void hoverOverPointerEvent(const OverlayID& overlayID, const PointerEvent& event); - void hoverLeavePointerEvent(const OverlayID& overlayID, const PointerEvent& event); - signals: /**jsdoc * Emitted when an overlay is deleted @@ -358,6 +351,14 @@ private: OverlayID _currentHoverOverOverlayID { UNKNOWN_OVERLAY_ID }; RayToOverlayIntersectionResult findRayIntersectionForMouseEvent(PickRay ray); + +private slots: + void mousePressPointerEvent(const OverlayID& overlayID, const PointerEvent& event); + void mouseMovePointerEvent(const OverlayID& overlayID, const PointerEvent& event); + void mouseReleasePointerEvent(const OverlayID& overlayID, const PointerEvent& event); + void hoverEnterPointerEvent(const OverlayID& overlayID, const PointerEvent& event); + void hoverOverPointerEvent(const OverlayID& overlayID, const PointerEvent& event); + void hoverLeavePointerEvent(const OverlayID& overlayID, const PointerEvent& event); }; #endif // hifi_Overlays_h diff --git a/interface/src/ui/overlays/Shape3DOverlay.cpp b/interface/src/ui/overlays/Shape3DOverlay.cpp index ca6446d215..07949d5349 100644 --- a/interface/src/ui/overlays/Shape3DOverlay.cpp +++ b/interface/src/ui/overlays/Shape3DOverlay.cpp @@ -18,8 +18,9 @@ QString const Shape3DOverlay::TYPE = "shape"; -Shape3DOverlay::Shape3DOverlay(const Shape3DOverlay* Shape3DOverlay) : - Volume3DOverlay(Shape3DOverlay) +Shape3DOverlay::Shape3DOverlay(const Shape3DOverlay* shape3DOverlay) : + Volume3DOverlay(shape3DOverlay), + _shape(shape3DOverlay->_shape) { } diff --git a/interface/src/ui/overlays/Shape3DOverlay.h b/interface/src/ui/overlays/Shape3DOverlay.h index e9e26e3c94..7fc95ec981 100644 --- a/interface/src/ui/overlays/Shape3DOverlay.h +++ b/interface/src/ui/overlays/Shape3DOverlay.h @@ -23,7 +23,7 @@ public: virtual QString getType() const override { return TYPE; } Shape3DOverlay() {} - Shape3DOverlay(const Shape3DOverlay* Shape3DOverlay); + Shape3DOverlay(const Shape3DOverlay* shape3DOverlay); virtual void render(RenderArgs* args) override; virtual const render::ShapeKey getShapeKey() override; diff --git a/interface/src/ui/overlays/Volume3DOverlay.cpp b/interface/src/ui/overlays/Volume3DOverlay.cpp index b580545288..49c76e6108 100644 --- a/interface/src/ui/overlays/Volume3DOverlay.cpp +++ b/interface/src/ui/overlays/Volume3DOverlay.cpp @@ -14,7 +14,8 @@ #include Volume3DOverlay::Volume3DOverlay(const Volume3DOverlay* volume3DOverlay) : - Base3DOverlay(volume3DOverlay) + Base3DOverlay(volume3DOverlay), + _localBoundingBox(volume3DOverlay->_localBoundingBox) { } diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index 34b8c6c865..7d5fefcb31 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -55,17 +55,15 @@ #include #include "ui/Snapshot.h" #include "SoundCache.h" - #include "raypick/PointerScriptingInterface.h" -static const float DPI = 30.47f; -static const float INCHES_TO_METERS = 1.0f / 39.3701f; +static int MAX_WINDOW_SIZE = 4096; static const float METERS_TO_INCHES = 39.3701f; static const float OPAQUE_ALPHA_THRESHOLD = 0.99f; const QString Web3DOverlay::TYPE = "web3d"; const QString Web3DOverlay::QML = "Web3DOverlay.qml"; -Web3DOverlay::Web3DOverlay() : _dpi(DPI) { +Web3DOverlay::Web3DOverlay() { _touchDevice.setCapabilities(QTouchDevice::Position); _touchDevice.setType(QTouchDevice::TouchScreen); _touchDevice.setName("Web3DOverlayTouchDevice"); @@ -82,7 +80,6 @@ Web3DOverlay::Web3DOverlay(const Web3DOverlay* Web3DOverlay) : _url(Web3DOverlay->_url), _scriptURL(Web3DOverlay->_scriptURL), _dpi(Web3DOverlay->_dpi), - _resolution(Web3DOverlay->_resolution), _showKeyboardFocusHighlight(Web3DOverlay->_showKeyboardFocusHighlight) { _geometryId = DependencyManager::get()->allocateID(); @@ -154,7 +151,7 @@ void Web3DOverlay::buildWebSurface() { setupQmlSurface(); } _webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(getWorldPosition())); - _webSurface->resize(QSize(_resolution.x, _resolution.y)); + onResizeWebSurface(); _webSurface->resume(); }); @@ -244,8 +241,16 @@ void Web3DOverlay::setMaxFPS(uint8_t maxFPS) { } void Web3DOverlay::onResizeWebSurface() { - _mayNeedResize = false; - _webSurface->resize(QSize(_resolution.x, _resolution.y)); + glm::vec2 dims = glm::vec2(getDimensions()); + dims *= METERS_TO_INCHES * _dpi; + + // ensure no side is never larger then MAX_WINDOW_SIZE + float max = (dims.x > dims.y) ? dims.x : dims.y; + if (max > MAX_WINDOW_SIZE) { + dims *= MAX_WINDOW_SIZE / max; + } + + _webSurface->resize(QSize(dims.x, dims.y)); } unsigned int Web3DOverlay::deviceIdByTouchPoint(qreal x, qreal y) { @@ -266,14 +271,14 @@ void Web3DOverlay::render(RenderArgs* args) { return; } - if (_currentMaxFPS != _desiredMaxFPS) { - setMaxFPS(_desiredMaxFPS); - } - if (_mayNeedResize) { emit resizeWebSurface(); } + if (_currentMaxFPS != _desiredMaxFPS) { + setMaxFPS(_desiredMaxFPS); + } + vec4 color(toGlm(getColor()), getAlpha()); if (!_texture) { @@ -310,7 +315,7 @@ void Web3DOverlay::render(RenderArgs* args) { Transform Web3DOverlay::evalRenderTransform() { Transform transform = Parent::evalRenderTransform(); transform.setScale(1.0f); - transform.postScale(glm::vec3(getSize(), 1.0f)); + transform.postScale(glm::vec3(getDimensions(), 1.0f)); return transform; } @@ -434,18 +439,10 @@ void Web3DOverlay::setProperties(const QVariantMap& properties) { } } - auto resolution = properties["resolution"]; - if (resolution.isValid()) { - bool valid; - auto res = vec2FromVariant(resolution, valid); - if (valid) { - _resolution = res; - } - } - auto dpi = properties["dpi"]; if (dpi.isValid()) { _dpi = dpi.toFloat(); + _mayNeedResize = true; } auto maxFPS = properties["maxFPS"]; @@ -467,8 +464,6 @@ void Web3DOverlay::setProperties(const QVariantMap& properties) { _inputMode = Touch; } } - - _mayNeedResize = true; } QVariant Web3DOverlay::getProperty(const QString& property) { @@ -478,9 +473,6 @@ QVariant Web3DOverlay::getProperty(const QString& property) { if (property == "scriptURL") { return _scriptURL; } - if (property == "resolution") { - return vec2toVariant(_resolution); - } if (property == "dpi") { return _dpi; } @@ -536,17 +528,18 @@ void Web3DOverlay::setScriptURL(const QString& scriptURL) { } } -glm::vec2 Web3DOverlay::getSize() const { - return _resolution / _dpi * INCHES_TO_METERS * getDimensions(); -}; - bool Web3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal) { - // FIXME - face and surfaceNormal not being returned + glm::vec2 dimensions = getDimensions(); + glm::quat rotation = getWorldOrientation(); + glm::vec3 position = getWorldPosition(); - // Don't call applyTransformTo() or setTransform() here because this code runs too frequently. - - // Produce the dimensions of the overlay based on the image's aspect ratio and the overlay's scale. - return findRayRectangleIntersection(origin, direction, getWorldOrientation(), getWorldPosition(), getSize(), distance); + if (findRayRectangleIntersection(origin, direction, rotation, position, dimensions, distance)) { + surfaceNormal = rotation * Vectors::UNIT_Z; + face = glm::dot(surfaceNormal, direction) > 0 ? MIN_Z_FACE : MAX_Z_FACE; + return true; + } else { + return false; + } } Web3DOverlay* Web3DOverlay::createClone() const { @@ -555,4 +548,4 @@ Web3DOverlay* Web3DOverlay::createClone() const { void Web3DOverlay::emitScriptEvent(const QVariant& message) { QMetaObject::invokeMethod(this, "scriptEventReceived", Q_ARG(QVariant, message)); -} +} \ No newline at end of file diff --git a/interface/src/ui/overlays/Web3DOverlay.h b/interface/src/ui/overlays/Web3DOverlay.h index 563c1c0c4e..4098e98488 100644 --- a/interface/src/ui/overlays/Web3DOverlay.h +++ b/interface/src/ui/overlays/Web3DOverlay.h @@ -52,8 +52,6 @@ public: void setProperties(const QVariantMap& properties) override; QVariant getProperty(const QString& property) override; - glm::vec2 getSize() const override; - virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal) override; @@ -93,10 +91,9 @@ private: gpu::TexturePointer _texture; QString _url; QString _scriptURL; - float _dpi; - vec2 _resolution{ 640, 480 }; + float _dpi { 30.0f }; int _geometryId { 0 }; - bool _showKeyboardFocusHighlight{ true }; + bool _showKeyboardFocusHighlight { true }; QTouchDevice _touchDevice; diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index e9cc444bd4..e738ad1c19 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -231,6 +231,9 @@ public: const glm::mat4& getGeometryToRigTransform() const { return _geometryToRigTransform; } + const AnimPose& getModelOffsetPose() const { return _modelOffset; } + const AnimPose& getGeometryOffsetPose() const { return _geometryOffset; } + void setEnableDebugDrawIKTargets(bool enableDebugDrawIKTargets) { _enableDebugDrawIKTargets = enableDebugDrawIKTargets; } void setEnableDebugDrawIKConstraints(bool enableDebugDrawIKConstraints) { _enableDebugDrawIKConstraints = enableDebugDrawIKConstraints; } void setEnableDebugDrawIKChains(bool enableDebugDrawIKChains) { _enableDebugDrawIKChains = enableDebugDrawIKChains; } diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 055a3cf24a..437ac623e3 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -162,6 +162,7 @@ AABox Avatar::getBounds() const { } void Avatar::animateScaleChanges(float deltaTime) { + if (_isAnimatingScale) { float currentScale = getModelScale(); float desiredScale = getDomainLimitedScale(); @@ -172,7 +173,7 @@ void Avatar::animateScaleChanges(float deltaTime) { float animatedScale = (1.0f - blendFactor) * currentScale + blendFactor * desiredScale; // snap to the end when we get close enough - const float MIN_RELATIVE_ERROR = 0.03f; + const float MIN_RELATIVE_ERROR = 0.001f; float relativeError = fabsf(desiredScale - currentScale) / desiredScale; if (relativeError < MIN_RELATIVE_ERROR) { animatedScale = desiredScale; @@ -698,6 +699,7 @@ void Avatar::fixupModelsInScene(const render::ScenePointer& scene) { _skeletonModel->removeFromScene(scene, transaction); _skeletonModel->addToScene(scene, transaction); canTryFade = true; + _isAnimatingScale = true; } for (auto attachmentModel : _attachmentModels) { if (attachmentModel->isRenderable() && attachmentModel->needsFixupInScene()) { @@ -1195,6 +1197,8 @@ void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { void Avatar::setModelURLFinished(bool success) { invalidateJointIndicesCache(); + _isAnimatingScale = true; + if (!success && _skeletonModelURL != AvatarData::defaultFullAvatarModelUrl()) { const int MAX_SKELETON_DOWNLOAD_ATTEMPTS = 4; // NOTE: we don't want to be as generous as ResourceCache is, we only want 4 attempts if (_skeletonModel->getResourceDownloadAttemptsRemaining() <= 0 || @@ -1588,45 +1592,80 @@ float Avatar::getEyeHeight() const { return result; } + return getModelScale() * getUnscaledEyeHeight(); +} + +float Avatar::getUnscaledEyeHeight() const { + float skeletonHeight = getUnscaledEyeHeightFromSkeleton(); + + // Sanity check by looking at the model extents. + Extents meshExtents = _skeletonModel->getUnscaledMeshExtents(); + float meshHeight = meshExtents.size().y; + + // if we determine the mesh is much larger then the skeleton, then we use the mesh height instead. + // This helps prevent absurdly large avatars from exceeding the domain height limit. + const float MESH_SLOP_RATIO = 1.5f; + if (meshHeight > skeletonHeight * MESH_SLOP_RATIO) { + return meshHeight; + } else { + return skeletonHeight; + } +} + +float Avatar::getUnscaledEyeHeightFromSkeleton() const { + // TODO: if performance becomes a concern we can cache this value rather then computing it everytime. - // Makes assumption that the y = 0 plane in geometry is the ground plane. - // We also make that assumption in Rig::computeAvatarBoundingCapsule() - float avatarScale = getModelScale(); + if (_skeletonModel) { auto& rig = _skeletonModel->getRig(); + + // Normally the model offset transform will contain the avatar scale factor, we explicitly remove it here. + AnimPose modelOffsetWithoutAvatarScale(glm::vec3(1.0f), rig.getModelOffsetPose().rot(), rig.getModelOffsetPose().trans()); + AnimPose geomToRigWithoutAvatarScale = modelOffsetWithoutAvatarScale * rig.getGeometryOffsetPose(); + + // This factor can be used to scale distances in the geometry frame into the unscaled rig frame. + // Typically it will be the unit conversion from cm to m. + float scaleFactor = geomToRigWithoutAvatarScale.scale().x; // in practice this always a uniform scale factor. + int headTopJoint = rig.indexOfJoint("HeadTop_End"); int headJoint = rig.indexOfJoint("Head"); int eyeJoint = rig.indexOfJoint("LeftEye") != -1 ? rig.indexOfJoint("LeftEye") : rig.indexOfJoint("RightEye"); int toeJoint = rig.indexOfJoint("LeftToeBase") != -1 ? rig.indexOfJoint("LeftToeBase") : rig.indexOfJoint("RightToeBase"); + + // Makes assumption that the y = 0 plane in geometry is the ground plane. + // We also make that assumption in Rig::computeAvatarBoundingCapsule() + const float GROUND_Y = 0.0f; + + // Values from the skeleton are in the geometry coordinate frame. + auto skeleton = rig.getAnimSkeleton(); if (eyeJoint >= 0 && toeJoint >= 0) { - // measure from eyes to toes. - float eyeHeight = rig.getAbsoluteDefaultPose(eyeJoint).trans().y - rig.getAbsoluteDefaultPose(toeJoint).trans().y; - return eyeHeight; + // Measure from eyes to toes. + float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - skeleton->getAbsoluteDefaultPose(toeJoint).trans().y; + return scaleFactor * eyeHeight; } else if (eyeJoint >= 0) { - // measure eyes to y = 0 plane. - float groundHeight = transformPoint(rig.getGeometryToRigTransform(), glm::vec3(0.0f)).y; - float eyeHeight = rig.getAbsoluteDefaultPose(eyeJoint).trans().y - groundHeight; - return eyeHeight; + // Measure Eye joint to y = 0 plane. + float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - GROUND_Y; + return scaleFactor * eyeHeight; } else if (headTopJoint >= 0 && toeJoint >= 0) { - // measure toe to top of head. Note: default poses already include avatar scale factor + // Measure from ToeBase joint to HeadTop_End joint, then remove forehead distance. const float ratio = DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD / DEFAULT_AVATAR_HEIGHT; - float height = rig.getAbsoluteDefaultPose(headTopJoint).trans().y - rig.getAbsoluteDefaultPose(toeJoint).trans().y; - return height - height * ratio; + float height = skeleton->getAbsoluteDefaultPose(headTopJoint).trans().y - skeleton->getAbsoluteDefaultPose(toeJoint).trans().y; + return scaleFactor * (height - height * ratio); } else if (headTopJoint >= 0) { + // Measure from HeadTop_End joint to the ground, then remove forehead distance. const float ratio = DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD / DEFAULT_AVATAR_HEIGHT; - float groundHeight = transformPoint(rig.getGeometryToRigTransform(), glm::vec3(0.0f)).y; - float headHeight = rig.getAbsoluteDefaultPose(headTopJoint).trans().y - groundHeight; - return headHeight - headHeight * ratio; + float headHeight = skeleton->getAbsoluteDefaultPose(headTopJoint).trans().y - GROUND_Y; + return scaleFactor * (headHeight - headHeight * ratio); } else if (headJoint >= 0) { - float groundHeight = transformPoint(rig.getGeometryToRigTransform(), glm::vec3(0.0f)).y; + // Measure Head joint to the ground, then add in distance from neck to eye. const float DEFAULT_AVATAR_NECK_TO_EYE = DEFAULT_AVATAR_NECK_TO_TOP_OF_HEAD - DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD; const float ratio = DEFAULT_AVATAR_NECK_TO_EYE / DEFAULT_AVATAR_NECK_HEIGHT; - float neckHeight = rig.getAbsoluteDefaultPose(headJoint).trans().y - groundHeight; - return neckHeight + neckHeight * ratio; + float neckHeight = skeleton->getAbsoluteDefaultPose(headJoint).trans().y - GROUND_Y; + return scaleFactor * (neckHeight + neckHeight * ratio); } else { - return avatarScale * DEFAULT_AVATAR_EYE_HEIGHT; + return DEFAULT_AVATAR_EYE_HEIGHT; } } else { - return avatarScale * DEFAULT_AVATAR_EYE_HEIGHT; + return DEFAULT_AVATAR_EYE_HEIGHT; } } diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index a723a8e1b0..44d4ef4c51 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -255,12 +255,16 @@ public: bool isFading() const { return _isFading; } void updateFadingStatus(render::ScenePointer scene); - /**jsdoc - * Provides read only access to the current eye height of the avatar. - * @function Avatar.getEyeHeight - * @returns {number} eye height of avatar in meters - */ - Q_INVOKABLE float getEyeHeight() const; + Q_INVOKABLE virtual float getEyeHeight() const override; + + // returns eye height of avatar in meters, ignoreing avatar scale. + // if _targetScale is 1 then this will be identical to getEyeHeight; + virtual float getUnscaledEyeHeight() const override; + + // returns true, if an acurate eye height estimage can be obtained by inspecting the avatar model skeleton and geometry, + // not all subclasses of AvatarData have access to this data. + virtual bool canMeasureEyeHeight() const override { return true; } + virtual float getModelScale() const { return _modelScale; } virtual void setModelScale(float scale) { _modelScale = scale; } @@ -279,6 +283,7 @@ public slots: void setModelURLFinished(bool success); protected: + float getUnscaledEyeHeightFromSkeleton() const; virtual const QString& getSessionDisplayNameForTransport() const override { return _empty; } // Save a tiny bit of bandwidth. Mixer won't look at what we send. QString _empty{}; virtual void maybeUpdateSessionDisplayNameFromTransport(const QString& sessionDisplayName) override { _sessionDisplayName = sessionDisplayName; } // don't use no-op setter! @@ -349,7 +354,7 @@ protected: RateCounter<> _skeletonModelSimulationRate; RateCounter<> _jointDataSimulationRate; -private: +protected: class AvatarEntityDataHash { public: AvatarEntityDataHash(uint32_t h) : hash(h) {}; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index cb43aeee5d..f2053e29d7 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -117,6 +117,55 @@ void AvatarData::setTargetScale(float targetScale) { } } +float AvatarData::getDomainLimitedScale() const { + if (canMeasureEyeHeight()) { + const float minScale = getDomainMinScale(); + const float maxScale = getDomainMaxScale(); + return glm::clamp(_targetScale, minScale, maxScale); + } else { + // We can't make a good estimate. + return _targetScale; + } +} + +void AvatarData::setDomainMinimumHeight(float domainMinimumHeight) { + _domainMinimumHeight = glm::clamp(domainMinimumHeight, MIN_AVATAR_HEIGHT, MAX_AVATAR_HEIGHT); +} + +void AvatarData::setDomainMaximumHeight(float domainMaximumHeight) { + _domainMaximumHeight = glm::clamp(domainMaximumHeight, MIN_AVATAR_HEIGHT, MAX_AVATAR_HEIGHT); +} + +float AvatarData::getDomainMinScale() const { + float unscaledHeight = getUnscaledHeight(); + const float EPSILON = 1.0e-4f; + if (unscaledHeight <= EPSILON) { + unscaledHeight = DEFAULT_AVATAR_HEIGHT; + } + return _domainMinimumHeight / unscaledHeight; +} + +float AvatarData::getDomainMaxScale() const { + float unscaledHeight = getUnscaledHeight(); + const float EPSILON = 1.0e-4f; + if (unscaledHeight <= EPSILON) { + unscaledHeight = DEFAULT_AVATAR_HEIGHT; + } + return _domainMaximumHeight / unscaledHeight; +} + +float AvatarData::getUnscaledHeight() const { + const float eyeHeight = getUnscaledEyeHeight(); + const float ratio = eyeHeight / DEFAULT_AVATAR_HEIGHT; + return eyeHeight + ratio * DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD; +} + +float AvatarData::getHeight() const { + const float eyeHeight = getEyeHeight(); + const float ratio = eyeHeight / DEFAULT_AVATAR_HEIGHT; + return eyeHeight + ratio * DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD; +} + glm::vec3 AvatarData::getHandPosition() const { return getWorldOrientation() * _handPosition + getWorldPosition(); } @@ -2387,63 +2436,10 @@ void RayToAvatarIntersectionResultFromScriptValue(const QScriptValue& object, Ra const float AvatarData::OUT_OF_VIEW_PENALTY = -10.0f; -float AvatarData::_avatarSortCoefficientSize { 0.5f }; +float AvatarData::_avatarSortCoefficientSize { 1.0f }; float AvatarData::_avatarSortCoefficientCenter { 0.25 }; float AvatarData::_avatarSortCoefficientAge { 1.0f }; -void AvatarData::sortAvatars( - QList avatarList, - const ViewFrustum& cameraView, - std::priority_queue& sortedAvatarsOut, - std::function getLastUpdated, - std::function getBoundingRadius, - std::function shouldIgnore) { - - PROFILE_RANGE(simulation, "sort"); - uint64_t now = usecTimestampNow(); - - glm::vec3 frustumCenter = cameraView.getPosition(); - const glm::vec3& forward = cameraView.getDirection(); - for (int32_t i = 0; i < avatarList.size(); ++i) { - const auto& avatar = avatarList.at(i); - - if (shouldIgnore(avatar)) { - continue; - } - - // priority = weighted linear combination of: - // (a) apparentSize - // (b) proximity to center of view - // (c) time since last update - glm::vec3 avatarPosition = avatar->getWorldPosition(); - glm::vec3 offset = avatarPosition - frustumCenter; - float distance = glm::length(offset) + 0.001f; // add 1mm to avoid divide by zero - - // FIXME - AvatarData has something equivolent to this - float radius = getBoundingRadius(avatar); - - float apparentSize = 2.0f * radius / distance; - float cosineAngle = glm::dot(offset, forward) / distance; - float age = (float)(now - getLastUpdated(avatar)) / (float)(USECS_PER_SECOND); - - // NOTE: we are adding values of different units to get a single measure of "priority". - // Thus we multiply each component by a conversion "weight" that scales its units relative to the others. - // These weights are pure magic tuning and should be hard coded in the relation below, - // but are currently exposed for anyone who would like to explore fine tuning: - float priority = _avatarSortCoefficientSize * apparentSize - + _avatarSortCoefficientCenter * cosineAngle - + _avatarSortCoefficientAge * age; - - // decrement priority of avatars outside keyhole - if (distance > cameraView.getCenterRadius()) { - if (!cameraView.sphereIntersectsFrustum(avatarPosition, radius)) { - priority += OUT_OF_VIEW_PENALTY; - } - } - sortedAvatarsOut.push(AvatarPriority(avatar, priority)); - } -} - QScriptValue AvatarEntityMapToScriptValue(QScriptEngine* engine, const AvatarEntityMap& value) { QScriptValue obj = engine->newObject(); for (auto entityID : value.keys()) { diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 2f3154ad08..de98675733 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -35,6 +35,7 @@ #include #include +#include #include #include #include @@ -257,9 +258,6 @@ namespace AvatarDataPacket { size_t maxJointDataSize(size_t numJoints); } -static const float MAX_AVATAR_SCALE = 1000.0f; -static const float MIN_AVATAR_SCALE = .005f; - const float MAX_AUDIO_LOUDNESS = 1000.0f; // close enough for mouth animation const int AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS = 1000; @@ -484,12 +482,38 @@ public: // Scale virtual void setTargetScale(float targetScale); - float getDomainLimitedScale() const { return glm::clamp(_targetScale, _domainMinimumScale, _domainMaximumScale); } + float getDomainLimitedScale() const; + float getDomainMinScale() const; + float getDomainMaxScale() const; - void setDomainMinimumScale(float domainMinimumScale) - { _domainMinimumScale = glm::clamp(domainMinimumScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE); _scaleChanged = usecTimestampNow(); } - void setDomainMaximumScale(float domainMaximumScale) - { _domainMaximumScale = glm::clamp(domainMaximumScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE); _scaleChanged = usecTimestampNow(); } + // returns eye height of avatar in meters, ignoreing avatar scale. + // if _targetScale is 1 then this will be identical to getEyeHeight; + virtual float getUnscaledEyeHeight() const { return DEFAULT_AVATAR_EYE_HEIGHT; } + + // returns true, if an acurate eye height estimage can be obtained by inspecting the avatar model skeleton and geometry, + // not all subclasses of AvatarData have access to this data. + virtual bool canMeasureEyeHeight() const { return false; } + + /**jsdoc + * Provides read only access to the current eye height of the avatar. + * This height is only an estimate and might be incorrect for avatars that are missing standard joints. + * @function AvatarData.getEyeHeight + * @returns {number} eye height of avatar in meters + */ + Q_INVOKABLE virtual float getEyeHeight() const { return _targetScale * getUnscaledEyeHeight(); } + + /**jsdoc + * Provides read only access to the current height of the avatar. + * This height is only an estimate and might be incorrect for avatars that are missing standard joints. + * @function AvatarData.getHeight + * @returns {number} height of avatar in meters + */ + Q_INVOKABLE virtual float getHeight() const; + + float getUnscaledHeight() const; + + void setDomainMinimumHeight(float domainMinimumHeight); + void setDomainMaximumHeight(float domainMaximumHeight); // Hand State Q_INVOKABLE void setHandState(char s) { _handState = s; } @@ -629,14 +653,6 @@ public: static const float OUT_OF_VIEW_PENALTY; - static void sortAvatars( - QList avatarList, - const ViewFrustum& cameraView, - std::priority_queue& sortedAvatarsOut, - std::function getLastUpdated, - std::function getBoundingRadius, - std::function shouldIgnore); - // TODO: remove this HACK once we settle on optimal sort coefficients // These coefficients exposed for fine tuning the sort priority for transfering new _jointData to the render pipeline. static float _avatarSortCoefficientSize; @@ -706,8 +722,8 @@ protected: // Body scale float _targetScale; - float _domainMinimumScale { MIN_AVATAR_SCALE }; - float _domainMaximumScale { MAX_AVATAR_SCALE }; + float _domainMinimumHeight { MIN_AVATAR_HEIGHT }; + float _domainMaximumHeight { MAX_AVATAR_HEIGHT }; // Hand state (are we grabbing something or not) char _handState; diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp index 7b639e8308..f3f81c0b2e 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp @@ -437,9 +437,11 @@ glm::mat4 CompositorHelper::getReticleTransform(const glm::mat4& eyePose, const } else { d = glm::normalize(overlaySurfacePoint); } - reticlePosition = headPosition + (d * getReticleDepth()); + // Our sensor to world matrix always has uniform scale + float sensorSpaceReticleDepth = getReticleDepth() / extractScale(_sensorToWorldMatrix).x; + reticlePosition = headPosition + (d * sensorSpaceReticleDepth); quat reticleOrientation = cancelOutRoll(glm::quat_cast(_currentDisplayPlugin->getHeadPose())); - vec3 reticleScale = vec3(Cursor::Manager::instance().getScale() * reticleSize * getReticleDepth()); + vec3 reticleScale = vec3(Cursor::Manager::instance().getScale() * reticleSize * sensorSpaceReticleDepth); return glm::inverse(eyePose) * createMatFromScaleQuatAndPos(reticleScale, reticleOrientation, reticlePosition); } else { static const float CURSOR_PIXEL_SIZE = 32.0f; diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index c8e7ce6c11..48370b02fd 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -1605,6 +1605,48 @@ void EntityItem::setParentID(const QUuid& value) { if (tree && !oldParentID.isNull()) { tree->removeFromChildrenOfAvatars(getThisPointer()); } + + uint32_t oldParentNoBootstrapping = 0; + uint32_t newParentNoBootstrapping = 0; + if (!value.isNull() && tree) { + EntityItemPointer entity = tree->findEntityByEntityItemID(value); + if (entity) { + newParentNoBootstrapping = entity->getDirtyFlags() & Simulation::NO_BOOTSTRAPPING; + } + } + + if (!oldParentID.isNull() && tree) { + EntityItemPointer entity = tree->findEntityByEntityItemID(oldParentID); + if (entity) { + oldParentNoBootstrapping = entity->getDirtyFlags() & Simulation::NO_BOOTSTRAPPING; + } + } + + if (!value.isNull() && (value == Physics::getSessionUUID() || value == AVATAR_SELF_ID)) { + newParentNoBootstrapping |= Simulation::NO_BOOTSTRAPPING; + } + + if ((bool)(oldParentNoBootstrapping ^ newParentNoBootstrapping)) { + if ((bool)(newParentNoBootstrapping & Simulation::NO_BOOTSTRAPPING)) { + markDirtyFlags(Simulation::NO_BOOTSTRAPPING); + forEachDescendant([&](SpatiallyNestablePointer object) { + if (object->getNestableType() == NestableType::Entity) { + EntityItemPointer entity = std::static_pointer_cast(object); + entity->markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP | Simulation::NO_BOOTSTRAPPING); + } + }); + } else { + clearDirtyFlags(Simulation::NO_BOOTSTRAPPING); + forEachDescendant([&](SpatiallyNestablePointer object) { + if (object->getNestableType() == NestableType::Entity) { + EntityItemPointer entity = std::static_pointer_cast(object); + entity->markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP); + entity->clearDirtyFlags(Simulation::NO_BOOTSTRAPPING); + } + }); + } + } + SpatiallyNestable::setParentID(value); // children are forced to be kinematic // may need to not collide with own avatar @@ -1834,39 +1876,8 @@ void EntityItem::computeCollisionGroupAndFinalMask(int16_t& group, int16_t& mask } } - if (userMask & USER_COLLISION_GROUP_MY_AVATAR) { - bool iAmHoldingThis = false; - // if this entity is a descendant of MyAvatar, don't collide with MyAvatar. This avoids the - // "bootstrapping" problem where you can shoot yourself across the room by grabbing something - // and holding it against your own avatar. - if (isChildOfMyAvatar()) { - iAmHoldingThis = true; - } - // also, don't bootstrap our own avatar with a hold action - QList holdActions = getActionsOfType(DYNAMIC_TYPE_HOLD); - QList::const_iterator i = holdActions.begin(); - while (i != holdActions.end()) { - EntityDynamicPointer action = *i; - if (action->isMine()) { - iAmHoldingThis = true; - break; - } - i++; - } - QList farGrabActions = getActionsOfType(DYNAMIC_TYPE_FAR_GRAB); - i = farGrabActions.begin(); - while (i != farGrabActions.end()) { - EntityDynamicPointer action = *i; - if (action->isMine()) { - iAmHoldingThis = true; - break; - } - i++; - } - - if (iAmHoldingThis) { - userMask &= ~USER_COLLISION_GROUP_MY_AVATAR; - } + if ((bool)(_dirtyFlags & Simulation::NO_BOOTSTRAPPING)) { + userMask &= ~USER_COLLISION_GROUP_MY_AVATAR; } mask = Physics::getDefaultCollisionMask(group) & (int16_t)(userMask); } @@ -1961,7 +1972,20 @@ bool EntityItem::addActionInternal(EntitySimulationPointer simulation, EntityDyn if (success) { _allActionsDataCache = newDataCache; _dirtyFlags |= Simulation::DIRTY_PHYSICS_ACTIVATION; - _dirtyFlags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar + + auto actionType = action->getType(); + if (actionType == DYNAMIC_TYPE_HOLD || actionType == DYNAMIC_TYPE_FAR_GRAB) { + if (!(bool)(_dirtyFlags & Simulation::NO_BOOTSTRAPPING)) { + _dirtyFlags |= Simulation::NO_BOOTSTRAPPING; + _dirtyFlags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar + forEachDescendant([&](SpatiallyNestablePointer child) { + if (child->getNestableType() == NestableType::Entity) { + EntityItemPointer entity = std::static_pointer_cast(child); + entity->markDirtyFlags(Simulation::NO_BOOTSTRAPPING | Simulation::DIRTY_COLLISION_GROUP); + } + }); + } + } } else { qCDebug(entities) << "EntityItem::addActionInternal -- serializeActions failed"; } @@ -2002,6 +2026,29 @@ bool EntityItem::removeAction(EntitySimulationPointer simulation, const QUuid& a return success; } +bool EntityItem::stillHasGrabActions() const { + QList holdActions = getActionsOfType(DYNAMIC_TYPE_HOLD); + QList::const_iterator i = holdActions.begin(); + while (i != holdActions.end()) { + EntityDynamicPointer action = *i; + if (action->isMine()) { + return true; + } + i++; + } + QList farGrabActions = getActionsOfType(DYNAMIC_TYPE_FAR_GRAB); + i = farGrabActions.begin(); + while (i != farGrabActions.end()) { + EntityDynamicPointer action = *i; + if (action->isMine()) { + return true; + } + i++; + } + + return false; +} + bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulationPointer simulation) { _previouslyDeletedActions.insert(actionID, usecTimestampNow()); if (_objectActions.contains(actionID)) { @@ -2015,7 +2062,6 @@ bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulationPoi action->setOwnerEntity(nullptr); action->setIsMine(false); - _objectActions.remove(actionID); if (simulation) { action->removeFromSimulation(simulation); @@ -2024,7 +2070,23 @@ bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulationPoi bool success = true; serializeActions(success, _allActionsDataCache); _dirtyFlags |= Simulation::DIRTY_PHYSICS_ACTIVATION; - _dirtyFlags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar + auto removedActionType = action->getType(); + if ((removedActionType == DYNAMIC_TYPE_HOLD || removedActionType == DYNAMIC_TYPE_FAR_GRAB) && !stillHasGrabActions()) { + _dirtyFlags &= ~Simulation::NO_BOOTSTRAPPING; + _dirtyFlags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar + forEachDescendant([&](SpatiallyNestablePointer child) { + if (child->getNestableType() == NestableType::Entity) { + EntityItemPointer entity = std::static_pointer_cast(child); + entity->markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP); + entity->clearDirtyFlags(Simulation::NO_BOOTSTRAPPING); + } + }); + } else { + // NO-OP: we assume NO_BOOTSTRAPPING bits and collision group are correct + // because they should have been set correctly when the action was added + // and/or when children were linked + } + _objectActions.remove(actionID); setDynamicDataNeedsTransmit(true); return success; } diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 882b8e6812..4c7f37bd6a 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -470,6 +470,7 @@ protected: void setSimulated(bool simulated) { _simulated = simulated; } const QByteArray getDynamicDataInternal() const; + bool stillHasGrabActions() const; void setDynamicDataInternal(QByteArray dynamicData); virtual void dimensionsChanged() override; diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 5ab4bdee01..9f7ba1cc80 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -2486,7 +2486,7 @@ QByteArray EntityItemProperties::getStaticCertificateJSON() const { ADD_STRING_PROPERTY(collisionSoundURL, CollisionSoundURL); ADD_STRING_PROPERTY(compoundShapeURL, CompoundShapeURL); ADD_INT_PROPERTY(editionNumber, EditionNumber); - ADD_INT_PROPERTY(instanceNumber, EntityInstanceNumber); + ADD_INT_PROPERTY(entityInstanceNumber, EntityInstanceNumber); ADD_STRING_PROPERTY(itemArtist, ItemArtist); ADD_STRING_PROPERTY(itemCategories, ItemCategories); ADD_STRING_PROPERTY(itemDescription, ItemDescription); diff --git a/libraries/entities/src/SimulationFlags.h b/libraries/entities/src/SimulationFlags.h index e2b2224b4a..aaa92000e7 100644 --- a/libraries/entities/src/SimulationFlags.h +++ b/libraries/entities/src/SimulationFlags.h @@ -27,6 +27,7 @@ namespace Simulation { const uint32_t DIRTY_PHYSICS_ACTIVATION = 0x0800; // should activate object in physics engine const uint32_t DIRTY_SIMULATOR_ID = 0x1000; // the simulatorID has changed const uint32_t DIRTY_SIMULATION_OWNERSHIP_PRIORITY = 0x2000; // our own bid priority has changed + const uint32_t NO_BOOTSTRAPPING = 0x4000; const uint32_t DIRTY_TRANSFORM = DIRTY_POSITION | DIRTY_ROTATION; const uint32_t DIRTY_VELOCITIES = DIRTY_LINEAR_VELOCITY | DIRTY_ANGULAR_VELOCITY; diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index 4184351c2d..d25c5225d6 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -1006,14 +1006,11 @@ NetworkTexturePointer TextureCache::getResourceTexture(QUrl resourceTextureUrl) if (!_spectatorCameraNetworkTexture) { _spectatorCameraNetworkTexture.reset(new NetworkTexture(resourceTextureUrl)); } - if (_spectatorCameraFramebuffer) { - texture = _spectatorCameraFramebuffer->getRenderBuffer(0); - if (texture) { - texture->setSource(SPECTATOR_CAMERA_FRAME_URL.toString().toStdString()); - _spectatorCameraNetworkTexture->setImage(texture, texture->getWidth(), texture->getHeight()); - return _spectatorCameraNetworkTexture; - } + if (!_spectatorCameraFramebuffer) { + getSpectatorCameraFramebuffer(); // initialize frame buffer } + updateSpectatorCameraNetworkTexture(); + return _spectatorCameraNetworkTexture; } // FIXME: Generalize this, DRY up this code if (resourceTextureUrl == HMD_PREVIEW_FRAME_URL) { @@ -1052,7 +1049,18 @@ const gpu::FramebufferPointer& TextureCache::getSpectatorCameraFramebuffer(int w // If we aren't taking a screenshot, we might need to resize or create the camera buffer if (!_spectatorCameraFramebuffer || _spectatorCameraFramebuffer->getWidth() != width || _spectatorCameraFramebuffer->getHeight() != height) { _spectatorCameraFramebuffer.reset(gpu::Framebuffer::create("spectatorCamera", gpu::Element::COLOR_SRGBA_32, width, height)); + updateSpectatorCameraNetworkTexture(); emit spectatorCameraFramebufferReset(); } return _spectatorCameraFramebuffer; } + +void TextureCache::updateSpectatorCameraNetworkTexture() { + if (_spectatorCameraFramebuffer && _spectatorCameraNetworkTexture) { + gpu::TexturePointer texture = _spectatorCameraFramebuffer->getRenderBuffer(0); + if (texture) { + texture->setSource(SPECTATOR_CAMERA_FRAME_URL.toString().toStdString()); + _spectatorCameraNetworkTexture->setImage(texture, texture->getWidth(), texture->getHeight()); + } + } +} diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h index 1102694f86..5a96fcf5e6 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.h +++ b/libraries/model-networking/src/model-networking/TextureCache.h @@ -171,6 +171,7 @@ public: const gpu::FramebufferPointer& getHmdPreviewFramebuffer(int width, int height); const gpu::FramebufferPointer& getSpectatorCameraFramebuffer(); const gpu::FramebufferPointer& getSpectatorCameraFramebuffer(int width, int height); + void updateSpectatorCameraNetworkTexture(); static const int DEFAULT_SPECTATOR_CAM_WIDTH { 2048 }; static const int DEFAULT_SPECTATOR_CAM_HEIGHT { 1024 }; diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index fa5028e1bb..207ddf6bbb 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -74,6 +74,8 @@ PacketVersion versionForPacketType(PacketType packetType) { return static_cast(AudioVersion::HighDynamicRangeVolume); case PacketType::ICEPing: return static_cast(IcePingVersion::SendICEPeerID); + case PacketType::DomainSettings: + return 18; // replace min_avatar_scale and max_avatar_scale with min_avatar_height and max_avatar_height default: return 17; } diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 8ebce9f811..7e8b431ceb 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -700,7 +700,7 @@ uint32_t EntityMotionState::getIncomingDirtyFlags() { void EntityMotionState::clearIncomingDirtyFlags() { assert(entityTreeIsLocked()); if (_body && _entity) { - _entity->clearDirtyFlags(); + _entity->clearDirtyFlags(DIRTY_PHYSICS_FLAGS); } } diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 428fcc7a54..199cb29f53 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -163,7 +163,7 @@ void Model::setScale(const glm::vec3& scale) { _scaledToFit = false; } -const float SCALE_CHANGE_EPSILON = 0.01f; +const float SCALE_CHANGE_EPSILON = 0.001f; void Model::setScaleInternal(const glm::vec3& scale) { if (glm::distance(_scale, scale) > SCALE_CHANGE_EPSILON) { diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index c537a928b3..fadd44b745 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -204,6 +204,9 @@ public: /// Returns the extents of the model's mesh Extents getMeshExtents() const; + /// Returns the unscaled extents of the model's mesh + Extents getUnscaledMeshExtents() const; + void setTranslation(const glm::vec3& translation); void setRotation(const glm::quat& rotation); void setTransformNoUpdateRenderItems(const Transform& transform); // temporary HACK @@ -276,9 +279,6 @@ protected: void setBlendshapeCoefficients(const QVector& coefficients) { _blendshapeCoefficients = coefficients; } const QVector& getBlendshapeCoefficients() const { return _blendshapeCoefficients; } - /// Returns the unscaled extents of the model's mesh - Extents getUnscaledMeshExtents() const; - /// Clear the joint states void clearJointState(int index); diff --git a/libraries/shared/src/AvatarConstants.h b/libraries/shared/src/AvatarConstants.h index a7a80471be..4942c63e27 100644 --- a/libraries/shared/src/AvatarConstants.h +++ b/libraries/shared/src/AvatarConstants.h @@ -12,6 +12,8 @@ #ifndef hifi_AvatarConstants_h #define hifi_AvatarConstants_h +#include "GLMHelpers.h" + // 50th Percentile Man const float DEFAULT_AVATAR_HEIGHT = 1.755f; // meters const float DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD = 0.11f; // meters @@ -52,5 +54,10 @@ const float DEFAULT_AVATAR_JUMP_HEIGHT = (DEFAULT_AVATAR_JUMP_SPEED * DEFAULT_AV const float DEFAULT_AVATAR_FALL_HEIGHT = 20.0f; // meters const float DEFAULT_AVATAR_MIN_HOVER_HEIGHT = 2.5f; // meters +static const float MAX_AVATAR_SCALE = 1000.0f; +static const float MIN_AVATAR_SCALE = 0.005f; + +static const float MAX_AVATAR_HEIGHT = 1000.0f * DEFAULT_AVATAR_HEIGHT; // meters +static const float MIN_AVATAR_HEIGHT = 0.005f * DEFAULT_AVATAR_HEIGHT; // meters #endif // hifi_AvatarConstants_h diff --git a/libraries/shared/src/PrioritySortUtil.h b/libraries/shared/src/PrioritySortUtil.h index 1d11a04265..dc6a877bb9 100644 --- a/libraries/shared/src/PrioritySortUtil.h +++ b/libraries/shared/src/PrioritySortUtil.h @@ -12,6 +12,9 @@ #define hifi_PrioritySortUtil_h #include +#include + +#include "NumericalConstants.h" #include "ViewFrustum.h" /* PrioritySortUtil is a helper for sorting 3D things relative to a ViewFrustum. To use: @@ -32,11 +35,10 @@ (2) Make a PrioritySortUtil::PriorityQueue and add them to the queue: - PrioritySortUtil::Prioritizer prioritizer(viewFrustum); + PrioritySortUtil::PriorityQueue sortedThings(viewFrustum); std::priority_queue< PrioritySortUtil::Sortable > sortedThings; for (thing in things) { - float priority = prioritizer.computePriority(PrioritySortUtil::PrioritizableThing(thing)); - sortedThings.push(PrioritySortUtil::Sortable entry(thing, priority)); + sortedThings.push(SortableWrapper(thing)); } (3) Loop over your priority queue and do timeboxed work: @@ -65,6 +67,7 @@ namespace PrioritySortUtil { virtual uint64_t getTimestamp() const = 0; void setPriority(float priority) { _priority = priority; } + float getPriority() const { return _priority; } bool operator<(const Sortable& other) const { return _priority < other._priority; } private: float _priority { 0.0f }; @@ -109,11 +112,19 @@ namespace PrioritySortUtil { glm::vec3 position = thing.getPosition(); glm::vec3 offset = position - _view.getPosition(); float distance = glm::length(offset) + 0.001f; // add 1mm to avoid divide by zero - float radius = thing.getRadius(); + const float MIN_RADIUS = 0.1f; // WORKAROUND for zero size objects (we still want them to sort by distance) + float radius = glm::min(thing.getRadius(), MIN_RADIUS); + float cosineAngle = (glm::dot(offset, _view.getDirection()) / distance); + float age = (float)(usecTimestampNow() - thing.getTimestamp()); - float priority = _angularWeight * (radius / distance) - + _centerWeight * (glm::dot(offset, _view.getDirection()) / distance) - + _ageWeight * (float)(usecTimestampNow() - thing.getTimestamp()); + // we modulatate "age" drift rate by the cosineAngle term to make periphrial objects sort forward + // at a reduced rate but we don't want the "age" term to go zero or negative so we clamp it + const float MIN_COSINE_ANGLE_FACTOR = 0.1f; + float cosineAngleFactor = glm::max(cosineAngle, MIN_COSINE_ANGLE_FACTOR); + + float priority = _angularWeight * glm::max(radius, MIN_RADIUS) / distance + + _centerWeight * cosineAngle + + _ageWeight * cosineAngleFactor * age; // decrement priority of things outside keyhole if (distance - radius > _view.getCenterRadius()) { diff --git a/scripts/system/directory.js b/scripts/system/directory.js index 8b9ec17f05..f84429ab95 100644 --- a/scripts/system/directory.js +++ b/scripts/system/directory.js @@ -63,7 +63,7 @@ var toolBar = (function() { y: -TOOLBAR_MARGIN_Y - toolHeight }); browseDirectoryButton = toolBar.addTool({ - imageURL: toolIconUrl + "directory-01.svg", + imageURL: toolIconUrl + "directory.svg", subImage: { x: 0, y: Tool.IMAGE_WIDTH, diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index 6aacaa5333..736d42d593 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -1415,6 +1415,9 @@ input#reset-to-natural-dimensions { } /* ----- Order of Menu items for Primitive ----- */ +/* Entity Menu classes are specified by selected entity + within entityProperties.js +*/ #properties-list.ShapeMenu #general, #properties-list.BoxMenu #general, #properties-list.SphereMenu #general { @@ -1469,6 +1472,34 @@ input#reset-to-natural-dimensions { display: none; } +/* ----- ParticleEffectMenu ----- */ +#properties-list.ParticleEffectMenu #general { + order: 1; +} +#properties-list.ParticleEffectMenu #collision-info { + order: 2; +} +#properties-list.ParticleEffectMenu #physical { + order: 3; +} +#properties-list.ParticleEffectMenu #spatial { + order: 4; +} +#properties-list.ParticleEffectMenu #behavior { + order: 5; +} + +/* items to hide */ +#properties-list.ParticleEffectMenu #base-color-section, +#properties-list.ParticleEffectMenu #hyperlink, +#properties-list.ParticleEffectMenu #light, +#properties-list.ParticleEffectMenu #model, +#properties-list.ParticleEffectMenu #shape-list, +#properties-list.ParticleEffectMenu #text, +#properties-list.ParticleEffectMenu #web, +#properties-list.ParticleEffectMenu #zone { + display: none; +} /* ----- Order of Menu items for Light ----- */ #properties-list.LightMenu #general { @@ -1500,8 +1531,8 @@ input#reset-to-natural-dimensions { display: none; } /* items to hide */ -#properties-list.LightMenu .shape-group.shape-section.property.dropdown, -#properties-list.LightMenu color-section.color-control1 { +#properties-list.LightMenu #shape-list, +#properties-list.LightMenu #base-color-section { display: none } @@ -1536,8 +1567,8 @@ input#reset-to-natural-dimensions { display: none; } /* items to hide */ -#properties-list.ModelMenu .shape-group.shape-section.property.dropdown, -#properties-list.ModelMenu .color-section.color-control1 { +#properties-list.ModelMenu #shape-list, +#properties-list.ModelMenu #base-color-section { display: none } @@ -1572,8 +1603,8 @@ input#reset-to-natural-dimensions { display: none; } /* items to hide */ -#properties-list.ZoneMenu .shape-group.shape-section.property.dropdown, -#properties-list.ZoneMenu .color-section.color-control1 { +#properties-list.ZoneMenu #shape-list, +#properties-list.ZoneMenu #base-color-section { display: none } @@ -1608,8 +1639,8 @@ input#reset-to-natural-dimensions { display: none; } /* items to hide */ -#properties-list.WebMenu .shape-group.shape-section.property.dropdown, -#properties-list.WebMenu .color-section.color-control1 { +#properties-list.WebMenu #shape-list, +#properties-list.WebMenu #base-color-section { display: none; } @@ -1645,8 +1676,8 @@ input#reset-to-natural-dimensions { display: none; } /* items to hide */ -#properties-list.TextMenu .shape-group.shape-section.property.dropdown, -#properties-list.TextMenu .color-section.color-control1 { +#properties-list.TextMenu #shape-list, +#properties-list.TextMenu #base-color-section { display: none } diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index 8b2a088d83..2ccad2c169 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -44,7 +44,7 @@
-
-
+ +
+ CollisionM
@@ -216,17 +223,6 @@
-
-
-
- -
-
-
-
-
-
-
diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index f54394a353..0ab9f7a9cb 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -6,6 +6,9 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +/* global alert, augmentSpinButtons, clearTimeout, console, document, Element, EventBridge, + HifiEntityUI, JSONEditor, openEventBridge, setUpKeyboardControl, setTimeout, window, _ $ */ + var PI = 3.14159265358979; var DEGREES_TO_RADIANS = PI / 180.0; var RADIANS_TO_DEGREES = 180.0 / PI; @@ -22,33 +25,33 @@ var ICON_FOR_TYPE = { PolyVox: "", Multiple: "", PolyLine: "" -} +}; var EDITOR_TIMEOUT_DURATION = 1500; -const KEY_P = 80; //Key code for letter p used for Parenting hotkey. +var KEY_P = 80; // Key code for letter p used for Parenting hotkey. var colorPickers = []; var lastEntityID = null; -debugPrint = function(message) { +function debugPrint(message) { EventBridge.emitWebEvent( JSON.stringify({ type: "print", message: message }) ); -}; +} function enableChildren(el, selector) { - els = el.querySelectorAll(selector); - for (var i = 0; i < els.length; i++) { - els[i].removeAttribute('disabled'); + var elSelectors = el.querySelectorAll(selector); + for (var selectorIndex = 0; selectorIndex < elSelectors.length; ++selectorIndex) { + elSelectors[selectorIndex].removeAttribute('disabled'); } } function disableChildren(el, selector) { - els = el.querySelectorAll(selector); - for (var i = 0; i < els.length; i++) { - els[i].setAttribute('disabled', 'disabled'); + var elSelectors = el.querySelectorAll(selector); + for (var selectorIndex = 0; selectorIndex < elSelectors.length; ++selectorIndex) { + elSelectors[selectorIndex].setAttribute('disabled', 'disabled'); } } @@ -103,16 +106,6 @@ function createEmitCheckedPropertyUpdateFunction(propertyName) { }; } -function createEmitCheckedToStringPropertyUpdateFunction(checkboxElement, name, propertyName) { - var newString = ""; - if (checkboxElement.checked) { - newString += name + ""; - } else { - - } - -} - function createEmitGroupCheckedPropertyUpdateFunction(group, propertyName) { return function() { var properties = {}; @@ -123,7 +116,7 @@ function createEmitGroupCheckedPropertyUpdateFunction(group, propertyName) { } function createEmitNumberPropertyUpdateFunction(propertyName, decimals) { - decimals = decimals == undefined ? 4 : decimals; + decimals = ((decimals === undefined) ? 4 : decimals); return function() { var value = parseFloat(this.value).toFixed(decimals); updateProperty(propertyName, value); @@ -146,7 +139,9 @@ function createEmitTextPropertyUpdateFunction(propertyName) { }; } -function createZoneComponentModeChangedFunction(zoneComponent, zoneComponentModeInherit, zoneComponentModeDisabled, zoneComponentModeEnabled) { +function createZoneComponentModeChangedFunction(zoneComponent, zoneComponentModeInherit, + zoneComponentModeDisabled, zoneComponentModeEnabled) { + return function() { var zoneComponentMode; @@ -159,7 +154,7 @@ function createZoneComponentModeChangedFunction(zoneComponent, zoneComponentMode } updateProperty(zoneComponent, zoneComponentMode); - } + }; } function createEmitGroupTextPropertyUpdateFunction(group, propertyName) { @@ -177,11 +172,11 @@ function createEmitVec3PropertyUpdateFunction(property, elX, elY, elZ) { properties[property] = { x: elX.value, y: elY.value, - z: elZ.value, + z: elZ.value }; updateProperties(properties); - } -}; + }; +} function createEmitGroupVec3PropertyUpdateFunction(group, property, elX, elY, elZ) { return function() { @@ -190,11 +185,11 @@ function createEmitGroupVec3PropertyUpdateFunction(group, property, elX, elY, el properties[group][property] = { x: elX.value, y: elY.value, - z: elZ ? elZ.value : 0, + z: elZ ? elZ.value : 0 }; updateProperties(properties); - } -}; + }; +} function createEmitVec3PropertyUpdateFunctionWithMultiplier(property, elX, elY, elZ, multiplier) { return function() { @@ -202,17 +197,17 @@ function createEmitVec3PropertyUpdateFunctionWithMultiplier(property, elX, elY, properties[property] = { x: elX.value * multiplier, y: elY.value * multiplier, - z: elZ.value * multiplier, + z: elZ.value * multiplier }; updateProperties(properties); - } -}; + }; +} function createEmitColorPropertyUpdateFunction(property, elRed, elGreen, elBlue) { return function() { emitColorPropertyUpdate(property, elRed.value, elGreen.value, elBlue.value); - } -}; + }; +} function emitColorPropertyUpdate(property, red, green, blue, group) { var properties = {}; @@ -221,17 +216,17 @@ function emitColorPropertyUpdate(property, red, green, blue, group) { properties[group][property] = { red: red, green: green, - blue: blue, + blue: blue }; } else { properties[property] = { red: red, green: green, - blue: blue, + blue: blue }; } updateProperties(properties); -}; +} function createEmitGroupColorPropertyUpdateFunction(group, property, elRed, elGreen, elBlue) { @@ -241,11 +236,11 @@ function createEmitGroupColorPropertyUpdateFunction(group, property, elRed, elGr properties[group][property] = { red: elRed.value, green: elGreen.value, - blue: elBlue.value, + blue: elBlue.value }; updateProperties(properties); - } -}; + }; +} function updateCheckedSubProperty(propertyName, propertyValue, subPropertyElement, subPropertyString) { if (subPropertyElement.checked) { @@ -264,12 +259,12 @@ function setUserDataFromEditor(noUpdate) { try { json = editor.get(); } catch (e) { - alert('Invalid JSON code - look for red X in your code ', +e) + alert('Invalid JSON code - look for red X in your code ', +e); } if (json === null) { return; } else { - var text = editor.getText() + var text = editor.getText(); if (noUpdate === true) { EventBridge.emitWebEvent( JSON.stringify({ @@ -277,7 +272,7 @@ function setUserDataFromEditor(noUpdate) { type: "saveUserData", properties: { userData: text - }, + } }) ); return; @@ -292,22 +287,24 @@ function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults) { var parsedData = {}; try { if ($('#userdata-editor').css('height') !== "0px") { - //if there is an expanded, we want to use its json. + // if there is an expanded, we want to use its json. parsedData = getEditorJSON(); } else { parsedData = JSON.parse(userDataElement.value); } - } catch (e) {} + } catch (e) { + // TODO: Should an alert go here? + } if (!(groupName in parsedData)) { - parsedData[groupName] = {} + parsedData[groupName] = {}; } 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].type === "checkbox") { if (updateKeyPair[key].checked !== defaults[key]) { parsedData[groupName][key] = updateKeyPair[key].checked; } @@ -322,16 +319,16 @@ function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults) { } } }); - if (Object.keys(parsedData[groupName]).length == 0) { + if (Object.keys(parsedData[groupName]).length === 0) { delete parsedData[groupName]; } if (Object.keys(parsedData).length > 0) { - properties['userData'] = JSON.stringify(parsedData); + properties.userData = JSON.stringify(parsedData); } else { - properties['userData'] = ''; + properties.userData = ''; } - userDataElement.value = properties['userData']; + userDataElement.value = properties.userData; updateProperties(properties); } @@ -340,13 +337,12 @@ function userDataChanger(groupName, keyName, values, userDataElement, defaultVal val[keyName] = values; def[keyName] = defaultValue; multiDataUpdater(groupName, val, userDataElement, def); -}; +} function setTextareaScrolling(element) { var isScrolling = element.scrollHeight > element.offsetHeight; element.setAttribute("scrolling", isScrolling ? "true" : "false"); -}; - +} var editor = null; @@ -364,7 +360,7 @@ function createJSONEditor() { $('.jsoneditor-poweredBy').remove(); }, onError: function(e) { - alert('JSON editor:' + e) + alert('JSON editor:' + e); }, onChange: function() { var currentJSONString = editor.getText(); @@ -372,22 +368,22 @@ function createJSONEditor() { if (currentJSONString === '{"":""}') { return; } - $('#userdata-save').attr('disabled', false) + $('#userdata-save').attr('disabled', false); } }; editor = new JSONEditor(container, options); -}; +} function hideNewJSONEditorButton() { $('#userdata-new-editor').hide(); -}; +} function hideClearUserDataButton() { $('#userdata-clear').hide(); -}; +} function showSaveUserDataButton() { $('#userdata-save').show(); @@ -401,65 +397,65 @@ function hideSaveUserDataButton() { function showNewJSONEditorButton() { $('#userdata-new-editor').show(); -}; +} function showClearUserDataButton() { $('#userdata-clear').show(); -}; +} function showUserDataTextArea() { $('#property-user-data').show(); -}; +} function hideUserDataTextArea() { $('#property-user-data').hide(); -}; +} function showStaticUserData() { if (editor !== null) { $('#static-userdata').show(); - $('#static-userdata').css('height', $('#userdata-editor').height()) + $('#static-userdata').css('height', $('#userdata-editor').height()); $('#static-userdata').text(editor.getText()); } -}; +} function removeStaticUserData() { $('#static-userdata').hide(); -}; +} function setEditorJSON(json) { - editor.set(json) + editor.set(json); if (editor.hasOwnProperty('expandAll')) { editor.expandAll(); } -}; +} function getEditorJSON() { return editor.get(); -}; +} function deleteJSONEditor() { if (editor !== null) { editor.destroy(); editor = null; } -}; +} var savedJSONTimer = null; function saveJSONUserData(noUpdate) { setUserDataFromEditor(noUpdate); $('#userdata-saved').show(); - $('#userdata-save').attr('disabled', true) + $('#userdata-save').attr('disabled', true); if (savedJSONTimer !== null) { clearTimeout(savedJSONTimer); } savedJSONTimer = setTimeout(function() { $('#userdata-saved').hide(); - }, 1500) + }, EDITOR_TIMEOUT_DURATION); } function bindAllNonJSONEditorElements() { @@ -468,6 +464,8 @@ function bindAllNonJSONEditorElements() { for (i = 0; i < inputs.length; i++) { var input = inputs[i]; var field = $(input); + // TODO FIXME: (JSHint) Functions declared within loops referencing + // an outer scoped variable may lead to confusing semantics. field.on('focus', function(e) { if (e.target.id === "userdata-new-editor" || e.target.id === "userdata-clear") { return; @@ -477,7 +475,7 @@ function bindAllNonJSONEditorElements() { } } - }) + }); } } @@ -492,18 +490,17 @@ function unbindAllInputs() { } function clearSelection() { - if(document.selection && document.selection.empty) { - document.selection.empty(); - } else if(window.getSelection) { - var sel = window.getSelection(); - sel.removeAllRanges(); - } + if (document.selection && document.selection.empty) { + document.selection.empty(); + } else if (window.getSelection) { + var sel = window.getSelection(); + sel.removeAllRanges(); + } } function loaded() { openEventBridge(function() { - var allSections = []; var elPropertiesList = document.getElementById("properties-list"); var elID = document.getElementById("property-id"); var elType = document.getElementById("property-type"); @@ -589,21 +586,14 @@ function loaded() { var elUserData = document.getElementById("property-user-data"); var elClearUserData = document.getElementById("userdata-clear"); var elSaveUserData = document.getElementById("userdata-save"); - var elJSONEditor = document.getElementById("userdata-editor"); var elNewJSONEditor = document.getElementById('userdata-new-editor'); - var elColorSections = document.querySelectorAll(".color-section"); - var elColorControl1 = document.getElementById("property-color-control1"); - var elColorControl2 = document.getElementById("property-color-control2"); + var elColorControlVariant2 = document.getElementById("property-color-control2"); var elColorRed = document.getElementById("property-color-red"); var elColorGreen = document.getElementById("property-color-green"); var elColorBlue = document.getElementById("property-color-blue"); - var elShapeSections = document.querySelectorAll(".shape-section"); - allSections.push(elShapeSections); var elShape = document.getElementById("property-shape"); - var elLightSections = document.querySelectorAll(".light-section"); - allSections.push(elLightSections); var elLightSpotLight = document.getElementById("property-light-spot-light"); var elLightColor = document.getElementById("property-light-color"); var elLightColorRed = document.getElementById("property-light-color-red"); @@ -615,8 +605,6 @@ function loaded() { var elLightExponent = document.getElementById("property-light-exponent"); var elLightCutoff = document.getElementById("property-light-cutoff"); - var elModelSections = document.querySelectorAll(".model-section"); - allSections.push(elModelSections); var elModelURL = document.getElementById("property-model-url"); var elShapeType = document.getElementById("property-shape-type"); var elCompoundShapeURL = document.getElementById("property-compound-shape-url"); @@ -632,8 +620,6 @@ function loaded() { var elModelTextures = document.getElementById("property-model-textures"); var elModelOriginalTextures = document.getElementById("property-model-original-textures"); - var elWebSections = document.querySelectorAll(".web-section"); - allSections.push(elWebSections); var elWebSourceURL = document.getElementById("property-web-source-url"); var elWebDPI = document.getElementById("property-web-dpi"); @@ -641,11 +627,7 @@ function loaded() { var elHyperlinkHref = document.getElementById("property-hyperlink-href"); - var elHyperlinkSections = document.querySelectorAll(".hyperlink-section"); - - var elTextSections = document.querySelectorAll(".text-section"); - allSections.push(elTextSections); var elTextText = document.getElementById("property-text-text"); var elTextLineHeight = document.getElementById("property-text-line-height"); var elTextTextColor = document.getElementById("property-text-text-color"); @@ -653,13 +635,10 @@ function loaded() { var elTextTextColorRed = document.getElementById("property-text-text-color-red"); var elTextTextColorGreen = document.getElementById("property-text-text-color-green"); var elTextTextColorBlue = document.getElementById("property-text-text-color-blue"); - var elTextBackgroundColor = document.getElementById("property-text-background-color"); var elTextBackgroundColorRed = document.getElementById("property-text-background-color-red"); var elTextBackgroundColorGreen = document.getElementById("property-text-background-color-green"); var elTextBackgroundColorBlue = document.getElementById("property-text-background-color-blue"); - var elZoneSections = document.querySelectorAll(".zone-section"); - allSections.push(elZoneSections); var elZoneStageSunModelEnabled = document.getElementById("property-zone-stage-sun-model-enabled"); var elZoneKeyLightColor = document.getElementById("property-zone-key-light-color"); @@ -670,7 +649,6 @@ function loaded() { var elZoneKeyLightAmbientIntensity = document.getElementById("property-zone-key-ambient-intensity"); var elZoneKeyLightDirectionX = document.getElementById("property-zone-key-light-direction-x"); var elZoneKeyLightDirectionY = document.getElementById("property-zone-key-light-direction-y"); - var elZoneKeyLightDirectionZ = document.getElementById("property-zone-key-light-direction-z"); var elZoneKeyLightAmbientURL = document.getElementById("property-zone-key-ambient-url"); var elZoneHazeModeInherit = document.getElementById("property-zone-haze-mode-inherit"); @@ -694,11 +672,7 @@ function loaded() { var elZoneHazeCeiling = document.getElementById("property-zone-haze-ceiling"); var elZoneHazeBackgroundBlend = document.getElementById("property-zone-haze-background-blend"); - - var elZoneHazeAttenuateKeyLight = document.getElementById("property-zone-haze-attenuate-keylight"); - var elZoneHazeKeyLightRange = document.getElementById("property-zone-haze-keylight-range"); - var elZoneHazeKeyLightAltitude = document.getElementById("property-zone-haze-keylight-altitude"); - + var elZoneStageLatitude = document.getElementById("property-zone-stage-latitude"); var elZoneStageLongitude = document.getElementById("property-zone-stage-longitude"); var elZoneStageAltitude = document.getElementById("property-zone-stage-altitude"); @@ -718,8 +692,6 @@ function loaded() { var elZoneGhostingAllowed = document.getElementById("property-zone-ghosting-allowed"); var elZoneFilterURL = document.getElementById("property-zone-filter-url"); - var elPolyVoxSections = document.querySelectorAll(".poly-vox-section"); - allSections.push(elPolyVoxSections); var elVoxelVolumeSizeX = document.getElementById("property-voxel-volume-size-x"); var elVoxelVolumeSizeY = document.getElementById("property-voxel-volume-size-y"); var elVoxelVolumeSizeZ = document.getElementById("property-voxel-volume-size-z"); @@ -732,10 +704,10 @@ function loaded() { var properties; EventBridge.scriptEventReceived.connect(function(data) { data = JSON.parse(data); - if (data.type == "server_script_status") { + if (data.type === "server_script_status") { elServerScriptError.value = data.errorInfo; // If we just set elServerScriptError's diplay to block or none, we still end up with - //it's parent contributing 21px bottom padding even when elServerScriptError is display:none. + // it's parent contributing 21px bottom padding even when elServerScriptError is display:none. // So set it's parent to block or none elServerScriptError.parentElement.style.display = data.errorInfo ? "block" : "none"; if (data.statusRetrieved === false) { @@ -744,18 +716,18 @@ function loaded() { var ENTITY_SCRIPT_STATUS = { pending: "Pending", loading: "Loading", - error_loading_script: "Error loading script", - error_running_script: "Error running script", + error_loading_script: "Error loading script", // eslint-disable-line camelcase + error_running_script: "Error running script", // eslint-disable-line camelcase running: "Running", - unloaded: "Unloaded", + unloaded: "Unloaded" }; elServerScriptStatus.innerText = ENTITY_SCRIPT_STATUS[data.status] || data.status; } else { elServerScriptStatus.innerText = "Not running"; } - } else if (data.type == "update") { + } else if (data.type === "update") { - if (!data.selections || data.selections.length == 0) { + if (!data.selections || data.selections.length === 0) { if (editor !== null && lastEntityID !== null) { saveJSONUserData(true); deleteJSONEditor(); @@ -775,20 +747,19 @@ function loaded() { for (var i = 0; i < selections.length; i++) { ids.push(selections[i].id); - var type = selections[i].properties.type; - if (types[type] === undefined) { - types[type] = 0; + var currentSelectedType = selections[i].properties.type; + if (types[currentSelectedType] === undefined) { + types[currentSelectedType] = 0; numTypes += 1; } - types[type]++; + types[currentSelectedType]++; } - var type; + var type = "Multiple"; if (numTypes === 1) { type = selections[0].properties.type; - } else { - type = "Multiple"; } + elType.innerHTML = type + " (" + data.selections.length + ")"; elTypeIcon.innerHTML = ICON_FOR_TYPE[type]; elTypeIcon.style.display = "inline-block"; @@ -804,12 +775,14 @@ 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; + // Create class name for css ruleset filtering elPropertiesList.className = properties.type + 'Menu'; + elType.innerHTML = properties.type; elTypeIcon.innerHTML = ICON_FOR_TYPE[properties.type]; elTypeIcon.style.display = "inline-block"; @@ -883,13 +856,13 @@ function loaded() { elCloneableLifetime.value = 300; var grabbablesSet = false; - var parsedUserData = {} + var parsedUserData = {}; try { parsedUserData = JSON.parse(properties.userData); if ("grabbableKey" in parsedUserData) { grabbablesSet = true; - var grabbableData = parsedUserData["grabbableKey"]; + var grabbableData = parsedUserData.grabbableKey; if ("grabbable" in grabbableData) { elGrabbable.checked = grabbableData.grabbable; } else { @@ -907,27 +880,28 @@ function loaded() { } if ("cloneable" in grabbableData) { elCloneable.checked = grabbableData.cloneable; - elCloneableGroup.style.display = elCloneable.checked ? "block": "none"; + elCloneableGroup.style.display = elCloneable.checked ? "block" : "none"; elCloneableDynamic.checked = grabbableData.cloneDynamic ? grabbableData.cloneDynamic : properties.dynamic; if (elCloneable.checked) { - if ("cloneLifetime" in grabbableData) { - elCloneableLifetime.value = - grabbableData.cloneLifetime ? grabbableData.cloneLifetime : 300; - } - if ("cloneLimit" in grabbableData) { - elCloneableLimit.value = grabbableData.cloneLimit ? grabbableData.cloneLimit : 0; - } - if ("cloneAvatarEntity" in grabbableData) { - elCloneableAvatarEntity.checked = - grabbableData.cloneAvatarEntity ? grabbableData.cloneAvatarEntity : false; - } + if ("cloneLifetime" in grabbableData) { + elCloneableLifetime.value = + grabbableData.cloneLifetime ? grabbableData.cloneLifetime : 300; + } + if ("cloneLimit" in grabbableData) { + elCloneableLimit.value = grabbableData.cloneLimit ? grabbableData.cloneLimit : 0; + } + if ("cloneAvatarEntity" in grabbableData) { + elCloneableAvatarEntity.checked = + grabbableData.cloneAvatarEntity ? grabbableData.cloneAvatarEntity : false; + } } } else { elCloneable.checked = false; } } } catch (e) { + // TODO: What should go here? } if (!grabbablesSet) { elGrabbable.checked = true; @@ -946,7 +920,7 @@ function loaded() { try { json = JSON.parse(properties.userData); } catch (e) { - //normal text + // normal text deleteJSONEditor(); elUserData.value = properties.userData; showUserDataTextArea(); @@ -968,19 +942,21 @@ function loaded() { elDescription.value = properties.description; - if (properties.type == "Shape" || properties.type == "Box" || properties.type == "Sphere") { + if (properties.type === "Shape" || properties.type === "Box" || properties.type === "Sphere") { elShape.value = properties.shape; setDropdownText(elShape); } - if (properties.type == "Shape" || properties.type == "Box" || properties.type == "Sphere" || properties.type == "ParticleEffect") { + if (properties.type === "Shape" || properties.type === "Box" || + properties.type === "Sphere" || properties.type === "ParticleEffect") { elColorRed.value = properties.color.red; elColorGreen.value = properties.color.green; elColorBlue.value = properties.color.blue; - elColorControl1.style.backgroundColor = elColorControl2.style.backgroundColor = "rgb(" + properties.color.red + "," + properties.color.green + "," + properties.color.blue + ")"; + elColorControlVariant2.style.backgroundColor = "rgb(" + properties.color.red + "," + + properties.color.green + "," + properties.color.blue + ")"; } - if (properties.type == "Model") { + if (properties.type === "Model") { elModelURL.value = properties.modelURL; elShapeType.value = properties.shapeType; setDropdownText(elShapeType); @@ -998,24 +974,26 @@ function loaded() { setTextareaScrolling(elModelTextures); elModelOriginalTextures.value = properties.originalTextures; setTextareaScrolling(elModelOriginalTextures); - } else if (properties.type == "Web") { + } else if (properties.type === "Web") { elWebSourceURL.value = properties.sourceUrl; elWebDPI.value = properties.dpi; - } else if (properties.type == "Text") { + } else if (properties.type === "Text") { elTextText.value = properties.text; elTextLineHeight.value = properties.lineHeight.toFixed(4); elTextFaceCamera.checked = properties.faceCamera; - elTextTextColor.style.backgroundColor = "rgb(" + properties.textColor.red + "," + properties.textColor.green + "," + properties.textColor.blue + ")"; + elTextTextColor.style.backgroundColor = "rgb(" + properties.textColor.red + "," + + properties.textColor.green + "," + properties.textColor.blue + ")"; elTextTextColorRed.value = properties.textColor.red; elTextTextColorGreen.value = properties.textColor.green; elTextTextColorBlue.value = properties.textColor.blue; elTextBackgroundColorRed.value = properties.backgroundColor.red; elTextBackgroundColorGreen.value = properties.backgroundColor.green; elTextBackgroundColorBlue.value = properties.backgroundColor.blue; - } else if (properties.type == "Light") { + } else if (properties.type === "Light") { elLightSpotLight.checked = properties.isSpotlight; - elLightColor.style.backgroundColor = "rgb(" + properties.color.red + "," + properties.color.green + "," + properties.color.blue + ")"; + elLightColor.style.backgroundColor = "rgb(" + properties.color.red + "," + + properties.color.green + "," + properties.color.blue + ")"; elLightColorRed.value = properties.color.red; elLightColorGreen.value = properties.color.green; elLightColorBlue.value = properties.color.blue; @@ -1024,9 +1002,10 @@ function loaded() { elLightFalloffRadius.value = properties.falloffRadius.toFixed(1); elLightExponent.value = properties.exponent.toFixed(2); elLightCutoff.value = properties.cutoff.toFixed(2); - } else if (properties.type == "Zone") { + } else if (properties.type === "Zone") { elZoneStageSunModelEnabled.checked = properties.stage.sunModelEnabled; - elZoneKeyLightColor.style.backgroundColor = "rgb(" + properties.keyLight.color.red + "," + properties.keyLight.color.green + "," + properties.keyLight.color.blue + ")"; + elZoneKeyLightColor.style.backgroundColor = "rgb(" + properties.keyLight.color.red + "," + + properties.keyLight.color.green + "," + properties.keyLight.color.blue + ")"; elZoneKeyLightColorRed.value = properties.keyLight.color.red; elZoneKeyLightColorGreen.value = properties.keyLight.color.green; elZoneKeyLightColorBlue.value = properties.keyLight.color.blue; @@ -1036,9 +1015,9 @@ function loaded() { elZoneKeyLightDirectionY.value = properties.keyLight.direction.y.toFixed(2); elZoneKeyLightAmbientURL.value = properties.keyLight.ambientURL; - elZoneHazeModeInherit.checked = (properties.hazeMode == 'inherit'); - elZoneHazeModeDisabled.checked = (properties.hazeMode == 'disabled'); - elZoneHazeModeEnabled.checked = (properties.hazeMode == 'enabled'); + elZoneHazeModeInherit.checked = (properties.hazeMode === 'inherit'); + elZoneHazeModeDisabled.checked = (properties.hazeMode === 'disabled'); + elZoneHazeModeEnabled.checked = (properties.hazeMode === 'enabled'); elZoneHazeRange.value = properties.haze.hazeRange.toFixed(0); elZoneHazeColor.style.backgroundColor = "rgb(" + @@ -1069,10 +1048,6 @@ function loaded() { elZoneHazeBackgroundBlend.value = properties.haze.hazeBackgroundBlend.toFixed(2); -// elZoneHazeAttenuateKeyLight.checked = properties.haze.hazeAttenuateKeyLight; -// elZoneHazeKeyLightRange.value = properties.haze.hazeKeyLightRange.toFixed(0); -// elZoneHazeKeyLightAltitude.value = properties.haze.hazeKeyLightAltitude.toFixed(0); - elZoneStageLatitude.value = properties.stage.latitude.toFixed(2); elZoneStageLongitude.value = properties.stage.longitude.toFixed(2); elZoneStageAltitude.value = properties.stage.altitude.toFixed(2); @@ -1085,7 +1060,8 @@ function loaded() { elZoneBackgroundMode.value = properties.backgroundMode; setDropdownText(elZoneBackgroundMode); - elZoneSkyboxColor.style.backgroundColor = "rgb(" + properties.skybox.color.red + "," + properties.skybox.color.green + "," + properties.skybox.color.blue + ")"; + elZoneSkyboxColor.style.backgroundColor = "rgb(" + properties.skybox.color.red + "," + + properties.skybox.color.green + "," + properties.skybox.color.blue + ")"; elZoneSkyboxColorRed.value = properties.skybox.color.red; elZoneSkyboxColorGreen.value = properties.skybox.color.green; elZoneSkyboxColorBlue.value = properties.skybox.color.blue; @@ -1095,8 +1071,9 @@ function loaded() { elZoneGhostingAllowed.checked = properties.ghostingAllowed; elZoneFilterURL.value = properties.filterURL; - showElements(document.getElementsByClassName('skybox-section'), elZoneBackgroundMode.value == 'skybox'); - } else if (properties.type == "PolyVox") { + showElements(document.getElementsByClassName('skybox-section'), + elZoneBackgroundMode.value === 'skybox'); + } else if (properties.type === "PolyVox") { elVoxelVolumeSizeX.value = properties.voxelVolumeSize.x.toFixed(2); elVoxelVolumeSizeY.value = properties.voxelVolumeSize.y.toFixed(2); elVoxelVolumeSizeZ.value = properties.voxelVolumeSize.z.toFixed(2); @@ -1121,7 +1098,7 @@ function loaded() { activeElement.select(); } } - clearSelection(); + clearSelection(); } }); } @@ -1228,29 +1205,30 @@ function loaded() { var checked = event.target.checked; if (checked) { multiDataUpdater("grabbableKey", { - cloneLifetime: elCloneableLifetime, - cloneLimit: elCloneableLimit, - cloneDynamic: elCloneableDynamic, - cloneAvatarEntity: elCloneableAvatarEntity, - cloneable: event.target, - grabbable: null - }, elUserData, {}); + cloneLifetime: elCloneableLifetime, + cloneLimit: elCloneableLimit, + cloneDynamic: elCloneableDynamic, + cloneAvatarEntity: elCloneableAvatarEntity, + cloneable: event.target, + grabbable: null + }, elUserData, {}); elCloneableGroup.style.display = "block"; updateProperty('dynamic', false); } else { multiDataUpdater("grabbableKey", { - cloneLifetime: null, - cloneLimit: null, - cloneDynamic: null, - cloneAvatarEntity: null, - cloneable: false - }, elUserData, {}); + cloneLifetime: null, + cloneLimit: null, + cloneDynamic: null, + cloneAvatarEntity: 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); + userDataChanger("grabbableKey", + event.target.getAttribute("data-user-data-type"), parseInt(event.target.value), elUserData, false); }; elCloneableLifetime.addEventListener('change', numberListener); elCloneableLimit.addEventListener('change', numberListener); @@ -1279,7 +1257,7 @@ function loaded() { showUserDataTextArea(); showNewJSONEditorButton(); hideSaveUserDataButton(); - updateProperty('userData', elUserData.value) + updateProperty('userData', elUserData.value); }); elSaveUserData.addEventListener("click", function() { @@ -1303,24 +1281,6 @@ function loaded() { elColorRed.addEventListener('change', colorChangeFunction); elColorGreen.addEventListener('change', colorChangeFunction); elColorBlue.addEventListener('change', colorChangeFunction); - colorPickers.push($('#property-color-control1').colpick({ - colorScheme: 'dark', - layout: 'hex', - color: '000000', - onShow: function(colpick) { - $('#property-color-control1').attr('active', 'true'); - }, - onHide: function(colpick) { - $('#property-color-control1').attr('active', 'false'); - }, - onSubmit: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - $(el).colpickHide(); - emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b); - // Keep the companion control in sync - elColorControl2.style.backgroundColor = "rgb(" + rgb.r + "," + rgb.g + "," + rgb.b + ")"; - } - })); colorPickers.push($('#property-color-control2').colpick({ colorScheme: 'dark', layout: 'hex', @@ -1335,9 +1295,6 @@ function loaded() { $(el).css('background-color', '#' + hex); $(el).colpickHide(); emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b); - // Keep the companion control in sync - elColorControl1.style.backgroundColor = "rgb(" + rgb.r + "," + rgb.g + "," + rgb.b + ")"; - } })); @@ -1380,14 +1337,18 @@ function loaded() { elCompoundShapeURL.addEventListener('change', createEmitTextPropertyUpdateFunction('compoundShapeURL')); elModelAnimationURL.addEventListener('change', createEmitGroupTextPropertyUpdateFunction('animation', 'url')); - elModelAnimationPlaying.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('animation', 'running')); + elModelAnimationPlaying.addEventListener('change',createEmitGroupCheckedPropertyUpdateFunction('animation', 'running')); elModelAnimationFPS.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('animation', 'fps')); - elModelAnimationFrame.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('animation', 'currentFrame')); - elModelAnimationFirstFrame.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('animation', 'firstFrame')); - elModelAnimationLastFrame.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('animation', 'lastFrame')); + elModelAnimationFrame.addEventListener('change', + createEmitGroupNumberPropertyUpdateFunction('animation', 'currentFrame')); + elModelAnimationFirstFrame.addEventListener('change', + createEmitGroupNumberPropertyUpdateFunction('animation', 'firstFrame')); + elModelAnimationLastFrame.addEventListener('change', + createEmitGroupNumberPropertyUpdateFunction('animation', 'lastFrame')); elModelAnimationLoop.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('animation', 'loop')); elModelAnimationHold.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('animation', 'hold')); - elModelAnimationAllowTranslation.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('animation', 'allowTranslation')); + elModelAnimationAllowTranslation.addEventListener('change', + createEmitGroupCheckedPropertyUpdateFunction('animation', 'allowTranslation')); elModelTextures.addEventListener('change', createEmitTextPropertyUpdateFunction('textures')); @@ -1439,7 +1400,8 @@ function loaded() { } })); - elZoneStageSunModelEnabled.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('stage', 'sunModelEnabled')); + elZoneStageSunModelEnabled.addEventListener('change', + createEmitGroupCheckedPropertyUpdateFunction('stage', 'sunModelEnabled')); colorPickers.push($('#property-zone-key-light-color').colpick({ colorScheme: 'dark', layout: 'hex', @@ -1456,18 +1418,26 @@ function loaded() { emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b, 'keyLight'); } })); - var zoneKeyLightColorChangeFunction = createEmitGroupColorPropertyUpdateFunction('keyLight', 'color', elZoneKeyLightColorRed, elZoneKeyLightColorGreen, elZoneKeyLightColorBlue); + var zoneKeyLightColorChangeFunction = createEmitGroupColorPropertyUpdateFunction('keyLight', 'color', + elZoneKeyLightColorRed, elZoneKeyLightColorGreen, elZoneKeyLightColorBlue); elZoneKeyLightColorRed.addEventListener('change', zoneKeyLightColorChangeFunction); elZoneKeyLightColorGreen.addEventListener('change', zoneKeyLightColorChangeFunction); elZoneKeyLightColorBlue.addEventListener('change', zoneKeyLightColorChangeFunction); - elZoneKeyLightIntensity.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('keyLight', 'intensity')); - elZoneKeyLightAmbientIntensity.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('keyLight', 'ambientIntensity')); - elZoneKeyLightAmbientURL.addEventListener('change', createEmitGroupTextPropertyUpdateFunction('keyLight', 'ambientURL')); - var zoneKeyLightDirectionChangeFunction = createEmitGroupVec3PropertyUpdateFunction('keyLight', 'direction', elZoneKeyLightDirectionX, elZoneKeyLightDirectionY); + elZoneKeyLightIntensity.addEventListener('change', + createEmitGroupNumberPropertyUpdateFunction('keyLight', 'intensity')); + elZoneKeyLightAmbientIntensity.addEventListener('change', + createEmitGroupNumberPropertyUpdateFunction('keyLight', 'ambientIntensity')); + elZoneKeyLightAmbientURL.addEventListener('change', + createEmitGroupTextPropertyUpdateFunction('keyLight', 'ambientURL')); + var zoneKeyLightDirectionChangeFunction = + createEmitGroupVec3PropertyUpdateFunction('keyLight', 'direction', + elZoneKeyLightDirectionX, elZoneKeyLightDirectionY); elZoneKeyLightDirectionX.addEventListener('change', zoneKeyLightDirectionChangeFunction); elZoneKeyLightDirectionY.addEventListener('change', zoneKeyLightDirectionChangeFunction); - var hazeModeChanged = createZoneComponentModeChangedFunction('hazeMode', elZoneHazeModeInherit, elZoneHazeModeDisabled, elZoneHazeModeEnabled) + var hazeModeChanged = + createZoneComponentModeChangedFunction('hazeMode', elZoneHazeModeInherit, + elZoneHazeModeDisabled, elZoneHazeModeEnabled); elZoneHazeModeInherit.addEventListener('change', hazeModeChanged); elZoneHazeModeDisabled.addEventListener('change', hazeModeChanged); elZoneHazeModeEnabled.addEventListener('change', hazeModeChanged); @@ -1524,23 +1494,23 @@ function loaded() { elZoneHazeGlareColorGreen.addEventListener('change', zoneHazeGlareColorChangeFunction); elZoneHazeGlareColorBlue.addEventListener('change', zoneHazeGlareColorChangeFunction); - elZoneHazeEnableGlare.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('haze', 'hazeEnableGlare')); + elZoneHazeEnableGlare.addEventListener('change', + createEmitGroupCheckedPropertyUpdateFunction('haze', 'hazeEnableGlare')); elZonehazeGlareAngle.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('haze', 'hazeGlareAngle')); - elZoneHazeAltitudeEffect.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('haze', 'hazeAltitudeEffect')); + elZoneHazeAltitudeEffect.addEventListener('change', + createEmitGroupCheckedPropertyUpdateFunction('haze', 'hazeAltitudeEffect')); elZoneHazeCeiling.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('haze', 'hazeCeiling')); elZoneHazeBaseRef.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('haze', 'hazeBaseRef')); - elZoneHazeBackgroundBlend.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('haze', 'hazeBackgroundBlend')); - -// elZoneHazeAttenuateKeyLight.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('haze', 'hazeAttenuateKeyLight')); -// elZoneHazeKeyLightRange.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('haze', 'hazeKeyLightRange')); -// elZoneHazeKeyLightAltitude.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('haze', 'hazeKeyLightAltitude')); + elZoneHazeBackgroundBlend.addEventListener('change', + createEmitGroupNumberPropertyUpdateFunction('haze', 'hazeBackgroundBlend')); elZoneStageLatitude.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('stage', 'latitude')); elZoneStageLongitude.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('stage', 'longitude')); elZoneStageAltitude.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('stage', 'altitude')); - elZoneStageAutomaticHourDay.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('stage', 'automaticHourDay')); + elZoneStageAutomaticHourDay.addEventListener('change', + createEmitGroupCheckedPropertyUpdateFunction('stage', 'automaticHourDay')); elZoneStageDay.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('stage', 'day')); elZoneStageHour.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('stage', 'hour')); @@ -1587,26 +1557,26 @@ function loaded() { elMoveSelectionToGrid.addEventListener("click", function() { EventBridge.emitWebEvent(JSON.stringify({ type: "action", - action: "moveSelectionToGrid", + action: "moveSelectionToGrid" })); }); elMoveAllToGrid.addEventListener("click", function() { EventBridge.emitWebEvent(JSON.stringify({ type: "action", - action: "moveAllToGrid", + action: "moveAllToGrid" })); }); elResetToNaturalDimensions.addEventListener("click", function() { EventBridge.emitWebEvent(JSON.stringify({ type: "action", - action: "resetToNaturalDimensions", + action: "resetToNaturalDimensions" })); }); elRescaleDimensionsButton.addEventListener("click", function() { EventBridge.emitWebEvent(JSON.stringify({ type: "action", action: "rescaleDimensions", - percentage: parseFloat(elRescaleDimensionsPct.value), + percentage: parseFloat(elRescaleDimensionsPct.value) })); }); elReloadScriptsButton.addEventListener("click", function() { @@ -1625,20 +1595,20 @@ 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' })); - } - } + 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"); ev.initEvent("change", true, true); document.activeElement.dispatchEvent(ev); - } + }; // For input and textarea elements, select all of the text on focus // WebKit-based browsers, such as is used with QWebView, have a quirk @@ -1653,13 +1623,17 @@ function loaded() { for (var i = 0; i < els.length; i++) { var clicked = false; var originalText; + // TODO FIXME: (JSHint) Functions declared within loops referencing + // an outer scoped variable may lead to confusing semantics. els[i].onfocus = function(e) { originalText = this.value; this.select(); clicked = false; }; + // TODO FIXME: (JSHint) Functions declared within loops referencing + // an outer scoped variable may lead to confusing semantics. els[i].onmouseup = function(e) { - if (!clicked && originalText == this.value) { + if (!clicked && originalText === this.value) { e.preventDefault(); } clicked = true; @@ -1674,15 +1648,15 @@ function loaded() { var toggleCollapsedEvent = function(event) { var element = event.target.parentNode.parentNode; var isCollapsed = element.dataset.collapsed !== "true"; - element.dataset.collapsed = isCollapsed ? "true" : false + element.dataset.collapsed = isCollapsed ? "true" : false; element.setAttribute("collapsed", isCollapsed ? "true" : "false"); element.getElementsByTagName("span")[0].textContent = isCollapsed ? "L" : "M"; }; - for (var i = 0, length = elCollapsible.length; i < length; i++) { - var element = elCollapsible[i]; - element.addEventListener("click", toggleCollapsedEvent, true); - }; + for (var collapseIndex = 0, numCollapsibles = elCollapsible.length; collapseIndex < numCollapsibles; ++collapseIndex) { + var curCollapsibleElement = elCollapsible[collapseIndex]; + curCollapsibleElement.addEventListener("click", toggleCollapsedEvent, true); + } // Textarea scrollbars @@ -1690,17 +1664,17 @@ function loaded() { var textareaOnChangeEvent = function(event) { setTextareaScrolling(event.target); - } + }; - for (var i = 0, length = elTextareas.length; i < length; i++) { - var element = elTextareas[i]; - setTextareaScrolling(element); - element.addEventListener("input", textareaOnChangeEvent, false); - element.addEventListener("change", textareaOnChangeEvent, false); + for (var textAreaIndex = 0, numTextAreas = elTextareas.length; textAreaIndex < numTextAreas; ++textAreaIndex) { + var curTextAreaElement = elTextareas[textAreaIndex]; + setTextareaScrolling(curTextAreaElement); + curTextAreaElement.addEventListener("input", textareaOnChangeEvent, false); + curTextAreaElement.addEventListener("change", textareaOnChangeEvent, false); /* FIXME: Detect and update textarea scrolling attribute on resize. Unfortunately textarea doesn't have a resize event; mouseup is a partial stand-in but doesn't handle resizing if mouse moves outside textarea rectangle. */ - element.addEventListener("mouseup", textareaOnChangeEvent, false); - }; + curTextAreaElement.addEventListener("mouseup", textareaOnChangeEvent, false); + } // Dropdowns // For each dropdown the following replacement is created in place of the oriringal dropdown... @@ -1749,22 +1723,23 @@ function loaded() { } var elDropdowns = document.getElementsByTagName("select"); - for (var i = 0; i < elDropdowns.length; i++) { - var options = elDropdowns[i].getElementsByTagName("option"); + for (var dropDownIndex = 0; dropDownIndex < elDropdowns.length; ++dropDownIndex) { + var options = elDropdowns[dropDownIndex].getElementsByTagName("option"); var selectedOption = 0; - for (var j = 0; j < options.length; j++) { - if (options[j].getAttribute("selected") === "selected") { - selectedOption = j; + for (var optionIndex = 0; optionIndex < options.length; ++optionIndex) { + if (options[optionIndex].getAttribute("selected") === "selected") { + selectedOption = optionIndex; + // TODO: Shouldn't there be a break here? } } - var div = elDropdowns[i].parentNode; + var div = elDropdowns[dropDownIndex].parentNode; var dl = document.createElement("dl"); div.appendChild(dl); var dt = document.createElement("dt"); - dt.name = elDropdowns[i].name; - dt.id = elDropdowns[i].id; + dt.name = elDropdowns[dropDownIndex].name; + dt.id = elDropdowns[dropDownIndex].id; dt.addEventListener("click", toggleDropdown, true); dl.appendChild(dt); @@ -1773,9 +1748,9 @@ function loaded() { span.textContent = options[selectedOption].firstChild.textContent; dt.appendChild(span); - var span = document.createElement("span"); - span.textContent = "5"; // caratDn - dt.appendChild(span); + var spanCaratDown = document.createElement("span"); + spanCaratDown.textContent = "5"; // caratDn + dt.appendChild(spanCaratDown); var dd = document.createElement("dd"); dl.appendChild(dd); @@ -1783,10 +1758,10 @@ function loaded() { var ul = document.createElement("ul"); dd.appendChild(ul); - for (var j = 0; j < options.length; j++) { + for (var listOptionIndex = 0; listOptionIndex < options.length; ++listOptionIndex) { var li = document.createElement("li"); - li.setAttribute("value", options[j].value); - li.textContent = options[j].firstChild.textContent; + li.setAttribute("value", options[listOptionIndex].value); + li.textContent = options[listOptionIndex].firstChild.textContent; li.addEventListener("click", setDropdownValue); ul.appendChild(li); } diff --git a/scripts/system/libraries/WebTablet.js b/scripts/system/libraries/WebTablet.js index 57b17f3d72..4217ec503e 100644 --- a/scripts/system/libraries/WebTablet.js +++ b/scripts/system/libraries/WebTablet.js @@ -118,15 +118,16 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) { Overlays.deleteOverlay(this.webOverlayID); } - var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2) * (1 / sensorScaleFactor); - var WEB_ENTITY_Y_OFFSET = 0.004 * (1 / sensorScaleFactor); - + var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2.0) / sensorScaleFactor; + var WEB_ENTITY_Y_OFFSET = 0.004; + var screenWidth = 0.82 * tabletWidth; + var screenHeight = 0.81 * tabletHeight; this.webOverlayID = Overlays.addOverlay("web3d", { name: "WebTablet Web", url: url, localPosition: { x: 0, y: WEB_ENTITY_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET }, localRotation: Quat.angleAxis(180, Y_AXIS), - resolution: this.getTabletTextureResolution(), + dimensions: {x: screenWidth, y: screenHeight, z: 0.1}, dpi: tabletDpi, color: { red: 255, green: 255, blue: 255 }, alpha: 1.0, @@ -136,12 +137,15 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) { visible: visible }); - var HOME_BUTTON_Y_OFFSET = ((tabletHeight / 2) - (tabletHeight / 20)) * (1 / sensorScaleFactor); - this.homeButtonID = Overlays.addOverlay("sphere", { + var HOME_BUTTON_Y_OFFSET = ((tabletHeight / 2) - (tabletHeight / 20)) * (1 / sensorScaleFactor) - 0.003; + // FIXME: Circle3D overlays currently at the wrong dimensions, so we need to account for that here + var homeButtonDim = 4.0 * tabletScaleFactor / 3.0; + this.homeButtonID = Overlays.addOverlay("circle3d", { name: "homeButton", - localPosition: {x: -0.001, y: -HOME_BUTTON_Y_OFFSET, z: 0.0}, - localRotation: {x: 0, y: 1, z: 0, w: 0}, - dimensions: { x: 4 * tabletScaleFactor, y: 4 * tabletScaleFactor, z: 4 * tabletScaleFactor}, + localPosition: { x: 0.0, y: -HOME_BUTTON_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET }, + localRotation: { x: 0, y: 1, z: 0, w: 0}, + dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim }, + solid: true, alpha: 0.0, visible: visible, drawInFront: false, @@ -151,14 +155,14 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) { this.homeButtonHighlightID = Overlays.addOverlay("circle3d", { name: "homeButtonHighlight", - localPosition: { x: 0, y: -HOME_BUTTON_Y_OFFSET + 0.003, z: -0.0158 }, + localPosition: { x: 0, y: -HOME_BUTTON_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET }, localRotation: { x: 0, y: 1, z: 0, w: 0 }, - dimensions: { x: 4 * tabletScaleFactor, y: 4 * tabletScaleFactor, z: 4 * tabletScaleFactor }, + dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim }, + color: { red: 255, green: 255, blue: 255 }, solid: true, innerRadius: 0.9, ignoreIntersection: true, alpha: 1.0, - color: { red: 255, green: 255, blue: 255 }, visible: visible, drawInFront: false, parentID: this.tabletEntityID, @@ -265,11 +269,16 @@ WebTablet.prototype.setLandscape = function(newLandscapeValue) { this.landscape = newLandscapeValue; Overlays.editOverlay(this.tabletEntityID, - { rotation: this.landscape ? Quat.multiply(Camera.orientation, ROT_LANDSCAPE) : - Quat.multiply(Camera.orientation, ROT_Y_180) }); + { rotation: Quat.multiply(Camera.orientation, this.landscape ? ROT_LANDSCAPE : ROT_Y_180) }); + + var tabletWidth = getTabletWidthFromSettings() * MyAvatar.sensorToWorldScale; + var tabletScaleFactor = tabletWidth / TABLET_NATURAL_DIMENSIONS.x; + var tabletHeight = TABLET_NATURAL_DIMENSIONS.y * tabletScaleFactor; + var screenWidth = 0.82 * tabletWidth; + var screenHeight = 0.81 * tabletHeight; Overlays.editOverlay(this.webOverlayID, { - resolution: this.getTabletTextureResolution(), - rotation: Quat.multiply(Camera.orientation, ROT_LANDSCAPE_WINDOW) + rotation: Quat.multiply(Camera.orientation, ROT_LANDSCAPE_WINDOW), + dimensions: {x: this.landscape ? screenHeight : screenWidth, y: this.landscape ? screenWidth : screenHeight, z: 0.1} }); }; @@ -505,31 +514,17 @@ WebTablet.prototype.getPosition = function () { }; WebTablet.prototype.mousePressEvent = function (event) { - var pickRay = Camera.computePickRay(event.x, event.y); - var entityPickResults; - entityPickResults = Overlays.findRayIntersection(pickRay, true, [this.tabletEntityID]); - if (entityPickResults.intersects && (entityPickResults.entityID === this.tabletEntityID || - entityPickResults.overlayID === this.tabletEntityID)) { - var overlayPickResults = Overlays.findRayIntersection(pickRay, true, [this.webOverlayID, this.homeButtonID], []); - if (overlayPickResults.intersects && overlayPickResults.overlayID === this.homeButtonID) { - var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - var onHomeScreen = tablet.onHomeScreen(); - var isMessageOpen = tablet.isMessageDialogOpen(); - if (onHomeScreen) { - if (isMessageOpen === false) { - HMD.closeTablet(); - } - } else { - if (isMessageOpen === false) { - tablet.gotoHomeScreen(); - this.setHomeButtonTexture(); - } + if (!HMD.active) { + var pickRay = Camera.computePickRay(event.x, event.y); + var tabletBackPickResults = Overlays.findRayIntersection(pickRay, true, [this.tabletEntityID]); + if (tabletBackPickResults.intersects) { + var overlayPickResults = Overlays.findRayIntersection(pickRay, true, [this.webOverlayID, this.homeButtonID]); + if (!overlayPickResults.intersects) { + this.dragging = true; + var invCameraXform = new Xform(Camera.orientation, Camera.position).inv(); + this.initialLocalIntersectionPoint = invCameraXform.xformPoint(tabletBackPickResults.intersection); + this.initialLocalPosition = Overlays.getProperty(this.tabletEntityID, "localPosition"); } - } else if (!HMD.active && (!overlayPickResults.intersects || overlayPickResults.overlayID !== this.webOverlayID)) { - this.dragging = true; - var invCameraXform = new Xform(Camera.orientation, Camera.position).inv(); - this.initialLocalIntersectionPoint = invCameraXform.xformPoint(entityPickResults.intersection); - this.initialLocalPosition = Overlays.getProperty(this.tabletEntityID, "localPosition"); } } }; diff --git a/scripts/system/libraries/controllerDispatcherUtils.js b/scripts/system/libraries/controllerDispatcherUtils.js index fd0db91fec..e0971201e6 100644 --- a/scripts/system/libraries/controllerDispatcherUtils.js +++ b/scripts/system/libraries/controllerDispatcherUtils.js @@ -272,22 +272,8 @@ projectOntoEntityXYPlane = function (entityID, worldPos, props) { projectOntoOverlayXYPlane = function projectOntoOverlayXYPlane(overlayID, worldPos) { var position = Overlays.getProperty(overlayID, "position"); var rotation = Overlays.getProperty(overlayID, "rotation"); - var dimensions; - - var dpi = Overlays.getProperty(overlayID, "dpi"); - if (dpi) { - // Calculate physical dimensions for web3d overlay from resolution and dpi; "dimensions" property is used as a scale. - var resolution = Overlays.getProperty(overlayID, "resolution"); - resolution.z = 1; // Circumvent divide-by-zero. - var scale = Overlays.getProperty(overlayID, "dimensions"); - scale.z = 0.01; // overlay dimensions are 2D, not 3D. - dimensions = Vec3.multiplyVbyV(Vec3.multiply(resolution, INCHES_TO_METERS / dpi), scale); - } else { - dimensions = Overlays.getProperty(overlayID, "dimensions"); - if (dimensions.z) { - dimensions.z = 0.01; // overlay dimensions are 2D, not 3D. - } - } + var dimensions = Overlays.getProperty(overlayID, "dimensions"); + dimensions.z = 0.01; // we are projecting onto the XY plane of the overlay, so ignore the z dimension return projectOntoXYPlane(worldPos, position, rotation, dimensions, DEFAULT_REGISTRATION_POINT); }; diff --git a/scripts/system/libraries/touchEventUtils.js b/scripts/system/libraries/touchEventUtils.js index c9fd825a09..f0f7ec46fe 100644 --- a/scripts/system/libraries/touchEventUtils.js +++ b/scripts/system/libraries/touchEventUtils.js @@ -169,32 +169,12 @@ function calculateTouchTargetFromOverlay(touchTip, overlayID) { // calclulate normalized position var invRot = Quat.inverse(overlayRotation); var localPos = Vec3.multiplyQbyV(invRot, Vec3.subtract(position, overlayPosition)); - var dpi = Overlays.getProperty(overlayID, "dpi"); - var dimensions; - if (dpi) { - // Calculate physical dimensions for web3d overlay from resolution and dpi; "dimensions" property - // is used as a scale. - var resolution = Overlays.getProperty(overlayID, "resolution"); - if (resolution === undefined) { - return; - } - resolution.z = 1; // Circumvent divide-by-zero. - var scale = Overlays.getProperty(overlayID, "dimensions"); - if (scale === undefined) { - return; - } - scale.z = 0.01; // overlay dimensions are 2D, not 3D. - dimensions = Vec3.multiplyVbyV(Vec3.multiply(resolution, INCHES_TO_METERS / dpi), scale); - } else { - dimensions = Overlays.getProperty(overlayID, "dimensions"); - if (dimensions === undefined) { - return; - } - if (!dimensions.z) { - dimensions.z = 0.01; // sometimes overlay dimensions are 2D, not 3D. - } + var dimensions = Overlays.getProperty(overlayID, "dimensions"); + if (dimensions === undefined) { + return; } + dimensions.z = 0.01; // we are projecting onto the XY plane of the overlay, so ignore the z dimension var invDimensions = { x: 1 / dimensions.x, y: 1 / dimensions.y, z: 1 / dimensions.z }; var normalizedPosition = Vec3.sum(Vec3.multiplyVbyV(localPos, invDimensions), DEFAULT_REGISTRATION_POINT); diff --git a/scripts/system/libraries/utils.js b/scripts/system/libraries/utils.js index 9706073081..4a1fcdf301 100644 --- a/scripts/system/libraries/utils.js +++ b/scripts/system/libraries/utils.js @@ -185,7 +185,7 @@ logTrace = function(str) { // (the vector that would move the point outside the sphere) // otherwise returns false findSphereHit = function(point, sphereRadius) { - var EPSILON = 0.000001; //smallish positive number - used as margin of error for some computations + var EPSILON = 0.000001; //smallish positive number - used as margin of error for some computations var vectorLength = Vec3.length(point); if (vectorLength < EPSILON) { return true; @@ -400,25 +400,28 @@ resizeTablet = function (width, newParentJointIndex, sensorToWorldScaleOverride) }); // update webOverlay - var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2) * sensorScaleOffsetOverride; - var WEB_ENTITY_Y_OFFSET = 0.004 * sensorScaleOffsetOverride; + var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2.0) * sensorScaleOffsetOverride; + var WEB_ENTITY_Y_OFFSET = 0.004 * sensorScaleFactor * sensorScaleOffsetOverride; + var screenWidth = 0.82 * tabletWidth; + var screenHeight = 0.81 * tabletHeight; + var landscape = Tablet.getTablet("com.highfidelity.interface.tablet.system").landscape; Overlays.editOverlay(HMD.tabletScreenID, { localPosition: { x: 0, y: WEB_ENTITY_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET }, + dimensions: {x: landscape ? screenHeight : screenWidth, y: landscape ? screenWidth : screenHeight, z: 0.1}, dpi: tabletDpi }); // update homeButton - var HOME_BUTTON_Y_OFFSET = ((tabletHeight / 2) - (tabletHeight / 20)) * sensorScaleOffsetOverride; - var homeButtonDim = 4 * tabletScaleFactor; + var HOME_BUTTON_Y_OFFSET = ((tabletHeight / 2) - (tabletHeight / 20) - 0.003 * sensorScaleFactor) * sensorScaleOffsetOverride; + // FIXME: Circle3D overlays currently at the wrong dimensions, so we need to account for that here + var homeButtonDim = 4.0 * tabletScaleFactor / 3.0; Overlays.editOverlay(HMD.homeButtonID, { - localPosition: {x: -0.001, y: -HOME_BUTTON_Y_OFFSET, z: 0.0}, - dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim} + localPosition: { x: 0, y: -HOME_BUTTON_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET }, + dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim } }); - // Circle3D overlays render at 1.5x their proper dimensions - var highlightDim = homeButtonDim / 3.0; Overlays.editOverlay(HMD.homeButtonHighlightID, { - localPosition: { x: 0, y: -HOME_BUTTON_Y_OFFSET + 0.003, z: -0.0158 }, - dimensions: { x: highlightDim, y: highlightDim, z: highlightDim } + localPosition: { x: 0, y: -HOME_BUTTON_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET }, + dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim } }); }; diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 80ef79a543..f0f3416819 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -428,8 +428,10 @@ tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH); break; case 'checkout_itemLinkClicked': - case 'checkout_continueShopping': tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.itemId, MARKETPLACES_INJECT_SCRIPT_URL); + break; + case 'checkout_continueShopping': + tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); //tablet.popFromStack(); break; case 'purchases_itemInfoClicked': diff --git a/scripts/system/tablet-ui/tabletUI.js b/scripts/system/tablet-ui/tabletUI.js index 17821c737e..36a1cbcdd9 100644 --- a/scripts/system/tablet-ui/tabletUI.js +++ b/scripts/system/tablet-ui/tabletUI.js @@ -47,7 +47,7 @@ } return false; } - if (Overlays.getProperty(HMD.homeButtonID, "type") != "sphere" || + if (Overlays.getProperty(HMD.homeButtonID, "type") != "circle3d" || Overlays.getProperty(HMD.tabletScreenID, "type") != "web3d") { if (debugTablet) { print("TABLET is invalid due to other");