diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index f8077303d2..ffd7cc703b 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -94,7 +94,8 @@ AudioMixer::AudioMixer(ReceivedMessage& message) : packetReceiver.registerListener(PacketType::MuteEnvironment, this, "handleMuteEnvironmentPacket"); packetReceiver.registerListener(PacketType::NodeIgnoreRequest, this, "handleNodeIgnoreRequestPacket"); packetReceiver.registerListener(PacketType::KillAvatar, this, "handleKillAvatarPacket"); - + packetReceiver.registerListener(PacketType::NodeMuteRequest, this, "handleNodeMuteRequestPacket"); + connect(nodeList.data(), &NodeList::nodeKilled, this, &AudioMixer::handleNodeKilled); } @@ -385,8 +386,11 @@ bool AudioMixer::prepareMixForListeningNode(Node* node) { // loop through all other nodes that have sufficient audio to mix DependencyManager::get()->eachNode([&](const SharedNodePointer& otherNode){ - // make sure that we have audio data for this other node and that it isn't being ignored by our listening node - if (otherNode->getLinkedData() && !node->isIgnoringNodeWithID(otherNode->getUUID())) { + // make sure that we have audio data for this other node + // and that it isn't being ignored by our listening node + // and that it isn't ignoring our listening node + if (otherNode->getLinkedData() + && !node->isIgnoringNodeWithID(otherNode->getUUID()) && !otherNode->isIgnoringNodeWithID(node->getUUID())) { AudioMixerClientData* otherNodeClientData = (AudioMixerClientData*) otherNode->getLinkedData(); // enumerate the ARBs attached to the otherNode and add all that should be added to mix @@ -599,6 +603,23 @@ void AudioMixer::handleNodeKilled(SharedNodePointer killedNode) { }); } +void AudioMixer::handleNodeMuteRequestPacket(QSharedPointer packet, SharedNodePointer sendingNode) { + auto nodeList = DependencyManager::get(); + QUuid nodeUUID = QUuid::fromRfc4122(packet->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); + if (sendingNode->getCanKick()) { + auto node = nodeList->nodeWithUUID(nodeUUID); + if (node) { + // we need to set a flag so we send them the appropriate packet to mute them + AudioMixerClientData* nodeData = (AudioMixerClientData*)node->getLinkedData(); + nodeData->setShouldMuteClient(true); + } else { + qWarning() << "Node mute packet received for unknown node " << uuidStringWithoutCurlyBraces(nodeUUID); + } + } else { + qWarning() << "Node mute packet received from node that cannot mute, ignoring"; + } +} + void AudioMixer::handleKillAvatarPacket(QSharedPointer packet, SharedNodePointer sendingNode) { auto clientData = dynamic_cast(sendingNode->getLinkedData()); if (clientData) { @@ -814,9 +835,13 @@ void AudioMixer::broadcastMixes() { // if the stream should be muted, send mute packet if (nodeData->getAvatarAudioStream() - && shouldMute(nodeData->getAvatarAudioStream()->getQuietestFrameLoudness())) { + && (shouldMute(nodeData->getAvatarAudioStream()->getQuietestFrameLoudness()) + || nodeData->shouldMuteClient())) { auto mutePacket = NLPacket::create(PacketType::NoisyMute, 0); nodeList->sendPacket(std::move(mutePacket), *node); + + // probably now we just reset the flag, once should do it (?) + nodeData->setShouldMuteClient(false); } if (node->getType() == NodeType::Agent && node->getActiveSocket() diff --git a/assignment-client/src/audio/AudioMixer.h b/assignment-client/src/audio/AudioMixer.h index 3c68e4c6af..91eafadd9d 100644 --- a/assignment-client/src/audio/AudioMixer.h +++ b/assignment-client/src/audio/AudioMixer.h @@ -49,6 +49,7 @@ private slots: void handleNodeKilled(SharedNodePointer killedNode); void handleNodeIgnoreRequestPacket(QSharedPointer packet, SharedNodePointer sendingNode); void handleKillAvatarPacket(QSharedPointer packet, SharedNodePointer sendingNode); + void handleNodeMuteRequestPacket(QSharedPointer packet, SharedNodePointer sendingNode); void removeHRTFsForFinishedInjector(const QUuid& streamID); diff --git a/assignment-client/src/audio/AudioMixerClientData.h b/assignment-client/src/audio/AudioMixerClientData.h index 52c659c240..c74461a444 100644 --- a/assignment-client/src/audio/AudioMixerClientData.h +++ b/assignment-client/src/audio/AudioMixerClientData.h @@ -86,6 +86,9 @@ public: bool shouldFlushEncoder() { return _shouldFlushEncoder; } QString getCodecName() { return _selectedCodecName; } + + bool shouldMuteClient() { return _shouldMuteClient; } + void setShouldMuteClient(bool shouldMuteClient) { _shouldMuteClient = shouldMuteClient; } signals: void injectorStreamFinished(const QUuid& streamIdentifier); @@ -114,6 +117,8 @@ private: Decoder* _decoder{ nullptr }; // for mic stream bool _shouldFlushEncoder { false }; + + bool _shouldMuteClient { false }; }; #endif // hifi_AudioMixerClientData_h diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index d87a5f1cc9..2c9fadc7b1 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -230,9 +230,11 @@ void AvatarMixer::broadcastAvatarData() { [&](const SharedNodePointer& otherNode)->bool { // make sure we have data for this avatar, that it isn't the same node, // and isn't an avatar that the viewing node has ignored + // or that has ignored the viewing node if (!otherNode->getLinkedData() || otherNode->getUUID() == node->getUUID() - || node->isIgnoringNodeWithID(otherNode->getUUID())) { + || node->isIgnoringNodeWithID(otherNode->getUUID()) + || otherNode->isIgnoringNodeWithID(node->getUUID())) { return false; } else { return true; @@ -512,12 +514,19 @@ void AvatarMixer::domainSettingsRequestComplete() { auto nodeList = DependencyManager::get(); nodeList->addNodeTypeToInterestSet(NodeType::Agent); - nodeList->linkedDataCreateCallback = [] (Node* node) { - node->setLinkedData(std::unique_ptr { new AvatarMixerClientData }); - }; - // parse the settings to pull out the values we need parseDomainServerSettings(nodeList->getDomainHandler().getSettingsObject()); + + float domainMinimumScale = _domainMinimumScale; + float domainMaximumScale = _domainMaximumScale; + + nodeList->linkedDataCreateCallback = [domainMinimumScale, domainMaximumScale] (Node* node) { + auto clientData = std::unique_ptr { new AvatarMixerClientData }; + clientData->getAvatar().setDomainMinimumScale(domainMinimumScale); + clientData->getAvatar().setDomainMaximumScale(domainMaximumScale); + + node->setLinkedData(std::move(clientData)); + }; // start the broadcastThread _broadcastThread.start(); @@ -549,4 +558,22 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) { _maxKbpsPerNode = nodeBandwidthValue.toDouble(DEFAULT_NODE_SEND_BANDWIDTH) * KILO_PER_MEGA; qDebug() << "The maximum send bandwidth per node is" << _maxKbpsPerNode << "kbps."; + + 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 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); + + // make sure that the domain owner didn't flip min and max + if (_domainMinimumScale > _domainMaximumScale) { + std::swap(_domainMinimumScale, _domainMaximumScale); + } + + qDebug() << "This domain requires a minimum avatar scale of" << _domainMinimumScale + << "and a maximum avatar scale of" << _domainMaximumScale; } diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index 9286cd4691..6e1d722145 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -59,6 +59,9 @@ private: float _maxKbpsPerNode = 0.0f; + float _domainMinimumScale { MIN_AVATAR_SCALE }; + float _domainMaximumScale { MAX_AVATAR_SCALE }; + QTimer* _broadcastTimer = nullptr; }; diff --git a/assignment-client/src/avatars/ScriptableAvatar.h b/assignment-client/src/avatars/ScriptableAvatar.h index 56707de471..18d64f4ac5 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.h +++ b/assignment-client/src/avatars/ScriptableAvatar.h @@ -17,7 +17,7 @@ #include #include -class ScriptableAvatar : public AvatarData, public Dependency{ +class ScriptableAvatar : public AvatarData, public Dependency { Q_OBJECT public: @@ -39,4 +39,4 @@ private: std::shared_ptr _animSkeleton; }; -#endif // hifi_ScriptableAvatar_h \ No newline at end of file +#endif // hifi_ScriptableAvatar_h diff --git a/cmake/macros/MemoryDebugger.cmake b/cmake/macros/MemoryDebugger.cmake index 7808812493..6df41257f2 100644 --- a/cmake/macros/MemoryDebugger.cmake +++ b/cmake/macros/MemoryDebugger.cmake @@ -16,6 +16,7 @@ if (HIFI_MEMORY_DEBUGGING) if (UNIX) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -U_FORTIFY_SOURCE -fno-stack-protector -fno-omit-frame-pointer") SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libasan -static-libstdc++ -fsanitize=address") + SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libasan -static-libstdc++ -fsanitize=address") endif (UNIX) endif () endmacro(SETUP_MEMORY_DEBUGGER) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index e6663888b4..911732fcef 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -388,6 +388,23 @@ "default": "", "advanced": false }, + { + "name": "ac_subnet_whitelist", + "label": "Assignment Client IP address Whitelist", + "type": "table", + "can_add_new_rows": true, + "help": "The IP addresses or subnets of ACs that can connect to this server. You can specify an IP address or a subnet in CIDR notation ('A.B.C.D/E', Example: '10.0.0.0/24'). Local ACs (localhost) are always permitted and do not need to be added here.", + "numbered": false, + "advanced": true, + "columns": [ + { + "name": "ip", + "label": "IP Address", + "type": "ip", + "can_set": true + } + ] + }, { "name": "standard_permissions", "type": "table", @@ -866,6 +883,29 @@ } ] }, + { + "name": "avatars", + "label": "Avatars", + "assignment-types": [1, 2], + "settings": [ + { + "name": "min_avatar_scale", + "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 + }, + { + "name": "max_avatar_scale", + "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 + } + ] + }, { "name": "audio_env", "label": "Audio Environment", diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index d13f9b883f..5208cb2326 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -158,6 +158,42 @@ DomainServer::DomainServer(int argc, char* argv[]) : qDebug() << "domain-server is running"; + static const QString AC_SUBNET_WHITELIST_SETTING_PATH = "security.ac_subnet_whitelist"; + + static const Subnet LOCALHOST { QHostAddress("127.0.0.1"), 32 }; + _acSubnetWhitelist = { LOCALHOST }; + + auto whitelist = _settingsManager.valueOrDefaultValueForKeyPath(AC_SUBNET_WHITELIST_SETTING_PATH).toStringList(); + for (auto& subnet : whitelist) { + auto netmaskParts = subnet.trimmed().split("/"); + + if (netmaskParts.size() > 2) { + qDebug() << "Ignoring subnet in whitelist, malformed: " << subnet; + continue; + } + + // The default netmask is 32 if one has not been specified, which will + // match only the ip provided. + int netmask = 32; + + if (netmaskParts.size() == 2) { + bool ok; + netmask = netmaskParts[1].toInt(&ok); + if (!ok) { + qDebug() << "Ignoring subnet in whitelist, bad netmask: " << subnet; + continue; + } + } + + auto ip = QHostAddress(netmaskParts[0]); + + if (!ip.isNull()) { + qDebug() << "Adding AC whitelist subnet: " << subnet << " -> " << (ip.toString() + "/" + QString::number(netmask)); + _acSubnetWhitelist.push_back({ ip , netmask }); + } else { + qDebug() << "Ignoring subnet in whitelist, invalid ip portion: " << subnet; + } + } } void DomainServer::parseCommandLine() { @@ -1001,6 +1037,21 @@ void DomainServer::processRequestAssignmentPacket(QSharedPointergetSenderSockAddr().getAddress(); + + auto isHostAddressInSubnet = [&senderAddr](const Subnet& mask) -> bool { + return senderAddr.isInSubnet(mask); + }; + + auto it = find_if(_acSubnetWhitelist.begin(), _acSubnetWhitelist.end(), isHostAddressInSubnet); + if (it == _acSubnetWhitelist.end()) { + static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex( + "Received an assignment connect request from a disallowed ip address: [^ ]+"); + qDebug() << "Received an assignment connect request from a disallowed ip address:" + << senderAddr.toString(); + return; + } + // Suppress these for Assignment::AgentType to once per 5 seconds static QElapsedTimer noisyMessageTimer; static bool wasNoisyTimerStarted = false; diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index c14ec5eee0..73135695eb 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -36,6 +36,9 @@ typedef QSharedPointer SharedAssignmentPointer; typedef QMultiHash TransactionHash; +using Subnet = QPair; +using SubnetList = std::vector; + class DomainServer : public QCoreApplication, public HTTPSRequestHandler { Q_OBJECT public: @@ -156,6 +159,8 @@ private: void setupGroupCacheRefresh(); + SubnetList _acSubnetWhitelist; + DomainGatekeeper _gatekeeper; HTTPManager _httpManager; diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index c7944bbcad..fbc5fd4bd5 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -634,7 +634,6 @@ bool DomainServerSettingsManager::ensurePermissionsForGroupRanks() { return changed; } - void DomainServerSettingsManager::processNodeKickRequestPacket(QSharedPointer message, SharedNodePointer sendingNode) { // before we do any processing on this packet make sure it comes from a node that is allowed to kick if (sendingNode->getCanKick()) { @@ -1077,6 +1076,9 @@ QJsonObject DomainServerSettingsManager::settingDescriptionFromGroup(const QJson } bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject) { + static const QString SECURITY_ROOT_KEY = "security"; + static const QString AC_SUBNET_WHITELIST_KEY = "ac_subnet_whitelist"; + auto& settingsVariant = _configMap.getConfig(); bool needRestart = false; @@ -1127,7 +1129,7 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ if (!matchingDescriptionObject.isEmpty()) { updateSetting(rootKey, rootValue, *thisMap, matchingDescriptionObject); - if (rootKey != "security") { + if (rootKey != SECURITY_ROOT_KEY) { needRestart = true; } } else { @@ -1143,7 +1145,7 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ if (!matchingDescriptionObject.isEmpty()) { QJsonValue settingValue = rootValue.toObject()[settingKey]; updateSetting(settingKey, settingValue, *thisMap, matchingDescriptionObject); - if (rootKey != "security") { + if (rootKey != SECURITY_ROOT_KEY || settingKey == AC_SUBNET_WHITELIST_KEY) { needRestart = true; } } else { diff --git a/interface/resources/controllers/xbox.json b/interface/resources/controllers/xbox.json index b0e97b849f..36065b87a1 100644 --- a/interface/resources/controllers/xbox.json +++ b/interface/resources/controllers/xbox.json @@ -4,12 +4,12 @@ { "from": "GamePad.LY", "filters": { "type": "deadZone", "min": 0.05 }, "to": "Actions.TranslateZ" }, { "from": "GamePad.LX", "filters": { "type": "deadZone", "min": 0.05 }, "to": "Actions.TranslateX" }, - { "from": "GamePad.LT", "to": "Standard.LTClick", + { "from": "GamePad.LT", "to": "Standard.LTClick", "peek": true, "filters": [ { "type": "hysteresis", "min": 0.85, "max": 0.9 } ] }, - { "from": "GamePad.LT", "to": "Standard.LT" }, - { "from": "GamePad.LB", "to": "Standard.LB" }, + { "from": "GamePad.LT", "to": "Standard.LT" }, + { "from": "GamePad.LB", "to": "Standard.LB" }, { "from": "GamePad.LS", "to": "Standard.LS" }, @@ -27,34 +27,34 @@ { "from": "GamePad.RX", "to": "Actions.Yaw" }, - { "from": "GamePad.RY", - "to": "Actions.VERTICAL_UP", - "filters": + { "from": "GamePad.RY", + "to": "Actions.VERTICAL_UP", + "filters": [ { "type": "deadZone", "min": 0.95 }, "invert" ] - }, + }, - { "from": "GamePad.RT", "to": "Standard.RTClick", + { "from": "GamePad.RT", "to": "Standard.RTClick", "peek": true, "filters": [ { "type": "hysteresis", "min": 0.85, "max": 0.9 } ] }, - { "from": "GamePad.RT", "to": "Standard.RT" }, - { "from": "GamePad.RB", "to": "Standard.RB" }, + { "from": "GamePad.RT", "to": "Standard.RT" }, + { "from": "GamePad.RB", "to": "Standard.RB" }, { "from": "GamePad.RS", "to": "Standard.RS" }, { "from": "GamePad.Start", "to": "Actions.CycleCamera" }, - { "from": "GamePad.Back", "to": "Actions.ContextMenu" }, + { "from": "GamePad.Back", "to": "Standard.Start" }, { "from": "GamePad.DU", "to": "Standard.DU" }, - { "from": "GamePad.DD", "to": "Standard.DD" }, + { "from": "GamePad.DD", "to": "Standard.DD" }, { "from": "GamePad.DL", "to": "Standard.DL" }, - { "from": "GamePad.DR", "to": "Standard.DR" }, + { "from": "GamePad.DR", "to": "Standard.DR" }, { "from": [ "GamePad.Y" ], "to": "Standard.RightPrimaryThumb", "peek": true }, - { "from": "GamePad.A", "to": "Standard.A" }, - { "from": "GamePad.B", "to": "Standard.B" }, + { "from": "GamePad.A", "to": "Standard.A" }, + { "from": "GamePad.B", "to": "Standard.B" }, { "from": "GamePad.X", "to": "Standard.X" }, { "from": "GamePad.Y", "to": "Standard.Y" } ] diff --git a/interface/resources/images/steam-min-spec-failed.png b/interface/resources/images/steam-min-spec-failed.png index 99abac9e1c..0061167a92 100644 Binary files a/interface/resources/images/steam-min-spec-failed.png and b/interface/resources/images/steam-min-spec-failed.png differ diff --git a/interface/resources/qml/Stats.qml b/interface/resources/qml/Stats.qml index 9c55b1ce2d..734c442437 100644 --- a/interface/resources/qml/Stats.qml +++ b/interface/resources/qml/Stats.qml @@ -69,6 +69,10 @@ Item { StatText { text: "Present Drop Rate: " + root.presentdroprate.toFixed(2); } + StatText { + text: "Stutter Rate: " + root.stutterrate.toFixed(3); + visible: root.stutterrate != -1; + } StatText { text: "Simrate: " + root.simrate } @@ -241,7 +245,7 @@ Item { text: "GPU Buffers: " } StatText { - text: " Count: " + root.gpuTextures; + text: " Count: " + root.gpuBuffers; } StatText { text: " Memory: " + root.gpuBufferMemory; diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index cc81396d84..089983d8ca 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1197,6 +1197,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo properties["present_rate"] = displayPlugin->presentRate(); properties["new_frame_present_rate"] = displayPlugin->newFramePresentRate(); properties["dropped_frame_rate"] = displayPlugin->droppedFrameRate(); + properties["stutter_rate"] = displayPlugin->stutterRate(); properties["sim_rate"] = getAverageSimsPerSecond(); properties["avatar_sim_rate"] = getAvatarSimrate(); properties["has_async_reprojection"] = displayPlugin->hasAsyncReprojection(); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 6207ecdb3c..dfa1a2f8b9 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -396,15 +396,6 @@ Menu::Menu() { }); } - // Developer > Render > Enable Incremental Texture Transfer - { - auto action = addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::IncrementalTextureTransfer, 0, gpu::Texture::getEnableIncrementalTextureTransfers()); - connect(action, &QAction::triggered, [&](bool checked) { - qDebug() << "[TEXTURE TRANSFER SUPPORT] --- Enable Incremental Texture Transfer menu option:" << checked; - gpu::Texture::setEnableIncrementalTextureTransfers(checked); - }); - } - #else qDebug() << "[TEXTURE TRANSFER SUPPORT] Incremental Texture Transfer and Dynamic Texture Management not supported on this platform."; #endif diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 87e0e678f1..e339da4d25 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -112,7 +112,6 @@ namespace MenuOption { const QString FrameTimer = "Show Timer"; const QString FullscreenMirror = "Mirror"; const QString Help = "Help..."; - const QString IncrementalTextureTransfer = "Enable Incremental Texture Transfer"; const QString IncreaseAvatarSize = "Increase Avatar Size"; const QString IndependentMode = "Independent Mode"; const QString ActionMotorControl = "Enable Default Motor Control"; diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 872d2af04e..46ee15de08 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -165,16 +165,17 @@ AABox Avatar::getBounds() const { void Avatar::animateScaleChanges(float deltaTime) { float currentScale = getUniformScale(); - if (currentScale != _targetScale) { - // use exponential decay toward _targetScale + auto desiredScale = getDomainLimitedScale(); + if (currentScale != desiredScale) { + // use exponential decay toward the domain limit clamped scale const float SCALE_ANIMATION_TIMESCALE = 0.5f; float blendFactor = glm::clamp(deltaTime / SCALE_ANIMATION_TIMESCALE, 0.0f, 1.0f); - float animatedScale = (1.0f - blendFactor) * currentScale + blendFactor * _targetScale; + float animatedScale = (1.0f - blendFactor) * currentScale + blendFactor * desiredScale; // snap to the end when we get close enough const float MIN_RELATIVE_SCALE_ERROR = 0.03f; - if (fabsf(_targetScale - currentScale) / _targetScale < MIN_RELATIVE_SCALE_ERROR) { - animatedScale = _targetScale; + if (fabsf(desiredScale - currentScale) / desiredScale < MIN_RELATIVE_SCALE_ERROR) { + animatedScale = desiredScale; } setScale(glm::vec3(animatedScale)); // avatar scale is uniform diff --git a/interface/src/avatar/AvatarActionHold.cpp b/interface/src/avatar/AvatarActionHold.cpp index 85ff485d7a..32a3586dba 100644 --- a/interface/src/avatar/AvatarActionHold.cpp +++ b/interface/src/avatar/AvatarActionHold.cpp @@ -152,7 +152,7 @@ bool AvatarActionHold::getTarget(float deltaTimeStep, glm::quat& rotation, glm:: Transform avatarTransform; auto myAvatar = DependencyManager::get()->getMyAvatar(); avatarTransform = myAvatar->getTransform(); - palmPosition = avatarTransform.transform(pose.getTranslation() / myAvatar->getTargetScale()); + palmPosition = avatarTransform.transform(pose.getTranslation() / myAvatar->getDomainLimitedScale()); palmRotation = avatarTransform.getRotation() * pose.getRotation(); } else { glm::vec3 avatarRigidBodyPosition; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 735ba05810..b49ee94ff6 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -130,6 +130,15 @@ MyAvatar::MyAvatar(RigPointer rig) : connect(DependencyManager::get().data(), &AddressManager::locationChangeRequired, this, static_cast(&MyAvatar::goToLocation)); + // handle scale constraints imposed on us by the domain-server + auto& domainHandler = DependencyManager::get()->getDomainHandler(); + + // when we connect to a domain and retrieve its settings, we restrict our max/min scale based on those settings + connect(&domainHandler, &DomainHandler::settingsReceived, this, &MyAvatar::restrictScaleFromDomainSettings); + + // when we leave a domain we lift whatever restrictions that domain may have placed on our scale + connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, &MyAvatar::clearScaleRestriction); + _characterController.setEnabled(true); _bodySensorMatrix = deriveBodyFromHMDSensor(); @@ -1823,25 +1832,104 @@ bool findAvatarAvatarPenetration(const glm::vec3 positionA, float radiusA, float return false; } -void MyAvatar::increaseSize() { - if ((1.0f + SCALING_RATIO) * _targetScale < MAX_AVATAR_SCALE) { - _targetScale *= (1.0f + SCALING_RATIO); - qCDebug(interfaceapp, "Changed scale to %f", (double)_targetScale); +// There can be a separation between the _targetScale and the actual scale of the rendered avatar in a domain. +// When the avatar enters a domain where their target scale is not allowed according to the min/max +// we do not change their saved target scale. Instead, we use getDomainLimitedScale() to render the avatar +// at a domain appropriate size. When the avatar leaves the limiting domain, we'll return them to their previous target scale. +// While connected to a domain that limits avatar scale if the user manually changes their avatar scale, we change +// 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); +} + +void MyAvatar::increaseSize() { + // make sure we're starting from an allowable scale + clampTargetScaleToDomainLimits(); + + // calculate what our new scale should be + float updatedTargetScale = _targetScale * (1.0f + SCALING_RATIO); + + // attempt to change to desired scale (clamped to the domain limits) + clampScaleChangeToDomainLimits(updatedTargetScale); +} + void MyAvatar::decreaseSize() { - if (MIN_AVATAR_SCALE < (1.0f - SCALING_RATIO) * _targetScale) { - _targetScale *= (1.0f - SCALING_RATIO); - qCDebug(interfaceapp, "Changed scale to %f", (double)_targetScale); - } + // make sure we're starting from an allowable scale + clampTargetScaleToDomainLimits(); + + // calculate what our new scale should be + float updatedTargetScale = _targetScale * (1.0f - SCALING_RATIO); + + // attempt to change to desired scale (clamped to the domain limits) + clampScaleChangeToDomainLimits(updatedTargetScale); } void MyAvatar::resetSize() { - _targetScale = 1.0f; - qCDebug(interfaceapp, "Reset scale to %f", (double)_targetScale); + // attempt to reset avatar size to the default (clamped to domain limits) + const float DEFAULT_AVATAR_SCALE = 1.0f; + + clampScaleChangeToDomainLimits(DEFAULT_AVATAR_SCALE); } +void MyAvatar::restrictScaleFromDomainSettings(const QJsonObject& domainSettingsObject) { + // pull out the minimum and maximum scale 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 MAX_SCALE_OPTION = "max_avatar_scale"; + float settingMaxScale = avatarsObject[MAX_SCALE_OPTION].toDouble(MAX_AVATAR_SCALE); + setDomainMaximumScale(settingMaxScale); + + // make sure that the domain owner didn't flip min and max + if (_domainMinimumScale > _domainMaximumScale) { + std::swap(_domainMinimumScale, _domainMaximumScale); + } + + qCDebug(interfaceapp, "This domain requires a minimum avatar scale of %f and a maximum avatar scale of %f", + (double)_domainMinimumScale, (double)_domainMaximumScale); + + // debug to log if this avatar's scale in this domain will be clamped + auto clampedScale = glm::clamp(_targetScale, _domainMinimumScale, _domainMaximumScale); + + if (_targetScale != clampedScale) { + qCDebug(interfaceapp, "Avatar scale will be clamped to %f because %f is not allowed by current domain", + (double)clampedScale, (double)_targetScale); + } +} + +void MyAvatar::clearScaleRestriction() { + _domainMinimumScale = MIN_AVATAR_SCALE; + _domainMaximumScale = MAX_AVATAR_SCALE; +} void MyAvatar::goToLocation(const QVariant& propertiesVar) { qCDebug(interfaceapp, "MyAvatar QML goToLocation"); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 71f185c6ed..60049bea67 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -292,6 +292,9 @@ public slots: bool shouldFaceLocation = false); void goToLocation(const QVariant& properties); + void restrictScaleFromDomainSettings(const QJsonObject& domainSettingsObject); + void clearScaleRestriction(); + // Set/Get update the thrust that will move the avatar around void addThrust(glm::vec3 newThrust) { _thrust += newThrust; }; glm::vec3 getThrust() { return _thrust; }; @@ -369,6 +372,8 @@ private: virtual void updatePalms() override {} void lateUpdatePalms(); + void clampTargetScaleToDomainLimits(); + void clampScaleChangeToDomainLimits(float desiredScale); float _driveKeys[MAX_DRIVE_KEYS]; bool _wasPushing; diff --git a/interface/src/scripting/MenuScriptingInterface.cpp b/interface/src/scripting/MenuScriptingInterface.cpp index 2fa7470561..df75d331d6 100644 --- a/interface/src/scripting/MenuScriptingInterface.cpp +++ b/interface/src/scripting/MenuScriptingInterface.cpp @@ -126,6 +126,23 @@ void MenuScriptingInterface::setIsOptionChecked(const QString& menuOption, bool Q_ARG(bool, isChecked)); } +bool MenuScriptingInterface::isMenuEnabled(const QString& menuOption) { + if (QThread::currentThread() == qApp->thread()) { + return Menu::getInstance()->isOptionChecked(menuOption); + } + bool result; + QMetaObject::invokeMethod(Menu::getInstance(), "isMenuEnabled", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(bool, result), + Q_ARG(const QString&, menuOption)); + return result; +} + +void MenuScriptingInterface::setMenuEnabled(const QString& menuOption, bool isChecked) { + QMetaObject::invokeMethod(Menu::getInstance(), "setMenuEnabled", + Q_ARG(const QString&, menuOption), + Q_ARG(bool, isChecked)); +} + void MenuScriptingInterface::triggerOption(const QString& menuOption) { QMetaObject::invokeMethod(Menu::getInstance(), "triggerOption", Q_ARG(const QString&, menuOption)); } diff --git a/interface/src/scripting/MenuScriptingInterface.h b/interface/src/scripting/MenuScriptingInterface.h index 5b8a437529..855b1af13b 100644 --- a/interface/src/scripting/MenuScriptingInterface.h +++ b/interface/src/scripting/MenuScriptingInterface.h @@ -50,6 +50,9 @@ public slots: void setIsOptionChecked(const QString& menuOption, bool isChecked); void triggerOption(const QString& menuOption); + + bool isMenuEnabled(const QString& menuName); + void setMenuEnabled(const QString& menuName, bool isEnabled); signals: void menuItemEvent(const QString& menuItem); diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index edf72d9758..bad2c3c056 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -128,7 +128,8 @@ void Stats::updateStats(bool force) { STAT_UPDATE(renderrate, displayPlugin->renderRate()); STAT_UPDATE(presentrate, displayPlugin->presentRate()); STAT_UPDATE(presentnewrate, displayPlugin->newFramePresentRate()); - STAT_UPDATE(presentdroprate, qApp->getActiveDisplayPlugin()->droppedFrameRate()); + STAT_UPDATE(presentdroprate, displayPlugin->droppedFrameRate()); + STAT_UPDATE(stutterrate, displayPlugin->stutterRate()); } else { STAT_UPDATE(presentrate, -1); STAT_UPDATE(presentnewrate, -1); diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h index ffa5c08bc6..5d1aac4287 100644 --- a/interface/src/ui/Stats.h +++ b/interface/src/ui/Stats.h @@ -36,7 +36,9 @@ class Stats : public QQuickItem { STATS_PROPERTY(float, renderrate, 0) // How often the display plugin is presenting to the device STATS_PROPERTY(float, presentrate, 0) - + // How often the display device reprojecting old frames + STATS_PROPERTY(float, stutterrate, 0) + STATS_PROPERTY(float, presentnewrate, 0) STATS_PROPERTY(float, presentdroprate, 0) STATS_PROPERTY(int, simrate, 0) @@ -140,6 +142,7 @@ signals: void presentrateChanged(); void presentnewrateChanged(); void presentdroprateChanged(); + void stutterrateChanged(); void simrateChanged(); void avatarSimrateChanged(); void avatarCountChanged(); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 9df23dad2c..2469d0df04 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -210,7 +210,7 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) { packFloatAngleToTwoByte((uint8_t*)(header->localOrientation + 0), bodyEulerAngles.y); packFloatAngleToTwoByte((uint8_t*)(header->localOrientation + 1), bodyEulerAngles.x); packFloatAngleToTwoByte((uint8_t*)(header->localOrientation + 2), bodyEulerAngles.z); - packFloatRatioToTwoByte((uint8_t*)(&header->scale), _targetScale); + packFloatRatioToTwoByte((uint8_t*)(&header->scale), getDomainLimitedScale()); header->lookAtPosition[0] = _headData->_lookAtPosition.x; header->lookAtPosition[1] = _headData->_lookAtPosition.y; header->lookAtPosition[2] = _headData->_lookAtPosition.z; @@ -516,7 +516,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { } return buffer.size(); } - _targetScale = std::max(MIN_AVATAR_SCALE, std::min(MAX_AVATAR_SCALE, scale)); + setTargetScale(scale); glm::vec3 lookAt = glm::vec3(header->lookAtPosition[0], header->lookAtPosition[1], header->lookAtPosition[2]); if (isNaN(lookAt)) { @@ -1439,7 +1439,7 @@ QJsonObject AvatarData::toJson() const { if (!success) { qDebug() << "Warning -- AvatarData::toJson couldn't get avatar transform"; } - avatarTransform.setScale(getTargetScale()); + avatarTransform.setScale(getDomainLimitedScale()); if (recordingBasis) { root[JSON_AVATAR_BASIS] = Transform::toJson(*recordingBasis); // Find the relative transform @@ -1451,7 +1451,7 @@ QJsonObject AvatarData::toJson() const { root[JSON_AVATAR_RELATIVE] = Transform::toJson(avatarTransform); } - auto scale = getTargetScale(); + auto scale = getDomainLimitedScale(); if (scale != 1.0f) { root[JSON_AVATAR_SCALE] = scale; } diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index cb3ef0c40e..97879700ee 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -243,6 +243,12 @@ public: void setTargetScale(float targetScale); void setTargetScaleVerbose(float targetScale); + float getDomainLimitedScale() const { return glm::clamp(_targetScale, _domainMinimumScale, _domainMaximumScale); } + void setDomainMinimumScale(float domainMinimumScale) + { _domainMinimumScale = glm::clamp(domainMinimumScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE); } + void setDomainMaximumScale(float domainMaximumScale) + { _domainMaximumScale = glm::clamp(domainMaximumScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE); } + // Hand State Q_INVOKABLE void setHandState(char s) { _handState = s; } Q_INVOKABLE char getHandState() const { return _handState; } @@ -377,6 +383,8 @@ protected: // Body scale float _targetScale; + float _domainMinimumScale { MIN_AVATAR_SCALE }; + float _domainMaximumScale { MAX_AVATAR_SCALE }; // Hand state (are we grabbing something or not) char _handState; diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h index 2e4e57e15a..f3dd50602c 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h @@ -18,7 +18,7 @@ class Basic2DWindowOpenGLDisplayPlugin : public OpenGLDisplayPlugin { Q_OBJECT using Parent = OpenGLDisplayPlugin; public: - virtual const QString& getName() const override { return NAME; } + virtual const QString getName() const override { return NAME; } virtual float getTargetFrameRate() const override { return _framerateTarget ? (float) _framerateTarget : TARGET_FRAMERATE_Basic2DWindowOpenGL; } diff --git a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h index 1852ed53ee..1da0441d8f 100644 --- a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h @@ -12,7 +12,7 @@ class NullDisplayPlugin : public DisplayPlugin { public: ~NullDisplayPlugin() final {} - const QString& getName() const override { return NAME; } + const QString getName() const override { return NAME; } grouping getGrouping() const override { return DEVELOPER; } glm::uvec2 getRecommendedRenderSize() const override; diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index c84f6b9954..63c4692a5f 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -758,7 +758,6 @@ void OpenGLDisplayPlugin::render(std::function f) { OpenGLDisplayPlugin::~OpenGLDisplayPlugin() { - qDebug() << "Destroying OpenGLDisplayPlugin"; } void OpenGLDisplayPlugin::updateCompositeFramebuffer() { diff --git a/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.h index 509e13eda7..9bb82b1836 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.h @@ -13,7 +13,7 @@ class DebugHmdDisplayPlugin : public HmdDisplayPlugin { using Parent = HmdDisplayPlugin; public: - const QString& getName() const override { return NAME; } + const QString getName() const override { return NAME; } grouping getGrouping() const override { return DEVELOPER; } bool isSupported() const override; diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 2e66659de7..c5d7ac5690 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -710,3 +710,7 @@ void HmdDisplayPlugin::compositeExtra() { HmdDisplayPlugin::~HmdDisplayPlugin() { qDebug() << "Destroying HmdDisplayPlugin"; } + +float HmdDisplayPlugin::stutterRate() const { + return _stutterRate.rate(); +} diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h index e50183dd90..435f547899 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h @@ -44,6 +44,8 @@ public: return false; } + float stutterRate() const override; + protected: virtual void hmdPresent() = 0; virtual bool isHmdMounted() const = 0; @@ -108,8 +110,9 @@ protected: QMap _frameInfos; FrameInfo _currentPresentFrameInfo; FrameInfo _currentRenderFrameInfo; + RateCounter<> _stutterRate; - bool _disablePreview{ true }; + bool _disablePreview { true }; private: ivec4 getViewportForSourceSize(const uvec2& size) const; float getLeftCenterPixel() const; diff --git a/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.h index 8c3ebcaa6d..debd340f24 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.h @@ -13,7 +13,7 @@ class InterleavedStereoDisplayPlugin : public StereoDisplayPlugin { Q_OBJECT using Parent = StereoDisplayPlugin; public: - const QString& getName() const override { return NAME; } + const QString getName() const override { return NAME; } grouping getGrouping() const override { return ADVANCED; } glm::uvec2 getRecommendedRenderSize() const override; diff --git a/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.h index d4d20243ea..79bf2a9ecb 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.h @@ -15,7 +15,7 @@ class SideBySideStereoDisplayPlugin : public StereoDisplayPlugin { Q_OBJECT using Parent = StereoDisplayPlugin; public: - virtual const QString& getName() const override { return NAME; } + virtual const QString getName() const override { return NAME; } virtual grouping getGrouping() const override { return ADVANCED; } virtual glm::uvec2 getRecommendedRenderSize() const override; diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 68636415f8..babf714d9b 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -43,6 +43,7 @@ EntityItem::EntityItem(const EntityItemID& entityItemID) : _lastSimulated(0), _lastUpdated(0), _lastEdited(0), + _lastEditedBy(ENTITY_ITEM_DEFAULT_LAST_EDITED_BY), _lastEditedFromRemote(0), _lastEditedFromRemoteInRemoteTime(0), _created(UNKNOWN_CREATED_TIME), @@ -141,6 +142,8 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param requestedProperties += PROP_CLIENT_ONLY; requestedProperties += PROP_OWNING_AVATAR_ID; + requestedProperties += PROP_LAST_EDITED_BY; + return requestedProperties; } @@ -279,6 +282,7 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet APPEND_ENTITY_PROPERTY(PROP_PARENT_ID, getParentID()); APPEND_ENTITY_PROPERTY(PROP_PARENT_JOINT_INDEX, getParentJointIndex()); APPEND_ENTITY_PROPERTY(PROP_QUERY_AA_CUBE, getQueryAACube()); + APPEND_ENTITY_PROPERTY(PROP_LAST_EDITED_BY, getLastEditedBy()); appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties, @@ -803,6 +807,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef } READ_ENTITY_PROPERTY(PROP_QUERY_AA_CUBE, AACube, setQueryAACube); + READ_ENTITY_PROPERTY(PROP_LAST_EDITED_BY, QUuid, setLastEditedBy); bytesRead += readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, propertyFlags, overwriteLocalData, somethingChanged); @@ -1205,6 +1210,8 @@ EntityItemProperties EntityItem::getProperties(EntityPropertyFlags desiredProper COPY_ENTITY_PROPERTY_TO_PROPERTIES(clientOnly, getClientOnly); COPY_ENTITY_PROPERTY_TO_PROPERTIES(owningAvatarID, getOwningAvatarID); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(lastEditedBy, getLastEditedBy); + properties._defaultSettings = false; return properties; @@ -1307,6 +1314,8 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(clientOnly, setClientOnly); SET_ENTITY_PROPERTY_FROM_PROPERTIES(owningAvatarID, setOwningAvatarID); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(lastEditedBy, setLastEditedBy); + AACube saveQueryAACube = _queryAACube; checkAndAdjustQueryAACube(); if (saveQueryAACube != _queryAACube) { diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index d3c4cbfdfc..19d6aec783 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -449,6 +449,9 @@ public: virtual void emitScriptEvent(const QVariant& message) {} + QUuid getLastEditedBy() const { return _lastEditedBy; } + void setLastEditedBy(QUuid value) { _lastEditedBy = value; } + protected: void setSimulated(bool simulated) { _simulated = simulated; } @@ -464,6 +467,7 @@ protected: // and physics changes quint64 _lastUpdated; // last time this entity called update(), this includes animations and non-physics changes quint64 _lastEdited; // last official local or remote edit time + QUuid _lastEditedBy; // id of last editor quint64 _lastBroadcast; // the last time we sent an edit packet about this entity quint64 _lastEditedFromRemote; // last time we received and edit from the server diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index a0142dec8f..e20bd81ab3 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -227,6 +227,7 @@ void EntityItemProperties::setBackgroundModeFromString(const QString& background EntityPropertyFlags EntityItemProperties::getChangedProperties() const { EntityPropertyFlags changedProperties; + CHECK_PROPERTY_CHANGE(PROP_LAST_EDITED_BY, lastEditedBy); CHECK_PROPERTY_CHANGE(PROP_POSITION, position); CHECK_PROPERTY_CHANGE(PROP_DIMENSIONS, dimensions); CHECK_PROPERTY_CHANGE(PROP_ROTATION, rotation); @@ -368,6 +369,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_NO_SKIP(ageAsText, formatSecondsElapsed(getAge())); // gettable, but not settable } + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LAST_EDITED_BY, lastEditedBy); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_POSITION, position); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_DIMENSIONS, dimensions); if (!skipDefaults) { @@ -611,6 +613,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool setType(typeScriptValue.toVariant().toString()); } + COPY_PROPERTY_FROM_QSCRIPTVALUE(lastEditedBy, QUuid, setLastEditedBy); COPY_PROPERTY_FROM_QSCRIPTVALUE(position, glmVec3, setPosition); COPY_PROPERTY_FROM_QSCRIPTVALUE(dimensions, glmVec3, setDimensions); COPY_PROPERTY_FROM_QSCRIPTVALUE(rotation, glmQuat, setRotation); @@ -750,6 +753,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool } void EntityItemProperties::merge(const EntityItemProperties& other) { + COPY_PROPERTY_IF_CHANGED(lastEditedBy); COPY_PROPERTY_IF_CHANGED(position); COPY_PROPERTY_IF_CHANGED(dimensions); COPY_PROPERTY_IF_CHANGED(rotation); @@ -1667,6 +1671,7 @@ bool EntityItemProperties::encodeEraseEntityMessage(const EntityItemID& entityIt } void EntityItemProperties::markAllChanged() { + _lastEditedByChanged = true; _simulationOwnerChanged = true; _positionChanged = true; _dimensionsChanged = true; diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index d5cce92f87..5a812ffe37 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -83,6 +83,7 @@ public: quint64 getLastEdited() const { return _lastEdited; } float getEditedAgo() const /// Elapsed seconds since this entity was last edited { return (float)(usecTimestampNow() - getLastEdited()) / (float)USECS_PER_SECOND; } + EntityPropertyFlags getChangedProperties() const; bool parentDependentPropertyChanged() const; // was there a changed in a property that requires parent info to interpret? @@ -218,6 +219,8 @@ public: DEFINE_PROPERTY_REF(PROP_DPI, DPI, dpi, uint16_t, ENTITY_ITEM_DEFAULT_DPI); + DEFINE_PROPERTY_REF(PROP_LAST_EDITED_BY, LastEditedBy, lastEditedBy, QUuid, ENTITY_ITEM_DEFAULT_LAST_EDITED_BY); + static QString getBackgroundModeString(BackgroundMode mode); @@ -455,6 +458,8 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { DEBUG_PROPERTY_IF_CHANGED(debug, properties, ClientOnly, clientOnly, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, OwningAvatarID, owningAvatarID, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, LastEditedBy, lastEditedBy, ""); + properties.getAnimation().debugDump(); properties.getSkybox().debugDump(); properties.getStage().debugDump(); diff --git a/libraries/entities/src/EntityItemPropertiesDefaults.h b/libraries/entities/src/EntityItemPropertiesDefaults.h index 3ab827a222..ca7ac669f3 100644 --- a/libraries/entities/src/EntityItemPropertiesDefaults.h +++ b/libraries/entities/src/EntityItemPropertiesDefaults.h @@ -75,4 +75,6 @@ const QString ENTITY_ITEM_DEFAULT_NAME = QString(""); const uint16_t ENTITY_ITEM_DEFAULT_DPI = 30; +const QUuid ENTITY_ITEM_DEFAULT_LAST_EDITED_BY = QUuid(); + #endif // hifi_EntityItemPropertiesDefaults_h diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index e09db1e867..127f3d0eea 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -181,6 +181,8 @@ enum EntityPropertyList { PROP_LOCAL_VELOCITY, // only used to convert values to and from scripts PROP_LOCAL_ANGULAR_VELOCITY, // only used to convert values to and from scripts + PROP_LAST_EDITED_BY, + //////////////////////////////////////////////////////////////////////////////////////////////////// // ATTENTION: add new properties to end of list just ABOVE this line PROP_AFTER_LAST_ITEM, diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 5399adfa82..a03e4b8f08 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1013,6 +1013,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c endLogging = usecTimestampNow(); startUpdate = usecTimestampNow(); + properties.setLastEditedBy(senderNode->getUUID()); updateEntity(entityItemID, properties, senderNode); existingEntity->markAsChangedOnServer(); endUpdate = usecTimestampNow(); @@ -1021,6 +1022,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c if (senderNode->getCanRez() || senderNode->getCanRezTmp()) { // this is a new entity... assign a new entityID properties.setCreated(properties.getLastEdited()); + properties.setLastEditedBy(senderNode->getUUID()); startCreate = usecTimestampNow(); EntityItemPointer newEntity = addEntity(entityItemID, properties); endCreate = usecTimestampNow(); diff --git a/libraries/gl/src/gl/OpenGLVersionChecker.cpp b/libraries/gl/src/gl/OpenGLVersionChecker.cpp index 6473b6bf2b..428bf86c6f 100644 --- a/libraries/gl/src/gl/OpenGLVersionChecker.cpp +++ b/libraries/gl/src/gl/OpenGLVersionChecker.cpp @@ -21,6 +21,7 @@ #include "GLHelpers.h" +// Minimum gl version required is 4.1 #define MINIMUM_GL_VERSION 0x0401 OpenGLVersionChecker::OpenGLVersionChecker(int& argc, char** argv) : @@ -75,15 +76,26 @@ QJsonObject OpenGLVersionChecker::checkVersion(bool& valid, bool& override) { // - major_number.minor_number // - major_number.minor_number.release_number // Reference: https://www.opengl.org/sdk/docs/man/docbook4/xhtml/glGetString.xml - const QString version { "version" }; - QString glVersion = glData[version].toString(); - QStringList versionParts = glVersion.split(QRegularExpression("[\\.\\s]")); - int majorNumber = versionParts[0].toInt(); - int minorNumber = versionParts[1].toInt(); - int minimumMajorNumber = (MINIMUM_GL_VERSION >> 16); + + int minimumMajorNumber = (MINIMUM_GL_VERSION >> 8) & 0xFF; int minimumMinorNumber = (MINIMUM_GL_VERSION & 0xFF); - valid = (majorNumber > minimumMajorNumber - || (majorNumber == minimumMajorNumber && minorNumber >= minimumMinorNumber)); + int majorNumber = 0; + int minorNumber = 0; + const QString version { "version" }; + if (glData.contains(version)) { + QString glVersion = glData[version].toString(); + QStringList versionParts = glVersion.split(QRegularExpression("[\\.\\s]")); + if (versionParts.size() >= 2) { + majorNumber = versionParts[0].toInt(); + minorNumber = versionParts[1].toInt(); + valid = (majorNumber > minimumMajorNumber + || (majorNumber == minimumMajorNumber && minorNumber >= minimumMinorNumber)); + } else { + valid = false; + } + } else { + valid = false; + } // Prompt user if below minimum if (!valid) { diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h index 438b1e9454..291888e81b 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h +++ b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h @@ -14,6 +14,8 @@ #include "../gl/GLBackend.h" #include "../gl/GLTexture.h" +#define INCREMENTAL_TRANSFER 0 + namespace gpu { namespace gl45 { using namespace gpu::gl; @@ -56,6 +58,7 @@ public: GLint pageDimensionsIndex { 0 }; }; +#if INCREMENTAL_TRANSFER struct TransferState { TransferState(GL45Texture& texture); uvec3 currentPageSize() const; @@ -74,6 +77,10 @@ public: uvec3 mipOffset; const uint8_t* srcPointer { nullptr }; }; + protected: + TransferState _transferState; +#endif + protected: void updateMips() override; void stripToMip(uint16_t newMinMip); @@ -91,7 +98,6 @@ public: void derez(); SparseInfo _sparseInfo; - TransferState _transferState; uint32_t _allocatedPages { 0 }; uint32_t _lastMipAllocatedPages { 0 }; uint16_t _mipOffset { 0 }; diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp index f97364f5c5..0516bc6be0 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp @@ -162,6 +162,8 @@ void GL45Backend::initTextureManagementStage() { } } +#if INCREMENTAL_TRANSFER + using TransferState = GL45Backend::GL45Texture::TransferState; TransferState::TransferState(GL45Texture& texture) : texture(texture) { @@ -246,6 +248,7 @@ void TransferState::populatePage(std::vector& buffer) { uvec3 TransferState::currentPageSize() const { return glm::clamp(mipDimensions - mipOffset, uvec3(1), texture._sparseInfo.pageDimensions); } +#endif GLuint GL45Texture::allocate(const Texture& texture) { GLuint result; @@ -258,11 +261,19 @@ GLuint GL45Backend::getTextureID(const TexturePointer& texture, bool transfer) { } GL45Texture::GL45Texture(const std::weak_ptr& backend, const Texture& texture, GLuint externalId) - : GLTexture(backend, texture, externalId), _sparseInfo(*this), _transferState(*this) { + : GLTexture(backend, texture, externalId), _sparseInfo(*this) +#if INCREMENTAL_TRANSFER +, _transferState(*this) +#endif +{ } GL45Texture::GL45Texture(const std::weak_ptr& backend, const Texture& texture, bool transferrable) - : GLTexture(backend, texture, allocate(texture), transferrable), _sparseInfo(*this), _transferState(*this) { + : GLTexture(backend, texture, allocate(texture), transferrable), _sparseInfo(*this) +#if INCREMENTAL_TRANSFER +, _transferState(*this) +#endif + { auto theBackend = _backend.lock(); if (_transferrable && theBackend && theBackend->isTextureManagementSparseEnabled()) { @@ -274,10 +285,10 @@ GL45Texture::GL45Texture(const std::weak_ptr& backend, const Texture& } GL45Texture::~GL45Texture() { - // External textures cycle very quickly, so don't spam the log with messages about them. - if (!_gpuObject.getUsage().isExternal()) { - qCDebug(gpugl45logging) << "Destroying texture " << _id << " from source " << _source.c_str(); - } + // // External textures cycle very quickly, so don't spam the log with messages about them. + // if (!_gpuObject.getUsage().isExternal()) { + // qCDebug(gpugl45logging) << "Destroying texture " << _id << " from source " << _source.c_str(); + // } // Remove this texture from the candidate list of derezzable textures if (_transferrable) { @@ -375,39 +386,40 @@ void GL45Texture::updateSize() const { void GL45Texture::startTransfer() { Parent::startTransfer(); _sparseInfo.update(); +#if INCREMENTAL_TRANSFER _transferState.updateMip(); +#endif } bool GL45Texture::continueTransfer() { - if (!Texture::getEnableIncrementalTextureTransfers()) { - size_t maxFace = GL_TEXTURE_CUBE_MAP == _target ? CUBE_NUM_FACES : 1; - for (uint8_t face = 0; face < maxFace; ++face) { - for (uint16_t mipLevel = _minMip; mipLevel <= _maxMip; ++mipLevel) { - auto size = _gpuObject.evalMipDimensions(mipLevel); - if (_sparseInfo.sparse && mipLevel <= _sparseInfo.maxSparseLevel) { - glTexturePageCommitmentEXT(_id, mipLevel, 0, 0, face, size.x, size.y, 1, GL_TRUE); - _allocatedPages += _sparseInfo.getPageCount(size); - } - if (_gpuObject.isStoredMipFaceAvailable(mipLevel, face)) { - auto mip = _gpuObject.accessStoredMipFace(mipLevel, face); - GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat(), mip->getFormat()); - if (GL_TEXTURE_2D == _target) { - glTextureSubImage2D(_id, mipLevel, 0, 0, size.x, size.y, texelFormat.format, texelFormat.type, mip->readData()); - } else if (GL_TEXTURE_CUBE_MAP == _target) { - // DSA ARB does not work on AMD, so use EXT - // glTextureSubImage3D(_id, mipLevel, 0, 0, face, size.x, size.y, 1, texelFormat.format, texelFormat.type, mip->readData()); - auto target = CUBE_FACE_LAYOUT[face]; - glTextureSubImage2DEXT(_id, target, mipLevel, 0, 0, size.x, size.y, texelFormat.format, texelFormat.type, mip->readData()); - } else { - Q_ASSERT(false); - } - (void)CHECK_GL_ERROR(); +#if !INCREMENTAL_TRANSFER + size_t maxFace = GL_TEXTURE_CUBE_MAP == _target ? CUBE_NUM_FACES : 1; + for (uint8_t face = 0; face < maxFace; ++face) { + for (uint16_t mipLevel = _minMip; mipLevel <= _maxMip; ++mipLevel) { + auto size = _gpuObject.evalMipDimensions(mipLevel); + if (_sparseInfo.sparse && mipLevel <= _sparseInfo.maxSparseLevel) { + glTexturePageCommitmentEXT(_id, mipLevel, 0, 0, face, size.x, size.y, 1, GL_TRUE); + _allocatedPages += _sparseInfo.getPageCount(size); + } + if (_gpuObject.isStoredMipFaceAvailable(mipLevel, face)) { + auto mip = _gpuObject.accessStoredMipFace(mipLevel, face); + GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat(), mip->getFormat()); + if (GL_TEXTURE_2D == _target) { + glTextureSubImage2D(_id, mipLevel, 0, 0, size.x, size.y, texelFormat.format, texelFormat.type, mip->readData()); + } else if (GL_TEXTURE_CUBE_MAP == _target) { + // DSA ARB does not work on AMD, so use EXT + // glTextureSubImage3D(_id, mipLevel, 0, 0, face, size.x, size.y, 1, texelFormat.format, texelFormat.type, mip->readData()); + auto target = CUBE_FACE_LAYOUT[face]; + glTextureSubImage2DEXT(_id, target, mipLevel, 0, 0, size.x, size.y, texelFormat.format, texelFormat.type, mip->readData()); + } else { + Q_ASSERT(false); } + (void)CHECK_GL_ERROR(); } } - return false; } - + return false; +#else static std::vector buffer; if (buffer.empty()) { buffer.resize(DEFAULT_PAGE_BUFFER_SIZE); @@ -458,6 +470,7 @@ bool GL45Texture::continueTransfer() { _lastMipAllocatedPages = _allocatedPages; } return result; +#endif } void GL45Texture::finishTransfer() { diff --git a/libraries/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp index 33786155db..45aff54b8f 100755 --- a/libraries/gpu/src/gpu/Texture.cpp +++ b/libraries/gpu/src/gpu/Texture.cpp @@ -35,18 +35,15 @@ std::atomic Texture::_allowedCPUMemoryUsage { 0 }; #define MIN_CORES_FOR_INCREMENTAL_TEXTURES 5 -bool recommendedIncrementalTransfers = (QThread::idealThreadCount() >= MIN_CORES_FOR_INCREMENTAL_TEXTURES); -bool recommendedSparseTextures = recommendedIncrementalTransfers; +bool recommendedSparseTextures = (QThread::idealThreadCount() >= MIN_CORES_FOR_INCREMENTAL_TEXTURES); -std::atomic Texture::_enableSparseTextures { recommendedIncrementalTransfers }; -std::atomic Texture::_enableIncrementalTextureTransfers { recommendedSparseTextures }; +std::atomic Texture::_enableSparseTextures { recommendedSparseTextures }; struct ReportTextureState { ReportTextureState() { qDebug() << "[TEXTURE TRANSFER SUPPORT]" << "\n\tidealThreadCount:" << QThread::idealThreadCount() - << "\n\tRECOMMENDED enableSparseTextures:" << recommendedSparseTextures - << "\n\tRECOMMENDED enableIncrementalTextures:" << recommendedIncrementalTransfers; + << "\n\tRECOMMENDED enableSparseTextures:" << recommendedSparseTextures; } } report; @@ -59,16 +56,6 @@ void Texture::setEnableSparseTextures(bool enabled) { #endif } -void Texture::setEnableIncrementalTextureTransfers(bool enabled) { -#ifdef Q_OS_WIN - qDebug() << "[TEXTURE TRANSFER SUPPORT] SETTING - Enable Incremental Texture Transfer:" << enabled; - _enableIncrementalTextureTransfers = enabled; -#else - qDebug() << "[TEXTURE TRANSFER SUPPORT] Incremental Texture Transfer not supported on this platform."; -#endif -} - - void Texture::updateTextureCPUMemoryUsage(Size prevObjectSize, Size newObjectSize) { if (prevObjectSize == newObjectSize) { return; @@ -84,10 +71,6 @@ bool Texture::getEnableSparseTextures() { return _enableSparseTextures.load(); } -bool Texture::getEnableIncrementalTextureTransfers() { - return _enableIncrementalTextureTransfers.load(); -} - uint32_t Texture::getTextureCPUCount() { return _textureCPUCount.load(); } diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index 2a93ec3066..856bd4983d 100755 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -143,10 +143,8 @@ class Texture : public Resource { static std::atomic _textureCPUCount; static std::atomic _textureCPUMemoryUsage; static std::atomic _allowedCPUMemoryUsage; - static void updateTextureCPUMemoryUsage(Size prevObjectSize, Size newObjectSize); - static std::atomic _enableSparseTextures; - static std::atomic _enableIncrementalTextureTransfers; + static void updateTextureCPUMemoryUsage(Size prevObjectSize, Size newObjectSize); public: static uint32_t getTextureCPUCount(); @@ -162,10 +160,7 @@ public: static void setAllowedGPUMemoryUsage(Size size); static bool getEnableSparseTextures(); - static bool getEnableIncrementalTextureTransfers(); - static void setEnableSparseTextures(bool enabled); - static void setEnableIncrementalTextureTransfers(bool enabled); using ExternalRecycler = std::function; using ExternalIdAndFence = std::pair; diff --git a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp index c1f764f5fa..39371cc3e3 100755 --- a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp +++ b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp @@ -18,7 +18,7 @@ #include #include -const QString KeyboardMouseDevice::NAME = "Keyboard/Mouse"; +const char* KeyboardMouseDevice::NAME = "Keyboard/Mouse"; bool KeyboardMouseDevice::_enableTouch = true; void KeyboardMouseDevice::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { diff --git a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h index 8177c9bcc0..399ca4e93d 100644 --- a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h +++ b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h @@ -52,21 +52,21 @@ public: MOUSE_AXIS_WHEEL_X_POS, MOUSE_AXIS_WHEEL_X_NEG, }; - + enum TouchAxisChannel { TOUCH_AXIS_X_POS = MOUSE_AXIS_WHEEL_X_NEG + 1, TOUCH_AXIS_X_NEG, TOUCH_AXIS_Y_POS, TOUCH_AXIS_Y_NEG, }; - + enum TouchButtonChannel { TOUCH_BUTTON_PRESS = TOUCH_AXIS_Y_NEG + 1, }; // Plugin functions bool isSupported() const override { return true; } - const QString& getName() const override { return NAME; } + const QString getName() const override { return NAME; } bool isHandController() const override { return false; } @@ -88,8 +88,8 @@ public: void wheelEvent(QWheelEvent* event); static void enableTouch(bool enableTouch) { _enableTouch = enableTouch; } - - static const QString NAME; + + static const char* NAME; protected: diff --git a/libraries/input-plugins/src/input-plugins/TouchscreenDevice.cpp b/libraries/input-plugins/src/input-plugins/TouchscreenDevice.cpp index 64f02b5df3..20952df4d7 100644 --- a/libraries/input-plugins/src/input-plugins/TouchscreenDevice.cpp +++ b/libraries/input-plugins/src/input-plugins/TouchscreenDevice.cpp @@ -21,7 +21,7 @@ #include #include -const QString TouchscreenDevice::NAME = "Touchscreen"; +const char* TouchscreenDevice::NAME = "Touchscreen"; bool TouchscreenDevice::isSupported() const { for (auto touchDevice : QTouchDevice::devices()) { diff --git a/libraries/input-plugins/src/input-plugins/TouchscreenDevice.h b/libraries/input-plugins/src/input-plugins/TouchscreenDevice.h index 7bfaa23be8..65e771e8f0 100644 --- a/libraries/input-plugins/src/input-plugins/TouchscreenDevice.h +++ b/libraries/input-plugins/src/input-plugins/TouchscreenDevice.h @@ -22,7 +22,7 @@ class QGestureEvent; class TouchscreenDevice : public InputPlugin { Q_OBJECT public: - + enum TouchAxisChannel { TOUCH_AXIS_X_POS = 0, TOUCH_AXIS_X_NEG, @@ -37,7 +37,7 @@ public: // Plugin functions virtual bool isSupported() const override; - virtual const QString& getName() const override { return NAME; } + virtual const QString getName() const override { return NAME; } bool isHandController() const override { return false; } @@ -48,8 +48,8 @@ public: void touchEndEvent(const QTouchEvent* event); void touchUpdateEvent(const QTouchEvent* event); void touchGestureEvent(const QGestureEvent* event); - - static const QString NAME; + + static const char* NAME; protected: diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index eecc1515f5..5d7340b2ce 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -272,27 +272,17 @@ void DomainHandler::setIsConnected(bool isConnected) { } void DomainHandler::requestDomainSettings() { - // TODO: the nodes basically lock if they don't get a response - add a timeout to this so that they at least restart - // if they can't get settings - - NodeType_t owningNodeType = DependencyManager::get()->getOwnerType(); - if (owningNodeType == NodeType::Agent) { - // for now the agent nodes don't need any domain settings - _settingsObject = QJsonObject(); - emit settingsReceived(_settingsObject); - } else { - qCDebug(networking) << "Requesting settings from domain server"; - - Assignment::Type assignmentType = Assignment::typeForNodeType(DependencyManager::get()->getOwnerType()); - - auto packet = NLPacket::create(PacketType::DomainSettingsRequest, sizeof(assignmentType), true, false); - packet->writePrimitive(assignmentType); - - auto nodeList = DependencyManager::get(); - nodeList->sendPacket(std::move(packet), _sockAddr); - - _settingsTimer.start(); - } + qCDebug(networking) << "Requesting settings from domain server"; + + Assignment::Type assignmentType = Assignment::typeForNodeType(DependencyManager::get()->getOwnerType()); + + auto packet = NLPacket::create(PacketType::DomainSettingsRequest, sizeof(assignmentType), true, false); + packet->writePrimitive(assignmentType); + + auto nodeList = DependencyManager::get(); + nodeList->sendPacket(std::move(packet), _sockAddr); + + _settingsTimer.start(); } void DomainHandler::processSettingsPacketList(QSharedPointer packetList) { diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 82bac4cc3d..7a778edaad 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -815,3 +815,31 @@ void NodeList::kickNodeBySessionID(const QUuid& nodeID) { } } + +void NodeList::muteNodeBySessionID(const QUuid& nodeID) { + // cannot mute yourself, or nobody + if (!nodeID.isNull() && _sessionUUID != nodeID ) { + if (getThisNodeCanKick()) { + auto audioMixer = soloNodeOfType(NodeType::AudioMixer); + if (audioMixer) { + // setup the packet + auto mutePacket = NLPacket::create(PacketType::NodeMuteRequest, NUM_BYTES_RFC4122_UUID, true); + + // write the node ID to the packet + mutePacket->write(nodeID.toRfc4122()); + + qDebug() << "Sending packet to mute node" << uuidStringWithoutCurlyBraces(nodeID); + + sendPacket(std::move(mutePacket), *audioMixer); + } else { + qWarning() << "Couldn't find audio mixer to send node mute request"; + } + } else { + qWarning() << "You do not have permissions to mute in this domain." + << "Request to mute node" << uuidStringWithoutCurlyBraces(nodeID) << "will not be sent"; + } + } else { + qWarning() << "NodeList::muteNodeBySessionID called with an invalid ID or an ID which matches the current session ID."; + + } +} diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index 41a4a51515..4c06a13469 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -74,6 +74,7 @@ public: bool isIgnoringNode(const QUuid& nodeID) const; void kickNodeBySessionID(const QUuid& nodeID); + void muteNodeBySessionID(const QUuid& nodeID); public slots: void reset(); diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 6b86b7bc6e..88285602e1 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -26,7 +26,8 @@ const QSet NON_VERIFIED_PACKETS = QSet() << PacketType::NodeJsonStats << PacketType::EntityQuery << PacketType::OctreeDataNack << PacketType::EntityEditNack << PacketType::DomainListRequest << PacketType::StopNode - << PacketType::DomainDisconnectRequest << PacketType::NodeKickRequest; + << PacketType::DomainDisconnectRequest << PacketType::NodeKickRequest + << PacketType::NodeMuteRequest; const QSet NON_SOURCED_PACKETS = QSet() << PacketType::StunResponse << PacketType::CreateAssignment << PacketType::RequestAssignment @@ -47,7 +48,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::EntityAdd: case PacketType::EntityEdit: case PacketType::EntityData: - return VERSION_ENTITIES_ARROW_ACTION; + return VERSION_ENTITIES_LAST_EDITED_BY; case PacketType::AvatarIdentity: case PacketType::AvatarData: case PacketType::BulkAvatarData: diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 0eca24176c..502ecc3951 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -99,7 +99,8 @@ public: SelectedAudioFormat, MoreEntityShapes, NodeKickRequest, - LAST_PACKET_TYPE = NodeKickRequest + NodeMuteRequest, + LAST_PACKET_TYPE = NodeMuteRequest }; }; @@ -188,6 +189,7 @@ const PacketVersion VERSION_MODEL_ENTITIES_SUPPORT_STATIC_MESH = 61; const PacketVersion VERSION_MODEL_ENTITIES_SUPPORT_SIMPLE_HULLS = 62; const PacketVersion VERSION_WEB_ENTITIES_SUPPORT_DPI = 63; const PacketVersion VERSION_ENTITIES_ARROW_ACTION = 64; +const PacketVersion VERSION_ENTITIES_LAST_EDITED_BY = 65; enum class AssetServerPacketVersion: PacketVersion { VegasCongestionControl = 19 diff --git a/libraries/networking/src/udt/Socket.cpp b/libraries/networking/src/udt/Socket.cpp index 98cc62bdee..d2583dce72 100644 --- a/libraries/networking/src/udt/Socket.cpp +++ b/libraries/networking/src/udt/Socket.cpp @@ -165,6 +165,12 @@ qint64 Socket::writePacketList(std::unique_ptr packetList, const Hif // hand this packetList off to writeReliablePacketList // because Qt can't invoke with the unique_ptr we have to release it here and re-construct in writeReliablePacketList + if (packetList->getNumPackets() == 0) { + qCWarning(networking) << "Trying to send packet list with 0 packets, bailing."; + return 0; + } + + if (QThread::currentThread() != thread()) { auto ptr = packetList.release(); QMetaObject::invokeMethod(this, "writeReliablePacketList", Qt::AutoConnection, diff --git a/libraries/plugins/src/plugins/DisplayPlugin.h b/libraries/plugins/src/plugins/DisplayPlugin.h index 3a9107390a..c87669c99a 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.h +++ b/libraries/plugins/src/plugins/DisplayPlugin.h @@ -188,6 +188,8 @@ public: virtual float renderRate() const { return -1.0f; } // Rate at which we present to the display device virtual float presentRate() const { return -1.0f; } + // Rate at which old frames are presented to the device display + virtual float stutterRate() const { return -1.0f; } // Rate at which new frames are being presented to the display device virtual float newFramePresentRate() const { return -1.0f; } // Rate at which rendered frames are being skipped diff --git a/libraries/plugins/src/plugins/Plugin.cpp b/libraries/plugins/src/plugins/Plugin.cpp index 7c30f252c9..58c71aa97c 100644 --- a/libraries/plugins/src/plugins/Plugin.cpp +++ b/libraries/plugins/src/plugins/Plugin.cpp @@ -7,7 +7,7 @@ // #include "Plugin.h" -QString Plugin::UNKNOWN_PLUGIN_ID("unknown"); +const char* Plugin::UNKNOWN_PLUGIN_ID { "unknown" }; void Plugin::setContainer(PluginContainer* container) { _container = container; diff --git a/libraries/plugins/src/plugins/Plugin.h b/libraries/plugins/src/plugins/Plugin.h index 0452c7fbfe..15588fafa4 100644 --- a/libraries/plugins/src/plugins/Plugin.h +++ b/libraries/plugins/src/plugins/Plugin.h @@ -18,7 +18,7 @@ class Plugin : public QObject { Q_OBJECT public: /// \return human-readable name - virtual const QString& getName() const = 0; + virtual const QString getName() const = 0; typedef enum { STANDARD, ADVANCED, DEVELOPER } grouping; @@ -26,10 +26,10 @@ public: virtual grouping getGrouping() const { return STANDARD; } /// \return string ID (not necessarily human-readable) - virtual const QString& getID() const { assert(false); return UNKNOWN_PLUGIN_ID; } + virtual const QString getID() const { assert(false); return UNKNOWN_PLUGIN_ID; } virtual bool isSupported() const; - + void setContainer(PluginContainer* container); /// Called when plugin is initially loaded, typically at application start @@ -74,6 +74,6 @@ signals: protected: bool _active { false }; PluginContainer* _container { nullptr }; - static QString UNKNOWN_PLUGIN_ID; + static const char* UNKNOWN_PLUGIN_ID; }; diff --git a/libraries/render-utils/src/AnimDebugDraw.cpp b/libraries/render-utils/src/AnimDebugDraw.cpp index 80ba9cd1b2..bb0d5dbfab 100644 --- a/libraries/render-utils/src/AnimDebugDraw.cpp +++ b/libraries/render-utils/src/AnimDebugDraw.cpp @@ -121,7 +121,7 @@ AnimDebugDraw::AnimDebugDraw() : // HACK: add red, green and blue axis at (1,1,1) _animDebugDrawData->_vertexBuffer->resize(sizeof(AnimDebugDrawData::Vertex) * 6); - + static std::vector vertices({ AnimDebugDrawData::Vertex { glm::vec3(1.0, 1.0f, 1.0f), toRGBA(255, 0, 0, 255) }, AnimDebugDrawData::Vertex { glm::vec3(2.0, 1.0f, 1.0f), toRGBA(255, 0, 0, 255) }, @@ -162,9 +162,10 @@ static const uint32_t blue = toRGBA(0, 0, 255, 255); const int NUM_CIRCLE_SLICES = 24; -static void addBone(const AnimPose& rootPose, const AnimPose& pose, float radius, AnimDebugDrawData::Vertex*& v) { +static void addBone(const AnimPose& rootPose, const AnimPose& pose, float radius, glm::vec4& vecColor, AnimDebugDrawData::Vertex*& v) { const float XYZ_AXIS_LENGTH = radius * 4.0f; + const uint32_t color = toRGBA(vecColor); AnimPose finalPose = rootPose * pose; glm::vec3 base = rootPose * pose.trans; @@ -192,10 +193,10 @@ static void addBone(const AnimPose& rootPose, const AnimPose& pose, float radius // x-ring for (int i = 0; i < NUM_CIRCLE_SLICES; i++) { v->pos = xRing[i]; - v->rgba = red; + v->rgba = color; v++; v->pos = xRing[i + 1]; - v->rgba = red; + v->rgba = color; v++; } @@ -210,10 +211,10 @@ static void addBone(const AnimPose& rootPose, const AnimPose& pose, float radius // y-ring for (int i = 0; i < NUM_CIRCLE_SLICES; i++) { v->pos = yRing[i]; - v->rgba = green; + v->rgba = color; v++; v->pos = yRing[i + 1]; - v->rgba = green; + v->rgba = color; v++; } @@ -228,10 +229,10 @@ static void addBone(const AnimPose& rootPose, const AnimPose& pose, float radius // z-ring for (int i = 0; i < NUM_CIRCLE_SLICES; i++) { v->pos = zRing[i]; - v->rgba = blue; + v->rgba = color; v++; v->pos = zRing[i + 1]; - v->rgba = blue; + v->rgba = color; v++; } } @@ -367,7 +368,7 @@ void AnimDebugDraw::update() { const float radius = BONE_RADIUS / (absPoses[i].scale.x * rootPose.scale.x); // draw bone - addBone(rootPose, absPoses[i], radius, v); + addBone(rootPose, absPoses[i], radius, color, v); // draw link to parent auto parentIndex = skeleton->getParentIndex(i); @@ -382,20 +383,18 @@ void AnimDebugDraw::update() { for (auto& iter : markerMap) { glm::quat rot = std::get<0>(iter.second); glm::vec3 pos = std::get<1>(iter.second); - glm::vec4 color = std::get<2>(iter.second); // TODO: currently ignored. - Q_UNUSED(color); + glm::vec4 color = std::get<2>(iter.second); const float radius = POSE_RADIUS; - addBone(AnimPose::identity, AnimPose(glm::vec3(1), rot, pos), radius, v); + addBone(AnimPose::identity, AnimPose(glm::vec3(1), rot, pos), radius, color, v); } AnimPose myAvatarPose(glm::vec3(1), DebugDraw::getInstance().getMyAvatarRot(), DebugDraw::getInstance().getMyAvatarPos()); for (auto& iter : myAvatarMarkerMap) { glm::quat rot = std::get<0>(iter.second); glm::vec3 pos = std::get<1>(iter.second); - glm::vec4 color = std::get<2>(iter.second); // TODO: currently ignored. - Q_UNUSED(color); + glm::vec4 color = std::get<2>(iter.second); const float radius = POSE_RADIUS; - addBone(myAvatarPose, AnimPose(glm::vec3(1), rot, pos), radius, v); + addBone(myAvatarPose, AnimPose(glm::vec3(1), rot, pos), radius, color, v); } // draw rays from shared DebugDraw singleton diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 776c7cfec6..0becf6a8dd 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -1172,7 +1172,8 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac // Guard against meaningless query and fragment parts. // Do NOT use PreferLocalFile as its behavior is unpredictable (e.g., on defaultScriptsLocation()) const auto strippingFlags = QUrl::RemoveFilename | QUrl::RemoveQuery | QUrl::RemoveFragment; - for (QString file : includeFiles) { + for (QString includeFile : includeFiles) { + QString file = ResourceManager::normalizeURL(includeFile); QUrl thisURL; bool isStandardLibrary = false; if (file.startsWith("/~/")) { diff --git a/libraries/script-engine/src/UsersScriptingInterface.cpp b/libraries/script-engine/src/UsersScriptingInterface.cpp index 69ad8e04ad..702368c2b3 100644 --- a/libraries/script-engine/src/UsersScriptingInterface.cpp +++ b/libraries/script-engine/src/UsersScriptingInterface.cpp @@ -29,6 +29,11 @@ void UsersScriptingInterface::kick(const QUuid& nodeID) { DependencyManager::get()->kickNodeBySessionID(nodeID); } +void UsersScriptingInterface::mute(const QUuid& nodeID) { + // ask the NodeList to mute the user with the given session ID + DependencyManager::get()->muteNodeBySessionID(nodeID); +} + bool UsersScriptingInterface::getCanKick() { // ask the NodeList to return our ability to kick return DependencyManager::get()->getThisNodeCanKick(); diff --git a/libraries/script-engine/src/UsersScriptingInterface.h b/libraries/script-engine/src/UsersScriptingInterface.h index 712eeedeb6..3c98d0a393 100644 --- a/libraries/script-engine/src/UsersScriptingInterface.h +++ b/libraries/script-engine/src/UsersScriptingInterface.h @@ -28,6 +28,7 @@ public: public slots: void ignore(const QUuid& nodeID); void kick(const QUuid& nodeID); + void mute(const QUuid& nodeID); bool getCanKick(); diff --git a/libraries/shared/src/HifiConfigVariantMap.cpp b/libraries/shared/src/HifiConfigVariantMap.cpp index 252079f182..a1be6db448 100644 --- a/libraries/shared/src/HifiConfigVariantMap.cpp +++ b/libraries/shared/src/HifiConfigVariantMap.cpp @@ -226,5 +226,5 @@ QVariant* valueForKeyPath(QVariantMap& variantMap, const QString& keyPath, bool shouldCreateIfMissing); } - return NULL; + return nullptr; } diff --git a/libraries/shared/src/shared/Factory.h b/libraries/shared/src/shared/Factory.h index 6f1da6644b..00b7787fdb 100644 --- a/libraries/shared/src/shared/Factory.h +++ b/libraries/shared/src/shared/Factory.h @@ -23,12 +23,12 @@ namespace hifi { using Builder = std::function; using BuilderMap = std::map; - void registerBuilder(const Key& name, Builder builder) { + void registerBuilder(const Key name, Builder builder) { // FIXME don't allow name collisions _builders[name] = builder; } - Pointer create(const Key& name) const { + Pointer create(const Key name) const { const auto& entryIt = _builders.find(name); if (entryIt != _builders.end()) { return (*entryIt).second(); @@ -39,7 +39,7 @@ namespace hifi { template class Registrar { public: - Registrar(const Key& name, SimpleFactory& factory) { + Registrar(const Key name, SimpleFactory& factory) { factory.registerBuilder(name, [] { return std::make_shared(); }); } }; diff --git a/libraries/ui/src/ui/Menu.cpp b/libraries/ui/src/ui/Menu.cpp index aee8b40832..ba24adfc3f 100644 --- a/libraries/ui/src/ui/Menu.cpp +++ b/libraries/ui/src/ui/Menu.cpp @@ -428,6 +428,25 @@ bool Menu::menuExists(const QString& menuName) { return false; } +bool Menu::isMenuEnabled(const QString& menuName) { + QAction* action = getMenuAction(menuName); + + // only proceed if the menu actually exists + if (action) { + return action->isEnabled(); + } + return false; +} + +void Menu::setMenuEnabled(const QString& menuName, bool isEnabled) { + QAction* action = getMenuAction(menuName); + + // only proceed if the menu actually exists + if (action) { + action->setEnabled(isEnabled); + } +} + void Menu::addSeparator(const QString& menuName, const QString& separatorName, const QString& grouping) { MenuWrapper* menuObj = getMenu(menuName); if (menuObj) { diff --git a/libraries/ui/src/ui/Menu.h b/libraries/ui/src/ui/Menu.h index ee60a031c3..2711fc5921 100644 --- a/libraries/ui/src/ui/Menu.h +++ b/libraries/ui/src/ui/Menu.h @@ -106,6 +106,9 @@ public slots: bool isOptionChecked(const QString& menuOption) const; void setIsOptionChecked(const QString& menuOption, bool isChecked); + bool isMenuEnabled(const QString& menuName); + void setMenuEnabled(const QString& menuName, bool isEnabled); + bool getGroupingIsVisible(const QString& grouping); void setGroupingIsVisible(const QString& grouping, bool isVisible); /// NOTE: the "" grouping is always visible diff --git a/plugins/hifiCodec/src/HiFiCodec.cpp b/plugins/hifiCodec/src/HiFiCodec.cpp index 4e9336ff90..77c369dcae 100644 --- a/plugins/hifiCodec/src/HiFiCodec.cpp +++ b/plugins/hifiCodec/src/HiFiCodec.cpp @@ -18,7 +18,7 @@ #include "HiFiCodec.h" -const QString HiFiCodec::NAME = "hifiAC"; +const char* HiFiCodec::NAME { "hifiAC" }; void HiFiCodec::init() { } diff --git a/plugins/hifiCodec/src/HiFiCodec.h b/plugins/hifiCodec/src/HiFiCodec.h index eeba8d56d8..09b247ff7e 100644 --- a/plugins/hifiCodec/src/HiFiCodec.h +++ b/plugins/hifiCodec/src/HiFiCodec.h @@ -16,11 +16,11 @@ class HiFiCodec : public CodecPlugin { Q_OBJECT - + public: // Plugin functions bool isSupported() const override; - const QString& getName() const override { return NAME; } + const QString getName() const override { return NAME; } void init() override; void deinit() override; @@ -36,7 +36,7 @@ public: virtual void releaseDecoder(Decoder* decoder) override; private: - static const QString NAME; + static const char* NAME; }; #endif // hifi_HiFiCodec_h diff --git a/plugins/hifiNeuron/src/NeuronPlugin.cpp b/plugins/hifiNeuron/src/NeuronPlugin.cpp index e41472a8c5..aec8bf072a 100644 --- a/plugins/hifiNeuron/src/NeuronPlugin.cpp +++ b/plugins/hifiNeuron/src/NeuronPlugin.cpp @@ -27,8 +27,8 @@ Q_LOGGING_CATEGORY(inputplugins, "hifi.inputplugins") #include -const QString NeuronPlugin::NAME = "Neuron"; -const QString NeuronPlugin::NEURON_ID_STRING = "Perception Neuron"; +const char* NeuronPlugin::NAME = "Neuron"; +const char* NeuronPlugin::NEURON_ID_STRING = "Perception Neuron"; // indices of joints of the Neuron standard skeleton. // This is 'almost' the same as the High Fidelity standard skeleton. diff --git a/plugins/hifiNeuron/src/NeuronPlugin.h b/plugins/hifiNeuron/src/NeuronPlugin.h index 576deb64ae..36dcf93c23 100644 --- a/plugins/hifiNeuron/src/NeuronPlugin.h +++ b/plugins/hifiNeuron/src/NeuronPlugin.h @@ -29,8 +29,8 @@ public: // Plugin functions virtual bool isSupported() const override; - virtual const QString& getName() const override { return NAME; } - const QString& getID() const override { return NEURON_ID_STRING; } + virtual const QString getName() const override { return NAME; } + const QString getID() const override { return NEURON_ID_STRING; } virtual bool activate() override; virtual void deactivate() override; @@ -65,8 +65,8 @@ protected: std::shared_ptr _inputDevice { std::make_shared() }; - static const QString NAME; - static const QString NEURON_ID_STRING; + static const char* NAME; + static const char* NEURON_ID_STRING; std::string _serverAddress; int _serverPort; diff --git a/plugins/hifiSdl2/src/Joystick.h b/plugins/hifiSdl2/src/Joystick.h index a10e02d325..7ea17739e3 100644 --- a/plugins/hifiSdl2/src/Joystick.h +++ b/plugins/hifiSdl2/src/Joystick.h @@ -29,7 +29,7 @@ class Joystick : public QObject, public controller::InputDevice { public: using Pointer = std::shared_ptr; - const QString& getName() const { return _name; } + const QString getName() const { return _name; } SDL_GameController* getGameController() { return _sdlGameController; } diff --git a/plugins/hifiSdl2/src/SDL2Manager.cpp b/plugins/hifiSdl2/src/SDL2Manager.cpp index b6fa567aee..5ab5758412 100644 --- a/plugins/hifiSdl2/src/SDL2Manager.cpp +++ b/plugins/hifiSdl2/src/SDL2Manager.cpp @@ -41,7 +41,7 @@ static_assert( "SDL2 equvalence: Enums and values from StandardControls.h are assumed to match enums from SDL_gamecontroller.h"); -const QString SDL2Manager::NAME = "SDL2"; +const char* SDL2Manager::NAME = "SDL2"; SDL_JoystickID SDL2Manager::getInstanceId(SDL_GameController* controller) { SDL_Joystick* joystick = SDL_GameControllerGetJoystick(controller); diff --git a/plugins/hifiSdl2/src/SDL2Manager.h b/plugins/hifiSdl2/src/SDL2Manager.h index fc1654bce1..4501d0792b 100644 --- a/plugins/hifiSdl2/src/SDL2Manager.h +++ b/plugins/hifiSdl2/src/SDL2Manager.h @@ -20,11 +20,11 @@ class SDL2Manager : public InputPlugin { Q_OBJECT - + public: // Plugin functions bool isSupported() const override; - const QString& getName() const override { return NAME; } + const QString getName() const override { return NAME; } QStringList getSubdeviceNames() override; bool isHandController() const override { return false; } @@ -39,14 +39,14 @@ public: void pluginFocusOutEvent() override; void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; - + signals: void joystickAdded(Joystick* joystick); void joystickRemoved(Joystick* joystick); - + private: SDL_JoystickID getInstanceId(SDL_GameController* controller); - + int axisInvalid() const { return SDL_CONTROLLER_AXIS_INVALID; } int axisLeftX() const { return SDL_CONTROLLER_AXIS_LEFTX; } int axisLeftY() const { return SDL_CONTROLLER_AXIS_LEFTY; } @@ -55,7 +55,7 @@ private: int axisTriggerLeft() const { return SDL_CONTROLLER_AXIS_TRIGGERLEFT; } int axisTriggerRight() const { return SDL_CONTROLLER_AXIS_TRIGGERRIGHT; } int axisMax() const { return SDL_CONTROLLER_AXIS_MAX; } - + int buttonInvalid() const { return SDL_CONTROLLER_BUTTON_INVALID; } int buttonFaceBottom() const { return SDL_CONTROLLER_BUTTON_A; } int buttonFaceRight() const { return SDL_CONTROLLER_BUTTON_B; } @@ -73,13 +73,13 @@ private: int buttonDpadLeft() const { return SDL_CONTROLLER_BUTTON_DPAD_LEFT; } int buttonDpadRight() const { return SDL_CONTROLLER_BUTTON_DPAD_RIGHT; } int buttonMax() const { return SDL_CONTROLLER_BUTTON_MAX; } - + int buttonPressed() const { return SDL_PRESSED; } int buttonRelease() const { return SDL_RELEASED; } QMap _openJoysticks; bool _isInitialized { false } ; - static const QString NAME; + static const char* NAME; QStringList _subdeviceNames; }; diff --git a/plugins/hifiSixense/src/SixenseManager.cpp b/plugins/hifiSixense/src/SixenseManager.cpp index baf13f1fae..7d443bd50d 100644 --- a/plugins/hifiSixense/src/SixenseManager.cpp +++ b/plugins/hifiSixense/src/SixenseManager.cpp @@ -55,15 +55,15 @@ bool SixenseManager::_sixenseLoaded = false; -const QString SixenseManager::NAME = "Sixense"; -const QString SixenseManager::HYDRA_ID_STRING = "Razer Hydra"; +const char* SixenseManager::NAME { "Sixense" }; +const char* SixenseManager::HYDRA_ID_STRING { "Razer Hydra" }; -const QString MENU_PARENT = "Developer"; -const QString MENU_NAME = "Sixense"; -const QString MENU_PATH = MENU_PARENT + ">" + MENU_NAME; -const QString TOGGLE_SMOOTH = "Smooth Sixense Movement"; -const QString SHOW_DEBUG_RAW = "Debug Draw Raw Data"; -const QString SHOW_DEBUG_CALIBRATED = "Debug Draw Calibrated Data"; +const char* MENU_PARENT { "Developer" }; +const char* MENU_NAME { "Sixense" }; +const char* MENU_PATH { "Developer" ">" "Sixense" }; +const char* TOGGLE_SMOOTH { "Smooth Sixense Movement" }; +const char* SHOW_DEBUG_RAW { "Debug Draw Raw Data" }; +const char* SHOW_DEBUG_CALIBRATED { "Debug Draw Calibrated Data" }; bool SixenseManager::isSupported() const { #if defined(HAVE_SIXENSE) && !defined(Q_OS_OSX) diff --git a/plugins/hifiSixense/src/SixenseManager.h b/plugins/hifiSixense/src/SixenseManager.h index c77569474e..5237dba791 100644 --- a/plugins/hifiSixense/src/SixenseManager.h +++ b/plugins/hifiSixense/src/SixenseManager.h @@ -28,8 +28,8 @@ class SixenseManager : public InputPlugin { public: // Plugin functions virtual bool isSupported() const override; - virtual const QString& getName() const override { return NAME; } - virtual const QString& getID() const override { return HYDRA_ID_STRING; } + virtual const QString getName() const override { return NAME; } + virtual const QString getID() const override { return HYDRA_ID_STRING; } // Sixense always seems to initialize even if the hydras are not present. Is there // a way we can properly detect whether the hydras are present? @@ -92,8 +92,8 @@ private: std::shared_ptr _inputDevice { std::make_shared() }; - static const QString NAME; - static const QString HYDRA_ID_STRING; + static const char* NAME; + static const char* HYDRA_ID_STRING; static bool _sixenseLoaded; }; diff --git a/plugins/hifiSpacemouse/src/SpacemouseManager.h b/plugins/hifiSpacemouse/src/SpacemouseManager.h index a9933902e5..361a1fad2d 100644 --- a/plugins/hifiSpacemouse/src/SpacemouseManager.h +++ b/plugins/hifiSpacemouse/src/SpacemouseManager.h @@ -76,8 +76,8 @@ class SpacemouseManager : public InputPlugin, public QAbstractNativeEventFilter Q_OBJECT public: bool isSupported() const override; - const QString& getName() const override { return NAME; } - const QString& getID() const override { return NAME; } + const QString getName() const override { return NAME; } + const QString getID() const override { return NAME; } bool activate() override; void deactivate() override; @@ -127,7 +127,7 @@ private: // use to calculate distance traveled since last event DWORD fLast3dmouseInputTime; - static const QString NAME; + static const char* NAME; friend class SpacemouseDevice; }; diff --git a/plugins/oculus/src/OculusControllerManager.cpp b/plugins/oculus/src/OculusControllerManager.cpp index f0edc5a465..f56d594919 100644 --- a/plugins/oculus/src/OculusControllerManager.cpp +++ b/plugins/oculus/src/OculusControllerManager.cpp @@ -25,11 +25,11 @@ Q_DECLARE_LOGGING_CATEGORY(oculus) -static const QString MENU_PARENT = "Avatar"; -static const QString MENU_NAME = "Oculus Touch Controllers"; -static const QString MENU_PATH = MENU_PARENT + ">" + MENU_NAME; +static const char* MENU_PARENT = "Avatar"; +static const char* MENU_NAME = "Oculus Touch Controllers"; +static const char* MENU_PATH = "Avatar" ">" "Oculus Touch Controllers"; -const QString OculusControllerManager::NAME = "Oculus"; +const char* OculusControllerManager::NAME = "Oculus"; bool OculusControllerManager::isSupported() const { return oculusAvailable(); diff --git a/plugins/oculus/src/OculusControllerManager.h b/plugins/oculus/src/OculusControllerManager.h index 1ca9e0f47e..98e0e3d650 100644 --- a/plugins/oculus/src/OculusControllerManager.h +++ b/plugins/oculus/src/OculusControllerManager.h @@ -24,7 +24,7 @@ class OculusControllerManager : public InputPlugin { public: // Plugin functions bool isSupported() const override; - const QString& getName() const override { return NAME; } + const QString getName() const override { return NAME; } bool isHandController() const override { return _touch != nullptr; } QStringList getSubdeviceNames() override; @@ -95,7 +95,7 @@ private: ovrInputState _inputState {}; RemoteDevice::Pointer _remote; TouchDevice::Pointer _touch; - static const QString NAME; + static const char* NAME; }; #endif // hifi__OculusControllerManager diff --git a/plugins/oculus/src/OculusDebugDisplayPlugin.cpp b/plugins/oculus/src/OculusDebugDisplayPlugin.cpp index f1d22f3ceb..429d3ecd16 100644 --- a/plugins/oculus/src/OculusDebugDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusDebugDisplayPlugin.cpp @@ -8,7 +8,7 @@ #include "OculusDebugDisplayPlugin.h" #include -const QString OculusDebugDisplayPlugin::NAME("Oculus Rift (Simulator)"); +const char* OculusDebugDisplayPlugin::NAME { "Oculus Rift (Simulator)" }; static const QString DEBUG_FLAG("HIFI_DEBUG_OCULUS"); static bool enableDebugOculus = true || QProcessEnvironment::systemEnvironment().contains("HIFI_DEBUG_OCULUS"); diff --git a/plugins/oculus/src/OculusDebugDisplayPlugin.h b/plugins/oculus/src/OculusDebugDisplayPlugin.h index 983511ba01..ec05cd92e2 100644 --- a/plugins/oculus/src/OculusDebugDisplayPlugin.h +++ b/plugins/oculus/src/OculusDebugDisplayPlugin.h @@ -11,7 +11,7 @@ class OculusDebugDisplayPlugin : public OculusBaseDisplayPlugin { public: - const QString& getName() const override { return NAME; } + const QString getName() const override { return NAME; } grouping getGrouping() const override { return DEVELOPER; } bool isSupported() const override; @@ -20,6 +20,6 @@ protected: bool isHmdMounted() const override { return true; } private: - static const QString NAME; + static const char* NAME; }; diff --git a/plugins/oculus/src/OculusDisplayPlugin.cpp b/plugins/oculus/src/OculusDisplayPlugin.cpp index 415965e948..d7d734f6a2 100644 --- a/plugins/oculus/src/OculusDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusDisplayPlugin.cpp @@ -19,7 +19,7 @@ #include "OculusHelpers.h" -const QString OculusDisplayPlugin::NAME("Oculus Rift"); +const char* OculusDisplayPlugin::NAME { "Oculus Rift" }; static ovrPerfHudMode currentDebugMode = ovrPerfHud_Off; bool OculusDisplayPlugin::internalActivate() { @@ -146,6 +146,16 @@ void OculusDisplayPlugin::hmdPresent() { if (!OVR_SUCCESS(result)) { logWarning("Failed to present"); } + + static int droppedFrames = 0; + ovrPerfStats perfStats; + ovr_GetPerfStats(_session, &perfStats); + for (int i = 0; i < perfStats.FrameStatsCount; ++i) { + const auto& frameStats = perfStats.FrameStats[i]; + int delta = frameStats.CompositorDroppedFrameCount - droppedFrames; + _stutterRate.increment(delta); + droppedFrames = frameStats.CompositorDroppedFrameCount; + } } _presentRate.increment(); } diff --git a/plugins/oculus/src/OculusDisplayPlugin.h b/plugins/oculus/src/OculusDisplayPlugin.h index 0c7d57c4f4..fce8e9e6ce 100644 --- a/plugins/oculus/src/OculusDisplayPlugin.h +++ b/plugins/oculus/src/OculusDisplayPlugin.h @@ -13,7 +13,7 @@ class OculusDisplayPlugin : public OculusBaseDisplayPlugin { using Parent = OculusBaseDisplayPlugin; public: ~OculusDisplayPlugin(); - const QString& getName() const override { return NAME; } + const QString getName() const override { return NAME; } void init() override; @@ -29,7 +29,7 @@ protected: void cycleDebugOutput() override; private: - static const QString NAME; + static const char* NAME; ovrTextureSwapChain _textureSwapChain; gpu::FramebufferPointer _outputFramebuffer; bool _customized { false }; diff --git a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp index b7b6ae5768..09f3e6dc8c 100644 --- a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp +++ b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp @@ -30,7 +30,7 @@ #include "OculusHelpers.h" -const QString OculusLegacyDisplayPlugin::NAME("Oculus Rift"); +const char* OculusLegacyDisplayPlugin::NAME { "Oculus Rift" }; OculusLegacyDisplayPlugin::OculusLegacyDisplayPlugin() { } diff --git a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h index 6ffc1a7f44..20345467df 100644 --- a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h +++ b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h @@ -21,7 +21,7 @@ class OculusLegacyDisplayPlugin : public HmdDisplayPlugin { public: OculusLegacyDisplayPlugin(); bool isSupported() const override; - const QString& getName() const override { return NAME; } + const QString getName() const override { return NAME; } void init() override; @@ -41,9 +41,9 @@ protected: void uncustomizeContext() override; void hmdPresent() override; bool isHmdMounted() const override { return true; } - + private: - static const QString NAME; + static const char* NAME; GLWindow* _hmdWindow{ nullptr }; ovrHmd _hmd; diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index 1a4067a847..eb0e58eb4a 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -33,9 +33,9 @@ Q_DECLARE_LOGGING_CATEGORY(displayplugins) -const QString OpenVrDisplayPlugin::NAME("OpenVR (Vive)"); -const QString StandingHMDSensorMode = "Standing HMD Sensor Mode"; // this probably shouldn't be hardcoded here -const QString OpenVrThreadedSubmit = "OpenVR Threaded Submit"; // this probably shouldn't be hardcoded here +const char* OpenVrDisplayPlugin::NAME { "OpenVR (Vive)" }; +const char* StandingHMDSensorMode { "Standing HMD Sensor Mode" }; // this probably shouldn't be hardcoded here +const char* OpenVrThreadedSubmit { "OpenVR Threaded Submit" }; // this probably shouldn't be hardcoded here PoseData _nextRenderPoseData; PoseData _nextSimPoseData; @@ -641,6 +641,12 @@ void OpenVrDisplayPlugin::hmdPresent() { vr::VRCompositor()->PostPresentHandoff(); _presentRate.increment(); } + + vr::Compositor_FrameTiming frameTiming; + memset(&frameTiming, 0, sizeof(vr::Compositor_FrameTiming)); + frameTiming.m_nSize = sizeof(vr::Compositor_FrameTiming); + vr::VRCompositor()->GetFrameTiming(&frameTiming); + _stutterRate.increment(frameTiming.m_nNumDroppedFrames); } void OpenVrDisplayPlugin::postPreview() { diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.h b/plugins/openvr/src/OpenVrDisplayPlugin.h index 3403bae27c..a60c21a606 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.h +++ b/plugins/openvr/src/OpenVrDisplayPlugin.h @@ -36,7 +36,7 @@ class OpenVrDisplayPlugin : public HmdDisplayPlugin { using Parent = HmdDisplayPlugin; public: bool isSupported() const override; - const QString& getName() const override { return NAME; } + const QString getName() const override { return NAME; } void init() override; @@ -72,7 +72,7 @@ private: vr::IVRSystem* _system { nullptr }; std::atomic _hmdActivityLevel { vr::k_EDeviceActivityLevel_Unknown }; std::atomic _keyboardSupressionCount{ 0 }; - static const QString NAME; + static const char* NAME; vr::HmdMatrix34_t _lastGoodHMDPose; mat4 _sensorResetMat; diff --git a/plugins/openvr/src/OpenVrHelpers.cpp b/plugins/openvr/src/OpenVrHelpers.cpp index 2803ca424e..0c3a2faed6 100644 --- a/plugins/openvr/src/OpenVrHelpers.cpp +++ b/plugins/openvr/src/OpenVrHelpers.cpp @@ -331,7 +331,7 @@ controller::Pose openVrControllerPoseToHandPose(bool isLeftHand, const mat4& mat #define FAILED_MIN_SPEC_OVERLAY_FRIENDLY_NAME "Minimum specifications for SteamVR not met" #define FAILED_MIN_SPEC_UPDATE_INTERVAL_MS 10 #define FAILED_MIN_SPEC_AUTO_QUIT_INTERVAL_MS (MSECS_PER_SECOND * 30) -#define MIN_CORES_SPEC 5 +#define MIN_CORES_SPEC 3 void showMinSpecWarning() { auto vrSystem = acquireOpenVrSystem(); diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index ff8fc64474..2e930c0fdc 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -37,12 +37,12 @@ void releaseOpenVrSystem(); static const char* CONTROLLER_MODEL_STRING = "vr_controller_05_wireless_b"; -static const QString MENU_PARENT = "Avatar"; -static const QString MENU_NAME = "Vive Controllers"; -static const QString MENU_PATH = MENU_PARENT + ">" + MENU_NAME; -static const QString RENDER_CONTROLLERS = "Render Hand Controllers"; +static const char* MENU_PARENT = "Avatar"; +static const char* MENU_NAME = "Vive Controllers"; +static const char* MENU_PATH = "Avatar" ">" "Vive Controllers"; +static const char* RENDER_CONTROLLERS = "Render Hand Controllers"; -const QString ViveControllerManager::NAME = "OpenVR"; +const char* ViveControllerManager::NAME { "OpenVR" }; bool ViveControllerManager::isSupported() const { return openVrSupported(); diff --git a/plugins/openvr/src/ViveControllerManager.h b/plugins/openvr/src/ViveControllerManager.h index 5f34d70ba8..3fb166c842 100644 --- a/plugins/openvr/src/ViveControllerManager.h +++ b/plugins/openvr/src/ViveControllerManager.h @@ -33,7 +33,7 @@ class ViveControllerManager : public InputPlugin { public: // Plugin functions bool isSupported() const override; - const QString& getName() const override { return NAME; } + const QString getName() const override { return NAME; } bool isHandController() const override { return true; } @@ -125,7 +125,7 @@ private: vr::IVRSystem* _system { nullptr }; std::shared_ptr _inputDevice { std::make_shared(_system) }; - static const QString NAME; + static const char* NAME; }; #endif // hifi__ViveControllerManager diff --git a/plugins/pcmCodec/src/PCMCodecManager.cpp b/plugins/pcmCodec/src/PCMCodecManager.cpp index 315d0622ab..7278edaf92 100644 --- a/plugins/pcmCodec/src/PCMCodecManager.cpp +++ b/plugins/pcmCodec/src/PCMCodecManager.cpp @@ -15,7 +15,7 @@ #include "PCMCodecManager.h" -const QString PCMCodec::NAME = "pcm"; +const char* PCMCodec::NAME { "pcm" }; void PCMCodec::init() { } @@ -55,7 +55,7 @@ void PCMCodec::releaseDecoder(Decoder* decoder) { // do nothing } -const QString zLibCodec::NAME = "zlib"; +const char* zLibCodec::NAME { "zlib" }; void zLibCodec::init() { } diff --git a/plugins/pcmCodec/src/PCMCodecManager.h b/plugins/pcmCodec/src/PCMCodecManager.h index 55d7c866f1..d58a219fef 100644 --- a/plugins/pcmCodec/src/PCMCodecManager.h +++ b/plugins/pcmCodec/src/PCMCodecManager.h @@ -16,11 +16,11 @@ class PCMCodec : public CodecPlugin, public Encoder, public Decoder { Q_OBJECT - + public: // Plugin functions bool isSupported() const override; - const QString& getName() const override { return NAME; } + const QString getName() const override { return NAME; } void init() override; void deinit() override; @@ -45,7 +45,7 @@ public: virtual void trackLostFrames(int numFrames) override { } private: - static const QString NAME; + static const char* NAME; }; class zLibCodec : public CodecPlugin, public Encoder, public Decoder { @@ -54,7 +54,7 @@ class zLibCodec : public CodecPlugin, public Encoder, public Decoder { public: // Plugin functions bool isSupported() const override; - const QString& getName() const override { return NAME; } + const QString getName() const override { return NAME; } void init() override; void deinit() override; @@ -80,7 +80,7 @@ public: virtual void trackLostFrames(int numFrames) override { } private: - static const QString NAME; + static const char* NAME; }; #endif // hifi__PCMCodecManager_h diff --git a/scripts/system/assets/images/mute-target.svg b/scripts/system/assets/images/mute-target.svg new file mode 100755 index 0000000000..1ed642c79e --- /dev/null +++ b/scripts/system/assets/images/mute-target.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index 3f6ea17604..06da2b1ac8 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -205,6 +205,32 @@ function overlayFromWorldPoint(point) { return { x: horizontalPixels, y: verticalPixels }; } +var gamePad = Controller.findDevice("GamePad"); +function activeHudPoint2dGamePad() { + if (!HMD.active) { + return; + } + var headPosition = MyAvatar.getHeadPosition(); + var headDirection = Quat.getUp(Quat.multiply(MyAvatar.headOrientation, Quat.angleAxis(-90, { x: 1, y: 0, z: 0 }))); + + var hudPoint3d = calculateRayUICollisionPoint(headPosition, headDirection); + + if (!hudPoint3d) { + if (Menu.isOptionChecked("Overlays")) { // With our hud resetting strategy, hudPoint3d should be valid here + print('Controller is parallel to HUD'); // so let us know that our assumptions are wrong. + } + return; + } + var hudPoint2d = overlayFromWorldPoint(hudPoint3d); + + // We don't know yet if we'll want to make the cursor or laser visble, but we need to move it to see if + // it's pointing at a QML tool (aka system overlay). + setReticlePosition(hudPoint2d); + + return hudPoint2d; +} + + function activeHudPoint2d(activeHand) { // if controller is valid, update reticle position and answer 2d point. Otherwise falsey. var controllerPose = getControllerWorldLocation(activeHand, true); // note: this will return head pose if hand pose is invalid (third eye) if (!controllerPose.valid) { @@ -212,7 +238,7 @@ function activeHudPoint2d(activeHand) { // if controller is valid, update reticl } var controllerPosition = controllerPose.position; var controllerDirection = Quat.getUp(controllerPose.rotation); - + var hudPoint3d = calculateRayUICollisionPoint(controllerPosition, controllerDirection); if (!hudPoint3d) { if (Menu.isOptionChecked("Overlays")) { // With our hud resetting strategy, hudPoint3d should be valid here @@ -407,7 +433,7 @@ clickMapping.from(rightTrigger.full).when(isPointingAtOverlayStartedNonFullTrigg clickMapping.from(leftTrigger.full).when(isPointingAtOverlayStartedNonFullTrigger(leftTrigger)).to(Controller.Actions.ReticleClick); // The following is essentially like Left and Right versions of // clickMapping.from(Controller.Standard.RightSecondaryThumb).peek().to(Controller.Actions.ContextMenu); -// except that we first update the reticle position from the appropriate hand position, before invoking the ContextMenu. +// except that we first update the reticle position from the appropriate hand position, before invoking the . var wantsMenu = 0; clickMapping.from(function () { return wantsMenu; }).to(Controller.Actions.ContextMenu); clickMapping.from(Controller.Standard.RightSecondaryThumb).peek().to(function (clicked) { @@ -422,6 +448,13 @@ clickMapping.from(Controller.Standard.LeftSecondaryThumb).peek().to(function (cl } wantsMenu = clicked; }); +clickMapping.from(Controller.Standard.Start).peek().to(function (clicked) { + if (clicked) { + activeHudPoint2dGamePad(); + } + + wantsMenu = clicked; +}); clickMapping.from(Controller.Hardware.Keyboard.RightMouseClicked).peek().to(function () { // Allow the reticle depth to be set correctly: // Wait a tick for the context menu to be displayed, and then simulate a (non-hand-controller) mouse move @@ -474,13 +507,16 @@ function update() { expireMouseCursor(); clearSystemLaser(); } + updateSeeking(true); if (!handControllerLockOut.expired(now)) { return off(); // Let them use mouse in peace. } + if (!Menu.isOptionChecked("First Person")) { return off(); // What to do? menus can be behind hand! } + if ((!Window.hasFocus() && !HMD.active) || !Reticle.allowMouseCapture) { // In desktop it's pretty clear when another app is on top. In that case we bail, because // hand controllers might be sputtering "valid" data and that will keep someone from deliberately @@ -489,14 +525,18 @@ function update() { // other apps anyway. So in that case, we DO keep going even though we're not on top. (Fogbugz 1831.) return off(); // Don't mess with other apps or paused mouse activity } + leftTrigger.update(); rightTrigger.update(); if (!activeTrigger.state) { return off(); // No trigger } + if (getGrabCommunications()) { return off(); } + + var hudPoint2d = activeHudPoint2d(activeHand); if (!hudPoint2d) { return off(); diff --git a/scripts/system/hmd.js b/scripts/system/hmd.js index 84ff6b3c89..e1c846806f 100644 --- a/scripts/system/hmd.js +++ b/scripts/system/hmd.js @@ -24,10 +24,16 @@ var desktopMenuItemName = "Desktop"; var toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); var button; +// Independent and Entity mode make people sick. Third Person and Mirror have traps that we need to work through. +// Disable them in hmd. +var desktopOnlyViews = ['Third Person', 'Mirror', 'Independent Mode', 'Entity Mode']; function onHmdChanged(isHmd) { button.writeProperty('buttonState', isHmd ? 0 : 1); button.writeProperty('defaultState', isHmd ? 0 : 1); button.writeProperty('hoverState', isHmd ? 2 : 3); + desktopOnlyViews.forEach(function (view) { + Menu.setMenuEnabled("View>" + view, !isHmd); + }); } function onClicked(){ var isDesktop = Menu.isOptionChecked(desktopMenuItemName); diff --git a/scripts/system/mod.js b/scripts/system/mod.js index ea9355e376..7e5cc5d2a5 100644 --- a/scripts/system/mod.js +++ b/scripts/system/mod.js @@ -52,7 +52,9 @@ function removeOverlays() { for (var i = 0; i < modOverlayKeys.length; ++i) { var avatarID = modOverlayKeys[i]; - Overlays.deleteOverlay(modOverlays[avatarID]); + for (var j = 0; j < modOverlays[avatarID].length; ++j) { + Overlays.deleteOverlay(modOverlays[avatarID][j]); + } } modOverlays = {}; @@ -74,10 +76,14 @@ function buttonClicked(){ button.clicked.connect(buttonClicked); -function overlayURL() { +function kickOverlayURL() { return ASSETS_PATH + "/images/" + (Users.canKick ? "kick-target.svg" : "ignore-target.svg"); } +function muteOverlayURL() { + return ASSETS_PATH + "/images/" + "mute-target.svg"; +} + function updateOverlays() { if (isShowingOverlays) { @@ -101,20 +107,28 @@ function updateOverlays() { } // setup a position for the overlay that is just above this avatar's head - var overlayPosition = avatar.getJointPosition("Head"); - overlayPosition.y += 0.45; + var kickOverlayPosition = avatar.getJointPosition("Head"); + kickOverlayPosition.y += 0.45; + var muteOverlayPosition = avatar.getJointPosition("Head"); + muteOverlayPosition.y += 0.70; if (avatarID in modOverlays) { // keep the overlay above the current position of this avatar - Overlays.editOverlay(modOverlays[avatarID], { - position: overlayPosition, - url: overlayURL() + Overlays.editOverlay(modOverlays[avatarID][0], { + position: kickOverlayPosition, + url: kickOverlayURL() }); + if (Users.canKick) { + Overlays.editOverlay(modOverlays[avatarID][1], { + position: muteOverlayPosition, + url: muteOverlayURL() + }); + } } else { // add the overlay above this avatar - var newOverlay = Overlays.addOverlay("image3d", { - url: overlayURL(), - position: overlayPosition, + var newKickOverlay = Overlays.addOverlay("image3d", { + url: kickOverlayURL(), + position: kickOverlayPosition, size: 1, scale: 0.4, color: { red: 255, green: 255, blue: 255}, @@ -124,8 +138,23 @@ function updateOverlays() { drawInFront: true }); - // push this overlay to our array of overlays - modOverlays[avatarID] = newOverlay; + modOverlays[avatarID]=[newKickOverlay]; + + if (Users.canKick) { + var newMuteOverlay = Overlays.addOverlay("image3d", { + url: muteOverlayURL(), + position: muteOverlayPosition, + size: 1, + scale: 0.4, + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + solid: true, + isFacingAvatar: true, + drawInFront: true + }); + // push this overlay to our array of overlays + modOverlays[avatarID].push(newMuteOverlay); + } } } } @@ -137,9 +166,11 @@ AvatarList.avatarRemovedEvent.connect(function(avatarID){ if (isShowingOverlays) { // we are currently showing overlays and an avatar just went away - // first remove the rendered overlay - Overlays.deleteOverlay(modOverlays[avatarID]); - + // first remove the rendered overlays + for (var j = 0; j < modOverlays[avatarID].length; ++j) { + Overlays.deleteOverlay(modOverlays[avatarID][j]); + } + // delete the saved ID of the overlay from our mod overlays object delete modOverlays[avatarID]; } @@ -151,7 +182,8 @@ function handleSelectedOverlay(clickedOverlay) { var modOverlayKeys = Object.keys(modOverlays); for (var i = 0; i < modOverlayKeys.length; ++i) { var avatarID = modOverlayKeys[i]; - var modOverlay = modOverlays[avatarID]; + var modOverlay = modOverlays[avatarID][0]; + var muteOverlay = modOverlays[avatarID][1]; if (clickedOverlay.overlayID == modOverlay) { // matched to an overlay, ask for the matching avatar to be kicked or ignored @@ -160,8 +192,10 @@ function handleSelectedOverlay(clickedOverlay) { } else { Users.ignore(avatarID); } - // cleanup of the overlay is handled by the connection to avatarRemovedEvent + + } else if (muteOverlay && clickedOverlay.overlayID == muteOverlay) { + Users.mute(avatarID); } } } diff --git a/scripts/system/users.js b/scripts/system/users.js index a81903b07a..8c52240aa9 100644 --- a/scripts/system/users.js +++ b/scripts/system/users.js @@ -228,7 +228,7 @@ var usersWindow = (function () { var WINDOW_WIDTH = 260, WINDOW_MARGIN = 12, - WINDOW_BASE_MARGIN = 6, // A little less is needed in order look correct + WINDOW_BASE_MARGIN = 24, // A little less is needed in order look correct WINDOW_FONT = { size: 12 }, @@ -253,11 +253,17 @@ var usersWindow = (function () { windowPane, windowHeading, + // Margin on the left and right side of the window to keep + // it from getting too close to the edge of the screen which + // is unclickable. + WINDOW_MARGIN_X = 20, + // Window border is similar to that of edit.js. - WINDOW_BORDER_WIDTH = WINDOW_WIDTH + 2 * WINDOW_BASE_MARGIN, - WINDOW_BORDER_TOP_MARGIN = 2 * WINDOW_BASE_MARGIN, - WINDOW_BORDER_BOTTOM_MARGIN = WINDOW_BASE_MARGIN, - WINDOW_BORDER_LEFT_MARGIN = WINDOW_BASE_MARGIN, + WINDOW_MARGIN_HALF = WINDOW_MARGIN / 2, + WINDOW_BORDER_WIDTH = WINDOW_WIDTH + 2 * WINDOW_MARGIN_HALF, + WINDOW_BORDER_TOP_MARGIN = 2 * WINDOW_MARGIN_HALF, + WINDOW_BORDER_BOTTOM_MARGIN = WINDOW_MARGIN_HALF, + WINDOW_BORDER_LEFT_MARGIN = WINDOW_MARGIN_HALF, WINDOW_BORDER_RADIUS = 4, WINDOW_BORDER_COLOR = { red: 255, green: 255, blue: 255 }, WINDOW_BORDER_ALPHA = 0.5, @@ -450,7 +456,7 @@ var usersWindow = (function () { } function saturateWindowPosition() { - windowPosition.x = Math.max(0, Math.min(viewport.x - WINDOW_WIDTH, windowPosition.x)); + windowPosition.x = Math.max(WINDOW_MARGIN_X, Math.min(viewport.x - WINDOW_WIDTH - WINDOW_MARGIN_X, windowPosition.x)); windowPosition.y = Math.max(windowMinimumHeight, Math.min(viewport.y, windowPosition.y)); }