diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 47a64413c0..639e9f924b 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -395,8 +395,18 @@ void Agent::executeScript() { if (recordingInterface->getPlayFromCurrentLocation()) { scriptedAvatar->setRecordingBasis(); } + + // these procedural movements are included in the recordings + scriptedAvatar->setHasProceduralEyeFaceMovement(false); + scriptedAvatar->setHasProceduralBlinkFaceMovement(false); + scriptedAvatar->setHasAudioEnabledFaceMovement(false); } else { scriptedAvatar->clearRecordingBasis(); + + // restore procedural blendshape movement + scriptedAvatar->setHasProceduralEyeFaceMovement(true); + scriptedAvatar->setHasProceduralBlinkFaceMovement(true); + scriptedAvatar->setHasAudioEnabledFaceMovement(true); } }); diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index f524c071ec..6c01e6e02b 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -11,6 +11,7 @@ #include "AvatarMixerClientData.h" +#include #include #include @@ -218,6 +219,10 @@ uint16_t AvatarMixerClientData::getLastBroadcastSequenceNumber(const QUuid& node } void AvatarMixerClientData::ignoreOther(SharedNodePointer self, SharedNodePointer other) { + ignoreOther(self.data(), other.data()); +} + +void AvatarMixerClientData::ignoreOther(const Node* self, const Node* other) { if (!isRadiusIgnoring(other->getUUID())) { addToRadiusIgnoringSet(other->getUUID()); auto killPacket = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason), true); @@ -235,9 +240,20 @@ void AvatarMixerClientData::ignoreOther(SharedNodePointer self, SharedNodePointe } } -void AvatarMixerClientData::removeFromRadiusIgnoringSet(SharedNodePointer self, const QUuid& other) { - if (isRadiusIgnoring(other)) { - _radiusIgnoredOthers.erase(other); +bool AvatarMixerClientData::isRadiusIgnoring(const QUuid& other) const { + return std::find(_radiusIgnoredOthers.cbegin(), _radiusIgnoredOthers.cend(), other) != _radiusIgnoredOthers.cend(); +} + +void AvatarMixerClientData::addToRadiusIgnoringSet(const QUuid& other) { + if (!isRadiusIgnoring(other)) { + _radiusIgnoredOthers.push_back(other); + } +} + +void AvatarMixerClientData::removeFromRadiusIgnoringSet(const QUuid& other) { + auto ignoredOtherIter = std::find(_radiusIgnoredOthers.cbegin(), _radiusIgnoredOthers.cend(), other); + if (ignoredOtherIter != _radiusIgnoredOthers.cend()) { + _radiusIgnoredOthers.erase(ignoredOtherIter); } } diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index a892455fe3..d38a90ef1f 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -15,7 +15,7 @@ #include #include #include -#include +#include #include #include @@ -45,6 +45,7 @@ public: int parseData(ReceivedMessage& message) override; AvatarData& getAvatar() { return *_avatar; } + const AvatarData& getAvatar() const { return *_avatar; } const AvatarData* getConstAvatarData() const { return _avatar.get(); } AvatarSharedPointer getAvatarSharedPointer() const { return _avatar; } @@ -90,11 +91,11 @@ public: void loadJSONStats(QJsonObject& jsonObject) const; glm::vec3 getPosition() const { return _avatar ? _avatar->getWorldPosition() : glm::vec3(0); } - glm::vec3 getGlobalBoundingBoxCorner() const { return _avatar ? _avatar->getGlobalBoundingBoxCorner() : glm::vec3(0); } - bool isRadiusIgnoring(const QUuid& other) const { return _radiusIgnoredOthers.find(other) != _radiusIgnoredOthers.end(); } - void addToRadiusIgnoringSet(const QUuid& other) { _radiusIgnoredOthers.insert(other); } - void removeFromRadiusIgnoringSet(SharedNodePointer self, const QUuid& other); + bool isRadiusIgnoring(const QUuid& other) const; + void addToRadiusIgnoringSet(const QUuid& other); + void removeFromRadiusIgnoringSet(const QUuid& other); void ignoreOther(SharedNodePointer self, SharedNodePointer other); + void ignoreOther(const Node* self, const Node* other); void readViewFrustumPacket(const QByteArray& message); @@ -166,7 +167,7 @@ private: int _numOutOfOrderSends = 0; SimpleMovingAverage _avgOtherAvatarDataRate; - std::unordered_set _radiusIgnoredOthers; + std::vector _radiusIgnoredOthers; ConicalViewFrustums _currentViewFrustums; int _recentOtherAvatarsInView { 0 }; diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index c434d82116..7368db0c31 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -33,6 +34,8 @@ #include "AvatarMixer.h" #include "AvatarMixerClientData.h" +namespace chrono = std::chrono; + void AvatarMixerSlave::configure(ConstIter begin, ConstIter end) { _begin = begin; _end = end; @@ -209,7 +212,18 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { _stats.jobElapsedTime += (end - start); } +AABox computeBubbleBox(const AvatarData& avatar, float bubbleExpansionFactor) { + AABox box = avatar.getGlobalBoundingBox(); + glm::vec3 scale = box.getScale(); + scale *= bubbleExpansionFactor; + const glm::vec3 MIN_BUBBLE_SCALE(0.3f, 1.3f, 0.3); + scale = glm::max(scale, MIN_BUBBLE_SCALE); + box.setScaleStayCentered(glm::max(scale, MIN_BUBBLE_SCALE)); + return box; +} + void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) { + const Node* destinationNode = node.data(); auto nodeList = DependencyManager::get(); @@ -220,7 +234,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) _stats.nodesBroadcastedTo++; - AvatarMixerClientData* nodeData = reinterpret_cast(node->getLinkedData()); + AvatarMixerClientData* nodeData = reinterpret_cast(destinationNode->getLinkedData()); nodeData->resetInViewStats(); @@ -242,12 +256,8 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) int traitBytesSent = 0; // max number of avatarBytes per frame - auto maxAvatarBytesPerFrame = (_maxKbpsPerNode * BYTES_PER_KILOBIT) / AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND; + int maxAvatarBytesPerFrame = int(_maxKbpsPerNode * BYTES_PER_KILOBIT / AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND); - // FIXME - find a way to not send the sessionID for every avatar - int minimumBytesPerAvatar = AvatarDataPacket::AVATAR_HAS_FLAGS_SIZE + NUM_BYTES_RFC4122_UUID; - - int overBudgetAvatars = 0; // keep track of the number of other avatars held back in this frame int numAvatarsHeldBack = 0; @@ -260,66 +270,38 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) bool PALIsOpen = nodeData->getRequestsDomainListData(); // When this is true, the AvatarMixer will send Avatar data to a client about avatars that have ignored them - bool getsAnyIgnored = PALIsOpen && node->getCanKick(); + bool getsAnyIgnored = PALIsOpen && destinationNode->getCanKick(); - if (PALIsOpen) { - // Increase minimumBytesPerAvatar if the PAL is open - minimumBytesPerAvatar += sizeof(AvatarDataPacket::AvatarGlobalPosition) + - sizeof(AvatarDataPacket::AudioLoudness); - } + // Bandwidth allowance for data that must be sent. + int minimumBytesPerAvatar = PALIsOpen ? AvatarDataPacket::AVATAR_HAS_FLAGS_SIZE + NUM_BYTES_RFC4122_UUID + + sizeof(AvatarDataPacket::AvatarGlobalPosition) + sizeof(AvatarDataPacket::AudioLoudness) : 0; // setup a PacketList for the avatarPackets auto avatarPacketList = NLPacketList::create(PacketType::BulkAvatarData); + static auto maxAvatarDataBytes = avatarPacketList->getMaxSegmentSize() - NUM_BYTES_RFC4122_UUID; - // Define the minimum bubble size - static const glm::vec3 minBubbleSize = avatar.getSensorToWorldScale() * glm::vec3(0.3f, 1.3f, 0.3f); - // Define the scale of the box for the current node - glm::vec3 nodeBoxScale = (nodeData->getPosition() - nodeData->getGlobalBoundingBoxCorner()) * 2.0f * avatar.getSensorToWorldScale(); - // Set up the bounding box for the current node - AABox nodeBox(nodeData->getGlobalBoundingBoxCorner(), nodeBoxScale); - // Clamp the size of the bounding box to a minimum scale - if (glm::any(glm::lessThan(nodeBoxScale, minBubbleSize))) { - nodeBox.setScaleStayCentered(minBubbleSize); - } - // Quadruple the scale of both bounding boxes - nodeBox.embiggen(4.0f); - - - // setup list of AvatarData as well as maps to map betweeen the AvatarData and the original nodes - std::vector avatarsToSort; - std::unordered_map avatarDataToNodes; - std::unordered_map avatarEncodeTimes; - std::for_each(_begin, _end, [&](const SharedNodePointer& otherNode) { - // make sure this is an agent that we have avatar data for before considering it for inclusion - if (otherNode->getType() == NodeType::Agent - && otherNode->getLinkedData()) { - const AvatarMixerClientData* otherNodeData = reinterpret_cast(otherNode->getLinkedData()); - - AvatarSharedPointer otherAvatar = otherNodeData->getAvatarSharedPointer(); - avatarsToSort.push_back(otherAvatar); - avatarDataToNodes[otherAvatar] = otherNode; - QUuid id = otherAvatar->getSessionUUID(); - avatarEncodeTimes[id] = nodeData->getLastOtherAvatarEncodeTime(id); - } - }); + // compute node bounding box + const float MY_AVATAR_BUBBLE_EXPANSION_FACTOR = 4.0f; // magic number determined emperically + AABox nodeBox = computeBubbleBox(avatar, MY_AVATAR_BUBBLE_EXPANSION_FACTOR); class SortableAvatar: public PrioritySortUtil::Sortable { public: SortableAvatar() = delete; - SortableAvatar(const AvatarSharedPointer& avatar, uint64_t lastEncodeTime) - : _avatar(avatar), _lastEncodeTime(lastEncodeTime) {} - glm::vec3 getPosition() const override { return _avatar->getWorldPosition(); } + SortableAvatar(const AvatarData* avatar, const Node* avatarNode, uint64_t lastEncodeTime) + : _avatar(avatar), _node(avatarNode), _lastEncodeTime(lastEncodeTime) {} + glm::vec3 getPosition() const override { return _avatar->getClientGlobalPosition(); } float getRadius() const override { - glm::vec3 nodeBoxHalfScale = (_avatar->getWorldPosition() - _avatar->getGlobalBoundingBoxCorner() * _avatar->getSensorToWorldScale()); - return glm::max(nodeBoxHalfScale.x, glm::max(nodeBoxHalfScale.y, nodeBoxHalfScale.z)); + glm::vec3 nodeBoxScale = _avatar->getGlobalBoundingBox().getScale(); + return 0.5f * glm::max(nodeBoxScale.x, glm::max(nodeBoxScale.y, nodeBoxScale.z)); } uint64_t getTimestamp() const override { return _lastEncodeTime; } - AvatarSharedPointer getAvatar() const { return _avatar; } + const Node* getNode() const { return _node; } private: - AvatarSharedPointer _avatar; + const AvatarData* _avatar; + const Node* _node; uint64_t _lastEncodeTime; }; @@ -329,16 +311,18 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) AvatarData::_avatarSortCoefficientSize, AvatarData::_avatarSortCoefficientCenter, AvatarData::_avatarSortCoefficientAge); - sortedAvatars.reserve(avatarsToSort.size()); + sortedAvatars.reserve(_end - _begin); - // ignore or sort - const AvatarSharedPointer& thisAvatar = nodeData->getAvatarSharedPointer(); - for (const auto& avatar : avatarsToSort) { - if (avatar == thisAvatar) { - // don't echo updates to self + for (auto listedNode = _begin; listedNode != _end; ++listedNode) { + Node* otherNodeRaw = (*listedNode).data(); + if (otherNodeRaw->getType() != NodeType::Agent + || !otherNodeRaw->getLinkedData() + || otherNodeRaw == destinationNode) { continue; } + auto avatarNode = otherNodeRaw; + bool shouldIgnore = false; // We ignore other nodes for a couple of reasons: // 1) ignore bubbles and ignore specific node @@ -346,53 +330,39 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // happen if for example the avatar is connected on a desktop and sending // updates at ~30hz. So every 3 frames we skip a frame. - auto avatarNode = avatarDataToNodes[avatar]; assert(avatarNode); // we can't have gotten here without the avatarData being a valid key in the map - const AvatarMixerClientData* avatarNodeData = reinterpret_cast(avatarNode->getLinkedData()); - assert(avatarNodeData); // we can't have gotten here without avatarNode having valid data + const AvatarMixerClientData* avatarClientNodeData = reinterpret_cast(avatarNode->getLinkedData()); + assert(avatarClientNodeData); // we can't have gotten here without avatarNode having valid data quint64 startIgnoreCalculation = usecTimestampNow(); // 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 (!avatarNode->getLinkedData() - || avatarNode->getUUID() == node->getUUID() - || (node->isIgnoringNodeWithID(avatarNode->getUUID()) && !PALIsOpen) - || (avatarNode->isIgnoringNodeWithID(node->getUUID()) && !getsAnyIgnored)) { + if ((destinationNode->isIgnoringNodeWithID(avatarNode->getUUID()) && !PALIsOpen) + || (avatarNode->isIgnoringNodeWithID(destinationNode->getUUID()) && !getsAnyIgnored)) { shouldIgnore = true; } else { // Check to see if the space bubble is enabled // Don't bother with these checks if the other avatar has their bubble enabled and we're gettingAnyIgnored - if (node->isIgnoreRadiusEnabled() || (avatarNode->isIgnoreRadiusEnabled() && !getsAnyIgnored)) { - float sensorToWorldScale = avatarNodeData->getAvatarSharedPointer()->getSensorToWorldScale(); - // Define the scale of the box for the current other node - glm::vec3 otherNodeBoxScale = (avatarNodeData->getPosition() - avatarNodeData->getGlobalBoundingBoxCorner()) * 2.0f * sensorToWorldScale; - // Set up the bounding box for the current other node - AABox otherNodeBox(avatarNodeData->getGlobalBoundingBoxCorner(), otherNodeBoxScale); - // Clamp the size of the bounding box to a minimum scale - if (glm::any(glm::lessThan(otherNodeBoxScale, minBubbleSize))) { - otherNodeBox.setScaleStayCentered(minBubbleSize); - } - // Change the scale of both bounding boxes - // (This is an arbitrary number determined empirically) - otherNodeBox.embiggen(2.4f); - + if (destinationNode->isIgnoreRadiusEnabled() || (avatarNode->isIgnoreRadiusEnabled() && !getsAnyIgnored)) { // Perform the collision check between the two bounding boxes + const float OTHER_AVATAR_BUBBLE_EXPANSION_FACTOR = 2.4f; // magic number determined empirically + AABox otherNodeBox = computeBubbleBox(avatarClientNodeData->getAvatar(), OTHER_AVATAR_BUBBLE_EXPANSION_FACTOR); if (nodeBox.touches(otherNodeBox)) { - nodeData->ignoreOther(node, avatarNode); + nodeData->ignoreOther(destinationNode, avatarNode); shouldIgnore = !getsAnyIgnored; } } // Not close enough to ignore if (!shouldIgnore) { - nodeData->removeFromRadiusIgnoringSet(node, avatarNode->getUUID()); + nodeData->removeFromRadiusIgnoringSet(avatarNode->getUUID()); } } if (!shouldIgnore) { AvatarDataSequenceNumber lastSeqToReceiver = nodeData->getLastBroadcastSequenceNumber(avatarNode->getUUID()); - AvatarDataSequenceNumber lastSeqFromSender = avatarNodeData->getLastReceivedSequenceNumber(); + AvatarDataSequenceNumber lastSeqFromSender = avatarClientNodeData->getLastReceivedSequenceNumber(); // FIXME - This code does appear to be working. But it seems brittle. // It supports determining if the frame of data for this "other" @@ -417,12 +387,10 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) if (!shouldIgnore) { // sort this one for later - uint64_t lastEncodeTime = 0; - std::unordered_map::const_iterator itr = avatarEncodeTimes.find(avatar->getSessionUUID()); - if (itr != avatarEncodeTimes.end()) { - lastEncodeTime = itr->second; - } - sortedAvatars.push(SortableAvatar(avatar, lastEncodeTime)); + const AvatarData* avatarNodeData = avatarClientNodeData->getConstAvatarData(); + auto lastEncodeTime = nodeData->getLastOtherAvatarEncodeTime(avatarNodeData->getSessionUUID()); + + sortedAvatars.push(SortableAvatar(avatarNodeData, avatarNode, lastEncodeTime)); } } @@ -430,19 +398,31 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) int remainingAvatars = (int)sortedAvatars.size(); auto traitsPacketList = NLPacketList::create(PacketType::BulkAvatarTraits, QByteArray(), true, true); + const auto& sortedAvatarVector = sortedAvatars.getSortedVector(); for (const auto& sortedAvatar : sortedAvatarVector) { - const auto& avatarData = sortedAvatar.getAvatar(); - remainingAvatars--; + const Node* otherNode = sortedAvatar.getNode(); + auto lastEncodeForOther = sortedAvatar.getTimestamp(); - auto otherNode = avatarDataToNodes[avatarData]; assert(otherNode); // we can't have gotten here without the avatarData being a valid key in the map - // NOTE: Here's where we determine if we are over budget and drop to bare minimum data + AvatarData::AvatarDataDetail detail = AvatarData::NoData; + + // NOTE: Here's where we determine if we are over budget and drop remaining avatars, + // or send minimal avatar data in uncommon case of PALIsOpen. int minimRemainingAvatarBytes = minimumBytesPerAvatar * remainingAvatars; bool overBudget = (identityBytesSent + numAvatarDataBytes + minimRemainingAvatarBytes) > maxAvatarBytesPerFrame; + if (overBudget) { + if (PALIsOpen) { + _stats.overBudgetAvatars++; + detail = AvatarData::PALMinimum; + } else { + _stats.overBudgetAvatars += remainingAvatars; + break; + } + } - quint64 startAvatarDataPacking = usecTimestampNow(); + auto startAvatarDataPacking = chrono::high_resolution_clock::now(); ++numOtherAvatars; @@ -459,32 +439,18 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) nodeData->setLastBroadcastTime(otherNode->getUUID(), usecTimestampNow()); } - // determine if avatar is in view which determines how much data to send - glm::vec3 otherPosition = otherAvatar->getClientGlobalPosition(); - glm::vec3 otherNodeBoxScale = (otherPosition - otherNodeData->getGlobalBoundingBoxCorner()) * 2.0f * otherAvatar->getSensorToWorldScale(); - AABox otherNodeBox(otherNodeData->getGlobalBoundingBoxCorner(), otherNodeBoxScale); - bool isInView = nodeData->otherAvatarInView(otherNodeBox); + // Typically all out-of-view avatars but such avatars' priorities will rise with time: + bool isLowerPriority = sortedAvatar.getPriority() <= OUT_OF_VIEW_THRESHOLD; - // start a new segment in the PacketList for this avatar - avatarPacketList->startSegment(); - - AvatarData::AvatarDataDetail detail; - - if (overBudget) { - overBudgetAvatars++; - _stats.overBudgetAvatars++; - detail = PALIsOpen ? AvatarData::PALMinimum : AvatarData::NoData; - } else if (!isInView) { + if (isLowerPriority) { detail = PALIsOpen ? AvatarData::PALMinimum : AvatarData::MinimumData; nodeData->incrementAvatarOutOfView(); - } else { - detail = distribution(generator) < AVATAR_SEND_FULL_UPDATE_RATIO - ? AvatarData::SendAllData : AvatarData::CullSmallData; + } else if (!overBudget) { + detail = distribution(generator) < AVATAR_SEND_FULL_UPDATE_RATIO ? AvatarData::SendAllData : AvatarData::CullSmallData; nodeData->incrementAvatarInView(); } bool includeThisAvatar = true; - auto lastEncodeForOther = nodeData->getLastOtherAvatarEncodeTime(otherNode->getUUID()); QVector& lastSentJointsForOther = nodeData->getLastOtherAvatarSentJoints(otherNode->getUUID()); lastSentJointsForOther.resize(otherAvatar->getJointCount()); @@ -494,14 +460,14 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) AvatarDataPacket::HasFlags hasFlagsOut; // the result of the toByteArray bool dropFaceTracking = false; - quint64 start = usecTimestampNow(); + auto startSerialize = chrono::high_resolution_clock::now(); QByteArray bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, &lastSentJointsForOther); - quint64 end = usecTimestampNow(); - _stats.toByteArrayElapsedTime += (end - start); + auto endSerialize = chrono::high_resolution_clock::now(); + _stats.toByteArrayElapsedTime += + (quint64) chrono::duration_cast(endSerialize - startSerialize).count(); - static auto maxAvatarDataBytes = avatarPacketList->getMaxSegmentSize() - NUM_BYTES_RFC4122_UUID; if (bytes.size() > maxAvatarDataBytes) { qCWarning(avatars) << "otherAvatar.toByteArray() for" << otherNode->getUUID() << "resulted in very large buffer of" << bytes.size() << "bytes - dropping facial data"; @@ -527,8 +493,11 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) } if (includeThisAvatar) { + // start a new segment in the PacketList for this avatar + avatarPacketList->startSegment(); numAvatarDataBytes += avatarPacketList->write(otherNode->getUUID().toRfc4122()); numAvatarDataBytes += avatarPacketList->write(bytes); + avatarPacketList->endSegment(); if (detail != AvatarData::NoData) { _stats.numOthersIncluded++; @@ -546,15 +515,13 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // It would be nice if we could tweak its future sort priority to put it at the back of the list. } - avatarPacketList->endSegment(); - - quint64 endAvatarDataPacking = usecTimestampNow(); - _stats.avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); + auto endAvatarDataPacking = chrono::high_resolution_clock::now(); + _stats.avatarDataPackingElapsedTime += + (quint64) chrono::duration_cast(endAvatarDataPacking - startAvatarDataPacking).count(); // use helper to add any changed traits to our packet list traitBytesSent += addChangedTraitsToBulkPacket(nodeData, otherNodeData, *traitsPacketList); - - traitsPacketList->getDataSize(); + remainingAvatars--; } quint64 startPacketSending = usecTimestampNow(); @@ -566,7 +533,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) _stats.numBytesSent += numAvatarDataBytes; // send the avatar data PacketList - nodeList->sendPacketList(std::move(avatarPacketList), *node); + nodeList->sendPacketList(std::move(avatarPacketList), *destinationNode); // record the bytes sent for other avatar data in the AvatarMixerClientData nodeData->recordSentAvatarData(numAvatarDataBytes); @@ -576,7 +543,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) if (traitsPacketList->getNumPackets() >= 1) { // send the traits packet list - nodeList->sendPacketList(std::move(traitsPacketList), *node); + nodeList->sendPacketList(std::move(traitsPacketList), *destinationNode); } // record the number of avatars held back this frame diff --git a/assignment-client/src/avatars/ScriptableAvatar.cpp b/assignment-client/src/avatars/ScriptableAvatar.cpp index 6f04cfa196..7d2b267a05 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.cpp +++ b/assignment-client/src/avatars/ScriptableAvatar.cpp @@ -145,3 +145,15 @@ void ScriptableAvatar::update(float deltatime) { _clientTraitsHandler->sendChangedTraitsToMixer(); } + +void ScriptableAvatar::setHasProceduralBlinkFaceMovement(bool hasProceduralBlinkFaceMovement) { + _headData->setHasProceduralBlinkFaceMovement(hasProceduralBlinkFaceMovement); +} + +void ScriptableAvatar::setHasProceduralEyeFaceMovement(bool hasProceduralEyeFaceMovement) { + _headData->setHasProceduralEyeFaceMovement(hasProceduralEyeFaceMovement); +} + +void ScriptableAvatar::setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement) { + _headData->setHasAudioEnabledFaceMovement(hasAudioEnabledFaceMovement); +} diff --git a/assignment-client/src/avatars/ScriptableAvatar.h b/assignment-client/src/avatars/ScriptableAvatar.h index 89f9369133..52beba72a1 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.h +++ b/assignment-client/src/avatars/ScriptableAvatar.h @@ -157,9 +157,16 @@ public: virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking = false) override; + void setHasProceduralBlinkFaceMovement(bool hasProceduralBlinkFaceMovement); + bool getHasProceduralBlinkFaceMovement() const override { return _headData->getHasProceduralBlinkFaceMovement(); } + void setHasProceduralEyeFaceMovement(bool hasProceduralEyeFaceMovement); + bool getHasProceduralEyeFaceMovement() const override { return _headData->getHasProceduralEyeFaceMovement(); } + void setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement); + bool getHasAudioEnabledFaceMovement() const override { return _headData->getHasAudioEnabledFaceMovement(); } + private slots: void update(float deltatime); - + private: AnimationPointer _animation; AnimationDetails _animationDetails; diff --git a/cmake/macros/AutoScribeShader.cmake b/cmake/macros/AutoScribeShader.cmake index 1d1e5970d9..9c5ad70c78 100755 --- a/cmake/macros/AutoScribeShader.cmake +++ b/cmake/macros/AutoScribeShader.cmake @@ -9,7 +9,6 @@ # macro(AUTOSCRIBE_SHADER) - message(STATUS "Processing shader ${SHADER_FILE}") unset(SHADER_INCLUDE_FILES) # Grab include files foreach(includeFile ${ARGN}) diff --git a/cmake/macros/TargetJson.cmake b/cmake/macros/TargetJson.cmake index 057024cd0a..9262c2ce48 100644 --- a/cmake/macros/TargetJson.cmake +++ b/cmake/macros/TargetJson.cmake @@ -8,6 +8,5 @@ macro(TARGET_JSON) add_dependency_external_projects(json) find_package(JSON REQUIRED) - message("JSON_INCLUDE_DIRS ${JSON_INCLUDE_DIRS}") target_include_directories(${TARGET_NAME} PUBLIC ${JSON_INCLUDE_DIRS}) endmacro() \ No newline at end of file diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 990d84a774..cc9fe8ef4c 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -332,6 +332,10 @@ if (APPLE) COMMAND "${CMAKE_COMMAND}" -E copy_directory "${PROJECT_SOURCE_DIR}/resources/fonts" "${RESOURCES_DEV_DIR}/fonts" + # add redirect json to macOS builds. + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "${PROJECT_SOURCE_DIR}/resources/serverless/redirect.json" + "${RESOURCES_DEV_DIR}/serverless/redirect.json" ) # call the fixup_interface macro to add required bundling commands for installation @@ -360,6 +364,9 @@ else() COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${PROJECT_SOURCE_DIR}/resources/serverless/tutorial.json" "${RESOURCES_DEV_DIR}/serverless/tutorial.json" + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "${PROJECT_SOURCE_DIR}/resources/serverless/redirect.json" + "${RESOURCES_DEV_DIR}/serverless/redirect.json" # copy JSDoc files beside the executable COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_SOURCE_DIR}/tools/jsdoc/out" diff --git a/interface/resources/avatar/animations/idle.fbx b/interface/resources/avatar/animations/idle.fbx index 801237036f..88c79185a1 100644 Binary files a/interface/resources/avatar/animations/idle.fbx and b/interface/resources/avatar/animations/idle.fbx differ diff --git a/interface/resources/avatar/animations/jog_bwd.fbx b/interface/resources/avatar/animations/jog_bwd.fbx new file mode 100644 index 0000000000..479392d44c Binary files /dev/null and b/interface/resources/avatar/animations/jog_bwd.fbx differ diff --git a/interface/resources/avatar/animations/jump_in_air.fbx b/interface/resources/avatar/animations/jump_in_air.fbx index 72d8fc269c..2e6ad32f4f 100644 Binary files a/interface/resources/avatar/animations/jump_in_air.fbx and b/interface/resources/avatar/animations/jump_in_air.fbx differ diff --git a/interface/resources/avatar/animations/jump_land.fbx b/interface/resources/avatar/animations/jump_land.fbx index c0034bd091..f057c80085 100644 Binary files a/interface/resources/avatar/animations/jump_land.fbx and b/interface/resources/avatar/animations/jump_land.fbx differ diff --git a/interface/resources/avatar/animations/jump_running_launch_land.fbx b/interface/resources/avatar/animations/jump_running_launch_land.fbx new file mode 100644 index 0000000000..ad922d948e Binary files /dev/null and b/interface/resources/avatar/animations/jump_running_launch_land.fbx differ diff --git a/interface/resources/avatar/animations/jump_standing_apex.fbx b/interface/resources/avatar/animations/jump_standing_apex.fbx index 6385766b4a..53b79758aa 100644 Binary files a/interface/resources/avatar/animations/jump_standing_apex.fbx and b/interface/resources/avatar/animations/jump_standing_apex.fbx differ diff --git a/interface/resources/avatar/animations/jump_standing_land.fbx b/interface/resources/avatar/animations/jump_standing_land.fbx index fd97dae92a..aaeeb186fc 100644 Binary files a/interface/resources/avatar/animations/jump_standing_land.fbx and b/interface/resources/avatar/animations/jump_standing_land.fbx differ diff --git a/interface/resources/avatar/animations/jump_standing_land_settle.fbx b/interface/resources/avatar/animations/jump_standing_land_settle.fbx new file mode 100644 index 0000000000..1a11781e22 Binary files /dev/null and b/interface/resources/avatar/animations/jump_standing_land_settle.fbx differ diff --git a/interface/resources/avatar/animations/jump_standing_launch.fbx b/interface/resources/avatar/animations/jump_standing_launch.fbx new file mode 100644 index 0000000000..06479c8885 Binary files /dev/null and b/interface/resources/avatar/animations/jump_standing_launch.fbx differ diff --git a/interface/resources/avatar/animations/jump_takeoff.fbx b/interface/resources/avatar/animations/jump_takeoff.fbx index 4119b2417f..3976725676 100644 Binary files a/interface/resources/avatar/animations/jump_takeoff.fbx and b/interface/resources/avatar/animations/jump_takeoff.fbx differ diff --git a/interface/resources/avatar/animations/run_bwd.fbx b/interface/resources/avatar/animations/run_bwd.fbx new file mode 100644 index 0000000000..62f49c9729 Binary files /dev/null and b/interface/resources/avatar/animations/run_bwd.fbx differ diff --git a/interface/resources/avatar/animations/settle_to_idle.fbx b/interface/resources/avatar/animations/settle_to_idle.fbx index a5ccbfb490..fba4305e9a 100644 Binary files a/interface/resources/avatar/animations/settle_to_idle.fbx and b/interface/resources/avatar/animations/settle_to_idle.fbx differ diff --git a/interface/resources/avatar/animations/side_step_left.fbx b/interface/resources/avatar/animations/side_step_left.fbx index 4bface0c75..ef8cbe0a5c 100644 Binary files a/interface/resources/avatar/animations/side_step_left.fbx and b/interface/resources/avatar/animations/side_step_left.fbx differ diff --git a/interface/resources/avatar/animations/talk.fbx b/interface/resources/avatar/animations/talk.fbx index ba8f88589e..c29bf1e9bf 100644 Binary files a/interface/resources/avatar/animations/talk.fbx and b/interface/resources/avatar/animations/talk.fbx differ diff --git a/interface/resources/avatar/animations/walk_bwd_fast.fbx b/interface/resources/avatar/animations/walk_bwd_fast.fbx index f2007b03b1..f9a9ba0e26 100644 Binary files a/interface/resources/avatar/animations/walk_bwd_fast.fbx and b/interface/resources/avatar/animations/walk_bwd_fast.fbx differ diff --git a/interface/resources/avatar/animations/walk_fwd.fbx b/interface/resources/avatar/animations/walk_fwd.fbx index 7d8ab94494..8ad461f62e 100644 Binary files a/interface/resources/avatar/animations/walk_fwd.fbx and b/interface/resources/avatar/animations/walk_fwd.fbx differ diff --git a/interface/resources/avatar/animations/walk_fwd_fast.fbx b/interface/resources/avatar/animations/walk_fwd_fast.fbx index 1ba94a798f..db440fc9c4 100644 Binary files a/interface/resources/avatar/animations/walk_fwd_fast.fbx and b/interface/resources/avatar/animations/walk_fwd_fast.fbx differ diff --git a/interface/resources/avatar/animations/walk_short_bwd.fbx b/interface/resources/avatar/animations/walk_short_bwd.fbx index 89e940c888..d7e4bedf53 100644 Binary files a/interface/resources/avatar/animations/walk_short_bwd.fbx and b/interface/resources/avatar/animations/walk_short_bwd.fbx differ diff --git a/interface/resources/avatar/avatar-animation.json b/interface/resources/avatar/avatar-animation.json index 6ebbbf3000..50fe5019f9 100644 --- a/interface/resources/avatar/avatar-animation.json +++ b/interface/resources/avatar/avatar-animation.json @@ -585,149 +585,188 @@ "states": [ { "id": "idle", - "interpTarget": 0, - "interpDuration": 4, + "interpTarget": 20, + "interpDuration": 8, "interpType": "snapshotPrev", "transitions": [ - { "var": "isMovingForward", "state": "idleToWalkFwd" }, - { "var": "isMovingBackward", "state": "walkBwd" }, - { "var": "isMovingRight", "state": "strafeRight" }, - { "var": "isMovingLeft", "state": "strafeLeft" }, + { "var": "isMovingForward", "state": "WALKFWD" }, + { "var": "isMovingBackward", "state": "WALKBWD" }, + { "var": "isMovingRight", "state": "STRAFERIGHT" }, + { "var": "isMovingLeft", "state": "STRAFELEFT" }, { "var": "isTurningRight", "state": "turnRight" }, { "var": "isTurningLeft", "state": "turnLeft" }, { "var": "isFlying", "state": "fly" }, { "var": "isTakeoffStand", "state": "takeoffStand" }, - { "var": "isTakeoffRun", "state": "takeoffRun" }, + { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, { "var": "isInAirStand", "state": "inAirStand" }, - { "var": "isInAirRun", "state": "inAirRun" }, + { "var": "isInAirRun", "state": "INAIRRUN" }, { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } ] }, { "id": "idleToWalkFwd", - "interpTarget": 10, - "interpDuration": 4, - "interpType": "snapshotPrev", + "interpTarget": 12, + "interpDuration": 8, "transitions": [ - { "var": "idleToWalkFwdOnDone", "state": "walkFwd" }, + { "var": "idleToWalkFwdOnDone", "state": "WALKFWD" }, { "var": "isNotMoving", "state": "idle" }, - { "var": "isMovingBackward", "state": "walkBwd" }, - { "var": "isMovingRight", "state": "strafeRight" }, - { "var": "isMovingLeft", "state": "strafeLeft" }, + { "var": "isMovingBackward", "state": "WALKBWD" }, + { "var": "isMovingRight", "state": "STRAFERIGHT" }, + { "var": "isMovingLeft", "state": "STRAFELEFT" }, { "var": "isTurningRight", "state": "turnRight" }, { "var": "isTurningLeft", "state": "turnLeft" }, { "var": "isFlying", "state": "fly" }, { "var": "isTakeoffStand", "state": "takeoffStand" }, - { "var": "isTakeoffRun", "state": "takeoffRun" }, + { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, { "var": "isInAirStand", "state": "inAirStand" }, - { "var": "isInAirRun", "state": "inAirRun" }, + { "var": "isInAirRun", "state": "INAIRRUN" }, { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } ] }, { "id": "idleSettle", - "interpTarget": 10, - "interpDuration": 10, + "interpTarget": 15, + "interpDuration": 8, "interpType": "snapshotPrev", "transitions": [ {"var": "idleSettleOnDone", "state": "idle" }, - {"var": "isMovingForward", "state": "idleToWalkFwd" }, - { "var": "isMovingBackward", "state": "walkBwd" }, - { "var": "isMovingRight", "state": "strafeRight" }, - { "var": "isMovingLeft", "state": "strafeLeft" }, + {"var": "isMovingForward", "state": "WALKFWD" }, + { "var": "isMovingBackward", "state": "WALKBWD" }, + { "var": "isMovingRight", "state": "STRAFERIGHT" }, + { "var": "isMovingLeft", "state": "STRAFELEFT" }, { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" }, { "var": "isTurningRight", "state": "turnRight" }, { "var": "isTurningLeft", "state": "turnLeft" }, { "var": "isFlying", "state": "fly" }, { "var": "isTakeoffStand", "state": "takeoffStand" }, - { "var": "isTakeoffRun", "state": "takeoffRun" }, + { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, { "var": "isInAirStand", "state": "inAirStand" }, - { "var": "isInAirRun", "state": "inAirRun" } + { "var": "isInAirRun", "state": "INAIRRUN" } ] }, { - "id": "walkFwd", - "interpTarget": 16, - "interpDuration": 6, + "id": "WALKFWD", + "interpTarget": 35, + "interpDuration": 10, "interpType": "snapshotPrev", "transitions": [ { "var": "isNotMoving", "state": "idleSettle" }, - { "var": "isMovingBackward", "state": "walkBwd" }, - { "var": "isMovingRight", "state": "strafeRight" }, - { "var": "isMovingLeft", "state": "strafeLeft" }, + { "var": "isMovingBackward", "state": "WALKBWD" }, + { "var": "isMovingRight", "state": "STRAFERIGHT" }, + { "var": "isMovingLeft", "state": "STRAFELEFT" }, { "var": "isTurningRight", "state": "turnRight" }, { "var": "isTurningLeft", "state": "turnLeft" }, { "var": "isFlying", "state": "fly" }, { "var": "isTakeoffStand", "state": "takeoffStand" }, - { "var": "isTakeoffRun", "state": "takeoffRun" }, + { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, { "var": "isInAirStand", "state": "inAirStand" }, - { "var": "isInAirRun", "state": "inAirRun" }, + { "var": "isInAirRun", "state": "INAIRRUN" }, { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } ] }, { - "id": "walkBwd", - "interpTarget": 8, - "interpDuration": 6, + "id": "WALKBWD", + "interpTarget": 35, + "interpDuration": 10, "interpType": "snapshotPrev", "transitions": [ { "var": "isNotMoving", "state": "idleSettle" }, - { "var": "isMovingForward", "state": "walkFwd" }, - { "var": "isMovingRight", "state": "strafeRight" }, - { "var": "isMovingLeft", "state": "strafeLeft" }, + { "var": "isMovingForward", "state": "WALKFWD" }, + { "var": "isMovingRight", "state": "STRAFERIGHT" }, + { "var": "isMovingLeft", "state": "STRAFELEFT" }, { "var": "isTurningRight", "state": "turnRight" }, { "var": "isTurningLeft", "state": "turnLeft" }, { "var": "isFlying", "state": "fly" }, { "var": "isTakeoffStand", "state": "takeoffStand" }, - { "var": "isTakeoffRun", "state": "takeoffRun" }, + { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, { "var": "isInAirStand", "state": "inAirStand" }, - { "var": "isInAirRun", "state": "inAirRun" }, + { "var": "isInAirRun", "state": "INAIRRUN" }, { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } ] }, { - "id": "strafeRight", - "interpTarget": 5, + "id": "STRAFERIGHT", + "interpTarget": 25, "interpDuration": 8, "interpType": "snapshotPrev", "transitions": [ { "var": "isNotMoving", "state": "idleSettle" }, - { "var": "isMovingForward", "state": "walkFwd" }, - { "var": "isMovingBackward", "state": "walkBwd" }, - { "var": "isMovingLeft", "state": "strafeLeft" }, + { "var": "isMovingForward", "state": "WALKFWD" }, + { "var": "isMovingBackward", "state": "WALKBWD" }, + { "var": "isMovingLeft", "state": "STRAFELEFT" }, { "var": "isTurningRight", "state": "turnRight" }, { "var": "isTurningLeft", "state": "turnLeft" }, { "var": "isFlying", "state": "fly" }, { "var": "isTakeoffStand", "state": "takeoffStand" }, - { "var": "isTakeoffRun", "state": "takeoffRun" }, + { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, { "var": "isInAirStand", "state": "inAirStand" }, - { "var": "isInAirRun", "state": "inAirRun" }, + { "var": "isInAirRun", "state": "INAIRRUN" }, { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } ] }, { - "id": "strafeLeft", - "interpTarget": 5, + "id": "STRAFELEFT", + "interpTarget": 25, "interpDuration": 8, "interpType": "snapshotPrev", "transitions": [ { "var": "isNotMoving", "state": "idleSettle" }, - { "var": "isMovingForward", "state": "walkFwd" }, - { "var": "isMovingBackward", "state": "walkBwd" }, - { "var": "isMovingRight", "state": "strafeRight" }, + { "var": "isMovingForward", "state": "WALKFWD" }, + { "var": "isMovingBackward", "state": "WALKBWD" }, + { "var": "isMovingRight", "state": "STRAFERIGHT" }, { "var": "isTurningRight", "state": "turnRight" }, { "var": "isTurningLeft", "state": "turnLeft" }, { "var": "isFlying", "state": "fly" }, { "var": "isTakeoffStand", "state": "takeoffStand" }, - { "var": "isTakeoffRun", "state": "takeoffRun" }, + { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, { "var": "isInAirStand", "state": "inAirStand" }, - { "var": "isInAirRun", "state": "inAirRun" }, + { "var": "isInAirRun", "state": "INAIRRUN" }, + { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, + { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } + ] + }, + { + "id": "turnRight", + "interpTarget": 6, + "interpDuration": 8, + "transitions": [ + { "var": "isNotTurning", "state": "idle" }, + { "var": "isMovingForward", "state": "WALKFWD" }, + { "var": "isMovingBackward", "state": "WALKBWD" }, + { "var": "isMovingRight", "state": "STRAFERIGHT" }, + { "var": "isMovingLeft", "state": "STRAFELEFT" }, + { "var": "isTurningLeft", "state": "turnLeft" }, + { "var": "isFlying", "state": "fly" }, + { "var": "isTakeoffStand", "state": "takeoffStand" }, + { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, + { "var": "isInAirStand", "state": "inAirStand" }, + { "var": "isInAirRun", "state": "INAIRRUN" }, + { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, + { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } + ] + }, + { + "id": "turnLeft", + "interpTarget": 6, + "interpDuration": 8, + "transitions": [ + { "var": "isNotTurning", "state": "idle" }, + { "var": "isMovingForward", "state": "WALKFWD" }, + { "var": "isMovingBackward", "state": "WALKBWD" }, + { "var": "isMovingRight", "state": "STRAFERIGHT" }, + { "var": "isMovingLeft", "state": "STRAFELEFT" }, + { "var": "isTurningRight", "state": "turnRight" }, + { "var": "isFlying", "state": "fly" }, + { "var": "isTakeoffStand", "state": "takeoffStand" }, + { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, + { "var": "isInAirStand", "state": "inAirStand" }, + { "var": "isInAirRun", "state": "INAIRRUN" }, { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } ] @@ -739,18 +778,18 @@ "interpType": "snapshotPrev", "transitions": [ { "var": "isNotMoving", "state": "idleSettle" }, - { "var": "isMovingForward", "state": "walkFwd" }, - { "var": "isMovingBackward", "state": "walkBwd" }, + { "var": "isMovingForward", "state": "WALKFWD" }, + { "var": "isMovingBackward", "state": "WALKBWD" }, { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" }, - { "var": "isMovingRight", "state": "strafeRight" }, - { "var": "isMovingLeft", "state": "strafeLeft" }, + { "var": "isMovingRight", "state": "STRAFERIGHT" }, + { "var": "isMovingLeft", "state": "STRAFELEFT" }, { "var": "isTurningRight", "state": "turnRight" }, { "var": "isTurningLeft", "state": "turnLeft" }, { "var": "isFlying", "state": "fly" }, { "var": "isTakeoffStand", "state": "takeoffStand" }, - { "var": "isTakeoffRun", "state": "takeoffRun" }, + { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, { "var": "isInAirStand", "state": "inAirStand" }, - { "var": "isInAirRun", "state": "inAirRun" } + { "var": "isInAirRun", "state": "INAIRRUN" } ] }, { @@ -760,60 +799,18 @@ "interpType": "snapshotPrev", "transitions": [ { "var": "isNotMoving", "state": "idleSettle" }, - { "var": "isMovingForward", "state": "walkFwd" }, - { "var": "isMovingBackward", "state": "walkBwd" }, + { "var": "isMovingForward", "state": "WALKFWD" }, + { "var": "isMovingBackward", "state": "WALKBWD" }, { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, - { "var": "isMovingRight", "state": "strafeRight" }, - { "var": "isMovingLeft", "state": "strafeLeft" }, + { "var": "isMovingRight", "state": "STRAFERIGHT" }, + { "var": "isMovingLeft", "state": "STRAFELEFT" }, { "var": "isTurningRight", "state": "turnRight" }, { "var": "isTurningLeft", "state": "turnLeft" }, { "var": "isFlying", "state": "fly" }, { "var": "isTakeoffStand", "state": "takeoffStand" }, - { "var": "isTakeoffRun", "state": "takeoffRun" }, + { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, { "var": "isInAirStand", "state": "inAirStand" }, - { "var": "isInAirRun", "state": "inAirRun" } - ] - }, - { - "id": "turnRight", - "interpTarget": 6, - "interpDuration": 8, - "interpType": "snapshotPrev", - "transitions": [ - { "var": "isNotTurning", "state": "idle" }, - { "var": "isMovingForward", "state": "walkFwd" }, - { "var": "isMovingBackward", "state": "walkBwd" }, - { "var": "isMovingRight", "state": "strafeRight" }, - { "var": "isMovingLeft", "state": "strafeLeft" }, - { "var": "isTurningLeft", "state": "turnLeft" }, - { "var": "isFlying", "state": "fly" }, - { "var": "isTakeoffStand", "state": "takeoffStand" }, - { "var": "isTakeoffRun", "state": "takeoffRun" }, - { "var": "isInAirStand", "state": "inAirStand" }, - { "var": "isInAirRun", "state": "inAirRun" }, - { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, - { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } - ] - }, - { - "id": "turnLeft", - "interpTarget": 6, - "interpDuration": 8, - "interpType": "snapshotPrev", - "transitions": [ - { "var": "isNotTurning", "state": "idle" }, - { "var": "isMovingForward", "state": "walkFwd" }, - { "var": "isMovingBackward", "state": "walkBwd" }, - { "var": "isMovingRight", "state": "strafeRight" }, - { "var": "isMovingLeft", "state": "strafeLeft" }, - { "var": "isTurningRight", "state": "turnRight" }, - { "var": "isFlying", "state": "fly" }, - { "var": "isTakeoffStand", "state": "takeoffStand" }, - { "var": "isTakeoffRun", "state": "takeoffRun" }, - { "var": "isInAirStand", "state": "inAirStand" }, - { "var": "isInAirRun", "state": "inAirRun" }, - { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, - { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } + { "var": "isInAirRun", "state": "INAIRRUN" } ] }, { @@ -826,79 +823,79 @@ }, { "id": "takeoffStand", - "interpTarget": 0, - "interpDuration": 6, + "interpTarget": 2, + "interpDuration": 2, "transitions": [ { "var": "isNotTakeoff", "state": "inAirStand" } ] }, { - "id": "takeoffRun", - "interpTarget": 0, - "interpDuration": 6, + "id": "TAKEOFFRUN", + "interpTarget": 2, + "interpDuration": 2, "transitions": [ - { "var": "isNotTakeoff", "state": "inAirRun" } + { "var": "isNotTakeoff", "state": "INAIRRUN" } ] }, { "id": "inAirStand", - "interpTarget": 0, - "interpDuration": 6, + "interpTarget": 3, + "interpDuration": 3, "interpType": "snapshotPrev", "transitions": [ { "var": "isNotInAir", "state": "landStandImpact" } ] }, { - "id": "inAirRun", - "interpTarget": 0, - "interpDuration": 6, + "id": "INAIRRUN", + "interpTarget": 3, + "interpDuration": 3, "interpType": "snapshotPrev", "transitions": [ - { "var": "isNotInAir", "state": "landRun" } + { "var": "isNotInAir", "state": "WALKFWD" } ] }, { "id": "landStandImpact", - "interpTarget": 6, - "interpDuration": 4, + "interpTarget": 1, + "interpDuration": 1, "transitions": [ { "var": "isFlying", "state": "fly" }, { "var": "isTakeoffStand", "state": "takeoffStand" }, - { "var": "isTakeoffRun", "state": "takeoffRun" }, + { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, { "var": "landStandImpactOnDone", "state": "landStand" } ] }, { "id": "landStand", - "interpTarget": 0, + "interpTarget": 1, "interpDuration": 1, "transitions": [ - { "var": "isMovingForward", "state": "idleToWalkFwd" }, - { "var": "isMovingBackward", "state": "walkBwd" }, - { "var": "isMovingRight", "state": "strafeRight" }, - { "var": "isMovingLeft", "state": "strafeLeft" }, + { "var": "isMovingForward", "state": "WALKFWD" }, + { "var": "isMovingBackward", "state": "WALKBWD" }, + { "var": "isMovingRight", "state": "STRAFERIGHT" }, + { "var": "isMovingLeft", "state": "STRAFELEFT" }, { "var": "isTurningRight", "state": "turnRight" }, { "var": "isTurningLeft", "state": "turnLeft" }, { "var": "isFlying", "state": "fly" }, { "var": "isTakeoffStand", "state": "takeoffStand" }, - { "var": "isTakeoffRun", "state": "takeoffRun" }, + { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, { "var": "isInAirStand", "state": "inAirStand" }, - { "var": "isInAirRun", "state": "inAirRun" }, + { "var": "isInAirRun", "state": "INAIRRUN" }, { "var": "landStandOnDone", "state": "idle" }, { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } ] }, { - "id": "landRun", - "interpTarget": 1, - "interpDuration": 7, + "id": "LANDRUN", + "interpTarget": 2, + "interpDuration": 2, "transitions": [ { "var": "isFlying", "state": "fly" }, { "var": "isTakeoffStand", "state": "takeoffStand" }, - { "var": "isTakeoffRun", "state": "takeoffRun" }, - { "var": "landRunOnDone", "state": "walkFwd" } + { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, + { "var": "landRunOnDone", "state": "WALKFWD" } ] } ] @@ -913,7 +910,7 @@ { "id": "idleStand", "interpTarget": 6, - "interpDuration": 6, + "interpDuration": 10, "transitions": [ { "var": "isTalking", "state": "idleTalk" } ] @@ -921,7 +918,7 @@ { "id": "idleTalk", "interpTarget": 6, - "interpDuration": 6, + "interpDuration": 10, "transitions": [ { "var": "notIsTalking", "state": "idleStand" } ] @@ -956,12 +953,12 @@ ] }, { - "id": "walkFwd", + "id": "WALKFWD", "type": "blendLinearMove", "data": { "alpha": 0.0, "desiredSpeed": 1.4, - "characteristicSpeeds": [0.5, 1.5, 2.5, 3.2, 4.5], + "characteristicSpeeds": [0.5, 1.8, 2.3, 3.2, 4.5], "alphaVar": "moveForwardAlpha", "desiredSpeedVar": "moveForwardSpeed" }, @@ -984,7 +981,7 @@ "data": { "url": "qrc:///avatar/animations/walk_fwd.fbx", "startFrame": 0.0, - "endFrame": 35.0, + "endFrame": 30.0, "timeScale": 1.0, "loopFlag": true }, @@ -1046,25 +1043,25 @@ "data": { "url": "qrc:///avatar/animations/settle_to_idle.fbx", "startFrame": 1.0, - "endFrame": 48.0, + "endFrame": 59.0, "timeScale": 1.0, "loopFlag": false }, "children": [] }, { - "id": "walkBwd", + "id": "WALKBWD", "type": "blendLinearMove", "data": { "alpha": 0.0, "desiredSpeed": 1.4, - "characteristicSpeeds": [0.6, 1.7], + "characteristicSpeeds": [0.6, 1.6, 2.3, 3.1], "alphaVar": "moveBackwardAlpha", "desiredSpeedVar": "moveBackwardSpeed" }, "children": [ { - "id": "walkBwdShort", + "id": "walkBwdShort_c", "type": "clip", "data": { "url": "qrc:///avatar/animations/walk_short_bwd.fbx", @@ -1076,7 +1073,7 @@ "children": [] }, { - "id": "walkBwdNormal", + "id": "walkBwdFast_c", "type": "clip", "data": { "url": "qrc:///avatar/animations/walk_bwd_fast.fbx", @@ -1086,6 +1083,30 @@ "loopFlag": true }, "children": [] + }, + { + "id": "jogBwd_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jog_bwd.fbx", + "startFrame": 0.0, + "endFrame": 24.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "runBwd_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/run_bwd.fbx", + "startFrame": 0.0, + "endFrame": 16.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] } ] }, @@ -1115,18 +1136,18 @@ "children": [] }, { - "id": "strafeLeft", + "id": "STRAFELEFT", "type": "blendLinearMove", "data": { "alpha": 0.0, "desiredSpeed": 1.4, - "characteristicSpeeds": [0, 0.5, 1.5, 2.6, 3.0], + "characteristicSpeeds": [0.1, 0.5, 1.0, 2.6, 3.0], "alphaVar": "moveLateralAlpha", "desiredSpeedVar": "moveLateralSpeed" }, "children": [ { - "id": "strafeLeftShort_c", + "id": "strafeLeftShortStep_c", "type": "clip", "data": { "url": "qrc:///avatar/animations/side_step_short_left.fbx", @@ -1138,7 +1159,7 @@ "children": [] }, { - "id": "strafeLeft_c", + "id": "strafeLeftStep_c", "type": "clip", "data": { "url": "qrc:///avatar/animations/side_step_left.fbx", @@ -1150,19 +1171,19 @@ "children": [] }, { - "id": "strafeLeftAnim_c", + "id": "strafeLeftWalk_c", "type": "clip", "data": { "url": "qrc:///avatar/animations/walk_left.fbx", "startFrame": 0.0, - "endFrame": 33.0, + "endFrame": 35.0, "timeScale": 1.0, "loopFlag": true }, "children": [] }, { - "id": "strafeLeftFast_c", + "id": "strafeLeftWalkFast_c", "type": "clip", "data": { "url": "qrc:///avatar/animations/walk_left_fast.fbx", @@ -1188,17 +1209,17 @@ ] }, { - "id": "strafeRight", + "id": "STRAFERIGHT", "type": "blendLinearMove", "data": { "alpha": 0.0, "desiredSpeed": 1.4, - "characteristicSpeeds": [0, 0.5, 1.5, 2.6, 3.0], + "characteristicSpeeds": [0.1, 0.5, 1.0, 2.6, 3.0], "alphaVar": "moveLateralAlpha", "desiredSpeedVar": "moveLateralSpeed" }, "children": [ { - "id": "stepRightShort_c", + "id": "strafeRightShortStep_c", "type": "clip", "data": { "url": "qrc:///avatar/animations/side_step_short_left.fbx", @@ -1211,7 +1232,7 @@ "children": [] }, { - "id": "stepRight_c", + "id": "strafeRightStep_c", "type": "clip", "data": { "url": "qrc:///avatar/animations/side_step_left.fbx", @@ -1224,12 +1245,12 @@ "children": [] }, { - "id": "strafeRight_c", + "id": "strafeRightWalk_c", "type": "clip", "data": { "url": "qrc:///avatar/animations/walk_left.fbx", "startFrame": 0.0, - "endFrame": 33.0, + "endFrame": 35.0, "timeScale": 1.0, "loopFlag": true, "mirrorFlag": true @@ -1381,22 +1402,22 @@ "id": "takeoffStand", "type": "clip", "data": { - "url": "qrc:///avatar/animations/jump_standing_takeoff.fbx", - "startFrame": 17.0, - "endFrame": 25.0, + "url": "qrc:///avatar/animations/jump_standing_launch.fbx", + "startFrame": 2.0, + "endFrame": 16.0, "timeScale": 1.0, "loopFlag": false }, "children": [] }, { - "id": "takeoffRun", + "id": "TAKEOFFRUN", "type": "clip", "data": { - "url": "qrc:///avatar/animations/jump_takeoff.fbx", - "startFrame": 1.0, - "endFrame": 2.5, - "timeScale": 0.01, + "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", + "startFrame": 4.0, + "endFrame": 15.0, + "timeScale": 1.0, "loopFlag": false }, "children": [] @@ -1416,7 +1437,7 @@ "url": "qrc:///avatar/animations/jump_standing_apex.fbx", "startFrame": 0.0, "endFrame": 0.0, - "timeScale": 0.0, + "timeScale": 1.0, "loopFlag": false }, "children": [] @@ -1448,7 +1469,7 @@ ] }, { - "id": "inAirRun", + "id": "INAIRRUN", "type": "blendLinear", "data": { "alpha": 0.0, @@ -1459,10 +1480,10 @@ "id": "inAirRunPreApex", "type": "clip", "data": { - "url": "qrc:///avatar/animations/jump_in_air.fbx", - "startFrame": 0.0, - "endFrame": 0.0, - "timeScale": 0.0, + "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", + "startFrame": 16.0, + "endFrame": 16.0, + "timeScale": 1.0, "loopFlag": false }, "children": [] @@ -1471,9 +1492,9 @@ "id": "inAirRunApex", "type": "clip", "data": { - "url": "qrc:///avatar/animations/jump_in_air.fbx", - "startFrame": 6.0, - "endFrame": 6.0, + "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", + "startFrame": 22.0, + "endFrame": 22.0, "timeScale": 1.0, "loopFlag": false }, @@ -1483,9 +1504,9 @@ "id": "inAirRunPostApex", "type": "clip", "data": { - "url": "qrc:///avatar/animations/jump_in_air.fbx", - "startFrame": 11.0, - "endFrame": 11.0, + "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", + "startFrame": 33.0, + "endFrame": 33.0, "timeScale": 1.0, "loopFlag": false }, @@ -1497,7 +1518,7 @@ "id": "landStandImpact", "type": "clip", "data": { - "url": "qrc:///avatar/animations/jump_standing_land.fbx", + "url": "qrc:///avatar/animations/jump_standing_land_settle.fbx", "startFrame": 1.0, "endFrame": 6.0, "timeScale": 1.0, @@ -1509,22 +1530,22 @@ "id": "landStand", "type": "clip", "data": { - "url": "qrc:///avatar/animations/jump_standing_land.fbx", + "url": "qrc:///avatar/animations/jump_standing_land_settle.fbx", "startFrame": 6.0, - "endFrame": 28.0, + "endFrame": 68.0, "timeScale": 1.0, "loopFlag": false }, "children": [] }, { - "id": "landRun", + "id": "LANDRUN", "type": "clip", "data": { - "url": "qrc:///avatar/animations/jump_land.fbx", - "startFrame": 1.0, - "endFrame": 6.0, - "timeScale": 0.65, + "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", + "startFrame": 29.0, + "endFrame": 40.0, + "timeScale": 1.0, "loopFlag": false }, "children": [] diff --git a/interface/resources/icons/tablet-icons/people-a-msg.svg b/interface/resources/icons/tablet-icons/people-a-msg.svg new file mode 100644 index 0000000000..862ce936ce --- /dev/null +++ b/interface/resources/icons/tablet-icons/people-a-msg.svg @@ -0,0 +1,83 @@ + + + +image/svg+xml + + \ No newline at end of file diff --git a/interface/resources/icons/tablet-icons/people-i-msg.svg b/interface/resources/icons/tablet-icons/people-i-msg.svg new file mode 100644 index 0000000000..635a01be4b --- /dev/null +++ b/interface/resources/icons/tablet-icons/people-i-msg.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + diff --git a/interface/resources/images/eyeClosed.svg b/interface/resources/images/eyeClosed.svg new file mode 100644 index 0000000000..6719471b3d --- /dev/null +++ b/interface/resources/images/eyeClosed.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/interface/resources/images/eyeOpen.svg b/interface/resources/images/eyeOpen.svg new file mode 100644 index 0000000000..ec5ceb5238 --- /dev/null +++ b/interface/resources/images/eyeOpen.svg @@ -0,0 +1,4 @@ + + + + diff --git a/interface/resources/images/interstitialPage/goTo_button.png b/interface/resources/images/interstitialPage/goTo_button.png new file mode 100644 index 0000000000..7c1b0d8500 Binary files /dev/null and b/interface/resources/images/interstitialPage/goTo_button.png differ diff --git a/interface/resources/images/loadingBar_placard.png b/interface/resources/images/loadingBar_placard.png new file mode 100644 index 0000000000..9fd4c9e71f Binary files /dev/null and b/interface/resources/images/loadingBar_placard.png differ diff --git a/interface/resources/images/loadingBar_progress.png b/interface/resources/images/loadingBar_progress.png new file mode 100644 index 0000000000..2b1fb089d9 Binary files /dev/null and b/interface/resources/images/loadingBar_progress.png differ diff --git a/interface/resources/meshes/redirect/oopsDialog_auth.fbx b/interface/resources/meshes/redirect/oopsDialog_auth.fbx new file mode 100644 index 0000000000..3360de993f Binary files /dev/null and b/interface/resources/meshes/redirect/oopsDialog_auth.fbx differ diff --git a/interface/resources/meshes/redirect/oopsDialog_auth.png b/interface/resources/meshes/redirect/oopsDialog_auth.png new file mode 100644 index 0000000000..2165cfbfa9 Binary files /dev/null and b/interface/resources/meshes/redirect/oopsDialog_auth.png differ diff --git a/interface/resources/meshes/redirect/oopsDialog_protocol.fbx b/interface/resources/meshes/redirect/oopsDialog_protocol.fbx new file mode 100644 index 0000000000..794a5f2dfd Binary files /dev/null and b/interface/resources/meshes/redirect/oopsDialog_protocol.fbx differ diff --git a/interface/resources/meshes/redirect/oopsDialog_protocol.png b/interface/resources/meshes/redirect/oopsDialog_protocol.png new file mode 100644 index 0000000000..63c6e00c4e Binary files /dev/null and b/interface/resources/meshes/redirect/oopsDialog_protocol.png differ diff --git a/interface/resources/meshes/redirect/oopsDialog_timeout.fbx b/interface/resources/meshes/redirect/oopsDialog_timeout.fbx new file mode 100644 index 0000000000..846d38bbee Binary files /dev/null and b/interface/resources/meshes/redirect/oopsDialog_timeout.fbx differ diff --git a/interface/resources/meshes/redirect/oopsDialog_timeout.png b/interface/resources/meshes/redirect/oopsDialog_timeout.png new file mode 100644 index 0000000000..b118f4417c Binary files /dev/null and b/interface/resources/meshes/redirect/oopsDialog_timeout.png differ diff --git a/interface/resources/meshes/redirect/oopsDialog_vague.fbx b/interface/resources/meshes/redirect/oopsDialog_vague.fbx new file mode 100644 index 0000000000..707f09e51a Binary files /dev/null and b/interface/resources/meshes/redirect/oopsDialog_vague.fbx differ diff --git a/interface/resources/meshes/redirect/oopsDialog_vague.png b/interface/resources/meshes/redirect/oopsDialog_vague.png new file mode 100644 index 0000000000..8a02ad2c27 Binary files /dev/null and b/interface/resources/meshes/redirect/oopsDialog_vague.png differ diff --git a/interface/resources/qml/LoginDialog.qml b/interface/resources/qml/LoginDialog.qml index e21e8b7354..336858502d 100644 --- a/interface/resources/qml/LoginDialog.qml +++ b/interface/resources/qml/LoginDialog.qml @@ -23,6 +23,7 @@ ModalWindow { objectName: "LoginDialog" implicitWidth: 520 implicitHeight: 320 + closeButtonVisible: true destroyOnCloseButton: true destroyOnHidden: true visible: true diff --git a/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml index bf7807c85d..96b638c911 100644 --- a/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml @@ -117,27 +117,27 @@ Item { } spacing: hifi.dimensions.contentSpacing.y / 2 - TextField { - id: usernameField - anchors { - horizontalCenter: parent.horizontalCenter - } - width: 1080 - placeholderText: qsTr("Username or Email") + TextField { + id: usernameField + anchors { + horizontalCenter: parent.horizontalCenter } + width: 1080 + placeholderText: qsTr("Username or Email") + } - TextField { - id: passwordField - anchors { - horizontalCenter: parent.horizontalCenter - } - width: 1080 - - placeholderText: qsTr("Password") - echoMode: TextInput.Password - - Keys.onReturnPressed: linkAccountBody.login() + TextField { + id: passwordField + anchors { + horizontalCenter: parent.horizontalCenter } + width: 1080 + + placeholderText: qsTr("Password") + echoMode: TextInput.Password + + Keys.onReturnPressed: linkAccountBody.login() + } } InfoItem { @@ -176,7 +176,7 @@ Item { anchors { left: parent.left top: form.bottom - topMargin: hifi.dimensions.contentSpacing.y / 2 + topMargin: hifi.dimensions.contentSpacing.y / 2 } spacing: hifi.dimensions.contentSpacing.x @@ -201,7 +201,7 @@ Item { anchors { right: parent.right top: form.bottom - topMargin: hifi.dimensions.contentSpacing.y / 2 + topMargin: hifi.dimensions.contentSpacing.y / 2 } spacing: hifi.dimensions.contentSpacing.x onHeightChanged: d.resize(); onWidthChanged: d.resize(); diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index 4c6e5f6fce..57293cb5e3 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -15,7 +15,6 @@ import QtQuick.Controls.Styles 1.4 as OriginalStyles import "../controls-uit" import "../styles-uit" - Item { id: linkAccountBody clip: true @@ -87,6 +86,23 @@ Item { height: 48 } + ShortcutText { + id: flavorText + anchors { + top: parent.top + left: parent.left + margins: 0 + topMargin: hifi.dimensions.contentSpacing.y + } + + text: qsTr("Sign in to High Fidelity to make friends, get HFC, and buy interesting things on the Marketplace!") + width: parent.width + wrapMode: Text.WordWrap + lineHeight: 1 + lineHeightMode: Text.ProportionalHeight + horizontalAlignment: Text.AlignHCenter + } + ShortcutText { id: mainTextContainer anchors { @@ -97,7 +113,6 @@ Item { } visible: false - text: qsTr("Username or password incorrect.") wrapMode: Text.WordWrap color: hifi.colors.redAccent @@ -117,22 +132,21 @@ Item { } spacing: 2 * hifi.dimensions.contentSpacing.y - TextField { id: usernameField text: Settings.getValue("wallet/savedUsername", ""); width: parent.width focus: true - label: "Username or Email" + placeholderText: "Username or Email" activeFocusOnPress: true ShortcutText { z: 10 + y: usernameField.height anchors { - left: usernameField.left - top: usernameField.top - leftMargin: usernameField.textFieldLabel.contentWidth + 10 - topMargin: -19 + right: usernameField.right + top: usernameField.bottom + topMargin: 4 } text: "Forgot Username?" @@ -143,26 +157,32 @@ Item { onLinkActivated: loginDialog.openUrl(link) } + onFocusChanged: { root.text = ""; } + Component.onCompleted: { + var savedUsername = Settings.getValue("wallet/savedUsername", ""); + usernameField.text = savedUsername === "Unknown user" ? "" : savedUsername; + } } TextField { id: passwordField width: parent.width - - label: "Password" - echoMode: showPassword.checked ? TextInput.Normal : TextInput.Password + placeholderText: "Password" activeFocusOnPress: true + echoMode: TextInput.Password + onHeightChanged: d.resize(); onWidthChanged: d.resize(); ShortcutText { + id: forgotPasswordShortcut + y: passwordField.height z: 10 anchors { - left: passwordField.left - top: passwordField.top - leftMargin: passwordField.textFieldLabel.contentWidth + 10 - topMargin: -19 + right: passwordField.right + top: passwordField.bottom + topMargin: 4 } text: "Forgot Password?" @@ -179,12 +199,45 @@ Item { root.isPassword = true; } - Keys.onReturnPressed: linkAccountBody.login() - } + Rectangle { + id: showPasswordHitbox + z: 10 + x: passwordField.width - ((passwordField.height) * 31 / 23) + width: parent.width - (parent.width - (parent.height * 31/16)) + height: parent.height + anchors { + right: parent.right + } + color: "transparent" - CheckBox { - id: showPassword - text: "Show password" + Image { + id: showPasswordImage + y: (passwordField.height - (passwordField.height * 16 / 23)) / 2 + width: passwordField.width - (passwordField.width - (((passwordField.height) * 31/23))) + height: passwordField.height * 16 / 23 + anchors { + right: parent.right + rightMargin: 3 + } + source: "../../images/eyeOpen.svg" + } + + MouseArea { + id: passwordFieldMouseArea + anchors.fill: parent + acceptedButtons: Qt.LeftButton + property bool showPassword: false + onClicked: { + showPassword = !showPassword; + passwordField.echoMode = showPassword ? TextInput.Normal : TextInput.Password; + showPasswordImage.source = showPassword ? "../../images/eyeClosed.svg" : "../../images/eyeOpen.svg"; + showPasswordImage.height = showPassword ? passwordField.height : passwordField.height * 16 / 23; + showPasswordImage.y = showPassword ? 0 : (passwordField.height - showPasswordImage.height) / 2; + } + } + } + + Keys.onReturnPressed: linkAccountBody.login() } InfoItem { @@ -206,6 +259,26 @@ Item { onHeightChanged: d.resize(); onWidthChanged: d.resize(); anchors.horizontalCenter: parent.horizontalCenter + CheckBox { + id: autoLogoutCheckbox + checked: !Settings.getValue("wallet/autoLogout", true) + text: "Keep me signed in" + boxSize: 20; + labelFontSize: 15 + color: hifi.colors.black + onCheckedChanged: { + Settings.setValue("wallet/autoLogout", !checked); + if (checked) { + Settings.setValue("wallet/savedUsername", Account.username); + } else { + Settings.setValue("wallet/savedUsername", ""); + } + } + Component.onDestruction: { + Settings.setValue("wallet/autoLogout", !checked); + } + } + Button { id: linkAccountButton anchors.verticalCenter: parent.verticalCenter @@ -216,12 +289,6 @@ Item { onClicked: linkAccountBody.login() } - - Button { - anchors.verticalCenter: parent.verticalCenter - text: qsTr("Cancel") - onClicked: root.tryDestroy() - } } Row { @@ -234,7 +301,7 @@ Item { RalewaySemiBold { size: hifi.fontSizes.inputLabel anchors.verticalCenter: parent.verticalCenter - text: qsTr("Don't have an account?") + text: qsTr("New to High Fidelity?") } Button { @@ -279,7 +346,15 @@ Item { target: loginDialog onHandleLoginCompleted: { console.log("Login Succeeded, linking steam account") - + var poppedUp = Settings.getValue("loginDialogPoppedUp", false); + if (poppedUp) { + console.log("[ENCOURAGELOGINDIALOG]: logging in") + var data = { + "action": "user logged in" + }; + UserActivityLogger.logAction("encourageLoginDialog", data); + Settings.setValue("loginDialogPoppedUp", false); + } if (loginDialog.isSteamRunning()) { loginDialog.linkSteam() } else { @@ -290,6 +365,15 @@ Item { } onHandleLoginFailed: { console.log("Login Failed") + var poppedUp = Settings.getValue("loginDialogPoppedUp", false); + if (poppedUp) { + console.log("[ENCOURAGELOGINDIALOG]: failed logging in") + var data = { + "action": "user failed logging in" + }; + UserActivityLogger.logAction("encourageLoginDialog", data); + Settings.setValue("loginDialogPoppedUp", false); + } mainTextContainer.visible = true toggleLoading(false) } diff --git a/interface/resources/qml/hifi/AvatarApp.qml b/interface/resources/qml/hifi/AvatarApp.qml index d8eae311d8..aea5931627 100644 --- a/interface/resources/qml/hifi/AvatarApp.qml +++ b/interface/resources/qml/hifi/AvatarApp.qml @@ -46,7 +46,7 @@ Rectangle { } } - property var jointNames; + property var jointNames: [] property var currentAvatarSettings; function fetchAvatarModelName(marketId, avatar) { @@ -175,7 +175,14 @@ Rectangle { displayNameInput.text = getAvatarsData.displayName; currentAvatarSettings = getAvatarsData.currentAvatarSettings; - updateCurrentAvatarInBookmarks(currentAvatar); + var bookmarkAvatarIndex = allAvatars.findAvatarIndexByValue(currentAvatar); + if (bookmarkAvatarIndex === -1) { + currentAvatar.name = ''; + } else { + currentAvatar.name = allAvatars.get(bookmarkAvatarIndex).name; + allAvatars.move(bookmarkAvatarIndex, 0, 1); + } + view.setPage(0); } else if (message.method === 'updateAvatarInBookmarks') { updateCurrentAvatarInBookmarks(currentAvatar); } else if (message.method === 'selectAvatarEntity') { diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index 35a0078d32..e8fc41da63 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -271,6 +271,8 @@ Rectangle { connectionsUserModel.getFirstPage(); } activeTab = "connectionsTab"; + connectionsOnlineDot.visible = false; + pal.sendToScript({method: 'hideNotificationDot'}); connectionsHelpText.color = hifi.colors.blueAccent; } } @@ -298,6 +300,16 @@ Rectangle { } } } + Rectangle { + id: connectionsOnlineDot; + visible: false; + width: 10; + height: width; + radius: width; + color: "#EF3B4E" + anchors.left: parent.left; + anchors.verticalCenter: parent.verticalCenter; + } // "CONNECTIONS" text RalewaySemiBold { id: connectionsTabSelectorText; @@ -305,7 +317,11 @@ Rectangle { // Text size size: hifi.fontSizes.tabularData; // Anchors - anchors.fill: parent; + anchors.left: connectionsOnlineDot.visible ? connectionsOnlineDot.right : parent.left; + anchors.leftMargin: connectionsOnlineDot.visible ? 4 : 0; + anchors.top: parent.top; + anchors.bottom: parent.bottom; + anchors.right: parent.right; // Style font.capitalization: Font.AllUppercase; color: activeTab === "connectionsTab" ? hifi.colors.blueAccent : hifi.colors.baseGray; @@ -326,7 +342,7 @@ Rectangle { anchors.left: connectionsTabSelectorTextContainer.left; anchors.top: connectionsTabSelectorTextContainer.top; anchors.topMargin: 1; - anchors.leftMargin: connectionsTabSelectorTextMetrics.width + 42; + anchors.leftMargin: connectionsTabSelectorTextMetrics.width + 42 + connectionsOnlineDot.width + connectionsTabSelectorText.anchors.leftMargin; RalewayRegular { id: connectionsHelpText; text: "[?]"; @@ -1267,6 +1283,9 @@ Rectangle { case 'http.response': http.handleHttpResponse(message); break; + case 'changeConnectionsDotStatus': + connectionsOnlineDot.visible = message.shouldShowDot; + break; default: console.log('Unrecognized message:', JSON.stringify(message)); } diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index cc1ba49984..f4a708567a 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -26,7 +26,7 @@ Rectangle { HifiConstants { id: hifi; } property var eventBridge; - property string title: "Audio Settings - " + AudioScriptingInterface.context; + property string title: "Audio Settings" signal sendToScript(var message); color: hifi.colors.baseGray; diff --git a/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml b/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml index e80dab60c2..5bf867a331 100644 --- a/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml +++ b/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml @@ -25,7 +25,17 @@ Rectangle { modified = false; } - property var jointNames; + property var jointNames: [] + onJointNamesChanged: { + jointsModel.clear(); + for (var i = 0; i < jointNames.length; ++i) { + var jointName = jointNames[i]; + if (jointName !== 'LeftHand' && jointName !== 'RightHand') { + jointsModel.append({'text' : jointName, 'jointIndex' : i}); + } + } + } + property string avatarName: '' property var wearablesModel; @@ -268,20 +278,30 @@ Rectangle { anchors.right: parent.right enabled: getCurrentWearable() !== null && !isSoft.checked comboBox.displayText: isSoft.checked ? 'Hips' : comboBox.currentText + comboBox.textRole: "text" - model: jointNames + model: ListModel { + id: jointsModel + } property bool notify: false function set(jointIndex) { notify = false; - currentIndex = jointIndex; + for (var i = 0; i < jointsModel.count; ++i) { + if (jointsModel.get(i).jointIndex === jointIndex) { + currentIndex = i; + break; + } + } notify = true; } function notifyJointChanged() { modified = true; + var jointIndex = jointsModel.get(jointsCombobox.currentIndex).jointIndex; + var properties = { - parentJointIndex: currentIndex, + parentJointIndex: jointIndex, localPosition: { x: positionVector.xvalue, y: positionVector.yvalue, @@ -294,7 +314,7 @@ Rectangle { } }; - wearableUpdated(getCurrentWearable().id, wearablesCombobox.currentIndex, properties); + wearableUpdated(getCurrentWearable().id, jointIndex, properties); } onCurrentIndexChanged: { diff --git a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml index 65d98af234..47b9d354d0 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml @@ -408,9 +408,7 @@ Rectangle { Connections { onSendSignalToWallet: { - if (msg.method === 'walletReset' || msg.method === 'passphraseReset') { - sendToScript(msg); - } else if (msg.method === 'walletSecurity_changeSecurityImage') { + if (msg.method === 'walletSecurity_changeSecurityImage') { securityImageChange.initModel(); root.activeView = "securityImageChange"; } diff --git a/interface/resources/qml/hifi/dialogs/TabletDCDialog.qml b/interface/resources/qml/hifi/dialogs/TabletDCDialog.qml index 1ec7ea3e0e..afe06897df 100644 --- a/interface/resources/qml/hifi/dialogs/TabletDCDialog.qml +++ b/interface/resources/qml/hifi/dialogs/TabletDCDialog.qml @@ -18,6 +18,7 @@ import "../../windows" Rectangle { id: root objectName: "DCConectionTiming" + property string title: "Domain Connection Timing" signal sendToScript(var message); property bool isHMD: false @@ -33,7 +34,7 @@ Rectangle { Row { id: header anchors.top: parent.top - anchors.topMargin: hifi.dimensions.tabletMenuHeader + anchors.topMargin: hifi.dimensions.contentMargin.y anchors.leftMargin: 5 anchors.rightMargin: 5 anchors.left: parent.left diff --git a/interface/resources/qml/hifi/dialogs/TabletEntityStatistics.qml b/interface/resources/qml/hifi/dialogs/TabletEntityStatistics.qml index 7bce460283..24798af21a 100644 --- a/interface/resources/qml/hifi/dialogs/TabletEntityStatistics.qml +++ b/interface/resources/qml/hifi/dialogs/TabletEntityStatistics.qml @@ -18,6 +18,7 @@ import "../../windows" Rectangle { id: root objectName: "EntityStatistics" + property string title: "Entity Statistics" signal sendToScript(var message); property bool isHMD: false @@ -40,6 +41,7 @@ Rectangle { id: scrollView width: parent.width anchors.top: parent.top + anchors.topMargin: hifi.dimensions.contentMargin.y anchors.bottom: parent.bottom anchors.bottomMargin: hifi.dimensions.tabletMenuHeader contentWidth: column.implicitWidth @@ -48,10 +50,15 @@ Rectangle { Column { id: column - anchors.margins: 10 + anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - y: hifi.dimensions.tabletMenuHeader //-bgNavBar + anchors { + topMargin: 0 + leftMargin: 10 + rightMargin: 10 + bottomMargin: 0 + } spacing: 20 TabletEntityStatisticsItem { diff --git a/interface/resources/qml/hifi/tablet/ControllerSettings.qml b/interface/resources/qml/hifi/tablet/ControllerSettings.qml index 135c1379e2..6706830537 100644 --- a/interface/resources/qml/hifi/tablet/ControllerSettings.qml +++ b/interface/resources/qml/hifi/tablet/ControllerSettings.qml @@ -24,6 +24,8 @@ Item { height: parent.height width: parent.width + property string title: "Controls" + HifiConstants { id: hifi } TabBar { diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index c9d05aea51..3d518289fb 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -79,7 +79,7 @@ StackView { return; } location.text = targetString; - toggleOrGo(true, targetString); + toggleOrGo(targetString, true); clearAddressLineTimer.start(); } @@ -105,7 +105,6 @@ StackView { propagateComposedEvents: true onPressed: { parent.forceActiveFocus(); - addressBarDialog.keyboardEnabled = false; mouse.accepted = false; } } @@ -223,7 +222,6 @@ StackView { updateLocationText(text.length > 0); } onAccepted: { - addressBarDialog.keyboardEnabled = false; toggleOrGo(); } @@ -378,7 +376,7 @@ StackView { HifiControls.Keyboard { id: keyboard - raised: parent.keyboardEnabled + raised: parent.keyboardEnabled && parent.keyboardRaised numeric: parent.punctuationMode anchors { bottom: parent.bottom @@ -401,7 +399,7 @@ StackView { } } - function toggleOrGo(fromSuggestions, address) { + function toggleOrGo(address, fromSuggestions) { if (address !== undefined && address !== "") { addressBarDialog.loadAddress(address, fromSuggestions); clearAddressLineTimer.start(); diff --git a/interface/resources/qml/hifi/tablet/TabletMenu.qml b/interface/resources/qml/hifi/tablet/TabletMenu.qml index 57c1163eb8..6540d53fca 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenu.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenu.qml @@ -23,6 +23,8 @@ FocusScope { property string subMenu: "" signal sendToScript(var message); + HifiConstants { id: hifi } + Rectangle { id: bgNavBar height: 90 @@ -45,24 +47,22 @@ FocusScope { anchors.topMargin: 0 anchors.top: parent.top - Image { + HiFiGlyphs { id: menuRootIcon - width: 40 - height: 40 - source: "../../../icons/tablet-icons/menu-i.svg" + text: breadcrumbText.text !== "Menu" ? hifi.glyphs.backward : "" + size: 72 anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left - anchors.leftMargin: 15 + width: breadcrumbText.text === "Menu" ? 32 : 50 + visible: breadcrumbText.text !== "Menu" MouseArea { anchors.fill: parent hoverEnabled: true onEntered: iconColorOverlay.color = "#1fc6a6"; onExited: iconColorOverlay.color = "#34a2c7"; - // navigate back to root level menu onClicked: { - buildMenu(); - breadcrumbText.text = "Menu"; + menuPopperUpper.closeLastMenu(); tabletRoot.playButtonClickSound(); } } @@ -79,23 +79,10 @@ FocusScope { id: breadcrumbText text: "Menu" size: 26 - color: "#34a2c7" + color: "#e3e3e3" anchors.verticalCenter: parent.verticalCenter anchors.left: menuRootIcon.right anchors.leftMargin: 15 - MouseArea { - anchors.fill: parent - hoverEnabled: true - onEntered: breadcrumbText.color = "#1fc6a6"; - onExited: breadcrumbText.color = "#34a2c7"; - // navigate back to parent level menu if there is one - onClicked: { - if (breadcrumbText.text !== "Menu") { - menuPopperUpper.closeLastMenu(); - } - tabletRoot.playButtonClickSound(); - } - } } } @@ -103,7 +90,6 @@ FocusScope { menuPopperUpper.closeLastMenu(); } - function setRootMenu(rootMenu, subMenu) { tabletMenu.subMenu = subMenu; tabletMenu.rootMenu = rootMenu; diff --git a/interface/resources/qml/hifi/tablet/TabletMenuStack.qml b/interface/resources/qml/hifi/tablet/TabletMenuStack.qml index 4d36a57793..fe636dafa5 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenuStack.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenuStack.qml @@ -1,5 +1,5 @@ // -// MessageDialog.qml +// TabletMenuStack.qml // // Created by Dante Ruiz on 13 Feb 2017 // Copyright 2016 High Fidelity, Inc. @@ -66,7 +66,7 @@ Item { function popSource() { console.log("trying to pop page"); - d.pop(); + closeLastMenu(); } function toModel(items, newMenu) { diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml b/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml index 05f45dc61e..ec2c003383 100644 --- a/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml +++ b/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml @@ -41,7 +41,15 @@ Item { section.saveAll(); } - closeDialog(); + if (HMD.active) { + if (gotoPreviousApp) { + tablet.returnToPreviousApp(); + } else { + tablet.popFromStack(); + } + } else { + closeDialog(); + } } function restoreAll() { @@ -50,7 +58,15 @@ Item { section.restoreAll(); } - closeDialog(); + if (HMD.active) { + if (gotoPreviousApp) { + tablet.returnToPreviousApp(); + } else { + tablet.popFromStack(); + } + } else { + closeDialog(); + } } function closeDialog() { diff --git a/interface/resources/qml/windows/ModalFrame.qml b/interface/resources/qml/windows/ModalFrame.qml index 211353b5f3..cb23ccd5ad 100644 --- a/interface/resources/qml/windows/ModalFrame.qml +++ b/interface/resources/qml/windows/ModalFrame.qml @@ -94,5 +94,25 @@ Frame { color: hifi.colors.lightGray } } + + + GlyphButton { + id: closeButton + visible: window.closeButtonVisible + width: 30 + y: -hifi.dimensions.modalDialogTitleHeight + anchors { + top: parent.top + right: parent.right + topMargin: 10 + rightMargin: 10 + } + glyph: hifi.glyphs.close + size: 23 + onClicked: { + window.clickedCloseButton = true; + window.destroy(); + } + } } } diff --git a/interface/resources/qml/windows/ModalWindow.qml b/interface/resources/qml/windows/ModalWindow.qml index 2d56099051..ebd5293b0a 100644 --- a/interface/resources/qml/windows/ModalWindow.qml +++ b/interface/resources/qml/windows/ModalWindow.qml @@ -19,6 +19,9 @@ ScrollingWindow { destroyOnHidden: true frame: ModalFrame { } + property bool closeButtonVisible: false + // only applicable for if close button is visible. + property bool clickedCloseButton: false property int colorScheme: hifi.colorSchemes.light property bool draggable: false diff --git a/interface/resources/serverless/redirect.json b/interface/resources/serverless/redirect.json new file mode 100644 index 0000000000..64cb4d8a3f --- /dev/null +++ b/interface/resources/serverless/redirect.json @@ -0,0 +1,934 @@ +{ + "DataVersion": 0, + "Paths": + { + "/": "/4,1.4,4/0,0.49544,0,0.868645" + }, + "Entities": [ + { + "clientOnly": false, + "collidesWith": "static,dynamic,kinematic,otherAvatar,", + "collisionMask": 23, + "created": "2018-09-05T18:13:00Z", + "dimensions": { + "blue": 1.159199833869934, + "green": 2.8062009811401367, + "red": 1.6216505765914917, + "x": 1.6216505765914917, + "y": 2.8062009811401367, + "z": 1.159199833869934 + }, + "id": "{d0ed60b8-9174-4c56-8e78-2c5399329ae0}", + "lastEdited": 1536171372916208, + "lastEditedBy": "{151cb20e-715a-4c80-aa0d-5b58b1c8a0c9}", + "locked": true, + "name": "Try Again Zone", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue":4.015342712402344, + "green":1.649999976158142, + "red":2.00921893119812, + "x":2.00921893119812, + "y":1.649999976158142, + "z":4.015342712402344 + }, + "queryAACube": { + "scale": 3.4421300888061523, + "x": 1.6001315116882324, + "y": -0.07100248336791992, + "z": 0.14220571517944336 + }, + "rotation": { + "w": 0.9914448857307434, + "x": 0, + "y": -0.13052619993686676, + "z": 0 + }, + "script": "https://hifi-content.s3.amazonaws.com/wayne/404redirectionScripts/zoneTryAgainEntityScript.js", + "shapeType": "box", + "type": "Zone", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "clientOnly": false, + "color": { + "blue": 0, + "green": 0, + "red": 255 + }, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 8.645400047302246, + "green": 0.20000000298023224, + "red": 20.025121688842773, + "x": 20.025121688842773, + "y": 0.20000000298023224, + "z": 8.645400047302246 + }, + "id": "{e44fb546-b34a-4966-9b11-73556f800d21}", + "lastEdited": 1536107948776951, + "lastEditedBy": "{ce82d352-3002-44ae-9b76-66492989a1db}", + "locked": true, + "name": "ceiling", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 4.846520900726318, + "green": 2.912982940673828, + "red": 5.739595890045166, + "x": 5.739595890045166, + "y": 2.912982940673828, + "z": 4.846520900726318 + }, + "queryAACube": { + "scale": 21.812576293945312, + "x": -5.16669225692749, + "y": -7.993305206298828, + "z": -6.059767246246338 + }, + "rotation": { + "w": 0.970295786857605, + "x": 0, + "y": -0.24192190170288086, + "z": 0 + }, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}", + "visible": false + }, + { + "clientOnly": false, + "color": { + "blue": 0, + "green": 0, + "red": 0 + }, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 6.9401350021362305, + "green": 0.04553089290857315, + "red": 7.004304885864258, + "x": 7.004304885864258, + "y": 0.04553089290857315, + "z": 6.9401350021362305 + }, + "id": "{8cd93fe5-16c0-44b7-b1e9-e7e06c4e9228}", + "lastEdited": 1536107948774796, + "lastEditedBy": "{4eecd88f-ef9b-4a83-bb9a-7f7496209c6b}", + "locked": true, + "name": "floor", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 3.6175529956817627, + "green": 0, + "red": 4.102385997772217, + "x": 4.102385997772217, + "y": 0, + "z": 3.6175529956817627 + }, + "queryAACube": { + "scale": 9.860417366027832, + "x": -0.8278226852416992, + "y": -4.930208683013916, + "z": -1.3126556873321533 + }, + "rotation": { + "w": 0.8660253882408142, + "x": -1.5922749298624694e-05, + "y": 0.5, + "z": -4.572480611386709e-05 + }, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "clientOnly": false, + "color": { + "blue": 0, + "green": 0, + "red": 0 + }, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 11.117486953735352, + "green": 3.580313205718994, + "red": 0.20000000298023224, + "x": 0.20000000298023224, + "y": 3.580313205718994, + "z": 11.117486953735352 + }, + "id": "{147272dc-a344-4171-9621-efc1c2095997}", + "lastEdited": 1536107948776823, + "lastEditedBy": "{ce82d352-3002-44ae-9b76-66492989a1db}", + "locked": true, + "name": "leftWall", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 6.1806135177612305, + "green": 1.0066027641296387, + "red": 1.4690406322479248, + "x": 1.4690406322479248, + "y": 1.0066027641296387, + "z": 6.1806135177612305 + }, + "queryAACube": { + "scale": 11.681488037109375, + "x": -4.371703147888184, + "y": -4.834141254425049, + "z": 0.33986949920654297 + }, + "rotation": { + "w": 0.8637980222702026, + "x": -4.57763671875e-05, + "y": 0.5038070678710938, + "z": -1.52587890625e-05 + }, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}", + "visible": false + }, + { + "clientOnly": false, + "color": { + "blue": 0, + "green": 0, + "red": 0 + }, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 11.117486953735352, + "green": 3.580313205718994, + "red": 0.20000000298023224, + "x": 0.20000000298023224, + "y": 3.580313205718994, + "z": 11.117486953735352 + }, + "id": "{5f2b89b8-47e3-4915-a966-d46307a40f06}", + "lastEdited": 1536107948774605, + "lastEditedBy": "{ce82d352-3002-44ae-9b76-66492989a1db}", + "locked": true, + "name": "backWall", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 5.268576622009277, + "green": 1.0066027641296387, + "red": 6.093774318695068, + "x": 6.093774318695068, + "y": 1.0066027641296387, + "z": 5.268576622009277 + }, + "queryAACube": { + "scale": 11.681488037109375, + "x": 0.25303030014038086, + "y": -4.834141254425049, + "z": -0.5721673965454102 + }, + "rotation": { + "w": 0.9662165641784668, + "x": -4.57763671875e-05, + "y": -0.2576791048049927, + "z": 1.52587890625e-05 + }, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}", + "visible": false + }, + { + "clientOnly": false, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 14.40000057220459, + "green": 14.40000057220459, + "red": 14.40000057220459, + "x": 14.40000057220459, + "y": 14.40000057220459, + "z": 14.40000057220459 + }, + "id": "{baf96345-8f68-4068-af4c-3c690035852a}", + "lastEdited": 1536107948775591, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "locked": true, + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 2.3440732955932617, + "green": 1.6162219047546387, + "red": 1.8748211860656738, + "x": 1.8748211860656738, + "y": 1.6162219047546387, + "z": 2.3440732955932617 + }, + "queryAACube": { + "scale": 24.9415340423584, + "x": -10.595945358276367, + "y": -10.854545593261719, + "z": -10.126693725585938 + }, + "rotation": { + "w": 0.8697794675827026, + "x": -1.52587890625e-05, + "y": 0.4933699369430542, + "z": -4.57763671875e-05 + }, + "shapeType": "box", + "skyboxMode": "enabled", + "type": "Zone", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "alpha": 0, + "alphaFinish": 0, + "alphaStart": 1, + "clientOnly": false, + "color": { + "blue": 211, + "green": 227, + "red": 104 + }, + "colorFinish": { + "blue": 0, + "green": 0, + "red": 0, + "x": 0, + "y": 0, + "z": 0 + }, + "colorStart": { + "blue": 211, + "green": 227, + "red": 104, + "x": 104, + "y": 227, + "z": 211 + }, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 2.5, + "green": 2.5, + "red": 2.5, + "x": 2.5, + "y": 2.5, + "z": 2.5 + }, + "emitAcceleration": { + "blue": 0, + "green": 0, + "red": 0, + "x": 0, + "y": 0, + "z": 0 + }, + "emitDimensions": { + "blue": 1, + "green": 1, + "red": 1, + "x": 1, + "y": 1, + "z": 1 + }, + "emitOrientation": { + "w": 0.9993909597396851, + "x": 0.034897372126579285, + "y": -1.525880907138344e-05, + "z": -1.525880907138344e-05 + }, + "emitRate": 2, + "emitSpeed": 0, + "id": "{639a51f0-8613-4e46-bc7e-fef24597df73}", + "lastEdited": 1536107948776693, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "lifespan": 10, + "locked": true, + "maxParticles": 40, + "name": "Rays", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "particleRadius": 0.75, + "polarFinish": 3.1415927410125732, + "position": { + "blue": 1.3553659915924072, + "green": 1.2890124320983887, + "red": 2.5663273334503174, + "x": 2.5663273334503174, + "y": 1.2890124320983887, + "z": 1.3553659915924072 + }, + "queryAACube": { + "scale": 4.330127239227295, + "x": 0.4012637138366699, + "y": -0.8760511875152588, + "z": -0.8096976280212402 + }, + "radiusFinish": 0.10000000149011612, + "radiusStart": 0, + "rotation": { + "w": 0.9803768396377563, + "x": -1.52587890625e-05, + "y": 0.19707024097442627, + "z": -7.62939453125e-05 + }, + "speedSpread": 0, + "spinFinish": null, + "spinStart": null, + "textures": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/stripe.png", + "type": "ParticleEffect", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "alpha": 0, + "alphaFinish": 0, + "alphaStart": 1, + "clientOnly": false, + "color": { + "blue": 255, + "green": 205, + "red": 3 + }, + "colorFinish": { + "blue": 0, + "green": 0, + "red": 0, + "x": 0, + "y": 0, + "z": 0 + }, + "colorStart": { + "blue": 255, + "green": 204, + "red": 0, + "x": 0, + "y": 204, + "z": 255 + }, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 2.5, + "green": 2.5, + "red": 2.5, + "x": 2.5, + "y": 2.5, + "z": 2.5 + }, + "emitAcceleration": { + "blue": 0, + "green": 0, + "red": 0, + "x": 0, + "y": 0, + "z": 0 + }, + "emitDimensions": { + "blue": 1, + "green": 1, + "red": 1, + "x": 1, + "y": 1, + "z": 1 + }, + "emitOrientation": { + "w": 0.9993909597396851, + "x": 0.034897372126579285, + "y": -1.525880907138344e-05, + "z": -1.525880907138344e-05 + }, + "emitRate": 2, + "emitSpeed": 0, + "emitterShouldTrail": true, + "id": "{e62ced49-fa18-4ae1-977f-abef5bc0f3ba}", + "lastEdited": 1536107948775366, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "lifespan": 10, + "locked": true, + "maxParticles": 40, + "name": "Rays", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "particleRadius": 0.75, + "polarFinish": 3.1415927410125732, + "position": { + "blue": 3.814434051513672, + "green": 1.2890124320983887, + "red": 1.2254328727722168, + "x": 1.2254328727722168, + "y": 1.2890124320983887, + "z": 3.814434051513672 + }, + "queryAACube": { + "scale": 4.330127239227295, + "x": -0.9396307468414307, + "y": -0.8760511875152588, + "z": 1.6493704319000244 + }, + "radiusFinish": 0.10000000149011612, + "radiusStart": 0, + "rotation": { + "w": 0.9594720602035522, + "x": -1.52587890625e-05, + "y": 0.28178834915161133, + "z": -4.57763671875e-05 + }, + "speedSpread": 0, + "spinFinish": null, + "spinStart": null, + "textures": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/stripe.png", + "type": "ParticleEffect", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "alpha": 0, + "alphaFinish": 0, + "alphaStart": 0.25, + "clientOnly": false, + "colorFinish": { + "blue": 0, + "green": 0, + "red": 0, + "x": 0, + "y": 0, + "z": 0 + }, + "colorStart": { + "blue": 255, + "green": 255, + "red": 255, + "x": 255, + "y": 255, + "z": 255 + }, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 13.24000072479248, + "green": 13.24000072479248, + "red": 13.24000072479248, + "x": 13.24000072479248, + "y": 13.24000072479248, + "z": 13.24000072479248 + }, + "emitAcceleration": { + "blue": 0, + "green": 0.10000000149011612, + "red": 0, + "x": 0, + "y": 0.10000000149011612, + "z": 0 + }, + "emitDimensions": { + "blue": 1, + "green": 1, + "red": 1, + "x": 1, + "y": 1, + "z": 1 + }, + "emitOrientation": { + "w": 1, + "x": -1.52587890625e-05, + "y": -1.52587890625e-05, + "z": -1.52587890625e-05 + }, + "emitRate": 6, + "emitSpeed": 0, + "id": "{298c0571-cbd8-487b-8640-64037d6a8414}", + "lastEdited": 1536107948776382, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "lifespan": 10, + "locked": true, + "maxParticles": 10, + "name": "Stars", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "particleRadius": 0.07000000029802322, + "polarFinish": 3.1415927410125732, + "position": { + "blue": 1.3712034225463867, + "green": 0.3698839843273163, + "red": 2.6216418743133545, + "x": 2.6216418743133545, + "y": 0.3698839843273163, + "z": 1.3712034225463867 + }, + "queryAACube": { + "scale": 22.932353973388672, + "x": -8.844534873962402, + "y": -11.096293449401855, + "z": -10.09497356414795 + }, + "radiusFinish": 0, + "radiusStart": 0, + "rotation": { + "w": 0.9852597713470459, + "x": -1.52587890625e-05, + "y": -0.17106890678405762, + "z": -7.62939453125e-05 + }, + "speedSpread": 0, + "spinFinish": null, + "spinStart": null, + "textures": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/star.png", + "type": "ParticleEffect", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "clientOnly": false, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 2.1097896099090576, + "green": 0.04847164824604988, + "red": 1.458284616470337, + "x": 1.458284616470337, + "y": 0.04847164824604988, + "z": 2.1097896099090576 + }, + "id": "{6625dbb8-ff25-458d-a92e-644b58460604}", + "lastEdited": 1536107948776195, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "locked": true, + "modelURL": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/portal1.fbx", + "name": "Try Again", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 3.946338653564453, + "green": 0.09449335932731628, + "red": 1.594836711883545, + "x": 1.594836711883545, + "y": 0.09449335932731628, + "z": 3.946338653564453 + }, + "queryAACube": { + "scale": 2.5651814937591553, + "x": 0.3122459650039673, + "y": -1.188097357749939, + "z": 2.663747787475586 + }, + "rotation": { + "w": 0.8220492601394653, + "x": -1.52587890625e-05, + "y": 0.5693598985671997, + "z": -0.0001068115234375 + }, + "script": "https://hifi-content.s3.amazonaws.com/wayne/404redirectionScripts/tryAgainEntityScript.js", + "shapeType": "static-mesh", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "clientOnly": false, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 0.06014331430196762, + "green": 2.582186460494995, + "red": 2.582186698913574, + "x": 2.582186698913574, + "y": 2.582186460494995, + "z": 0.06014331430196762 + }, + "id": "{dfe92dce-f09d-4e9e-b3ed-c68ecd4d476f}", + "lastEdited": 1536108160862286, + "lastEditedBy": "{4656d4a8-5e61-4230-ab34-2888d7945bd6}", + "modelURL": "", + "name": "Oops Dialog", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 1.45927095413208, + "green": 1.6763916015625, + "red": 0, + "x": 0, + "y": 1.6763916015625, + "z": 1.45927095413208 + }, + "queryAACube": { + "scale": 3.6522583961486816, + "x": -1.8261291980743408, + "y": -0.14973759651184082, + "z": -0.36685824394226074 + }, + "rotation": { + "w": 0.8684672117233276, + "x": -4.57763671875e-05, + "y": 0.4957197904586792, + "z": -7.62939453125e-05 + }, + "script": "https://hifi-content.s3.amazonaws.com/wayne/404redirectionScripts/oopsEntityScript.js", + "scriptTimestamp": 1536102551825, + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "clientOnly": false, + "color": { + "blue": 0, + "green": 0, + "red": 0 + }, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 11.117486953735352, + "green": 3.580313205718994, + "red": 0.20000000298023224, + "x": 0.20000000298023224, + "y": 3.580313205718994, + "z": 11.117486953735352 + }, + "id": "{144a8cf4-b0e8-489a-9403-d74d4dc4cb3e}", + "lastEdited": 1536107948775774, + "lastEditedBy": "{ce82d352-3002-44ae-9b76-66492989a1db}", + "locked": true, + "name": "rightWall", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 0, + "green": 1.0061144828796387, + "red": 4.965089321136475, + "x": 4.965089321136475, + "y": 1.0061144828796387, + "z": 0 + }, + "queryAACube": { + "scale": 11.681488037109375, + "x": -0.8756546974182129, + "y": -4.834629535675049, + "z": -5.8407440185546875 + }, + "rotation": { + "w": 0.8637980222702026, + "x": -4.57763671875e-05, + "y": 0.5038070678710938, + "z": -1.52587890625e-05 + }, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}", + "visible": false + }, + { + "clientOnly": false, + "collidesWith": "static,dynamic,kinematic,otherAvatar,", + "collisionMask": 23, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 1.159199833869934, + "green": 2.8062009811401367, + "red": 1.6216505765914917, + "x": 1.6216505765914917, + "y": 2.8062009811401367, + "z": 1.159199833869934 + }, + "id": "{37f53408-3d0c-42a5-9891-e6c40a227349}", + "lastEdited": 1536107948775010, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "locked": true, + "name": "Back Zone", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 1.8632707595825195, + "green": 1.6500625610351562, + "red": 3.3211965560913086, + "x": 3.3211965560913086, + "y": 1.6500625610351562, + "z": 1.8632707595825195 + }, + "queryAACube": { + "scale": 3.4421300888061523, + "x": 1.6001315116882324, + "y": -0.07100248336791992, + "z": 0.14220571517944336 + }, + "rotation": { + "w": 0.9304176568984985, + "x": 0, + "y": -0.36650121212005615, + "z": 0 + }, + "script": "https://hifi-content.s3.amazonaws.com/wayne/404redirectionScripts/zoneBackEntityScript.js", + "shapeType": "box", + "type": "Zone", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "clientOnly": false, + "color": { + "blue": 0, + "green": 0, + "red": 0 + }, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 11.117486953735352, + "green": 3.580313205718994, + "red": 0.20000000298023224, + "x": 0.20000000298023224, + "y": 3.580313205718994, + "z": 11.117486953735352 + }, + "id": "{aa6e680c-6750-4776-95bc-ef3118cace5c}", + "lastEdited": 1536107948775945, + "lastEditedBy": "{ce82d352-3002-44ae-9b76-66492989a1db}", + "locked": true, + "name": "frontWall", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 2.662257671356201, + "green": 1.0063786506652832, + "red": 1.4868733882904053, + "x": 1.4868733882904053, + "y": 1.0063786506652832, + "z": 2.662257671356201 + }, + "queryAACube": { + "scale": 11.681488037109375, + "x": -4.353870391845703, + "y": -4.834365367889404, + "z": -3.1784863471984863 + }, + "rotation": { + "w": 0.9666743278503418, + "x": -4.57763671875e-05, + "y": -0.2560006380081177, + "z": 1.52587890625e-05 + }, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}", + "visible": false + }, + { + "clientOnly": false, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 2.1097896099090576, + "green": 0.04847164824604988, + "red": 1.458284616470337, + "x": 1.458284616470337, + "y": 0.04847164824604988, + "z": 2.1097896099090576 + }, + "id": "{303631f1-04f3-42a6-b8a8-8dd4b65d1231}", + "lastEdited": 1536107948776513, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "locked": true, + "modelURL": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/portal2.fbx", + "name": "Back", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 1.5835940837860107, + "green": 0.09449335932731628, + "red": 3.028078079223633, + "x": 3.028078079223633, + "y": 0.09449335932731628, + "z": 1.5835940837860107 + }, + "queryAACube": { + "scale": 2.5651814937591553, + "x": 1.7454873323440552, + "y": -1.188097357749939, + "z": 0.3010033369064331 + }, + "rotation": { + "w": 0.9084458351135254, + "x": -1.52587890625e-05, + "y": 0.4179598093032837, + "z": -0.0001068115234375 + }, + "script": "https://hifi-content.s3.amazonaws.com/wayne/404redirectionScripts/backEntityScript.js", + "scriptTimestamp": 1535751754379, + "shapeType": "static-mesh", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "alpha": 0, + "alphaFinish": 0, + "alphaStart": 0.25, + "clientOnly": false, + "colorFinish": { + "blue": 0, + "green": 0, + "red": 0, + "x": 0, + "y": 0, + "z": 0 + }, + "colorStart": { + "blue": 255, + "green": 255, + "red": 255, + "x": 255, + "y": 255, + "z": 255 + }, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 13.24000072479248, + "green": 13.24000072479248, + "red": 13.24000072479248, + "x": 13.24000072479248, + "y": 13.24000072479248, + "z": 13.24000072479248 + }, + "emitAcceleration": { + "blue": 0, + "green": 0.10000000149011612, + "red": 0, + "x": 0, + "y": 0.10000000149011612, + "z": 0 + }, + "emitDimensions": { + "blue": 1, + "green": 1, + "red": 1, + "x": 1, + "y": 1, + "z": 1 + }, + "emitOrientation": { + "w": 1, + "x": -1.52587890625e-05, + "y": -1.52587890625e-05, + "z": -1.52587890625e-05 + }, + "emitRate": 6, + "emitSpeed": 0, + "emitterShouldTrail": true, + "id": "{8ded39e6-303c-48f2-be79-81b715cca9f7}", + "lastEdited": 1536107948777127, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "lifespan": 10, + "locked": true, + "maxParticles": 10, + "name": "Stars", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "particleRadius": 0.07000000029802322, + "polarFinish": 3.1415927410125732, + "position": { + "blue": 3.78922963142395, + "green": 0.3698839843273163, + "red": 1.1863799095153809, + "x": 1.1863799095153809, + "y": 0.3698839843273163, + "z": 3.78922963142395 + }, + "queryAACube": { + "scale": 22.932353973388672, + "x": -10.279796600341797, + "y": -11.096293449401855, + "z": -7.676947593688965 + }, + "radiusFinish": 0, + "radiusStart": 0, + "rotation": { + "w": 0.996429443359375, + "x": -1.52587890625e-05, + "y": -0.08442819118499756, + "z": -4.57763671875e-05 + }, + "speedSpread": 0, + "spinFinish": null, + "spinStart": null, + "textures": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/star.png", + "type": "ParticleEffect", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + } + ], + "Id": "{18abccad-2d57-4176-9d89-24dc424916f5}", + "Version": 93 +} diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 2b9233fbde..46cebc1661 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1186,13 +1186,13 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo const DomainHandler& domainHandler = nodeList->getDomainHandler(); connect(&domainHandler, SIGNAL(domainURLChanged(QUrl)), SLOT(domainURLChanged(QUrl))); + connect(&domainHandler, SIGNAL(redirectToErrorDomainURL(QUrl)), SLOT(goToErrorDomainURL(QUrl))); connect(&domainHandler, &DomainHandler::domainURLChanged, [](QUrl domainURL){ setCrashAnnotation("domain", domainURL.toString().toStdString()); }); connect(&domainHandler, SIGNAL(resetting()), SLOT(resettingDomain())); connect(&domainHandler, SIGNAL(connectedToDomain(QUrl)), SLOT(updateWindowTitle())); connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(updateWindowTitle())); - connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, &Application::clearDomainAvatars); connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, [this]() { getOverlays().deleteOverlay(getTabletScreenID()); getOverlays().deleteOverlay(getTabletHomeButtonID()); @@ -1200,6 +1200,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo }); connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &Application::domainConnectionRefused); + nodeList->getDomainHandler().setErrorDomainURL(QUrl(REDIRECT_HIFI_ADDRESS)); + // We could clear ATP assets only when changing domains, but it's possible that the domain you are connected // to has gone down and switched to a new content set, so when you reconnect the cached ATP assets will no longer be valid. connect(&domainHandler, &DomainHandler::disconnectedFromDomain, DependencyManager::get().data(), &ScriptCache::clearATPScriptsFromCache); @@ -1641,7 +1643,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo audioClient->setMuted(!audioClient->isMuted()); } else if (action == controller::toInt(controller::Action::CYCLE_CAMERA)) { cycleCamera(); - } else if (action == controller::toInt(controller::Action::CONTEXT_MENU)) { + } else if (action == controller::toInt(controller::Action::CONTEXT_MENU) && !isInterstitialMode()) { toggleTabletUI(); } else if (action == controller::toInt(controller::Action::RETICLE_X)) { auto oldPos = getApplicationCompositor().getReticlePosition(); @@ -2250,6 +2252,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo connect(this, &QCoreApplication::aboutToQuit, this, &Application::addAssetToWorldMessageClose); connect(&domainHandler, &DomainHandler::domainURLChanged, this, &Application::addAssetToWorldMessageClose); + connect(&domainHandler, &DomainHandler::redirectToErrorDomainURL, this, &Application::addAssetToWorldMessageClose); updateSystemTabletMode(); @@ -2299,6 +2302,24 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo connect(&AndroidHelper::instance(), &AndroidHelper::enterBackground, this, &Application::enterBackground); connect(&AndroidHelper::instance(), &AndroidHelper::enterForeground, this, &Application::enterForeground); AndroidHelper::instance().notifyLoadComplete(); +#else + static int CHECK_LOGIN_TIMER = 3000; + QTimer* checkLoginTimer = new QTimer(this); + checkLoginTimer->setInterval(CHECK_LOGIN_TIMER); + checkLoginTimer->setSingleShot(true); + connect(checkLoginTimer, &QTimer::timeout, this, [this]() { + auto accountManager = DependencyManager::get(); + auto dialogsManager = DependencyManager::get(); + if (!accountManager->isLoggedIn()) { + Setting::Handle{"loginDialogPoppedUp", false}.set(true); + dialogsManager->showLoginDialog(); + QJsonObject loginData = {}; + loginData["action"] = "login dialog shown"; + UserActivityLogger::getInstance().logAction("encourageLoginDialog", loginData); + } + }); + Setting::Handle{"loginDialogPoppedUp", false}.set(false); + checkLoginTimer->start(); #endif } @@ -2433,6 +2454,8 @@ void Application::onAboutToQuit() { // so its persisted explicitly here Setting::Handle{ ACTIVE_DISPLAY_PLUGIN_SETTING_NAME }.set(getActiveDisplayPlugin()->getName()); + Setting::Handle{"loginDialogPoppedUp", false}.set(false); + getActiveDisplayPlugin()->deactivate(); if (_autoSwitchDisplayModeSupportedHMDPlugin && _autoSwitchDisplayModeSupportedHMDPlugin->isSessionActive()) { @@ -2664,6 +2687,10 @@ Application::~Application() { void Application::initializeGL() { qCDebug(interfaceapp) << "Created Display Window."; +#ifdef DISABLE_QML + setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity); +#endif + // initialize glut for shape drawing; Qt apparently initializes it on OS X if (_isGLInitialized) { return; @@ -2989,6 +3016,9 @@ void Application::initializeUi() { if (_window && _window->isFullScreen()) { setFullscreen(nullptr, true); } + + + setIsInterstitialMode(true); } @@ -3467,6 +3497,17 @@ bool Application::isServerlessMode() const { return false; } +void Application::setIsInterstitialMode(bool interstitialMode) { + Settings settings; + bool enableInterstitial = settings.value("enableIntersitialMode", false).toBool(); + if (_interstitialMode != interstitialMode && enableInterstitial) { + _interstitialMode = interstitialMode; + + DependencyManager::get()->setAudioPaused(_interstitialMode); + DependencyManager::get()->setMyAvatarDataPacketsPaused(_interstitialMode); + } +} + void Application::setIsServerlessMode(bool serverlessDomain) { auto tree = getEntities()->getTree(); if (tree) { @@ -3474,9 +3515,9 @@ void Application::setIsServerlessMode(bool serverlessDomain) { } } -void Application::loadServerlessDomain(QUrl domainURL) { +void Application::loadServerlessDomain(QUrl domainURL, bool errorDomain) { if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "loadServerlessDomain", Q_ARG(QUrl, domainURL)); + QMetaObject::invokeMethod(this, "loadServerlessDomain", Q_ARG(QUrl, domainURL), Q_ARG(bool, errorDomain)); return; } @@ -3508,8 +3549,11 @@ void Application::loadServerlessDomain(QUrl domainURL) { } std::map namedPaths = tmpTree->getNamedPaths(); - nodeList->getDomainHandler().connectedToServerless(namedPaths); - + if (errorDomain) { + nodeList->getDomainHandler().loadedErrorDomain(namedPaths); + } else { + nodeList->getDomainHandler().connectedToServerless(namedPaths); + } _fullSceneReceivedCounter++; } @@ -3744,7 +3788,7 @@ void Application::keyPressEvent(QKeyEvent* event) { _controllerScriptingInterface->emitKeyPressEvent(event); // send events to any registered scripts // if one of our scripts have asked to capture this event, then stop processing it - if (_controllerScriptingInterface->isKeyCaptured(event)) { + if (_controllerScriptingInterface->isKeyCaptured(event) || isInterstitialMode()) { return; } @@ -5531,6 +5575,7 @@ void Application::update(float deltaTime) { return; } + if (!_physicsEnabled) { if (!domainLoadingInProgress) { PROFILE_ASYNC_BEGIN(app, "Scene Loading", ""); @@ -5551,6 +5596,7 @@ void Application::update(float deltaTime) { // scene is ready to compute its collision shape. if (getMyAvatar()->isReadyForPhysics()) { _physicsEnabled = true; + setIsInterstitialMode(false); getMyAvatar()->updateMotionBehaviorFromMenu(); } } @@ -5630,7 +5676,7 @@ void Application::update(float deltaTime) { // Transfer the user inputs to the driveKeys // FIXME can we drop drive keys and just have the avatar read the action states directly? myAvatar->clearDriveKeys(); - if (_myCamera.getMode() != CAMERA_MODE_INDEPENDENT) { + if (_myCamera.getMode() != CAMERA_MODE_INDEPENDENT && !isInterstitialMode()) { if (!_controllerScriptingInterface->areActionsCaptured() && _myCamera.getMode() != CAMERA_MODE_MIRROR) { myAvatar->setDriveKey(MyAvatar::TRANSLATE_Z, -1.0f * userInputMapper->getActionState(controller::Action::TRANSLATE_Z)); myAvatar->setDriveKey(MyAvatar::TRANSLATE_Y, userInputMapper->getActionState(controller::Action::TRANSLATE_Y)); @@ -5946,7 +5992,7 @@ void Application::update(float deltaTime) { // send packet containing downstream audio stats to the AudioMixer { quint64 sinceLastNack = now - _lastSendDownstreamAudioStats; - if (sinceLastNack > TOO_LONG_SINCE_LAST_SEND_DOWNSTREAM_AUDIO_STATS) { + if (sinceLastNack > TOO_LONG_SINCE_LAST_SEND_DOWNSTREAM_AUDIO_STATS && !isInterstitialMode()) { _lastSendDownstreamAudioStats = now; QMetaObject::invokeMethod(DependencyManager::get().data(), "sendDownstreamAudioStatsPacket", Qt::QueuedConnection); @@ -6109,21 +6155,23 @@ void Application::updateRenderArgs(float deltaTime) { } void Application::queryAvatars() { - auto avatarPacket = NLPacket::create(PacketType::AvatarQuery); - auto destinationBuffer = reinterpret_cast(avatarPacket->getPayload()); - unsigned char* bufferStart = destinationBuffer; + if (!isInterstitialMode()) { + auto avatarPacket = NLPacket::create(PacketType::AvatarQuery); + auto destinationBuffer = reinterpret_cast(avatarPacket->getPayload()); + unsigned char* bufferStart = destinationBuffer; - uint8_t numFrustums = (uint8_t)_conicalViews.size(); - memcpy(destinationBuffer, &numFrustums, sizeof(numFrustums)); - destinationBuffer += sizeof(numFrustums); + uint8_t numFrustums = (uint8_t)_conicalViews.size(); + memcpy(destinationBuffer, &numFrustums, sizeof(numFrustums)); + destinationBuffer += sizeof(numFrustums); - for (const auto& view : _conicalViews) { - destinationBuffer += view.serialize(destinationBuffer); + for (const auto& view : _conicalViews) { + destinationBuffer += view.serialize(destinationBuffer); + } + + avatarPacket->setPayloadSize(destinationBuffer - bufferStart); + + DependencyManager::get()->broadcastToNodes(std::move(avatarPacket), NodeSet() << NodeType::AvatarMixer); } - - avatarPacket->setPayloadSize(destinationBuffer - bufferStart); - - DependencyManager::get()->broadcastToNodes(std::move(avatarPacket), NodeSet() << NodeType::AvatarMixer); } @@ -6300,6 +6348,7 @@ void Application::updateWindowTitle() const { auto nodeList = DependencyManager::get(); auto accountManager = DependencyManager::get(); + auto isInErrorState = nodeList->getDomainHandler().isInErrorState(); QString buildVersion = " - " + (BuildInfo::BUILD_TYPE == BuildInfo::BuildType::Stable ? QString("Version") : QString("Build")) @@ -6307,14 +6356,19 @@ void Application::updateWindowTitle() const { QString loginStatus = accountManager->isLoggedIn() ? "" : " (NOT LOGGED IN)"; - QString connectionStatus = nodeList->getDomainHandler().isConnected() ? "" : " (NOT CONNECTED)"; + QString connectionStatus = isInErrorState ? " (ERROR CONNECTING)" : + nodeList->getDomainHandler().isConnected() ? "" : " (NOT CONNECTED)"; QString username = accountManager->getAccountInfo().getUsername(); setCrashAnnotation("username", username.toStdString()); QString currentPlaceName; if (isServerlessMode()) { - currentPlaceName = "serverless: " + DependencyManager::get()->getDomainURL().toString(); + if (isInErrorState) { + currentPlaceName = "serverless: " + nodeList->getDomainHandler().getErrorDomainURL().toString(); + } else { + currentPlaceName = "serverless: " + DependencyManager::get()->getDomainURL().toString(); + } } else { currentPlaceName = DependencyManager::get()->getDomainURL().host(); if (currentPlaceName.isEmpty()) { @@ -6346,6 +6400,7 @@ void Application::clearDomainOctreeDetails() { qCDebug(interfaceapp) << "Clearing domain octree details..."; resetPhysicsReadyInformation(); + setIsInterstitialMode(true); _octreeServerSceneStats.withWriteLock([&] { _octreeServerSceneStats.clear(); @@ -6366,10 +6421,6 @@ void Application::clearDomainOctreeDetails() { getMyAvatar()->setAvatarEntityDataChanged(true); } -void Application::clearDomainAvatars() { - DependencyManager::get()->clearOtherAvatars(); -} - void Application::domainURLChanged(QUrl domainURL) { // disable physics until we have enough information about our new location to not cause craziness. resetPhysicsReadyInformation(); @@ -6380,6 +6431,16 @@ void Application::domainURLChanged(QUrl domainURL) { updateWindowTitle(); } +void Application::goToErrorDomainURL(QUrl errorDomainURL) { + // disable physics until we have enough information about our new location to not cause craziness. + resetPhysicsReadyInformation(); + setIsServerlessMode(errorDomainURL.scheme() != URL_SCHEME_HIFI); + if (isServerlessMode()) { + loadServerlessDomain(errorDomainURL, true); + } + updateWindowTitle(); +} + void Application::resettingDomain() { _notifiedPacketVersionMismatchThisDomain = false; @@ -6418,7 +6479,7 @@ void Application::nodeActivated(SharedNodePointer node) { _octreeQuery.incrementConnectionID(); } - if (node->getType() == NodeType::AudioMixer) { + if (node->getType() == NodeType::AudioMixer && !isInterstitialMode()) { DependencyManager::get()->negotiateAudioFormat(); } @@ -6438,8 +6499,10 @@ void Application::nodeActivated(SharedNodePointer node) { getMyAvatar()->markIdentityDataChanged(); getMyAvatar()->resetLastSent(); - // transmit a "sendAll" packet to the AvatarMixer we just connected to. - getMyAvatar()->sendAvatarDataPacket(true); + if (!isInterstitialMode()) { + // transmit a "sendAll" packet to the AvatarMixer we just connected to. + getMyAvatar()->sendAvatarDataPacket(true); + } } } @@ -6457,9 +6520,6 @@ void Application::nodeKilled(SharedNodePointer node) { } else if (node->getType() == NodeType::EntityServer) { // we lost an entity server, clear all of the domain octree details clearDomainOctreeDetails(); - } else if (node->getType() == NodeType::AvatarMixer) { - // our avatar mixer has gone away - clear the hash of avatars - DependencyManager::get()->clearOtherAvatars(); } else if (node->getType() == NodeType::AssetServer) { // asset server going away - check if we have the asset browser showing @@ -6872,6 +6932,9 @@ bool Application::askToLoadScript(const QString& scriptFilenameOrURL) { shortName = shortName.mid(startIndex, endIndex - startIndex); } +#ifdef DISABLE_QML + DependencyManager::get()->loadScript(scriptFilenameOrURL); +#else QString message = "Would you like to run this script:\n" + shortName; ModalDialogListener* dlg = OffscreenUi::asyncQuestion(getWindow(), "Run Script", message, QMessageBox::Yes | QMessageBox::No); @@ -6886,7 +6949,7 @@ bool Application::askToLoadScript(const QString& scriptFilenameOrURL) { } QObject::disconnect(dlg, &ModalDialogListener::response, this, nullptr); }); - +#endif return true; } @@ -7790,7 +7853,7 @@ float Application::getRenderResolutionScale() const { } void Application::notifyPacketVersionMismatch() { - if (!_notifiedPacketVersionMismatchThisDomain) { + if (!_notifiedPacketVersionMismatchThisDomain && !isInterstitialMode()) { _notifiedPacketVersionMismatchThisDomain = true; QString message = "The location you are visiting is running an incompatible server version.\n"; diff --git a/interface/src/Application.h b/interface/src/Application.h index aa8323cd38..3bebc60480 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -224,6 +224,7 @@ public: void setHmdTabletBecomesToolbarSetting(bool value); bool getPreferStylusOverLaser() { return _preferStylusOverLaserSetting.get(); } void setPreferStylusOverLaser(bool value); + // FIXME: Remove setting completely or make available through JavaScript API? //bool getPreferAvatarFingerOverStylus() { return _preferAvatarFingerOverStylusSetting.get(); } bool getPreferAvatarFingerOverStylus() { return false; } @@ -304,6 +305,7 @@ public: void saveNextPhysicsStats(QString filename); bool isServerlessMode() const; + bool isInterstitialMode() const { return _interstitialMode; } void replaceDomainContent(const QString& url); @@ -331,6 +333,8 @@ signals: void uploadRequest(QString path); + void loginDialogPoppedUp(); + public slots: QVector pasteEntities(float x, float y, float z); bool exportEntities(const QString& filename, const QVector& entityIDs, const glm::vec3* givenOffset = nullptr); @@ -338,6 +342,7 @@ public slots: bool importEntities(const QString& url); void updateThreadPoolCount() const; void updateSystemTabletMode(); + void goToErrorDomainURL(QUrl errorDomainURL); Q_INVOKABLE void loadDialog(); Q_INVOKABLE void loadScriptURLDialog() const; @@ -426,7 +431,8 @@ public slots: void setPreferredCursor(const QString& cursor); void setIsServerlessMode(bool serverlessDomain); - void loadServerlessDomain(QUrl domainURL); + void loadServerlessDomain(QUrl domainURL, bool errorDomain = false); + void setIsInterstitialMode(bool interstialMode); void updateVerboseLogging(); @@ -437,7 +443,6 @@ private slots: void onDesktopRootContextCreated(QQmlContext* qmlContext); void showDesktop(); void clearDomainOctreeDetails(); - void clearDomainAvatars(); void onAboutToQuit(); void onPresent(quint32 frameCount); @@ -626,6 +631,7 @@ private: QHash _keysPressed; bool _enableProcessOctreeThread; + bool _interstitialMode { false }; OctreePacketProcessor _octreeProcessor; EntityEditPacketSender _entityEditSender; diff --git a/interface/src/ConnectionMonitor.cpp b/interface/src/ConnectionMonitor.cpp index 8deddbda82..3c85cfb339 100644 --- a/interface/src/ConnectionMonitor.cpp +++ b/interface/src/ConnectionMonitor.cpp @@ -11,16 +11,18 @@ #include "ConnectionMonitor.h" +#include "Application.h" #include "ui/DialogsManager.h" #include #include +#include #include // Because the connection monitor is created at startup, the time we wait on initial load // should be longer to allow the application to initialize. -static const int ON_INITIAL_LOAD_DISPLAY_AFTER_DISCONNECTED_FOR_X_MS = 10000; -static const int DISPLAY_AFTER_DISCONNECTED_FOR_X_MS = 5000; +static const int ON_INITIAL_LOAD_REDIRECT_AFTER_DISCONNECTED_FOR_X_MS = 10000; +static const int REDIRECT_AFTER_DISCONNECTED_FOR_X_MS = 5000; void ConnectionMonitor::init() { // Connect to domain disconnected message @@ -30,23 +32,25 @@ void ConnectionMonitor::init() { connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, &ConnectionMonitor::startTimer); connect(&domainHandler, &DomainHandler::connectedToDomain, this, &ConnectionMonitor::stopTimer); connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &ConnectionMonitor::stopTimer); + connect(&domainHandler, &DomainHandler::redirectToErrorDomainURL, this, &ConnectionMonitor::stopTimer); + connect(this, &ConnectionMonitor::setRedirectErrorState, &domainHandler, &DomainHandler::setRedirectErrorState); _timer.setSingleShot(true); if (!domainHandler.isConnected()) { - _timer.start(ON_INITIAL_LOAD_DISPLAY_AFTER_DISCONNECTED_FOR_X_MS); + _timer.start(ON_INITIAL_LOAD_REDIRECT_AFTER_DISCONNECTED_FOR_X_MS); } - connect(&_timer, &QTimer::timeout, this, []() { - qDebug() << "ConnectionMonitor: Showing connection failure window"; - DependencyManager::get()->setDomainConnectionFailureVisibility(true); + connect(&_timer, &QTimer::timeout, this, [this]() { + qDebug() << "ConnectionMonitor: Redirecting to 404 error domain"; + // set in a timeout error + emit setRedirectErrorState(REDIRECT_HIFI_ADDRESS, 5); }); } void ConnectionMonitor::startTimer() { - _timer.start(DISPLAY_AFTER_DISCONNECTED_FOR_X_MS); + _timer.start(REDIRECT_AFTER_DISCONNECTED_FOR_X_MS); } void ConnectionMonitor::stopTimer() { _timer.stop(); - DependencyManager::get()->setDomainConnectionFailureVisibility(false); } diff --git a/interface/src/ConnectionMonitor.h b/interface/src/ConnectionMonitor.h index e3d393163b..5e75e2618b 100644 --- a/interface/src/ConnectionMonitor.h +++ b/interface/src/ConnectionMonitor.h @@ -15,6 +15,7 @@ #include #include +class QUrl; class QString; class ConnectionMonitor : public QObject { @@ -22,6 +23,9 @@ class ConnectionMonitor : public QObject { public: void init(); +signals: + void setRedirectErrorState(QUrl errorURL, int reasonCode); + private slots: void startTimer(); void stopTimer(); diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index 2b5ca9ae8c..729bfd9e45 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -45,10 +45,11 @@ const float LOD_ADJUST_RUNNING_AVG_TIMESCALE = 0.08f; // sec const float LOD_BATCH_TO_PRESENT_CUSHION_TIME = 3.0f; // msec void LODManager::setRenderTimes(float presentTime, float engineRunTime, float batchTime, float gpuTime) { - _presentTime = presentTime; - _engineRunTime = engineRunTime; - _batchTime = batchTime; - _gpuTime = gpuTime; + // Make sure the sampled time are positive values + _presentTime = std::max(0.0f, presentTime); + _engineRunTime = std::max(0.0f, engineRunTime); + _batchTime = std::max(0.0f, batchTime); + _gpuTime = std::max(0.0f, gpuTime); } void LODManager::autoAdjustLOD(float realTimeDelta) { @@ -64,16 +65,29 @@ void LODManager::autoAdjustLOD(float realTimeDelta) { auto presentTime = (_presentTime > _batchTime + LOD_BATCH_TO_PRESENT_CUSHION_TIME ? _batchTime + LOD_BATCH_TO_PRESENT_CUSHION_TIME : _presentTime); float maxRenderTime = glm::max(glm::max(presentTime, _engineRunTime), _gpuTime); - // compute time-weighted running average maxRenderTime - // Note: we MUST clamp the blend to 1.0 for stability + // maxRenderTime must be a realistic valid duration in order for the regulation to work correctly. + // We make sure it s a non zero positive value (1.0ms) under 1 sec + maxRenderTime = std::max(1.0f, std::min(maxRenderTime, (float)MSECS_PER_SECOND)); + + // realTimeDelta must be a realistic valid duration in order for the regulation to work correctly. + // We make sure it a positive value under 1 sec + // note that if real time delta is very small we will early exit to avoid division by zero + realTimeDelta = std::max(0.0f, std::min(realTimeDelta, 1.0f)); + + // compute time-weighted running average render time (now and smooth) + // We MUST clamp the blend between 0.0 and 1.0 for stability float nowBlend = (realTimeDelta < LOD_ADJUST_RUNNING_AVG_TIMESCALE) ? realTimeDelta / LOD_ADJUST_RUNNING_AVG_TIMESCALE : 1.0f; - _nowRenderTime = (1.0f - nowBlend) * _nowRenderTime + nowBlend * maxRenderTime; // msec - float smoothBlend = (realTimeDelta < LOD_ADJUST_RUNNING_AVG_TIMESCALE * _smoothScale) ? realTimeDelta / (LOD_ADJUST_RUNNING_AVG_TIMESCALE * _smoothScale) : 1.0f; - _smoothRenderTime = (1.0f - smoothBlend) * _smoothRenderTime + smoothBlend * maxRenderTime; // msec - if (!_automaticLODAdjust || _nowRenderTime == 0.0f || _smoothRenderTime == 0.0f) { - // early exit + //Evaluate the running averages for the render time + // We must sanity check for the output average evaluated to be in a valid range to avoid issues + _nowRenderTime = (1.0f - nowBlend) * _nowRenderTime + nowBlend * maxRenderTime; // msec + _nowRenderTime = std::max(0.0f, std::min(_nowRenderTime, (float)MSECS_PER_SECOND)); + _smoothRenderTime = (1.0f - smoothBlend) * _smoothRenderTime + smoothBlend * maxRenderTime; // msec + _smoothRenderTime = std::max(0.0f, std::min(_smoothRenderTime, (float)MSECS_PER_SECOND)); + + // Early exit if not regulating or if the simulation or render times don't matter + if (!_automaticLODAdjust || realTimeDelta <= 0.0f || _nowRenderTime <= 0.0f || _smoothRenderTime <= 0.0f) { return; } @@ -130,7 +144,8 @@ void LODManager::autoAdjustLOD(float realTimeDelta) { glm::clamp(integral, -1.0f, 1.0f); // Compute derivative - auto derivative = (error - previous_error) / dt; + // dt is never zero because realTimeDelta would have early exit above, but if it ever was let's zero the derivative term + auto derivative = (dt <= 0.0f ? 0.0f : (error - previous_error) / dt); // remember history _pidHistory.x = error; diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index f7be6d17ef..7a310e675f 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -255,7 +255,7 @@ Menu::Menu() { connect(action, &QAction::triggered, [] { auto tablet = DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system"); auto hmd = DependencyManager::get(); - tablet->loadQMLSource("hifi/tablet/ControllerSettings.qml"); + tablet->pushOntoStack("hifi/tablet/ControllerSettings.qml"); if (!hmd->getShouldShowTablet()) { hmd->toggleShouldShowTablet(); diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 2422395c0c..0263cc1c1e 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -196,7 +196,7 @@ void AvatarManager::updateMyAvatar(float deltaTime) { quint64 now = usecTimestampNow(); quint64 dt = now - _lastSendAvatarDataTime; - if (dt > MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS) { + if (dt > MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS && !_myAvatarDataPacketsPaused) { // send head/hand data to the avatar mixer and voxel server PerformanceTimer perfTimer("send"); _myAvatar->sendAvatarDataPacket(); @@ -214,6 +214,16 @@ float AvatarManager::getAvatarDataRate(const QUuid& sessionID, const QString& ra return avatar ? avatar->getDataRate(rateName) : 0.0f; } +void AvatarManager::setMyAvatarDataPacketsPaused(bool pause) { + if (_myAvatarDataPacketsPaused != pause) { + _myAvatarDataPacketsPaused = pause; + + if (!_myAvatarDataPacketsPaused) { + _myAvatar->sendAvatarDataPacket(true); + } + } +} + float AvatarManager::getAvatarUpdateRate(const QUuid& sessionID, const QString& rateName) const { auto avatar = getAvatarBySessionID(sessionID); return avatar ? avatar->getUpdateRate(rateName) : 0.0f; @@ -225,29 +235,29 @@ float AvatarManager::getAvatarSimulationRate(const QUuid& sessionID, const QStri } void AvatarManager::updateOtherAvatars(float deltaTime) { - // lock the hash for read to check the size - QReadLocker lock(&_hashLock); - if (_avatarHash.size() < 2 && _avatarsToFade.isEmpty()) { - return; + { + // lock the hash for read to check the size + QReadLocker lock(&_hashLock); + if (_avatarHash.size() < 2 && _avatarsToFade.isEmpty()) { + return; + } } - lock.unlock(); PerformanceTimer perfTimer("otherAvatars"); class SortableAvatar: public PrioritySortUtil::Sortable { public: SortableAvatar() = delete; - SortableAvatar(const AvatarSharedPointer& avatar) : _avatar(avatar) {} + SortableAvatar(const std::shared_ptr& avatar) : _avatar(avatar) {} glm::vec3 getPosition() const override { return _avatar->getWorldPosition(); } - float getRadius() const override { return std::static_pointer_cast(_avatar)->getBoundingRadius(); } - uint64_t getTimestamp() const override { return std::static_pointer_cast(_avatar)->getLastRenderUpdateTime(); } - AvatarSharedPointer getAvatar() const { return _avatar; } + float getRadius() const override { return _avatar->getBoundingRadius(); } + uint64_t getTimestamp() const override { return _avatar->getLastRenderUpdateTime(); } + std::shared_ptr getAvatar() const { return _avatar; } private: - AvatarSharedPointer _avatar; + std::shared_ptr _avatar; }; auto avatarMap = getHashCopy(); - AvatarHash::iterator itr = avatarMap.begin(); const auto& views = qApp->getConicalViews(); PrioritySortUtil::PriorityQueue sortedAvatars(views, @@ -256,22 +266,24 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { AvatarData::_avatarSortCoefficientAge); sortedAvatars.reserve(avatarMap.size() - 1); // don't include MyAvatar - // sort + // Build vector and compute priorities + auto nodeList = DependencyManager::get(); + AvatarHash::iterator itr = avatarMap.begin(); while (itr != avatarMap.end()) { const auto& avatar = std::static_pointer_cast(*itr); // DO NOT update _myAvatar! Its update has already been done earlier in the main loop. // DO NOT update or fade out uninitialized Avatars - if (avatar != _myAvatar && avatar->isInitialized()) { + if (avatar != _myAvatar && avatar->isInitialized() && !nodeList->isPersonalMutingNode(avatar->getID())) { sortedAvatars.push(SortableAvatar(avatar)); } ++itr; } + // Sort const auto& sortedAvatarVector = sortedAvatars.getSortedVector(); // process in sorted order uint64_t startTime = usecTimestampNow(); - const uint64_t UPDATE_BUDGET = 2000; // usec - uint64_t updateExpiry = startTime + UPDATE_BUDGET; + uint64_t updateExpiry = startTime + MAX_UPDATE_AVATARS_TIME_BUDGET; int numAvatarsUpdated = 0; int numAVatarsNotUpdated = 0; @@ -290,18 +302,12 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { avatar->updateOrbPosition(); } - bool ignoring = DependencyManager::get()->isPersonalMutingNode(avatar->getID()); - if (ignoring) { - continue; - } - // for ALL avatars... if (_shouldRender) { avatar->ensureInScene(avatar, qApp->getMain3DScene()); } avatar->animateScaleChanges(deltaTime); - const float OUT_OF_VIEW_THRESHOLD = 0.5f * AvatarData::OUT_OF_VIEW_PENALTY; uint64_t now = usecTimestampNow(); if (now < updateExpiry) { // we're within budget @@ -332,7 +338,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { // no time to simulate, but we take the time to count how many were tragically missed while (it != sortedAvatarVector.end()) { const SortableAvatar& newSortData = *it; - const auto newAvatar = std::static_pointer_cast(newSortData.getAvatar()); + const auto& newAvatar = newSortData.getAvatar(); bool inView = newSortData.getPriority() > OUT_OF_VIEW_THRESHOLD; // Once we reach an avatar that's not in view, all avatars after it will also be out of view if (!inView) { @@ -502,6 +508,11 @@ void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar avatar->die(); queuePhysicsChange(avatar); + // remove this avatar's entities from the tree now, if we wait (as we did previously) for this Avatar's destructor + // it might not fire until after we create a new instance for the same remote avatar, which creates a race + // on the creation of entities for that avatar instance and the deletion of entities for this instance + avatar->removeAvatarEntitiesFromTree(); + if (removalReason == KillAvatarReason::TheirAvatarEnteredYourBubble) { emit DependencyManager::get()->enteredIgnoreRadius(); } else if (removalReason == KillAvatarReason::AvatarDisconnected) { @@ -871,13 +882,13 @@ void AvatarManager::setAvatarSortCoefficient(const QString& name, const QScriptV QString currentSessionUUID = avatar->getSessionUUID().toString(); if (specificAvatarIdentifiers.isEmpty() || specificAvatarIdentifiers.contains(currentSessionUUID)) { QJsonObject thisAvatarPalData; - + auto myAvatar = DependencyManager::get()->getMyAvatar(); if (currentSessionUUID == myAvatar->getSessionUUID().toString()) { currentSessionUUID = ""; } - + thisAvatarPalData.insert("sessionUUID", currentSessionUUID); thisAvatarPalData.insert("sessionDisplayName", avatar->getSessionDisplayName()); thisAvatarPalData.insert("audioLoudness", avatar->getAudioLoudness()); diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 9cfff8b086..65a001bf53 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -91,9 +91,11 @@ public: void updateOtherAvatars(float deltaTime); void sendIdentityRequest(const QUuid& avatarID) const; + void setMyAvatarDataPacketsPaused(bool puase); + void postUpdate(float deltaTime, const render::ScenePointer& scene); - void clearOtherAvatars(); + void clearOtherAvatars() override; void deleteAllAvatars(); void getObjectsToRemoveFromPhysics(VectorOfMotionStates& motionStates); @@ -229,6 +231,7 @@ private: int _numAvatarsNotUpdated { 0 }; float _avatarSimulationTime { 0.0f }; bool _shouldRender { true }; + bool _myAvatarDataPacketsPaused { false }; mutable int _identityRequestsSent { 0 }; mutable std::mutex _spaceLock; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index e2471a3155..fd463e6b8e 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -113,6 +113,7 @@ MyAvatar::MyAvatar(QThread* thread) : _recentModeReadings(MODE_READINGS_RING_BUFFER_SIZE), _bodySensorMatrix(), _goToPending(false), + _goToSafe(true), _goToPosition(), _goToOrientation(), _prevShouldDrawHead(true), @@ -148,7 +149,8 @@ MyAvatar::MyAvatar(QThread* thread) : }); connect(_skeletonModel.get(), &Model::rigReady, this, &Avatar::rigReady); connect(_skeletonModel.get(), &Model::rigReset, this, &Avatar::rigReset); - + connect(&_skeletonModel->getRig(), &Rig::onLoadComplete, this, &MyAvatar::updateCollisionCapsuleCache); + connect(this, &MyAvatar::sensorToWorldScaleChanged, this, &MyAvatar::updateCollisionCapsuleCache); using namespace recording; _skeletonModel->flagAsCauterized(); @@ -254,6 +256,7 @@ MyAvatar::MyAvatar(QThread* thread) : }); connect(&(_skeletonModel->getRig()), SIGNAL(onLoadComplete()), this, SIGNAL(onLoadComplete())); + _characterController.setDensity(_density); } @@ -509,7 +512,9 @@ void MyAvatar::update(float deltaTime) { if (_physicsSafetyPending && qApp->isPhysicsEnabled() && _characterController.isEnabledAndReady()) { // When needed and ready, arrange to check and fix. _physicsSafetyPending = false; - safeLanding(_goToPosition); // no-op if already safe + if (_goToSafe) { + safeLanding(_goToPosition); // no-op if already safe + } } Head* head = getHead(); @@ -3019,7 +3024,7 @@ void MyAvatar::goToFeetLocation(const glm::vec3& newPosition, void MyAvatar::goToLocation(const glm::vec3& newPosition, bool hasOrientation, const glm::quat& newOrientation, - bool shouldFaceLocation) { + bool shouldFaceLocation, bool withSafeLanding) { // Most cases of going to a place or user go through this now. Some possible improvements to think about in the future: // - It would be nice if this used the same teleport steps and smoothing as in the teleport.js script, as long as it @@ -3039,6 +3044,7 @@ void MyAvatar::goToLocation(const glm::vec3& newPosition, _goToPending = true; _goToPosition = newPosition; + _goToSafe = withSafeLanding; _goToOrientation = getWorldOrientation(); if (hasOrientation) { qCDebug(interfaceapp).nospace() << "MyAvatar goToLocation - new orientation is " @@ -3311,6 +3317,22 @@ bool MyAvatar::getCollisionsEnabled() { return _characterController.computeCollisionGroup() != BULLET_COLLISION_GROUP_COLLISIONLESS; } +void MyAvatar::updateCollisionCapsuleCache() { + glm::vec3 start, end; + float radius; + getCapsule(start, end, radius); + QVariantMap capsule; + capsule["start"] = vec3toVariant(start); + capsule["end"] = vec3toVariant(end); + capsule["radius"] = QVariant(radius); + _collisionCapsuleCache.set(capsule); +} + +// thread safe +QVariantMap MyAvatar::getCollisionCapsule() const { + return _collisionCapsuleCache.get(); +} + void MyAvatar::setCharacterControllerEnabled(bool enabled) { qCDebug(interfaceapp) << "MyAvatar.characterControllerEnabled is deprecated. Use MyAvatar.collisionsEnabled instead."; setCollisionsEnabled(enabled); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 760feab265..40683f5848 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1017,6 +1017,12 @@ public: */ Q_INVOKABLE bool getCollisionsEnabled(); + /**jsdoc + * @function MyAvatar.getCollisionCapsule + * @returns {object} + */ + Q_INVOKABLE QVariantMap getCollisionCapsule() const; + /**jsdoc * @function MyAvatar.setCharacterControllerEnabled * @param {boolean} enabled @@ -1182,11 +1188,12 @@ public slots: * @param {boolean} [hasOrientation=false] - Set to true to set the orientation of the avatar. * @param {Quat} [orientation=Quat.IDENTITY] - The new orientation for the avatar. * @param {boolean} [shouldFaceLocation=false] - Set to true to position the avatar a short distance away from + * @param {boolean} [withSafeLanding=true] - Set to false MyAvatar::safeLanding will not be called (used when teleporting). * the new position and orientate the avatar to face the position. */ void goToLocation(const glm::vec3& newPosition, bool hasOrientation = false, const glm::quat& newOrientation = glm::quat(), - bool shouldFaceLocation = false); + bool shouldFaceLocation = false, bool withSafeLanding = true); /**jsdoc * @function MyAvatar.goToLocation * @param {object} properties @@ -1500,6 +1507,7 @@ signals: private slots: void leaveDomain(); + void updateCollisionCapsuleCache(); protected: virtual void beParentOfChild(SpatiallyNestablePointer newChild) const override; @@ -1724,6 +1732,7 @@ private: bool _goToPending { false }; bool _physicsSafetyPending { false }; + bool _goToSafe { true }; glm::vec3 _goToPosition; glm::quat _goToOrientation; diff --git a/interface/src/avatar/OtherAvatar.cpp b/interface/src/avatar/OtherAvatar.cpp index a0fa496c4c..29fa98fd1d 100644 --- a/interface/src/avatar/OtherAvatar.cpp +++ b/interface/src/avatar/OtherAvatar.cpp @@ -11,6 +11,25 @@ #include "AvatarMotionState.h" +static xColor getLoadingOrbColor(Avatar::LoadingStatus loadingStatus) { + + const xColor NO_MODEL_COLOR(0xe3, 0xe3, 0xe3); + const xColor LOAD_MODEL_COLOR(0xef, 0x93, 0xd1); + const xColor LOAD_SUCCESS_COLOR(0x1f, 0xc6, 0xa6); + const xColor LOAD_FAILURE_COLOR(0xc6, 0x21, 0x47); + switch (loadingStatus) { + case Avatar::LoadingStatus::NoModel: + return NO_MODEL_COLOR; + case Avatar::LoadingStatus::LoadModel: + return LOAD_MODEL_COLOR; + case Avatar::LoadingStatus::LoadSuccess: + return LOAD_SUCCESS_COLOR; + case Avatar::LoadingStatus::LoadFailure: + default: + return LOAD_FAILURE_COLOR; + } +} + OtherAvatar::OtherAvatar(QThread* thread) : Avatar(thread) { // give the pointer to our head to inherited _headData variable from AvatarData _headData = new Head(this); @@ -48,7 +67,7 @@ void OtherAvatar::createOrb() { if (_otherAvatarOrbMeshPlaceholderID.isNull()) { _otherAvatarOrbMeshPlaceholder = std::make_shared(); _otherAvatarOrbMeshPlaceholder->setAlpha(1.0f); - _otherAvatarOrbMeshPlaceholder->setColor({ 0xFF, 0x00, 0xFF }); + _otherAvatarOrbMeshPlaceholder->setColor(getLoadingOrbColor(_loadingStatus)); _otherAvatarOrbMeshPlaceholder->setIsSolid(false); _otherAvatarOrbMeshPlaceholder->setPulseMin(0.5); _otherAvatarOrbMeshPlaceholder->setPulseMax(1.0); @@ -64,6 +83,13 @@ void OtherAvatar::createOrb() { } } +void OtherAvatar::indicateLoadingStatus(LoadingStatus loadingStatus) { + Avatar::indicateLoadingStatus(loadingStatus); + if (_otherAvatarOrbMeshPlaceholder) { + _otherAvatarOrbMeshPlaceholder->setColor(getLoadingOrbColor(_loadingStatus)); + } +} + void OtherAvatar::setSpaceIndex(int32_t index) { assert(_spaceIndex == -1); _spaceIndex = index; diff --git a/interface/src/avatar/OtherAvatar.h b/interface/src/avatar/OtherAvatar.h index ccfe42dbed..94b98f2747 100644 --- a/interface/src/avatar/OtherAvatar.h +++ b/interface/src/avatar/OtherAvatar.h @@ -28,6 +28,7 @@ public: virtual void instantiableAvatar() override { }; virtual void createOrb() override; + virtual void indicateLoadingStatus(LoadingStatus loadingStatus) override; void updateOrbPosition(); void removeOrb(); diff --git a/interface/src/octree/OctreePacketProcessor.cpp b/interface/src/octree/OctreePacketProcessor.cpp index 4bc6817a9e..0d991fc9bc 100644 --- a/interface/src/octree/OctreePacketProcessor.cpp +++ b/interface/src/octree/OctreePacketProcessor.cpp @@ -16,7 +16,6 @@ #include "Application.h" #include "Menu.h" #include "SceneScriptingInterface.h" -#include "SafeLanding.h" OctreePacketProcessor::OctreePacketProcessor(): _safeLanding(new SafeLanding()) @@ -133,7 +132,3 @@ void OctreePacketProcessor::processPacket(QSharedPointer messag void OctreePacketProcessor::startEntitySequence() { _safeLanding->startEntitySequence(qApp->getEntities()); } - -bool OctreePacketProcessor::isLoadSequenceComplete() const { - return _safeLanding->isLoadSequenceComplete(); -} diff --git a/interface/src/octree/OctreePacketProcessor.h b/interface/src/octree/OctreePacketProcessor.h index f9c24ddc51..d6ffb942e6 100644 --- a/interface/src/octree/OctreePacketProcessor.h +++ b/interface/src/octree/OctreePacketProcessor.h @@ -15,7 +15,7 @@ #include #include -class SafeLanding; +#include "SafeLanding.h" /// Handles processing of incoming voxel packets for the interface application. As with other ReceivedPacketProcessor classes /// the user is responsible for reading inbound packets and adding them to the processing queue by calling queueReceivedPacket() @@ -26,7 +26,8 @@ public: ~OctreePacketProcessor(); void startEntitySequence(); - bool isLoadSequenceComplete() const; + bool isLoadSequenceComplete() const { return _safeLanding->isLoadSequenceComplete(); } + float domainLoadingProgress() const { return _safeLanding->loadingProgressPercentage(); } signals: void packetVersionMismatch(); @@ -40,4 +41,4 @@ private slots: private: std::unique_ptr _safeLanding; }; -#endif // hifi_OctreePacketProcessor_h +#endif // hifi_OctreePacketProcessor_h diff --git a/interface/src/octree/SafeLanding.cpp b/interface/src/octree/SafeLanding.cpp index cfb0a2c753..5d4ebe9853 100644 --- a/interface/src/octree/SafeLanding.cpp +++ b/interface/src/octree/SafeLanding.cpp @@ -13,6 +13,7 @@ #include "EntityTreeRenderer.h" #include "ModelEntityItem.h" #include "InterfaceLogging.h" +#include "Application.h" const int SafeLanding::SEQUENCE_MODULO = std::numeric_limits::max() + 1; @@ -53,6 +54,7 @@ void SafeLanding::startEntitySequence(QSharedPointer entityT void SafeLanding::stopEntitySequence() { Locker lock(_lock); _trackingEntities = false; + _maxTrackedEntityCount = 0; _initialStart = INVALID_SEQUENCE; _initialEnd = INVALID_SEQUENCE; _trackedEntities.clear(); @@ -64,20 +66,18 @@ void SafeLanding::addTrackedEntity(const EntityItemID& entityID) { Locker lock(_lock); EntityItemPointer entity = _entityTree->findEntityByID(entityID); - if (entity && !entity->getCollisionless()) { - const auto& entityType = entity->getType(); - if (entityType == EntityTypes::Model) { - ModelEntityItem * modelEntity = std::dynamic_pointer_cast(entity).get(); - static const std::set downloadedCollisionTypes - { SHAPE_TYPE_COMPOUND, SHAPE_TYPE_SIMPLE_COMPOUND, SHAPE_TYPE_STATIC_MESH, SHAPE_TYPE_SIMPLE_HULL }; - bool hasAABox; - entity->getAABox(hasAABox); - if (hasAABox && downloadedCollisionTypes.count(modelEntity->getShapeType()) != 0) { - // Only track entities with downloaded collision bodies. - _trackedEntities.emplace(entityID, entity); - } + if (entity) { + + _trackedEntities.emplace(entityID, entity); + int trackedEntityCount = (int)_trackedEntities.size(); + + if (trackedEntityCount > _maxTrackedEntityCount) { + _maxTrackedEntityCount = trackedEntityCount; } + qCDebug(interfaceapp) << "Safe Landing: Tracking entity " << entity->getItemName(); } + } else { + qCDebug(interfaceapp) << "Safe Landing: Null Entity: " << entityID; } } @@ -102,7 +102,7 @@ void SafeLanding::noteReceivedsequenceNumber(int sequenceNumber) { } bool SafeLanding::isLoadSequenceComplete() { - if (isEntityPhysicsComplete() && isSequenceNumbersComplete()) { + if (isEntityLoadingComplete() && isSequenceNumbersComplete()) { Locker lock(_lock); _trackedEntities.clear(); _initialStart = INVALID_SEQUENCE; @@ -114,6 +114,15 @@ bool SafeLanding::isLoadSequenceComplete() { return !_trackingEntities; } +float SafeLanding::loadingProgressPercentage() { + Locker lock(_lock); + if (_maxTrackedEntityCount > 0) { + return ((_maxTrackedEntityCount - _trackedEntities.size()) / (float)_maxTrackedEntityCount); + } + + return 0.0f; +} + bool SafeLanding::isSequenceNumbersComplete() { if (_initialStart != INVALID_SEQUENCE) { Locker lock(_lock); @@ -132,17 +141,53 @@ bool SafeLanding::isSequenceNumbersComplete() { return false; } -bool SafeLanding::isEntityPhysicsComplete() { - Locker lock(_lock); - for (auto entityMapIter = _trackedEntities.begin(); entityMapIter != _trackedEntities.end(); ++entityMapIter) { - auto entity = entityMapIter->second; - if (!entity->shouldBePhysical() || entity->isReadyToComputeShape()) { - entityMapIter = _trackedEntities.erase(entityMapIter); - if (entityMapIter == _trackedEntities.end()) { - break; +bool isEntityPhysicsReady(const EntityItemPointer& entity) { + if (entity && !entity->getCollisionless()) { + const auto& entityType = entity->getType(); + if (entityType == EntityTypes::Model) { + ModelEntityItem * modelEntity = std::dynamic_pointer_cast(entity).get(); + static const std::set downloadedCollisionTypes + { SHAPE_TYPE_COMPOUND, SHAPE_TYPE_SIMPLE_COMPOUND, SHAPE_TYPE_STATIC_MESH, SHAPE_TYPE_SIMPLE_HULL }; + bool hasAABox; + entity->getAABox(hasAABox); + if (hasAABox && downloadedCollisionTypes.count(modelEntity->getShapeType()) != 0) { + return (!entity->shouldBePhysical() || entity->isReadyToComputeShape()); } } } + + return true; +} + +bool SafeLanding::isEntityLoadingComplete() { + Locker lock(_lock); + + + auto entityTree = qApp->getEntities(); + auto entityMapIter = _trackedEntities.begin(); + + while (entityMapIter != _trackedEntities.end()) { + auto entity = entityMapIter->second; + + bool isVisuallyReady = true; + + Settings settings; + bool enableInterstitial = settings.value("enableIntersitialMode", false).toBool(); + + if (enableInterstitial) { + isVisuallyReady = (entity->isVisuallyReady() || !entityTree->renderableForEntityId(entityMapIter->first)); + } + + if (isEntityPhysicsReady(entity) && isVisuallyReady) { + entityMapIter = _trackedEntities.erase(entityMapIter); + } else { + if (!isVisuallyReady) { + entity->requestRenderUpdate(); + } + + entityMapIter++; + } + } return _trackedEntities.empty(); } diff --git a/interface/src/octree/SafeLanding.h b/interface/src/octree/SafeLanding.h index 9177930d81..317e4587c7 100644 --- a/interface/src/octree/SafeLanding.h +++ b/interface/src/octree/SafeLanding.h @@ -18,6 +18,7 @@ #include #include "EntityItem.h" +#include "EntityDynamicInterface.h" class EntityTreeRenderer; class EntityItemID; @@ -29,6 +30,7 @@ public: void setCompletionSequenceNumbers(int first, int last); // 'last' exclusive. void noteReceivedsequenceNumber(int sequenceNumber); bool isLoadSequenceComplete(); + float loadingProgressPercentage(); private slots: void addTrackedEntity(const EntityItemID& entityID); @@ -37,7 +39,7 @@ private slots: private: bool isSequenceNumbersComplete(); void debugDumpSequenceIDs() const; - bool isEntityPhysicsComplete(); + bool isEntityLoadingComplete(); std::mutex _lock; using Locker = std::lock_guard; @@ -49,6 +51,7 @@ private: static constexpr int INVALID_SEQUENCE = -1; int _initialStart { INVALID_SEQUENCE }; int _initialEnd { INVALID_SEQUENCE }; + int _maxTrackedEntityCount { 0 }; struct SequenceLessThan { bool operator()(const int& a, const int& b) const; diff --git a/interface/src/raypick/CollisionPick.cpp b/interface/src/raypick/CollisionPick.cpp index c21ee69b74..9aee76a3da 100644 --- a/interface/src/raypick/CollisionPick.cpp +++ b/interface/src/raypick/CollisionPick.cpp @@ -51,6 +51,7 @@ void buildObjectIntersectionsMap(IntersectionType intersectionType, const std::v QVariantMap collisionPointPair; collisionPointPair["pointOnPick"] = vec3toVariant(objectIntersection.testCollisionPoint); collisionPointPair["pointOnObject"] = vec3toVariant(objectIntersection.foundCollisionPoint); + collisionPointPair["normalOnPick"] = vec3toVariant(objectIntersection.collisionNormal); collisionPointPairs[objectIntersection.foundID].append(collisionPointPair); } @@ -397,7 +398,7 @@ PickResultPointer CollisionPick::getEntityIntersection(const CollisionRegion& pi } getShapeInfoReady(pick); - auto entityIntersections = _physicsEngine->contactTest(USER_COLLISION_MASK_ENTITIES, *_mathPick.shapeInfo, pick.transform, USER_COLLISION_GROUP_DYNAMIC, pick.threshold); + auto entityIntersections = _physicsEngine->contactTest(USER_COLLISION_MASK_ENTITIES, *_mathPick.shapeInfo, pick.transform, pick.collisionGroup, pick.threshold); filterIntersections(entityIntersections); return std::make_shared(pick, entityIntersections, std::vector()); } @@ -413,13 +414,13 @@ PickResultPointer CollisionPick::getAvatarIntersection(const CollisionRegion& pi } getShapeInfoReady(pick); - auto avatarIntersections = _physicsEngine->contactTest(USER_COLLISION_MASK_AVATARS, *_mathPick.shapeInfo, pick.transform, USER_COLLISION_GROUP_DYNAMIC, pick.threshold); + auto avatarIntersections = _physicsEngine->contactTest(USER_COLLISION_MASK_AVATARS, *_mathPick.shapeInfo, pick.transform, pick.collisionGroup, pick.threshold); filterIntersections(avatarIntersections); return std::make_shared(pick, std::vector(), avatarIntersections); } PickResultPointer CollisionPick::getHUDIntersection(const CollisionRegion& pick) { - return std::make_shared(pick.toVariantMap(), std::vector(), std::vector()); + return std::make_shared(pick, std::vector(), std::vector()); } Transform CollisionPick::getResultTransform() const { diff --git a/interface/src/raypick/CollisionPick.h b/interface/src/raypick/CollisionPick.h index fe0e5a6337..0662ab6c19 100644 --- a/interface/src/raypick/CollisionPick.h +++ b/interface/src/raypick/CollisionPick.h @@ -70,6 +70,9 @@ protected: CollisionRegion _mathPick; PhysicsEnginePointer _physicsEngine; QSharedPointer _cachedResource; + + // Options for what information to get from collision results + bool _includeNormals; }; #endif // hifi_CollisionPick_h \ No newline at end of file diff --git a/interface/src/raypick/ParabolaPointer.cpp b/interface/src/raypick/ParabolaPointer.cpp index 888b3ddbe8..ad698c409b 100644 --- a/interface/src/raypick/ParabolaPointer.cpp +++ b/interface/src/raypick/ParabolaPointer.cpp @@ -382,9 +382,8 @@ void ParabolaPointer::RenderState::ParabolaRenderItem::updateBounds() { const gpu::PipelinePointer ParabolaPointer::RenderState::ParabolaRenderItem::getParabolaPipeline() { if (!_parabolaPipeline || !_transparentParabolaPipeline) { - gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::parabola); - { + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::parabola); auto state = std::make_shared(); state->setDepthTest(true, true, gpu::LESS_EQUAL); state->setBlendFunction(false, @@ -396,6 +395,7 @@ const gpu::PipelinePointer ParabolaPointer::RenderState::ParabolaRenderItem::get } { + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::parabola_translucent); auto state = std::make_shared(); state->setDepthTest(true, true, gpu::LESS_EQUAL); state->setBlendFunction(true, diff --git a/interface/src/raypick/PickScriptingInterface.cpp b/interface/src/raypick/PickScriptingInterface.cpp index 0273b084b2..b9693f6782 100644 --- a/interface/src/raypick/PickScriptingInterface.cpp +++ b/interface/src/raypick/PickScriptingInterface.cpp @@ -270,6 +270,8 @@ unsigned int PickScriptingInterface::createParabolaPick(const QVariant& properti * @property {Quat} orientation - The orientation of the collision region, relative to a parent if defined. * @property {float} threshold - The approximate minimum penetration depth for a test object to be considered in contact with the collision region. * The depth is measured in world space, but will scale with the parent if defined. +* @property {CollisionMask} [collisionGroup=8] - The type of object this collision pick collides as. Objects whose collision masks overlap with the pick's collision group +* will be considered colliding with the pick. * @property {Uuid} parentID - The ID of the parent, either an avatar, an entity, or an overlay. * @property {number} parentJointIndex - The joint of the parent to parent to, for example, the joints on the model of an avatar. (default = 0, no joint) * @property {string} joint - If "Mouse," parents the pick to the mouse. If "Avatar," parents the pick to MyAvatar's head. Otherwise, parents to the joint of the given name on MyAvatar. diff --git a/interface/src/raypick/PickScriptingInterface.h b/interface/src/raypick/PickScriptingInterface.h index 4d99309618..36079cec2b 100644 --- a/interface/src/raypick/PickScriptingInterface.h +++ b/interface/src/raypick/PickScriptingInterface.h @@ -167,6 +167,7 @@ public: * @typedef {object} CollisionContact * @property {Vec3} pointOnPick A point representing a penetration of the object's surface into the volume of the pick, in world space. * @property {Vec3} pointOnObject A point representing a penetration of the pick's surface into the volume of the found object, in world space. + * @property {Vec3} normalOnPick The normalized vector pointing away from the pick, representing the direction of collision. */ /**jsdoc diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index ba86925581..e3ae65aee1 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -409,6 +409,10 @@ glm::vec2 WindowScriptingInterface::getDeviceSize() const { return qApp->getDeviceSize(); } +int WindowScriptingInterface::getLastDomainConnectionError() const { + return DependencyManager::get()->getDomainHandler().getLastDomainConnectionError(); +} + int WindowScriptingInterface::getX() { return qApp->getWindow()->geometry().x(); } @@ -584,3 +588,8 @@ void WindowScriptingInterface::onMessageBoxSelected(int button) { _messageBoxes.remove(id); } } + + +float WindowScriptingInterface::domainLoadingProgress() { + return qApp->getOctreePacketProcessor().domainLoadingProgress(); +} diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index 77895e0e76..3827406729 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -491,6 +491,13 @@ public slots: */ glm::vec2 getDeviceSize() const; + /**jsdoc + * Gets the last domain connection error when a connection is refused. + * @function Window.getLastDomainConnectionError + * @returns {Window.ConnectionRefusedReason} Integer number that enumerates the last domain connection refused. + */ + int getLastDomainConnectionError() const; + /**jsdoc * Open a non-modal message box that can have a variety of button combinations. See also, * {@link Window.updateMessageBox|updateMessageBox} and {@link Window.closeMessageBox|closeMessageBox}. @@ -561,6 +568,8 @@ public slots: */ void closeMessageBox(int id); + float domainLoadingProgress(); + private slots: void onWindowGeometryChanged(const QRect& geometry); void onMessageBoxSelected(int button); diff --git a/interface/src/ui/LoginDialog.cpp b/interface/src/ui/LoginDialog.cpp index 8a40ee2f83..43da6ea863 100644 --- a/interface/src/ui/LoginDialog.cpp +++ b/interface/src/ui/LoginDialog.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include "AccountManager.h" #include "DependencyManager.h" @@ -37,11 +38,19 @@ LoginDialog::LoginDialog(QQuickItem *parent) : OffscreenQmlDialog(parent) { connect(accountManager.data(), &AccountManager::loginFailed, this, &LoginDialog::handleLoginFailed); #endif - } -void LoginDialog::showWithSelection() -{ +LoginDialog::~LoginDialog() { + Setting::Handle loginDialogPoppedUp{ "loginDialogPoppedUp", false }; + if (loginDialogPoppedUp.get()) { + QJsonObject data; + data["action"] = "user opted out"; + UserActivityLogger::getInstance().logAction("encourageLoginDialog", data); + } + loginDialogPoppedUp.set(false); +} + +void LoginDialog::showWithSelection() { auto tabletScriptingInterface = DependencyManager::get(); auto tablet = dynamic_cast(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system")); auto hmd = DependencyManager::get(); @@ -73,9 +82,7 @@ void LoginDialog::toggleAction() { } else { // change the menu item to login loginAction->setText("Login / Sign Up"); - connection = connect(loginAction, &QAction::triggered, [] { - LoginDialog::showWithSelection(); - }); + connection = connect(loginAction, &QAction::triggered, [] { LoginDialog::showWithSelection(); }); } } @@ -158,7 +165,6 @@ void LoginDialog::createAccountFromStream(QString username) { QJsonDocument(payload).toJson()); }); } - } void LoginDialog::openUrl(const QString& url) const { @@ -200,25 +206,24 @@ void LoginDialog::createFailed(QNetworkReply* reply) { } void LoginDialog::signup(const QString& email, const QString& username, const QString& password) { - JSONCallbackParameters callbackParams; callbackParams.callbackReceiver = this; callbackParams.jsonCallbackMethod = "signupCompleted"; callbackParams.errorCallbackMethod = "signupFailed"; - + QJsonObject payload; - + QJsonObject userObject; userObject.insert("email", email); userObject.insert("username", username); userObject.insert("password", password); - + payload.insert("user", userObject); - + static const QString API_SIGNUP_PATH = "api/v1/users"; - + qDebug() << "Sending a request to create an account for" << username; - + auto accountManager = DependencyManager::get(); accountManager->sendRequest(API_SIGNUP_PATH, AccountManagerAuth::None, QNetworkAccessManager::PostOperation, callbackParams, @@ -240,41 +245,37 @@ QString errorStringFromAPIObject(const QJsonValue& apiObject) { } void LoginDialog::signupFailed(QNetworkReply* reply) { - // parse the returned JSON to see what the problem was auto jsonResponse = QJsonDocument::fromJson(reply->readAll()); - + static const QString RESPONSE_DATA_KEY = "data"; - + auto dataJsonValue = jsonResponse.object()[RESPONSE_DATA_KEY]; - + if (dataJsonValue.isObject()) { auto dataObject = dataJsonValue.toObject(); - + static const QString EMAIL_DATA_KEY = "email"; static const QString USERNAME_DATA_KEY = "username"; static const QString PASSWORD_DATA_KEY = "password"; - + QStringList errorStringList; - + if (dataObject.contains(EMAIL_DATA_KEY)) { errorStringList.append(QString("Email %1.").arg(errorStringFromAPIObject(dataObject[EMAIL_DATA_KEY]))); } - + if (dataObject.contains(USERNAME_DATA_KEY)) { errorStringList.append(QString("Username %1.").arg(errorStringFromAPIObject(dataObject[USERNAME_DATA_KEY]))); } - + if (dataObject.contains(PASSWORD_DATA_KEY)) { errorStringList.append(QString("Password %1.").arg(errorStringFromAPIObject(dataObject[PASSWORD_DATA_KEY]))); } - + emit handleSignupFailed(errorStringList.join('\n')); } else { static const QString DEFAULT_SIGN_UP_FAILURE_MESSAGE = "There was an unknown error while creating your account. Please try again later."; emit handleSignupFailed(DEFAULT_SIGN_UP_FAILURE_MESSAGE); } - - } - diff --git a/interface/src/ui/LoginDialog.h b/interface/src/ui/LoginDialog.h index ad8cab9699..ae069f9ab1 100644 --- a/interface/src/ui/LoginDialog.h +++ b/interface/src/ui/LoginDialog.h @@ -27,7 +27,10 @@ public: LoginDialog(QQuickItem* parent = nullptr); + ~LoginDialog(); + static void showWithSelection(); + signals: void handleLoginCompleted(); void handleLoginFailed(); @@ -62,7 +65,6 @@ protected slots: Q_INVOKABLE void signup(const QString& email, const QString& username, const QString& password); Q_INVOKABLE void openUrl(const QString& url) const; - }; #endif // hifi_LoginDialog_h diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index d51b90f15d..8faa51bc58 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -106,6 +106,10 @@ extern std::atomic DECIMATED_TEXTURE_COUNT; extern std::atomic RECTIFIED_TEXTURE_COUNT; void Stats::updateStats(bool force) { + + if (qApp->isInterstitialMode()) { + return; + } QQuickItem* parent = parentItem(); if (!force) { if (!Menu::getInstance()->isOptionChecked(MenuOption::Stats)) { diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index ba9a1f9fc9..5de8bb1a2a 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -252,12 +252,6 @@ bool ContextOverlayInterface::destroyContextOverlay(const EntityItemID& entityIt void ContextOverlayInterface::contextOverlays_mousePressOnOverlay(const OverlayID& overlayID, const PointerEvent& event) { if (overlayID == _contextOverlayID && event.getButton() == PointerEvent::PrimaryButton) { qCDebug(context_overlay) << "Clicked Context Overlay. Entity ID:" << _currentEntityWithContextOverlay << "Overlay ID:" << overlayID; - Setting::Handle _settingSwitch{ "commerce", true }; - if (_settingSwitch.get()) { - openInspectionCertificate(); - } else { - openMarketplace(); - } emit contextOverlayClicked(_currentEntityWithContextOverlay); _contextOverlayJustClicked = true; } @@ -390,34 +384,6 @@ void ContextOverlayInterface::requestOwnershipVerification(const QUuid& entityID } } -static const QString INSPECTION_CERTIFICATE_QML_PATH = "hifi/commerce/inspectionCertificate/InspectionCertificate.qml"; -void ContextOverlayInterface::openInspectionCertificate() { - // lets open the tablet to the inspection certificate QML - if (!_currentEntityWithContextOverlay.isNull() && _entityMarketplaceID.length() > 0) { - setLastInspectedEntity(_currentEntityWithContextOverlay); - auto tablet = dynamic_cast(_tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system")); - tablet->loadQMLSource(INSPECTION_CERTIFICATE_QML_PATH); - _hmdScriptingInterface->openTablet(); - } -} - -static const QString MARKETPLACE_BASE_URL = NetworkingConstants::METAVERSE_SERVER_URL().toString() + "/marketplace/items/"; - -void ContextOverlayInterface::openMarketplace() { - // lets open the tablet and go to the current item in - // the marketplace (if the current entity has a - // marketplaceID) - if (!_currentEntityWithContextOverlay.isNull() && _entityMarketplaceID.length() > 0) { - auto tablet = dynamic_cast(_tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system")); - // construct the url to the marketplace item - QString url = MARKETPLACE_BASE_URL + _entityMarketplaceID; - QString MARKETPLACES_INJECT_SCRIPT_PATH = "file:///" + qApp->applicationDirPath() + "/scripts/system/html/js/marketplacesInject.js"; - tablet->gotoWebScreen(url, MARKETPLACES_INJECT_SCRIPT_PATH); - _hmdScriptingInterface->openTablet(); - _isInMarketplaceInspectionMode = true; - } -} - void ContextOverlayInterface::enableEntityHighlight(const EntityItemID& entityItemID) { _selectionScriptingInterface->addToSelectedItemsList("contextOverlayHighlightList", "entity", entityItemID); } diff --git a/interface/src/ui/overlays/ContextOverlayInterface.h b/interface/src/ui/overlays/ContextOverlayInterface.h index 808c3a4ee3..48b14e1a91 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.h +++ b/interface/src/ui/overlays/ContextOverlayInterface.h @@ -94,8 +94,6 @@ private: bool _isInMarketplaceInspectionMode { false }; - void openInspectionCertificate(); - void openMarketplace(); void enableEntityHighlight(const EntityItemID& entityItemID); void disableEntityHighlight(const EntityItemID& entityItemID); diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index c16b4c016d..9d55c91ef3 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -132,7 +132,9 @@ void Web3DOverlay::destroyWebSurface() { if (rootItem && rootItem->objectName() == "tabletRoot") { auto tabletScriptingInterface = DependencyManager::get(); - tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", nullptr); + if (tabletScriptingInterface) { + tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", nullptr); + } } // Fix for crash in QtWebEngineCore when rapidly switching domains diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 6a9363f309..3a33eccc8a 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -305,6 +305,16 @@ void AudioClient::audioMixerKilled() { emit disconnected(); } +void AudioClient::setAudioPaused(bool pause) { + if (_audioPaused != pause) { + _audioPaused = pause; + + if (!_audioPaused) { + negotiateAudioFormat(); + } + } +} + QAudioDeviceInfo getNamedAudioDeviceForMode(QAudio::Mode mode, const QString& deviceName) { QAudioDeviceInfo result; foreach(QAudioDeviceInfo audioDevice, getAvailableDevices(mode)) { @@ -651,7 +661,6 @@ void AudioClient::stop() { } void AudioClient::handleAudioEnvironmentDataPacket(QSharedPointer message) { - char bitset; message->readPrimitive(&bitset); @@ -664,11 +673,10 @@ void AudioClient::handleAudioEnvironmentDataPacket(QSharedPointer message) { - if (message->getType() == PacketType::SilentAudioFrame) { _silentInbound.increment(); } else { @@ -1026,80 +1034,82 @@ void AudioClient::handleLocalEchoAndReverb(QByteArray& inputByteArray) { } void AudioClient::handleAudioInput(QByteArray& audioBuffer) { - if (_muted) { - _lastInputLoudness = 0.0f; - _timeSinceLastClip = 0.0f; - } else { - int16_t* samples = reinterpret_cast(audioBuffer.data()); - int numSamples = audioBuffer.size() / AudioConstants::SAMPLE_SIZE; - int numFrames = numSamples / (_isStereoInput ? AudioConstants::STEREO : AudioConstants::MONO); - - if (_isNoiseGateEnabled) { - // The audio gate includes DC removal - _audioGate->render(samples, samples, numFrames); - } else { - _audioGate->removeDC(samples, samples, numFrames); - } - - int32_t loudness = 0; - assert(numSamples < 65536); // int32_t loudness cannot overflow - bool didClip = false; - for (int i = 0; i < numSamples; ++i) { - const int32_t CLIPPING_THRESHOLD = (int32_t)(AudioConstants::MAX_SAMPLE_VALUE * 0.9f); - int32_t sample = std::abs((int32_t)samples[i]); - loudness += sample; - didClip |= (sample > CLIPPING_THRESHOLD); - } - _lastInputLoudness = (float)loudness / numSamples; - - if (didClip) { + if (!_audioPaused) { + if (_muted) { + _lastInputLoudness = 0.0f; _timeSinceLastClip = 0.0f; - } else if (_timeSinceLastClip >= 0.0f) { - _timeSinceLastClip += (float)numSamples / (float)AudioConstants::SAMPLE_RATE; + } else { + int16_t* samples = reinterpret_cast(audioBuffer.data()); + int numSamples = audioBuffer.size() / AudioConstants::SAMPLE_SIZE; + int numFrames = numSamples / (_isStereoInput ? AudioConstants::STEREO : AudioConstants::MONO); + + if (_isNoiseGateEnabled) { + // The audio gate includes DC removal + _audioGate->render(samples, samples, numFrames); + } else { + _audioGate->removeDC(samples, samples, numFrames); + } + + int32_t loudness = 0; + assert(numSamples < 65536); // int32_t loudness cannot overflow + bool didClip = false; + for (int i = 0; i < numSamples; ++i) { + const int32_t CLIPPING_THRESHOLD = (int32_t)(AudioConstants::MAX_SAMPLE_VALUE * 0.9f); + int32_t sample = std::abs((int32_t)samples[i]); + loudness += sample; + didClip |= (sample > CLIPPING_THRESHOLD); + } + _lastInputLoudness = (float)loudness / numSamples; + + if (didClip) { + _timeSinceLastClip = 0.0f; + } else if (_timeSinceLastClip >= 0.0f) { + _timeSinceLastClip += (float)numSamples / (float)AudioConstants::SAMPLE_RATE; + } + + emit inputReceived(audioBuffer); } - emit inputReceived(audioBuffer); + emit inputLoudnessChanged(_lastInputLoudness); + + // state machine to detect gate opening and closing + bool audioGateOpen = (_lastInputLoudness != 0.0f); + bool openedInLastBlock = !_audioGateOpen && audioGateOpen; // the gate just opened + bool closedInLastBlock = _audioGateOpen && !audioGateOpen; // the gate just closed + _audioGateOpen = audioGateOpen; + + if (openedInLastBlock) { + emit noiseGateOpened(); + } else if (closedInLastBlock) { + emit noiseGateClosed(); + } + + // the codec must be flushed to silence before sending silent packets, + // so delay the transition to silent packets by one packet after becoming silent. + auto packetType = _shouldEchoToServer ? PacketType::MicrophoneAudioWithEcho : PacketType::MicrophoneAudioNoEcho; + if (!audioGateOpen && !closedInLastBlock) { + packetType = PacketType::SilentAudioFrame; + _silentOutbound.increment(); + } else { + _audioOutbound.increment(); + } + + Transform audioTransform; + audioTransform.setTranslation(_positionGetter()); + audioTransform.setRotation(_orientationGetter()); + + QByteArray encodedBuffer; + if (_encoder) { + _encoder->encode(audioBuffer, encodedBuffer); + } else { + encodedBuffer = audioBuffer; + } + + emitAudioPacket(encodedBuffer.data(), encodedBuffer.size(), _outgoingAvatarAudioSequenceNumber, _isStereoInput, + audioTransform, avatarBoundingBoxCorner, avatarBoundingBoxScale, + packetType, _selectedCodecName); + _stats.sentPacket(); } - - emit inputLoudnessChanged(_lastInputLoudness); - - // state machine to detect gate opening and closing - bool audioGateOpen = (_lastInputLoudness != 0.0f); - bool openedInLastBlock = !_audioGateOpen && audioGateOpen; // the gate just opened - bool closedInLastBlock = _audioGateOpen && !audioGateOpen; // the gate just closed - _audioGateOpen = audioGateOpen; - - if (openedInLastBlock) { - emit noiseGateOpened(); - } else if (closedInLastBlock) { - emit noiseGateClosed(); - } - - // the codec must be flushed to silence before sending silent packets, - // so delay the transition to silent packets by one packet after becoming silent. - auto packetType = _shouldEchoToServer ? PacketType::MicrophoneAudioWithEcho : PacketType::MicrophoneAudioNoEcho; - if (!audioGateOpen && !closedInLastBlock) { - packetType = PacketType::SilentAudioFrame; - _silentOutbound.increment(); - } else { - _audioOutbound.increment(); - } - - Transform audioTransform; - audioTransform.setTranslation(_positionGetter()); - audioTransform.setRotation(_orientationGetter()); - - QByteArray encodedBuffer; - if (_encoder) { - _encoder->encode(audioBuffer, encodedBuffer); - } else { - encodedBuffer = audioBuffer; - } - - emitAudioPacket(encodedBuffer.data(), encodedBuffer.size(), _outgoingAvatarAudioSequenceNumber, _isStereoInput, - audioTransform, avatarBoundingBoxCorner, avatarBoundingBoxScale, - packetType, _selectedCodecName); - _stats.sentPacket(); } void AudioClient::handleMicAudioInput() { diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 8599c990a3..4640d7c045 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -162,6 +162,7 @@ public: bool startRecording(const QString& filename); void stopRecording(); + void setAudioPaused(bool pause); #ifdef Q_OS_WIN @@ -416,6 +417,7 @@ private: QVector _activeLocalAudioInjectors; bool _isPlayingBackRecording { false }; + bool _audioPaused { false }; CodecPluginPointer _codec; QString _selectedCodecName; diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 590e7c067e..9c9eae2cca 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -214,19 +214,11 @@ Avatar::Avatar(QThread* thread) : _leftPointerGeometryID = geometryCache->allocateID(); _rightPointerGeometryID = geometryCache->allocateID(); _lastRenderUpdateTime = usecTimestampNow(); + + indicateLoadingStatus(LoadingStatus::NoModel); } Avatar::~Avatar() { - auto treeRenderer = DependencyManager::get(); - EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; - if (entityTree) { - entityTree->withWriteLock([&] { - AvatarEntityMap avatarEntities = getAvatarEntityData(); - for (auto entityID : avatarEntities.keys()) { - entityTree->deleteEntity(entityID, true, true); - } - }); - } auto geometryCache = DependencyManager::get(); if (geometryCache) { geometryCache->releaseID(_nameRectGeometryID); @@ -471,6 +463,19 @@ void Avatar::updateAvatarEntities() { setAvatarEntityDataChanged(false); } +void Avatar::removeAvatarEntitiesFromTree() { + auto treeRenderer = DependencyManager::get(); + EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; + if (entityTree) { + entityTree->withWriteLock([&] { + AvatarEntityMap avatarEntities = getAvatarEntityData(); + for (auto entityID : avatarEntities.keys()) { + entityTree->deleteEntity(entityID, true, true); + } + }); + } +} + void Avatar::relayJointDataToChildren() { forEachChild([&](SpatiallyNestablePointer child) { if (child->getNestableType() == NestableType::Entity) { @@ -1469,12 +1474,15 @@ void Avatar::scaleVectorRelativeToPosition(glm::vec3 &positionToScale) const { } void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { - if (!isMyAvatar()) { - createOrb(); - } AvatarData::setSkeletonModelURL(skeletonModelURL); if (QThread::currentThread() == thread()) { + + if (!isMyAvatar()) { + createOrb(); + } + _skeletonModel->setURL(_skeletonModelURL); + indicateLoadingStatus(LoadingStatus::LoadModel); } else { QMetaObject::invokeMethod(_skeletonModel.get(), "setURL", Qt::QueuedConnection, Q_ARG(QUrl, _skeletonModelURL)); } @@ -1487,11 +1495,12 @@ void Avatar::setModelURLFinished(bool success) { _reconstructSoftEntitiesJointMap = true; if (!success && _skeletonModelURL != AvatarData::defaultFullAvatarModelUrl()) { + indicateLoadingStatus(LoadingStatus::LoadFailure); const int MAX_SKELETON_DOWNLOAD_ATTEMPTS = 4; // NOTE: we don't want to be as generous as ResourceCache is, we only want 4 attempts if (_skeletonModel->getResourceDownloadAttemptsRemaining() <= 0 || _skeletonModel->getResourceDownloadAttempts() > MAX_SKELETON_DOWNLOAD_ATTEMPTS) { qCWarning(avatars_renderer) << "Using default after failing to load Avatar model: " << _skeletonModelURL - << "after" << _skeletonModel->getResourceDownloadAttempts() << "attempts."; + << "after" << _skeletonModel->getResourceDownloadAttempts() << "attempts."; // call _skeletonModel.setURL, but leave our copy of _skeletonModelURL alone. This is so that // we don't redo this every time we receive an identity packet from the avatar with the bad url. QMetaObject::invokeMethod(_skeletonModel.get(), "setURL", @@ -1502,6 +1511,9 @@ void Avatar::setModelURLFinished(bool success) { << "out of:" << MAX_SKELETON_DOWNLOAD_ATTEMPTS; } } + if (success) { + indicateLoadingStatus(LoadingStatus::LoadSuccess); + } } // rig is ready diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 52aade80d1..1234ab897a 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -143,6 +143,7 @@ public: void init(); void updateAvatarEntities(); + void removeAvatarEntitiesFromTree(); void simulate(float deltaTime, bool inView); virtual void simulateAttachments(float deltaTime); @@ -177,6 +178,14 @@ public: virtual bool isMyAvatar() const override { return false; } virtual void createOrb() { } + enum class LoadingStatus { + NoModel, + LoadModel, + LoadSuccess, + LoadFailure + }; + virtual void indicateLoadingStatus(LoadingStatus loadingStatus) { _loadingStatus = loadingStatus; } + virtual QVector getJointRotations() const override; using AvatarData::getJointRotation; virtual glm::quat getJointRotation(int index) const override; @@ -621,6 +630,7 @@ protected: static const float ATTACHMENT_LOADING_PRIORITY; QUuid _transitEffectID{ QUuid::createUuid() }; + LoadingStatus _loadingStatus { LoadingStatus::NoModel }; }; #endif // hifi_Avatar_h diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index e7aaa18576..acf5696d65 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -363,13 +363,13 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent memcpy(destinationBuffer, &packetStateFlags, sizeof(packetStateFlags)); destinationBuffer += sizeof(packetStateFlags); +#define AVATAR_MEMCPY(src) \ + memcpy(destinationBuffer, &(src), sizeof(src)); \ + destinationBuffer += sizeof(src); + if (hasAvatarGlobalPosition) { auto startSection = destinationBuffer; - auto data = reinterpret_cast(destinationBuffer); - data->globalPosition[0] = _globalPosition.x; - data->globalPosition[1] = _globalPosition.y; - data->globalPosition[2] = _globalPosition.z; - destinationBuffer += sizeof(AvatarDataPacket::AvatarGlobalPosition); + AVATAR_MEMCPY(_globalPosition); int numBytes = destinationBuffer - startSection; @@ -380,17 +380,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent if (hasAvatarBoundingBox) { auto startSection = destinationBuffer; - auto data = reinterpret_cast(destinationBuffer); - - data->avatarDimensions[0] = _globalBoundingBoxDimensions.x; - data->avatarDimensions[1] = _globalBoundingBoxDimensions.y; - data->avatarDimensions[2] = _globalBoundingBoxDimensions.z; - - data->boundOriginOffset[0] = _globalBoundingBoxOffset.x; - data->boundOriginOffset[1] = _globalBoundingBoxOffset.y; - data->boundOriginOffset[2] = _globalBoundingBoxOffset.z; - - destinationBuffer += sizeof(AvatarDataPacket::AvatarBoundingBox); + AVATAR_MEMCPY(_globalBoundingBoxDimensions); + AVATAR_MEMCPY(_globalBoundingBoxOffset); int numBytes = destinationBuffer - startSection; if (outboundDataRateOut) { @@ -424,13 +415,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent if (hasLookAtPosition) { auto startSection = destinationBuffer; - auto data = reinterpret_cast(destinationBuffer); - auto lookAt = _headData->getLookAtPosition(); - data->lookAtPosition[0] = lookAt.x; - data->lookAtPosition[1] = lookAt.y; - data->lookAtPosition[2] = lookAt.z; - destinationBuffer += sizeof(AvatarDataPacket::LookAtPosition); - + AVATAR_MEMCPY(_headData->getLookAtPosition()); int numBytes = destinationBuffer - startSection; if (outboundDataRateOut) { outboundDataRateOut->lookAtPositionRate.increment(numBytes); @@ -531,12 +516,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent if (hasAvatarLocalPosition) { auto startSection = destinationBuffer; - auto data = reinterpret_cast(destinationBuffer); - auto localPosition = getLocalPosition(); - data->localPosition[0] = localPosition.x; - data->localPosition[1] = localPosition.y; - data->localPosition[2] = localPosition.z; - destinationBuffer += sizeof(AvatarDataPacket::AvatarLocalPosition); + const auto localPosition = getLocalPosition(); + AVATAR_MEMCPY(localPosition); int numBytes = destinationBuffer - startSection; if (outboundDataRateOut) { @@ -567,19 +548,24 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } } + QVector jointData; + if (hasJointData || hasJointDefaultPoseFlags) { + QReadLocker readLock(&_jointDataLock); + jointData = _jointData; + } + // If it is connected, pack up the data if (hasJointData) { auto startSection = destinationBuffer; - QReadLocker readLock(&_jointDataLock); // joint rotation data - int numJoints = _jointData.size(); + int numJoints = jointData.size(); *destinationBuffer++ = (uint8_t)numJoints; unsigned char* validityPosition = destinationBuffer; unsigned char validity = 0; int validityBit = 0; - int numValidityBytes = (int)std::ceil(numJoints / (float)BITS_IN_BYTE); + int numValidityBytes = calcBitVectorSize(numJoints); #ifdef WANT_DEBUG int rotationSentCount = 0; @@ -589,43 +575,37 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent destinationBuffer += numValidityBytes; // Move pointer past the validity bytes // sentJointDataOut and lastSentJointData might be the same vector - // build sentJointDataOut locally and then swap it at the end. - QVector localSentJointDataOut; if (sentJointDataOut) { - localSentJointDataOut.resize(numJoints); // Make sure the destination is resized before using it + sentJointDataOut->resize(numJoints); // Make sure the destination is resized before using it } - float minRotationDOT = !distanceAdjust ? AVATAR_MIN_ROTATION_DOT : getDistanceBasedMinRotationDOT(viewerPosition); + float minRotationDOT = (distanceAdjust && cullSmallChanges) ? getDistanceBasedMinRotationDOT(viewerPosition) : AVATAR_MIN_ROTATION_DOT; - for (int i = 0; i < _jointData.size(); i++) { - const JointData& data = _jointData[i]; + for (int i = 0; i < jointData.size(); i++) { + const JointData& data = jointData[i]; const JointData& last = lastSentJointData[i]; if (!data.rotationIsDefaultPose) { - bool mustSend = sendAll || last.rotationIsDefaultPose; - if (mustSend || last.rotation != data.rotation) { - - bool largeEnoughRotation = true; - if (cullSmallChanges) { - // The dot product for smaller rotations is a smaller number. - // So if the dot() is less than the value, then the rotation is a larger angle of rotation - largeEnoughRotation = fabsf(glm::dot(last.rotation, data.rotation)) < minRotationDOT; - } - - if (mustSend || !cullSmallChanges || largeEnoughRotation) { - validity |= (1 << validityBit); + // The dot product for larger rotations is a lower number. + // So if the dot() is less than the value, then the rotation is a larger angle of rotation + if (sendAll || last.rotationIsDefaultPose || (!cullSmallChanges && last.rotation != data.rotation) + || (cullSmallChanges && glm::dot(last.rotation, data.rotation) < minRotationDOT) ) { + validity |= (1 << validityBit); #ifdef WANT_DEBUG - rotationSentCount++; + rotationSentCount++; #endif - destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, data.rotation); + destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, data.rotation); - if (sentJointDataOut) { - localSentJointDataOut[i].rotation = data.rotation; - localSentJointDataOut[i].rotationIsDefaultPose = false; - } + if (sentJointDataOut) { + (*sentJointDataOut)[i].rotation = data.rotation; } } } + + if (sentJointDataOut) { + (*sentJointDataOut)[i].rotationIsDefaultPose = data.rotationIsDefaultPose; + } + if (++validityBit == BITS_IN_BYTE) { *validityPosition++ = validity; validityBit = validity = 0; @@ -647,35 +627,38 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent destinationBuffer += numValidityBytes; // Move pointer past the validity bytes - float minTranslation = !distanceAdjust ? AVATAR_MIN_TRANSLATION : getDistanceBasedMinTranslationDistance(viewerPosition); + float minTranslation = (distanceAdjust && cullSmallChanges) ? getDistanceBasedMinTranslationDistance(viewerPosition) : AVATAR_MIN_TRANSLATION; float maxTranslationDimension = 0.0; - for (int i = 0; i < _jointData.size(); i++) { - const JointData& data = _jointData[i]; + for (int i = 0; i < jointData.size(); i++) { + const JointData& data = jointData[i]; const JointData& last = lastSentJointData[i]; if (!data.translationIsDefaultPose) { - bool mustSend = sendAll || last.translationIsDefaultPose; - if (mustSend || last.translation != data.translation) { - if (mustSend || !cullSmallChanges || glm::distance(data.translation, lastSentJointData[i].translation) > minTranslation) { - validity |= (1 << validityBit); + if (sendAll || last.translationIsDefaultPose || (!cullSmallChanges && last.translation != data.translation) + || (cullSmallChanges && glm::distance(data.translation, lastSentJointData[i].translation) > minTranslation)) { + + validity |= (1 << validityBit); #ifdef WANT_DEBUG - translationSentCount++; + translationSentCount++; #endif - maxTranslationDimension = glm::max(fabsf(data.translation.x), maxTranslationDimension); - maxTranslationDimension = glm::max(fabsf(data.translation.y), maxTranslationDimension); - maxTranslationDimension = glm::max(fabsf(data.translation.z), maxTranslationDimension); + maxTranslationDimension = glm::max(fabsf(data.translation.x), maxTranslationDimension); + maxTranslationDimension = glm::max(fabsf(data.translation.y), maxTranslationDimension); + maxTranslationDimension = glm::max(fabsf(data.translation.z), maxTranslationDimension); - destinationBuffer += - packFloatVec3ToSignedTwoByteFixed(destinationBuffer, data.translation, TRANSLATION_COMPRESSION_RADIX); + destinationBuffer += + packFloatVec3ToSignedTwoByteFixed(destinationBuffer, data.translation, TRANSLATION_COMPRESSION_RADIX); - if (sentJointDataOut) { - localSentJointDataOut[i].translation = data.translation; - localSentJointDataOut[i].translationIsDefaultPose = false; - } + if (sentJointDataOut) { + (*sentJointDataOut)[i].translation = data.translation; } } } + + if (sentJointDataOut) { + (*sentJointDataOut)[i].translationIsDefaultPose = data.translationIsDefaultPose; + } + if (++validityBit == BITS_IN_BYTE) { *validityPosition++ = validity; validityBit = validity = 0; @@ -691,6 +674,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, controllerLeftHandTransform.getRotation()); destinationBuffer += packFloatVec3ToSignedTwoByteFixed(destinationBuffer, controllerLeftHandTransform.getTranslation(), TRANSLATION_COMPRESSION_RADIX); + Transform controllerRightHandTransform = Transform(getControllerRightHandMatrix()); destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, controllerRightHandTransform.getRotation()); destinationBuffer += packFloatVec3ToSignedTwoByteFixed(destinationBuffer, controllerRightHandTransform.getTranslation(), @@ -707,34 +691,27 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent glm::vec3 mouseFarGrabPosition = extractTranslation(mouseFarGrabMatrix); glm::quat mouseFarGrabRotation = extractRotation(mouseFarGrabMatrix); - data->leftFarGrabPosition[0] = leftFarGrabPosition.x; - data->leftFarGrabPosition[1] = leftFarGrabPosition.y; - data->leftFarGrabPosition[2] = leftFarGrabPosition.z; - + AVATAR_MEMCPY(leftFarGrabPosition); + // Can't do block copy as struct order is x, y, z, w. data->leftFarGrabRotation[0] = leftFarGrabRotation.w; data->leftFarGrabRotation[1] = leftFarGrabRotation.x; data->leftFarGrabRotation[2] = leftFarGrabRotation.y; data->leftFarGrabRotation[3] = leftFarGrabRotation.z; + destinationBuffer += sizeof(data->leftFarGrabPosition); - data->rightFarGrabPosition[0] = rightFarGrabPosition.x; - data->rightFarGrabPosition[1] = rightFarGrabPosition.y; - data->rightFarGrabPosition[2] = rightFarGrabPosition.z; - + AVATAR_MEMCPY(rightFarGrabPosition); data->rightFarGrabRotation[0] = rightFarGrabRotation.w; data->rightFarGrabRotation[1] = rightFarGrabRotation.x; data->rightFarGrabRotation[2] = rightFarGrabRotation.y; data->rightFarGrabRotation[3] = rightFarGrabRotation.z; + destinationBuffer += sizeof(data->rightFarGrabRotation); - data->mouseFarGrabPosition[0] = mouseFarGrabPosition.x; - data->mouseFarGrabPosition[1] = mouseFarGrabPosition.y; - data->mouseFarGrabPosition[2] = mouseFarGrabPosition.z; - + AVATAR_MEMCPY(mouseFarGrabPosition); data->mouseFarGrabRotation[0] = mouseFarGrabRotation.w; data->mouseFarGrabRotation[1] = mouseFarGrabRotation.x; data->mouseFarGrabRotation[2] = mouseFarGrabRotation.y; data->mouseFarGrabRotation[3] = mouseFarGrabRotation.z; - - destinationBuffer += sizeof(AvatarDataPacket::FarGrabJoints); + destinationBuffer += sizeof(data->mouseFarGrabRotation); int numBytes = destinationBuffer - startSection; @@ -761,41 +738,23 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent outboundDataRateOut->jointDataRate.increment(numBytes); } - if (sentJointDataOut) { - - // Mark default poses in lastSentJointData, so when they become non-default we send them. - for (int i = 0; i < _jointData.size(); i++) { - const JointData& data = _jointData[i]; - JointData& local = localSentJointDataOut[i]; - if (data.rotationIsDefaultPose) { - local.rotationIsDefaultPose = true; - } - if (data.translationIsDefaultPose) { - local.translationIsDefaultPose = true; - } - } - - // Push new sent joint data to sentJointDataOut - sentJointDataOut->swap(localSentJointDataOut); - } } if (hasJointDefaultPoseFlags) { auto startSection = destinationBuffer; - QReadLocker readLock(&_jointDataLock); // write numJoints - int numJoints = _jointData.size(); + int numJoints = jointData.size(); *destinationBuffer++ = (uint8_t)numJoints; // write rotationIsDefaultPose bits destinationBuffer += writeBitVector(destinationBuffer, numJoints, [&](int i) { - return _jointData[i].rotationIsDefaultPose; + return jointData[i].rotationIsDefaultPose; }); // write translationIsDefaultPose bits destinationBuffer += writeBitVector(destinationBuffer, numJoints, [&](int i) { - return _jointData[i].translationIsDefaultPose; + return jointData[i].translationIsDefaultPose; }); if (outboundDataRateOut) { @@ -880,7 +839,6 @@ const unsigned char* unpackFauxJoint(const unsigned char* sourceBuffer, ThreadSa // read data in packet starting at byte offset and return number of bytes parsed int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { - // lazily allocate memory for HeadData in case we're not an Avatar instance lazyInitHeadData(); @@ -932,7 +890,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { auto newValue = glm::vec3(data->globalPosition[0], data->globalPosition[1], data->globalPosition[2]) + offset; if (_globalPosition != newValue) { _globalPosition = newValue; - _globalPositionChanged = usecTimestampNow(); + _globalPositionChanged = now; } sourceBuffer += sizeof(AvatarDataPacket::AvatarGlobalPosition); int numBytesRead = sourceBuffer - startSection; @@ -956,11 +914,11 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { if (_globalBoundingBoxDimensions != newDimensions) { _globalBoundingBoxDimensions = newDimensions; - _avatarBoundingBoxChanged = usecTimestampNow(); + _avatarBoundingBoxChanged = now; } if (_globalBoundingBoxOffset != newOffset) { _globalBoundingBoxOffset = newOffset; - _avatarBoundingBoxChanged = usecTimestampNow(); + _avatarBoundingBoxChanged = now; } sourceBuffer += sizeof(AvatarDataPacket::AvatarBoundingBox); @@ -1061,7 +1019,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { glm::mat4 sensorToWorldMatrix = createMatFromScaleQuatAndPos(glm::vec3(sensorToWorldScale), sensorToWorldQuat, sensorToWorldTrans); if (_sensorToWorldMatrixCache.get() != sensorToWorldMatrix) { _sensorToWorldMatrixCache.set(sensorToWorldMatrix); - _sensorToWorldMatrixChanged = usecTimestampNow(); + _sensorToWorldMatrixChanged = now; } sourceBuffer += sizeof(AvatarDataPacket::SensorToWorldMatrix); int numBytesRead = sourceBuffer - startSection; @@ -1118,7 +1076,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { sourceBuffer += sizeof(AvatarDataPacket::AdditionalFlags); if (somethingChanged) { - _additionalFlagsChanged = usecTimestampNow(); + _additionalFlagsChanged = now; } int numBytesRead = sourceBuffer - startSection; _additionalFlagsRate.increment(numBytesRead); @@ -1138,7 +1096,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { if ((getParentID() != newParentID) || (getParentJointIndex() != parentInfo->parentJointIndex)) { SpatiallyNestable::setParentID(newParentID); SpatiallyNestable::setParentJointIndex(parentInfo->parentJointIndex); - _parentChanged = usecTimestampNow(); + _parentChanged = now; } int numBytesRead = sourceBuffer - startSection; @@ -1187,8 +1145,6 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { int numBytesRead = sourceBuffer - startSection; _faceTrackerRate.increment(numBytesRead); _faceTrackerUpdateRate.increment(); - } else { - _headData->_blendshapeCoefficients.fill(0, _headData->_blendshapeCoefficients.size()); } if (hasJointData) { @@ -1861,9 +1817,7 @@ qint64 AvatarData::packTrait(AvatarTraits::TraitType traitType, ExtendedIODevice } qint64 AvatarData::packTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID traitInstanceID, - ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion, - AvatarTraits::TraitInstanceID wireInstanceID) { - + ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion) { qint64 bytesWritten = 0; bytesWritten += destination.writePrimitive(traitType); @@ -1872,11 +1826,7 @@ qint64 AvatarData::packTraitInstance(AvatarTraits::TraitType traitType, AvatarTr bytesWritten += destination.writePrimitive(traitVersion); } - if (!wireInstanceID.isNull()) { - bytesWritten += destination.write(wireInstanceID.toRfc4122()); - } else { - bytesWritten += destination.write(traitInstanceID.toRfc4122()); - } + bytesWritten += destination.write(traitInstanceID.toRfc4122()); if (traitType == AvatarTraits::AvatarEntity) { // grab a read lock on the avatar entities and check for entity data for the given ID @@ -2879,10 +2829,10 @@ void RayToAvatarIntersectionResultFromScriptValue(const QScriptValue& object, Ra value.extraInfo = object.property("extraInfo").toVariant().toMap(); } -const float AvatarData::OUT_OF_VIEW_PENALTY = -10.0f; - -float AvatarData::_avatarSortCoefficientSize { 1.0f }; -float AvatarData::_avatarSortCoefficientCenter { 0.25 }; +// these coefficients can be changed via JS for experimental tuning +// use AvatatManager.setAvatarSortCoefficient("name", value) by a user with domain kick-rights +float AvatarData::_avatarSortCoefficientSize { 8.0f }; +float AvatarData::_avatarSortCoefficientCenter { 0.25f }; float AvatarData::_avatarSortCoefficientAge { 1.0f }; QScriptValue AvatarEntityMapToScriptValue(QScriptEngine* engine, const AvatarEntityMap& value) { diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index e9f1f5f6c3..39f0ea34f6 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -962,8 +962,7 @@ public: qint64 packTrait(AvatarTraits::TraitType traitType, ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion = AvatarTraits::NULL_TRAIT_VERSION); qint64 packTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID instanceID, - ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion = AvatarTraits::NULL_TRAIT_VERSION, - AvatarTraits::TraitInstanceID wireInstanceID = AvatarTraits::TraitInstanceID()); + ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion = AvatarTraits::NULL_TRAIT_VERSION); void prepareResetTraitInstances(); @@ -1098,7 +1097,7 @@ public: void fromJson(const QJsonObject& json, bool useFrameSkeleton = true); glm::vec3 getClientGlobalPosition() const { return _globalPosition; } - glm::vec3 getGlobalBoundingBoxCorner() const { return _globalPosition + _globalBoundingBoxOffset - _globalBoundingBoxDimensions; } + AABox getGlobalBoundingBox() const { return AABox(_globalPosition + _globalBoundingBoxOffset - _globalBoundingBoxDimensions, _globalBoundingBoxDimensions); } /**jsdoc * @function MyAvatar.getAvatarEntityData @@ -1170,8 +1169,6 @@ public: // A method intended to be overriden by MyAvatar for polling orientation for network transmission. virtual glm::quat getOrientationOutbound() const; - static const float OUT_OF_VIEW_PENALTY; - // TODO: remove this HACK once we settle on optimal sort coefficients // These coefficients exposed for fine tuning the sort priority for transfering new _jointData to the render pipeline. static float _avatarSortCoefficientSize; @@ -1193,9 +1190,6 @@ public: void setReplicaIndex(int replicaIndex) { _replicaIndex = replicaIndex; } int getReplicaIndex() { return _replicaIndex; } - const AvatarTraits::TraitInstanceID getTraitInstanceXORID() const { return _traitInstanceXORID; } - void cycleTraitInstanceXORID() { _traitInstanceXORID = QUuid::createUuid(); } - signals: /**jsdoc @@ -1444,6 +1438,8 @@ protected: ThreadSafeValueCache _farGrabLeftMatrixCache { glm::mat4() }; ThreadSafeValueCache _farGrabMouseMatrixCache { glm::mat4() }; + ThreadSafeValueCache _collisionCapsuleCache{ QVariantMap() }; + int getFauxJointIndex(const QString& name) const; float _audioLoudness { 0.0f }; @@ -1502,8 +1498,6 @@ private: // privatize the copy constructor and assignment operator so they cannot be called AvatarData(const AvatarData&); AvatarData& operator= (const AvatarData&); - - AvatarTraits::TraitInstanceID _traitInstanceXORID { QUuid::createUuid() }; }; Q_DECLARE_METATYPE(AvatarData*) diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index 424fef0168..959806495a 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -86,8 +86,7 @@ void AvatarReplicas::processDeletedTraitInstance(const QUuid& parentID, AvatarTr if (_replicasMap.find(parentID) != _replicasMap.end()) { auto &replicas = _replicasMap[parentID]; for (auto avatar : replicas) { - avatar->processDeletedTraitInstance(traitType, - AvatarTraits::xoredInstanceID(instanceID, avatar->getTraitInstanceXORID())); + avatar->processDeletedTraitInstance(traitType, instanceID); } } } @@ -96,9 +95,7 @@ void AvatarReplicas::processTraitInstance(const QUuid& parentID, AvatarTraits::T if (_replicasMap.find(parentID) != _replicasMap.end()) { auto &replicas = _replicasMap[parentID]; for (auto avatar : replicas) { - avatar->processTraitInstance(traitType, - AvatarTraits::xoredInstanceID(instanceID, avatar->getTraitInstanceXORID()), - traitBinaryData); + avatar->processTraitInstance(traitType, instanceID, traitBinaryData); } } } @@ -113,6 +110,12 @@ AvatarHashMap::AvatarHashMap() { packetReceiver.registerListener(PacketType::BulkAvatarTraits, this, "processBulkAvatarTraits"); connect(nodeList.data(), &NodeList::uuidChanged, this, &AvatarHashMap::sessionUUIDChanged); + + connect(nodeList.data(), &NodeList::nodeKilled, this, [this](SharedNodePointer killedNode){ + if (killedNode->getType() == NodeType::AvatarMixer) { + clearOtherAvatars(); + } + }); } QVector AvatarHashMap::getAvatarIdentifiers() { @@ -341,28 +344,16 @@ void AvatarHashMap::processBulkAvatarTraits(QSharedPointer mess AvatarTraits::TraitInstanceID traitInstanceID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); - // XOR the incoming trait instance ID with this avatar object's personal XOR ID - - // this ensures that we have separate entity instances in the local tree - // if we briefly end up with two Avatar objects for this node - - // (which can occur if the shared pointer for the - // previous instance of an avatar hasn't yet gone out of scope before the - // new instance is created) - - auto xoredInstanceID = AvatarTraits::xoredInstanceID(traitInstanceID, avatar->getTraitInstanceXORID()); - message->readPrimitive(&traitBinarySize); auto& processedInstanceVersion = lastProcessedVersions.getInstanceValueRef(traitType, traitInstanceID); if (packetTraitVersion > processedInstanceVersion) { - // in order to handle re-connections to the avatar mixer when the other if (traitBinarySize == AvatarTraits::DELETED_TRAIT_SIZE) { - avatar->processDeletedTraitInstance(traitType, xoredInstanceID); + avatar->processDeletedTraitInstance(traitType, traitInstanceID); _replicas.processDeletedTraitInstance(avatarID, traitType, traitInstanceID); } else { auto traitData = message->read(traitBinarySize); - avatar->processTraitInstance(traitType, xoredInstanceID, traitData); + avatar->processTraitInstance(traitType, traitInstanceID, traitData); _replicas.processTraitInstance(avatarID, traitType, traitInstanceID, traitData); } processedInstanceVersion = packetTraitVersion; @@ -430,3 +421,12 @@ void AvatarHashMap::sessionUUIDChanged(const QUuid& sessionUUID, const QUuid& ol emit avatarSessionChangedEvent(sessionUUID, oldUUID); } +void AvatarHashMap::clearOtherAvatars() { + QWriteLocker locker(&_hashLock); + + for (auto& av : _avatarHash) { + handleRemovedAvatar(av); + } + + _avatarHash.clear(); +} diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h index 0f847b2a61..70d7f8c04d 100644 --- a/libraries/avatars/src/AvatarHashMap.h +++ b/libraries/avatars/src/AvatarHashMap.h @@ -101,6 +101,8 @@ public: void setReplicaCount(int count); int getReplicaCount() { return _replicas.getReplicaCount(); }; + virtual void clearOtherAvatars(); + signals: /**jsdoc diff --git a/libraries/avatars/src/AvatarTraits.h b/libraries/avatars/src/AvatarTraits.h index 47be0d6111..f0c807a432 100644 --- a/libraries/avatars/src/AvatarTraits.h +++ b/libraries/avatars/src/AvatarTraits.h @@ -41,8 +41,7 @@ namespace AvatarTraits { const TraitWireSize DELETED_TRAIT_SIZE = -1; inline qint64 packInstancedTraitDelete(TraitType traitType, TraitInstanceID instanceID, ExtendedIODevice& destination, - TraitVersion traitVersion = NULL_TRAIT_VERSION, - TraitInstanceID xoredInstanceID = TraitInstanceID()) { + TraitVersion traitVersion = NULL_TRAIT_VERSION) { qint64 bytesWritten = 0; bytesWritten += destination.writePrimitive(traitType); @@ -51,28 +50,12 @@ namespace AvatarTraits { bytesWritten += destination.writePrimitive(traitVersion); } - if (xoredInstanceID.isNull()) { - bytesWritten += destination.write(instanceID.toRfc4122()); - } else { - bytesWritten += destination.write(xoredInstanceID.toRfc4122()); - } + bytesWritten += destination.write(instanceID.toRfc4122()); bytesWritten += destination.writePrimitive(DELETED_TRAIT_SIZE); return bytesWritten; } - - inline TraitInstanceID xoredInstanceID(TraitInstanceID localInstanceID, TraitInstanceID xorKeyID) { - QByteArray xoredInstanceID { NUM_BYTES_RFC4122_UUID, 0 }; - auto xorKeyIDBytes = xorKeyID.toRfc4122(); - auto localInstanceIDBytes = localInstanceID.toRfc4122(); - - for (auto i = 0; i < localInstanceIDBytes.size(); ++i) { - xoredInstanceID[i] = localInstanceIDBytes[i] ^ xorKeyIDBytes[i]; - } - - return QUuid::fromRfc4122(xoredInstanceID); - } }; #endif // hifi_AvatarTraits_h diff --git a/libraries/avatars/src/ClientTraitsHandler.cpp b/libraries/avatars/src/ClientTraitsHandler.cpp index 479852cf9a..a06b53da7c 100644 --- a/libraries/avatars/src/ClientTraitsHandler.cpp +++ b/libraries/avatars/src/ClientTraitsHandler.cpp @@ -43,9 +43,6 @@ void ClientTraitsHandler::resetForNewMixer() { // pre-fill the instanced statuses that we will need to send next frame _owningAvatar->prepareResetTraitInstances(); - - // reset the trait XOR ID since we're resetting for a new avatar mixer - _owningAvatar->cycleTraitInstanceXORID(); } void ClientTraitsHandler::sendChangedTraitsToMixer() { @@ -96,19 +93,11 @@ void ClientTraitsHandler::sendChangedTraitsToMixer() { || instanceIDValuePair.value == Updated) { // this is a changed trait we need to send or we haven't send out trait information yet // ask the owning avatar to pack it - - // since this is going to the mixer, use the XORed instance ID (to anonymize trait instance IDs - // that would typically persist across sessions) - _owningAvatar->packTraitInstance(instancedIt->traitType, instanceIDValuePair.id, *traitsPacketList, - AvatarTraits::NULL_TRAIT_VERSION, - AvatarTraits::xoredInstanceID(instanceIDValuePair.id, - _owningAvatar->getTraitInstanceXORID())); + _owningAvatar->packTraitInstance(instancedIt->traitType, instanceIDValuePair.id, *traitsPacketList); } else if (!_shouldPerformInitialSend && instanceIDValuePair.value == Deleted) { // pack delete for this trait instance AvatarTraits::packInstancedTraitDelete(instancedIt->traitType, instanceIDValuePair.id, - *traitsPacketList, AvatarTraits::NULL_TRAIT_VERSION, - AvatarTraits::xoredInstanceID(instanceIDValuePair.id, - _owningAvatar->getTraitInstanceXORID())); + *traitsPacketList); } } diff --git a/libraries/avatars/src/ClientTraitsHandler.h b/libraries/avatars/src/ClientTraitsHandler.h index 6d1592ba74..27ba58d46b 100644 --- a/libraries/avatars/src/ClientTraitsHandler.h +++ b/libraries/avatars/src/ClientTraitsHandler.h @@ -30,9 +30,9 @@ public: void markTraitUpdated(AvatarTraits::TraitType updatedTrait) { _traitStatuses[updatedTrait] = Updated; _hasChangedTraits = true; } - void markInstancedTraitUpdated(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID updatedInstanceID) + void markInstancedTraitUpdated(AvatarTraits::TraitType traitType, QUuid updatedInstanceID) { _traitStatuses.instanceInsert(traitType, updatedInstanceID, Updated); _hasChangedTraits = true; } - void markInstancedTraitDeleted(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID deleteInstanceID) + void markInstancedTraitDeleted(AvatarTraits::TraitType traitType, QUuid deleteInstanceID) { _traitStatuses.instanceInsert(traitType, deleteInstanceID, Deleted); _hasChangedTraits = true; } void resetForNewMixer(); diff --git a/libraries/display-plugins/src/display-plugins/InterleavedSrgbToLinear.slf b/libraries/display-plugins/src/display-plugins/InterleavedSrgbToLinear.slf index 5f7b3f3411..17dedce7f9 100644 --- a/libraries/display-plugins/src/display-plugins/InterleavedSrgbToLinear.slf +++ b/libraries/display-plugins/src/display-plugins/InterleavedSrgbToLinear.slf @@ -11,7 +11,7 @@ layout(location=0) in vec2 varTexCoord0; layout(location=0) out vec4 outFragColor; void main(void) { - ivec2 texCoord = ivec2(floor(varTexCoord0 * textureData.textureSize)); + ivec2 texCoord = ivec2(floor(varTexCoord0 * vec2(textureData.textureSize))); texCoord.x /= 2; int row = int(floor(gl_FragCoord.y)); if (row % 2 > 0) { diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 34936c2c48..3a01650a04 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -1297,9 +1297,20 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce } }); - // Check for removal ModelPointer model; withReadLock([&] { model = _model; }); + + withWriteLock([&] { + bool visuallyReady = true; + if (_hasModel) { + if (model && _didLastVisualGeometryRequestSucceed) { + visuallyReady = (_prevModelLoaded && _texturesLoaded); + } + } + entity->setVisuallyReady(visuallyReady); + }); + + // Check for removal if (!_hasModel) { if (model) { model->removeFromScene(scene, transaction); @@ -1311,6 +1322,8 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce emit DependencyManager::get()-> modelRemovedFromScene(entity->getEntityItemID(), NestableType::Entity, _model); } + setKey(false); + _didLastVisualGeometryRequestSucceed = false; return; } @@ -1336,6 +1349,7 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce if (_parsedModelURL != model->getURL()) { withWriteLock([&] { _texturesLoaded = false; + _jointMappingCompleted = false; model->setURL(_parsedModelURL); }); } @@ -1441,12 +1455,11 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce // That is where _currentFrame and _lastAnimated were updated. if (_animating) { DETAILED_PROFILE_RANGE(simulation_physics, "Animate"); - + if (!jointsMapped()) { mapJoints(entity, model->getJointNames()); //else the joint have been mapped before but we have a new animation to load - } else if (_animation && (_animation->getURL().toString() != entity->getAnimationURL())) { - _animation = DependencyManager::get()->getAnimation(entity->getAnimationURL()); + } else if (_animation && (_animation->getURL().toString() != entity->getAnimationURL())) { _jointMappingCompleted = false; mapJoints(entity, model->getJointNames()); } diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 45892fdd7f..75f35fae9c 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -187,7 +187,7 @@ private: const void* _collisionMeshKey { nullptr }; // used on client side - bool _jointMappingCompleted{ false }; + bool _jointMappingCompleted { false }; QVector _jointMapping; // domain is index into model-joints, range is index into animation-joints AnimationPointer _animation; QUrl _parsedModelURL; diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp index 18c4921836..1a263fba79 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp @@ -187,12 +187,10 @@ ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createPa particle.basePosition = baseTransform.getTranslation(); // Position, velocity, and acceleration + glm::vec3 emitDirection; if (polarStart == 0.0f && polarFinish == 0.0f && emitDimensions.z == 0.0f) { // Emit along z-axis from position - - particle.velocity = (emitSpeed + randFloatInRange(-1.0f, 1.0f) * speedSpread) * (emitOrientation * Vectors::UNIT_Z); - particle.acceleration = emitAcceleration + randFloatInRange(-1.0f, 1.0f) * accelerationSpread; - + emitDirection = Vectors::UNIT_Z; } else { // Emit around point or from ellipsoid // - Distribute directions evenly around point @@ -210,7 +208,6 @@ ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createPa azimuth = azimuthStart + (TWO_PI + azimuthFinish - azimuthStart) * randFloat(); } - glm::vec3 emitDirection; if (emitDimensions == Vectors::ZERO) { // Point emitDirection = glm::quat(glm::vec3(PI_OVER_TWO - elevation, 0.0f, azimuth)) * Vectors::UNIT_Z; @@ -235,10 +232,10 @@ ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createPa )); particle.relativePosition += emitOrientation * emitPosition; } - - particle.velocity = (emitSpeed + randFloatInRange(-1.0f, 1.0f) * speedSpread) * (emitOrientation * emitDirection); - particle.acceleration = emitAcceleration + randFloatInRange(-1.0f, 1.0f) * accelerationSpread; } + particle.velocity = (emitSpeed + randFloatInRange(-1.0f, 1.0f) * speedSpread) * (emitOrientation * emitDirection); + particle.acceleration = emitAcceleration + + glm::vec3(randFloatInRange(-1.0f, 1.0f), randFloatInRange(-1.0f, 1.0f), randFloatInRange(-1.0f, 1.0f)) * accelerationSpread; return particle; } diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 2da5c30dc0..a4a5a34683 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -1609,6 +1609,7 @@ PolyVoxEntityRenderer::PolyVoxEntityRenderer(const EntityItemPointer& entity) : _vertexFormat->setAttribute(gpu::Stream::POSITION, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0); _vertexFormat->setAttribute(gpu::Stream::NORMAL, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 12); }); + _params = std::make_shared(sizeof(glm::vec4), nullptr); } ShapeKey PolyVoxEntityRenderer::getShapeKey() { @@ -1671,9 +1672,12 @@ void PolyVoxEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& s void PolyVoxEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) { _lastVoxelToWorldMatrix = entity->voxelToWorldMatrix(); + _lastVoxelVolumeSize = entity->getVoxelVolumeSize(); + _params->setSubData(0, vec4(_lastVoxelVolumeSize, 0.0)); graphics::MeshPointer newMesh; entity->withReadLock([&] { newMesh = entity->_mesh; + }); if (newMesh && newMesh->getIndexBuffer()._buffer) { @@ -1686,6 +1690,7 @@ void PolyVoxEntityRenderer::doRender(RenderArgs* args) { return; } + PerformanceTimer perfTimer("RenderablePolyVoxEntityItem::render"); gpu::Batch& batch = *args->_batch; @@ -1695,6 +1700,7 @@ void PolyVoxEntityRenderer::doRender(RenderArgs* args) { batch.setInputBuffer(gpu::Stream::POSITION, _mesh->getVertexBuffer()._buffer, 0, sizeof(PolyVox::PositionMaterialNormal)); + // TODO -- should we be setting this? // batch.setInputBuffer(gpu::Stream::NORMAL, mesh->getVertexBuffer()._buffer, // 12, @@ -1710,7 +1716,7 @@ void PolyVoxEntityRenderer::doRender(RenderArgs* args) { } } - batch._glUniform3f(entities_renderer::slot::uniform::PolyvoxVoxelSize, _lastVoxelVolumeSize.x, _lastVoxelVolumeSize.y, _lastVoxelVolumeSize.z); + batch.setUniformBuffer(0, _params); batch.drawIndexed(gpu::TRIANGLES, (gpu::uint32)_mesh->getNumIndices(), 0); } diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h index 7afb9b41b4..366a3fdc70 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h @@ -187,6 +187,7 @@ private: #endif graphics::MeshPointer _mesh; + gpu::BufferPointer _params; std::array _xyzTextures; glm::vec3 _lastVoxelVolumeSize; glm::mat4 _lastVoxelToWorldMatrix; diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index 9430e97e54..cf452c9cf7 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -288,6 +288,17 @@ void ZoneEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scen updateHazeFromEntity(entity); } + + bool visuallyReady = true; + uint32_t skyboxMode = entity->getSkyboxMode(); + if (skyboxMode == COMPONENT_MODE_ENABLED && !_skyboxTextureURL.isEmpty()) { + bool skyboxLoadedOrFailed = (_skyboxTexture && (_skyboxTexture->isLoaded() || _skyboxTexture->isFailed())); + + visuallyReady = skyboxLoadedOrFailed; + } + + entity->setVisuallyReady(visuallyReady); + if (bloomChanged) { updateBloomFromEntity(entity); } diff --git a/libraries/entities-renderer/src/entities-renderer/ShaderConstants.h b/libraries/entities-renderer/src/entities-renderer/ShaderConstants.h index 58b0bf5688..d01075846a 100644 --- a/libraries/entities-renderer/src/entities-renderer/ShaderConstants.h +++ b/libraries/entities-renderer/src/entities-renderer/ShaderConstants.h @@ -15,7 +15,6 @@ #define ENTITIES_SHADER_CONSTANTS_H // Polyvox -#define ENTITIES_UNIFORM_POLYVOX_VOXEL_SIZE 0 #define ENTITIES_TEXTURE_POLYVOX_XMAP 0 #define ENTITIES_TEXTURE_POLYVOX_YMAP 1 #define ENTITIES_TEXTURE_POLYVOX_ZMAP 2 @@ -26,17 +25,6 @@ namespace entities_renderer { namespace slot { -namespace uniform { -enum Uniform { - PolyvoxVoxelSize = ENTITIES_UNIFORM_POLYVOX_VOXEL_SIZE, -}; -} - -namespace buffer { -enum Buffer { -}; -} // namespace buffer - namespace texture { enum Texture { PolyvoxXMap = ENTITIES_TEXTURE_POLYVOX_XMAP, diff --git a/libraries/entities-renderer/src/paintStroke_fade.slf b/libraries/entities-renderer/src/paintStroke_fade.slf index 8739c9bb9b..e5f70c8038 100644 --- a/libraries/entities-renderer/src/paintStroke_fade.slf +++ b/libraries/entities-renderer/src/paintStroke_fade.slf @@ -45,7 +45,7 @@ void main(void) { int frontCondition = 1 -int(gl_FrontFacing) * 2; vec3 color = varColor.rgb; packDeferredFragmentTranslucent( - interpolatedNormal * frontCondition, + interpolatedNormal * float(frontCondition), texel.a * varColor.a, polyline.color * texel.rgb + fadeEmissive, vec3(0.01, 0.01, 0.01), diff --git a/libraries/entities-renderer/src/polyvox.slf b/libraries/entities-renderer/src/polyvox.slf index ba2fd7031b..441dfc11e5 100644 --- a/libraries/entities-renderer/src/polyvox.slf +++ b/libraries/entities-renderer/src/polyvox.slf @@ -23,15 +23,22 @@ layout(location=RENDER_UTILS_ATTR_POSITION_WS) in vec4 _worldPosition; layout(binding=ENTITIES_TEXTURE_POLYVOX_XMAP) uniform sampler2D xMap; layout(binding=ENTITIES_TEXTURE_POLYVOX_YMAP) uniform sampler2D yMap; layout(binding=ENTITIES_TEXTURE_POLYVOX_ZMAP) uniform sampler2D zMap; -layout(location=ENTITIES_UNIFORM_POLYVOX_VOXEL_SIZE) uniform vec3 voxelVolumeSize; + +struct PolyvoxParams { + vec4 voxelVolumeSize; +}; + +layout(binding=0) uniform polyvoxParamsBuffer { + PolyvoxParams params; +}; void main(void) { vec3 worldNormal = cross(dFdy(_worldPosition.xyz), dFdx(_worldPosition.xyz)); worldNormal = normalize(worldNormal); - float inPositionX = (_worldPosition.x - 0.5) / voxelVolumeSize.x; - float inPositionY = (_worldPosition.y - 0.5) / voxelVolumeSize.y; - float inPositionZ = (_worldPosition.z - 0.5) / voxelVolumeSize.z; + float inPositionX = (_worldPosition.x - 0.5) / params.voxelVolumeSize.x; + float inPositionY = (_worldPosition.y - 0.5) / params.voxelVolumeSize.y; + float inPositionZ = (_worldPosition.z - 0.5) / params.voxelVolumeSize.z; vec4 xyDiffuse = texture(xMap, vec2(-inPositionX, -inPositionY)); vec4 xzDiffuse = texture(yMap, vec2(-inPositionX, inPositionZ)); diff --git a/libraries/entities-renderer/src/polyvox_fade.slf b/libraries/entities-renderer/src/polyvox_fade.slf index 2247e472ea..6e236193aa 100644 --- a/libraries/entities-renderer/src/polyvox_fade.slf +++ b/libraries/entities-renderer/src/polyvox_fade.slf @@ -27,7 +27,13 @@ layout(binding=ENTITIES_TEXTURE_POLYVOX_XMAP) uniform sampler2D xMap; layout(binding=ENTITIES_TEXTURE_POLYVOX_YMAP) uniform sampler2D yMap; layout(binding=ENTITIES_TEXTURE_POLYVOX_ZMAP) uniform sampler2D zMap; -layout(location=ENTITIES_UNIFORM_POLYVOX_VOXEL_SIZE) uniform vec3 voxelVolumeSize; +struct PolyvoxParams { + vec4 voxelVolumeSize; +}; + +layout(binding=0) uniform polyvoxParamsBuffer { + PolyvoxParams params; +}; // Declare after all samplers to prevent sampler location mix up with voxel shading (sampler locations are hardcoded in RenderablePolyVoxEntityItem) <$declareFadeFragment()$> @@ -42,9 +48,9 @@ void main(void) { vec3 worldNormal = cross(dFdy(_worldPosition.xyz), dFdx(_worldPosition.xyz)); worldNormal = normalize(worldNormal); - float inPositionX = (_worldPosition.x - 0.5) / voxelVolumeSize.x; - float inPositionY = (_worldPosition.y - 0.5) / voxelVolumeSize.y; - float inPositionZ = (_worldPosition.z - 0.5) / voxelVolumeSize.z; + float inPositionX = (_worldPosition.x - 0.5) / params.voxelVolumeSize.x; + float inPositionY = (_worldPosition.y - 0.5) / params.voxelVolumeSize.y; + float inPositionZ = (_worldPosition.z - 0.5) / params.voxelVolumeSize.z; vec4 xyDiffuse = texture(xMap, vec2(-inPositionX, -inPositionY)); vec4 xzDiffuse = texture(yMap, vec2(-inPositionX, inPositionZ)); diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 47ae8de9ad..490f9b9e6b 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -305,6 +305,7 @@ public: void setDynamic(bool value); virtual bool shouldBePhysical() const { return false; } + bool isVisuallyReady() const { return _visuallyReady; } bool getLocked() const; void setLocked(bool value); @@ -527,6 +528,7 @@ public: void removeCloneID(const QUuid& cloneID); const QVector getCloneIDs() const; void setCloneIDs(const QVector& cloneIDs); + void setVisuallyReady(bool visuallyReady) { _visuallyReady = visuallyReady; } signals: void requestRenderUpdate(); @@ -639,6 +641,7 @@ protected: EntityTreeElementPointer _element; // set by EntityTreeElement void* _physicsInfo { nullptr }; // set by EntitySimulation bool _simulated { false }; // set by EntitySimulation + bool _visuallyReady { true }; bool addActionInternal(EntitySimulationPointer simulation, EntityDynamicPointer action); bool removeActionInternal(const QUuid& actionID, EntitySimulationPointer simulation = nullptr); diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index 5d5344c9c8..f006692415 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -40,6 +40,7 @@ ModelEntityItem::ModelEntityItem(const EntityItemID& entityItemID) : EntityItem( _type = EntityTypes::Model; _lastKnownCurrentFrame = -1; _color[0] = _color[1] = _color[2] = 0; + _visuallyReady = false; } const QString ModelEntityItem::getTextures() const { @@ -65,7 +66,9 @@ EntityItemProperties ModelEntityItem::getProperties(EntityPropertyFlags desiredP COPY_ENTITY_PROPERTY_TO_PROPERTIES(jointTranslationsSet, getJointTranslationsSet); COPY_ENTITY_PROPERTY_TO_PROPERTIES(jointTranslations, getJointTranslations); COPY_ENTITY_PROPERTY_TO_PROPERTIES(relayParentJoints, getRelayParentJoints); - _animationProperties.getProperties(properties); + withReadLock([&] { + _animationProperties.getProperties(properties); + }); return properties; } @@ -122,15 +125,18 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, // grab a local copy of _animationProperties to avoid multiple locks int bytesFromAnimation; + AnimationPropertyGroup animationProperties; withReadLock([&] { - AnimationPropertyGroup animationProperties = _animationProperties; + animationProperties = _animationProperties; bytesFromAnimation = animationProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, propertyFlags, overwriteLocalData, animationPropertiesChanged); - if (animationPropertiesChanged) { - applyNewAnimationProperties(animationProperties); - somethingChanged = true; - } }); + if (animationPropertiesChanged) { + withWriteLock([&] { + applyNewAnimationProperties(animationProperties); + }); + somethingChanged = true; + } bytesRead += bytesFromAnimation; dataAt += bytesFromAnimation; @@ -304,69 +310,77 @@ void ModelEntityItem::setAnimationURL(const QString& url) { void ModelEntityItem::setAnimationSettings(const QString& value) { // NOTE: this method only called for old bitstream format + AnimationPropertyGroup animationProperties; + withReadLock([&] { + animationProperties = _animationProperties; + }); + + // the animations setting is a JSON string that may contain various animation settings. + // if it includes fps, currentFrame, or running, those values will be parsed out and + // will over ride the regular animation settings + QJsonDocument settingsAsJson = QJsonDocument::fromJson(value.toUtf8()); + QJsonObject settingsAsJsonObject = settingsAsJson.object(); + QVariantMap settingsMap = settingsAsJsonObject.toVariantMap(); + if (settingsMap.contains("fps")) { + float fps = settingsMap["fps"].toFloat(); + animationProperties.setFPS(fps); + } + + // old settings used frameIndex + if (settingsMap.contains("frameIndex")) { + float currentFrame = settingsMap["frameIndex"].toFloat(); + animationProperties.setCurrentFrame(currentFrame); + } + + if (settingsMap.contains("running")) { + bool running = settingsMap["running"].toBool(); + if (running != animationProperties.getRunning()) { + animationProperties.setRunning(running); + } + } + + if (settingsMap.contains("firstFrame")) { + float firstFrame = settingsMap["firstFrame"].toFloat(); + animationProperties.setFirstFrame(firstFrame); + } + + if (settingsMap.contains("lastFrame")) { + float lastFrame = settingsMap["lastFrame"].toFloat(); + animationProperties.setLastFrame(lastFrame); + } + + if (settingsMap.contains("loop")) { + bool loop = settingsMap["loop"].toBool(); + animationProperties.setLoop(loop); + } + + if (settingsMap.contains("hold")) { + bool hold = settingsMap["hold"].toBool(); + animationProperties.setHold(hold); + } + + if (settingsMap.contains("allowTranslation")) { + bool allowTranslation = settingsMap["allowTranslation"].toBool(); + animationProperties.setAllowTranslation(allowTranslation); + } + withWriteLock([&] { - auto animationProperties = _animationProperties; - - // the animations setting is a JSON string that may contain various animation settings. - // if it includes fps, currentFrame, or running, those values will be parsed out and - // will over ride the regular animation settings - QJsonDocument settingsAsJson = QJsonDocument::fromJson(value.toUtf8()); - QJsonObject settingsAsJsonObject = settingsAsJson.object(); - QVariantMap settingsMap = settingsAsJsonObject.toVariantMap(); - if (settingsMap.contains("fps")) { - float fps = settingsMap["fps"].toFloat(); - animationProperties.setFPS(fps); - } - - // old settings used frameIndex - if (settingsMap.contains("frameIndex")) { - float currentFrame = settingsMap["frameIndex"].toFloat(); - animationProperties.setCurrentFrame(currentFrame); - } - - if (settingsMap.contains("running")) { - bool running = settingsMap["running"].toBool(); - if (running != animationProperties.getRunning()) { - animationProperties.setRunning(running); - } - } - - if (settingsMap.contains("firstFrame")) { - float firstFrame = settingsMap["firstFrame"].toFloat(); - animationProperties.setFirstFrame(firstFrame); - } - - if (settingsMap.contains("lastFrame")) { - float lastFrame = settingsMap["lastFrame"].toFloat(); - animationProperties.setLastFrame(lastFrame); - } - - if (settingsMap.contains("loop")) { - bool loop = settingsMap["loop"].toBool(); - animationProperties.setLoop(loop); - } - - if (settingsMap.contains("hold")) { - bool hold = settingsMap["hold"].toBool(); - animationProperties.setHold(hold); - } - - if (settingsMap.contains("allowTranslation")) { - bool allowTranslation = settingsMap["allowTranslation"].toBool(); - animationProperties.setAllowTranslation(allowTranslation); - } applyNewAnimationProperties(animationProperties); }); } void ModelEntityItem::setAnimationIsPlaying(bool value) { _flags |= Simulation::DIRTY_UPDATEABLE; - _animationProperties.setRunning(value); + withWriteLock([&] { + _animationProperties.setRunning(value); + }); } void ModelEntityItem::setAnimationFPS(float value) { _flags |= Simulation::DIRTY_UPDATEABLE; - _animationProperties.setFPS(value); + withWriteLock([&] { + _animationProperties.setFPS(value); + }); } // virtual @@ -556,11 +570,9 @@ void ModelEntityItem::setColor(const xColor& value) { // Animation related items... AnimationPropertyGroup ModelEntityItem::getAnimationProperties() const { - AnimationPropertyGroup result; - withReadLock([&] { - result = _animationProperties; + return resultWithReadLock([&] { + return _animationProperties; }); - return result; } bool ModelEntityItem::hasAnimation() const { @@ -581,6 +593,18 @@ void ModelEntityItem::setAnimationCurrentFrame(float value) { }); } +void ModelEntityItem::setAnimationAllowTranslation(bool value) { + withWriteLock([&] { + _animationProperties.setAllowTranslation(value); + }); +} + +bool ModelEntityItem::getAnimationAllowTranslation() const { + return resultWithReadLock([&] { + return _animationProperties.getAllowTranslation(); + }); +} + void ModelEntityItem::setAnimationLoop(bool loop) { withWriteLock([&] { _animationProperties.setLoop(loop); diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index ad6cdf4040..ef758b9dde 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -88,8 +88,8 @@ public: void setAnimationIsPlaying(bool value); void setAnimationFPS(float value); - void setAnimationAllowTranslation(bool value) { _animationProperties.setAllowTranslation(value); }; - bool getAnimationAllowTranslation() const { return _animationProperties.getAllowTranslation(); }; + void setAnimationAllowTranslation(bool value); + bool getAnimationAllowTranslation() const; void setAnimationLoop(bool loop); bool getAnimationLoop() const; diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index a7dfa1a41e..c0ae61fdba 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -42,6 +42,7 @@ ZoneEntityItem::ZoneEntityItem(const EntityItemID& entityItemID) : EntityItem(en _shapeType = DEFAULT_SHAPE_TYPE; _compoundShapeURL = DEFAULT_COMPOUND_SHAPE_URL; + _visuallyReady = false; } EntityItemProperties ZoneEntityItem::getProperties(EntityPropertyFlags desiredProperties) const { diff --git a/libraries/fbx/src/FBX.h b/libraries/fbx/src/FBX.h index 7bd87a4554..fdebb16bc8 100644 --- a/libraries/fbx/src/FBX.h +++ b/libraries/fbx/src/FBX.h @@ -240,7 +240,6 @@ public: QVector vertices; QVector normals; QVector tangents; - mutable QVector normalsAndTangents; // Populated later if needed for blendshapes QVector colors; QVector texCoords; QVector texCoords1; diff --git a/libraries/fbx/src/FBXReader_Mesh.cpp b/libraries/fbx/src/FBXReader_Mesh.cpp index 801edddb06..e8b5abcc9c 100644 --- a/libraries/fbx/src/FBXReader_Mesh.cpp +++ b/libraries/fbx/src/FBXReader_Mesh.cpp @@ -585,13 +585,8 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { FBXMesh& fbxMesh = extractedMesh; graphics::MeshPointer mesh(new graphics::Mesh()); - - // Grab the vertices in a buffer - auto vb = std::make_shared(); - vb->setData(extractedMesh.vertices.size() * sizeof(glm::vec3), - (const gpu::Byte*) extractedMesh.vertices.data()); - gpu::BufferView vbv(vb, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); - mesh->setVertexBuffer(vbv); + bool hasBlendShapes = !fbxMesh.blendshapes.empty(); + int numVerts = extractedMesh.vertices.size(); if (!fbxMesh.normals.empty() && fbxMesh.tangents.empty()) { // Fill with a dummy value to force tangents to be present if there are normals @@ -607,43 +602,61 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { } } - // evaluate all attribute channels sizes - const int normalsSize = fbxMesh.normals.size() * sizeof(NormalType); - const int tangentsSize = fbxMesh.tangents.size() * sizeof(NormalType); + // evaluate all attribute elements and data sizes + + // Position is a vec3 + const auto positionElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ); + const int positionsSize = numVerts * positionElement.getSize(); + + // Normal and tangent are always there together packed in normalized xyz32bits word (times 2) + const auto normalElement = FBX_NORMAL_ELEMENT; + const int normalsSize = fbxMesh.normals.size() * normalElement.getSize(); + const int tangentsSize = fbxMesh.tangents.size() * normalElement.getSize(); // If there are normals then there should be tangents - assert(normalsSize <= tangentsSize); if (tangentsSize > normalsSize) { qWarning() << "Unexpected tangents in " << url; } const auto normalsAndTangentsSize = normalsSize + tangentsSize; - const int normalsAndTangentsStride = 2 * sizeof(NormalType); - const int colorsSize = fbxMesh.colors.size() * sizeof(ColorType); + const int normalsAndTangentsStride = 2 * normalElement.getSize(); + + // Color attrib + const auto colorElement = FBX_COLOR_ELEMENT; + const int colorsSize = fbxMesh.colors.size() * colorElement.getSize(); + // Texture coordinates are stored in 2 half floats - const int texCoordsSize = fbxMesh.texCoords.size() * sizeof(vec2h); - const int texCoords1Size = fbxMesh.texCoords1.size() * sizeof(vec2h); + const auto texCoordsElement = gpu::Element(gpu::VEC2, gpu::HALF, gpu::UV); + const int texCoordsSize = fbxMesh.texCoords.size() * texCoordsElement.getSize(); + const int texCoords1Size = fbxMesh.texCoords1.size() * texCoordsElement.getSize(); - int clusterIndicesSize = fbxMesh.clusterIndices.size() * sizeof(uint8_t); - if (fbxMesh.clusters.size() > UINT8_MAX) { - // we need 16 bits instead of just 8 for clusterIndices - clusterIndicesSize *= 2; - } + // Support for 4 skinning clusters: + // 4 Indices are uint8 ideally, uint16 if more than 256. + const auto clusterIndiceElement = (fbxMesh.clusters.size() < UINT8_MAX ? gpu::Element(gpu::VEC4, gpu::UINT8, gpu::XYZW) : gpu::Element(gpu::VEC4, gpu::UINT16, gpu::XYZW)); + // 4 Weights are normalized 16bits + const auto clusterWeightElement = gpu::Element(gpu::VEC4, gpu::NUINT16, gpu::XYZW); - const int clusterWeightsSize = fbxMesh.clusterWeights.size() * sizeof(uint16_t); + // Cluster indices and weights must be the same sizes + const int NUM_CLUSTERS_PER_VERT = 4; + const int numVertClusters = (fbxMesh.clusterIndices.size() == fbxMesh.clusterWeights.size() ? fbxMesh.clusterIndices.size() / NUM_CLUSTERS_PER_VERT : 0); + const int clusterIndicesSize = numVertClusters * clusterIndiceElement.getSize(); + const int clusterWeightsSize = numVertClusters * clusterWeightElement.getSize(); - // Normals and tangents are interleaved - const int normalsOffset = 0; - const int tangentsOffset = normalsOffset + sizeof(NormalType); - const int colorsOffset = normalsOffset + normalsSize + tangentsSize; + // Decide on where to put what seequencially in a big buffer: + const int positionsOffset = 0; + const int normalsAndTangentsOffset = positionsOffset + positionsSize; + const int colorsOffset = normalsAndTangentsOffset + normalsAndTangentsSize; const int texCoordsOffset = colorsOffset + colorsSize; const int texCoords1Offset = texCoordsOffset + texCoordsSize; const int clusterIndicesOffset = texCoords1Offset + texCoords1Size; const int clusterWeightsOffset = clusterIndicesOffset + clusterIndicesSize; - const int totalAttributeSize = clusterWeightsOffset + clusterWeightsSize; + const int totalVertsSize = clusterWeightsOffset + clusterWeightsSize; - // Copy all attribute data in a single attribute buffer - auto attribBuffer = std::make_shared(); - attribBuffer->resize(totalAttributeSize); + // Copy all vertex data in a single buffer + auto vertBuffer = std::make_shared(); + vertBuffer->resize(totalVertsSize); + + // First positions + vertBuffer->setSubData(positionsOffset, positionsSize, (const gpu::Byte*) extractedMesh.vertices.data()); // Interleave normals and tangents if (normalsSize > 0) { @@ -651,8 +664,8 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { normalsAndTangents.reserve(fbxMesh.normals.size() + fbxMesh.tangents.size()); for (auto normalIt = fbxMesh.normals.constBegin(), tangentIt = fbxMesh.tangents.constBegin(); - normalIt != fbxMesh.normals.constEnd(); - ++normalIt, ++tangentIt) { + normalIt != fbxMesh.normals.constEnd(); + ++normalIt, ++tangentIt) { #if FBX_PACK_NORMALS const auto normal = normalizeDirForPacking(*normalIt); const auto tangent = normalizeDirForPacking(*tangentIt); @@ -665,9 +678,10 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { normalsAndTangents.push_back(packedNormal); normalsAndTangents.push_back(packedTangent); } - attribBuffer->setSubData(normalsOffset, normalsAndTangentsSize, (const gpu::Byte*) normalsAndTangents.data()); + vertBuffer->setSubData(normalsAndTangentsOffset, normalsAndTangentsSize, (const gpu::Byte*) normalsAndTangents.data()); } + // Pack colors if (colorsSize > 0) { #if FBX_PACK_COLORS std::vector colors; @@ -676,12 +690,13 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { for (const auto& color : fbxMesh.colors) { colors.push_back(glm::packUnorm4x8(glm::vec4(color, 1.0f))); } - attribBuffer->setSubData(colorsOffset, colorsSize, (const gpu::Byte*) colors.data()); + vertBuffer->setSubData(colorsOffset, colorsSize, (const gpu::Byte*) colors.data()); #else - attribBuffer->setSubData(colorsOffset, colorsSize, (const gpu::Byte*) fbxMesh.colors.constData()); + vertBuffer->setSubData(colorsOffset, colorsSize, (const gpu::Byte*) fbxMesh.colors.constData()); #endif } + // Pack Texcoords 0 and 1 (if exists) if (texCoordsSize > 0) { QVector texCoordData; texCoordData.reserve(fbxMesh.texCoords.size()); @@ -692,9 +707,8 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { texCoordVec2h.y = glm::detail::toFloat16(texCoordVec2f.y); texCoordData.push_back(texCoordVec2h); } - attribBuffer->setSubData(texCoordsOffset, texCoordsSize, (const gpu::Byte*) texCoordData.constData()); + vertBuffer->setSubData(texCoordsOffset, texCoordsSize, (const gpu::Byte*) texCoordData.constData()); } - if (texCoords1Size > 0) { QVector texCoordData; texCoordData.reserve(fbxMesh.texCoords1.size()); @@ -705,69 +719,170 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { texCoordVec2h.y = glm::detail::toFloat16(texCoordVec2f.y); texCoordData.push_back(texCoordVec2h); } - attribBuffer->setSubData(texCoords1Offset, texCoords1Size, (const gpu::Byte*) texCoordData.constData()); + vertBuffer->setSubData(texCoords1Offset, texCoords1Size, (const gpu::Byte*) texCoordData.constData()); } - if (fbxMesh.clusters.size() < UINT8_MAX) { - // yay! we can fit the clusterIndices within 8-bits - int32_t numIndices = fbxMesh.clusterIndices.size(); - QVector clusterIndices; - clusterIndices.resize(numIndices); - for (int32_t i = 0; i < numIndices; ++i) { - assert(fbxMesh.clusterIndices[i] <= UINT8_MAX); - clusterIndices[i] = (uint8_t)(fbxMesh.clusterIndices[i]); + // Clusters data + if (clusterIndicesSize > 0) { + if (fbxMesh.clusters.size() < UINT8_MAX) { + // yay! we can fit the clusterIndices within 8-bits + int32_t numIndices = fbxMesh.clusterIndices.size(); + QVector clusterIndices; + clusterIndices.resize(numIndices); + for (int32_t i = 0; i < numIndices; ++i) { + assert(fbxMesh.clusterIndices[i] <= UINT8_MAX); + clusterIndices[i] = (uint8_t)(fbxMesh.clusterIndices[i]); + } + vertBuffer->setSubData(clusterIndicesOffset, clusterIndicesSize, (const gpu::Byte*) clusterIndices.constData()); + } else { + vertBuffer->setSubData(clusterIndicesOffset, clusterIndicesSize, (const gpu::Byte*) fbxMesh.clusterIndices.constData()); } - attribBuffer->setSubData(clusterIndicesOffset, clusterIndicesSize, (const gpu::Byte*) clusterIndices.constData()); - } else { - attribBuffer->setSubData(clusterIndicesOffset, clusterIndicesSize, (const gpu::Byte*) fbxMesh.clusterIndices.constData()); } - attribBuffer->setSubData(clusterWeightsOffset, clusterWeightsSize, (const gpu::Byte*) fbxMesh.clusterWeights.constData()); + if (clusterWeightsSize > 0) { + vertBuffer->setSubData(clusterWeightsOffset, clusterWeightsSize, (const gpu::Byte*) fbxMesh.clusterWeights.constData()); + } - if (normalsSize) { - mesh->addAttribute(gpu::Stream::NORMAL, - graphics::BufferView(attribBuffer, normalsOffset, normalsAndTangentsSize, - normalsAndTangentsStride, FBX_NORMAL_ELEMENT)); - mesh->addAttribute(gpu::Stream::TANGENT, - graphics::BufferView(attribBuffer, tangentsOffset, normalsAndTangentsSize, - normalsAndTangentsStride, FBX_NORMAL_ELEMENT)); + + // Now we decide on how to interleave the attributes and provide the vertices among bufers: + // Aka the Vertex format and the vertexBufferStream + auto vertexFormat = std::make_shared(); + auto vertexBufferStream = std::make_shared(); + + // Decision time: + // if blendshapes then keep position and normals/tangents as separated channel buffers from interleaved attributes + // else everything is interleaved in one buffer + + // Default case is no blend shapes + gpu::BufferPointer attribBuffer; + int totalAttribBufferSize = totalVertsSize; + gpu::uint8 posChannel = 0; + gpu::uint8 tangentChannel = posChannel; + gpu::uint8 attribChannel = posChannel; + bool interleavePositions = true; + bool interleaveNormalsTangents = true; + + // TODO: We are using the same vertex format layout for all meshes because this is more efficient + // This work is going into rc73 release which is meant to be used for the SPot500 event and we are picking the format + // that works best for blendshaped and skinned meshes aka the avatars. + // We will improve this technique in a hot fix to 73. + hasBlendShapes = true; + + // If has blend shapes allocate and assign buffers for pos and tangents now + if (hasBlendShapes) { + auto posBuffer = std::make_shared(); + posBuffer->setData(positionsSize, (const gpu::Byte*) vertBuffer->getData() + positionsOffset); + vertexBufferStream->addBuffer(posBuffer, 0, positionElement.getSize()); + + auto normalsAndTangentsBuffer = std::make_shared(); + normalsAndTangentsBuffer->setData(normalsAndTangentsSize, (const gpu::Byte*) vertBuffer->getData() + normalsAndTangentsOffset); + vertexBufferStream->addBuffer(normalsAndTangentsBuffer, 0, normalsAndTangentsStride); + + // update channels and attribBuffer size accordingly + interleavePositions = false; + interleaveNormalsTangents = false; + + tangentChannel = 1; + attribChannel = 2; + + totalAttribBufferSize = totalVertsSize - positionsSize - normalsAndTangentsSize; } + + // Define the vertex format, compute the offset for each attributes as we append them to the vertex format + gpu::Offset bufOffset = 0; + if (positionsSize) { + vertexFormat->setAttribute(gpu::Stream::POSITION, posChannel, positionElement, bufOffset); + bufOffset += positionElement.getSize(); + if (!interleavePositions) { + bufOffset = 0; + } + } + if (normalsSize) { + vertexFormat->setAttribute(gpu::Stream::NORMAL, tangentChannel, normalElement, bufOffset); + bufOffset += normalElement.getSize(); + vertexFormat->setAttribute(gpu::Stream::TANGENT, tangentChannel, normalElement, bufOffset); + bufOffset += normalElement.getSize(); + if (!interleaveNormalsTangents) { + bufOffset = 0; + } + } + + // Pack normal and Tangent with the rest of atributes if no blend shapes if (colorsSize) { - mesh->addAttribute(gpu::Stream::COLOR, - graphics::BufferView(attribBuffer, colorsOffset, colorsSize, FBX_COLOR_ELEMENT)); + vertexFormat->setAttribute(gpu::Stream::COLOR, attribChannel, colorElement, bufOffset); + bufOffset += colorElement.getSize(); } if (texCoordsSize) { - mesh->addAttribute(gpu::Stream::TEXCOORD, - graphics::BufferView( attribBuffer, texCoordsOffset, texCoordsSize, - gpu::Element(gpu::VEC2, gpu::HALF, gpu::UV))); + vertexFormat->setAttribute(gpu::Stream::TEXCOORD, attribChannel, texCoordsElement, bufOffset); + bufOffset += texCoordsElement.getSize(); } if (texCoords1Size) { - mesh->addAttribute( gpu::Stream::TEXCOORD1, - graphics::BufferView(attribBuffer, texCoords1Offset, texCoords1Size, - gpu::Element(gpu::VEC2, gpu::HALF, gpu::UV))); + vertexFormat->setAttribute(gpu::Stream::TEXCOORD1, attribChannel, texCoordsElement, bufOffset); + bufOffset += texCoordsElement.getSize(); } else if (texCoordsSize) { - mesh->addAttribute(gpu::Stream::TEXCOORD1, - graphics::BufferView(attribBuffer, texCoordsOffset, texCoordsSize, - gpu::Element(gpu::VEC2, gpu::HALF, gpu::UV))); + vertexFormat->setAttribute(gpu::Stream::TEXCOORD1, attribChannel, texCoordsElement, bufOffset - texCoordsElement.getSize()); } - if (clusterIndicesSize) { - if (fbxMesh.clusters.size() < UINT8_MAX) { - mesh->addAttribute(gpu::Stream::SKIN_CLUSTER_INDEX, - graphics::BufferView(attribBuffer, clusterIndicesOffset, clusterIndicesSize, - gpu::Element(gpu::VEC4, gpu::UINT8, gpu::XYZW))); - } else { - mesh->addAttribute(gpu::Stream::SKIN_CLUSTER_INDEX, - graphics::BufferView(attribBuffer, clusterIndicesOffset, clusterIndicesSize, - gpu::Element(gpu::VEC4, gpu::UINT16, gpu::XYZW))); - } + vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_INDEX, attribChannel, clusterIndiceElement, bufOffset); + bufOffset += clusterIndiceElement.getSize(); } if (clusterWeightsSize) { - mesh->addAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT, - graphics::BufferView(attribBuffer, clusterWeightsOffset, clusterWeightsSize, - gpu::Element(gpu::VEC4, gpu::NUINT16, gpu::XYZW))); + vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT, attribChannel, clusterWeightElement, bufOffset); + bufOffset += clusterWeightElement.getSize(); } + // Finally, allocate and fill the attribBuffer interleaving the attributes as needed: + { + auto vPositionOffset = 0; + auto vPositionSize = (interleavePositions ? positionsSize / numVerts : 0); + auto vNormalsAndTangentsOffset = vPositionOffset + vPositionSize; + auto vNormalsAndTangentsSize = (interleaveNormalsTangents ? normalsAndTangentsSize / numVerts : 0); + + auto vColorOffset = vNormalsAndTangentsOffset + vNormalsAndTangentsSize; + auto vColorSize = colorsSize / numVerts; + + auto vTexcoord0Offset = vColorOffset + vColorSize; + auto vTexcoord0Size = texCoordsSize / numVerts; + + auto vTexcoord1Offset = vTexcoord0Offset + vTexcoord0Size; + auto vTexcoord1Size = texCoords1Size / numVerts; + + auto vClusterIndiceOffset = vTexcoord1Offset + vTexcoord1Size; + auto vClusterIndiceSize = clusterIndicesSize / numVerts; + + auto vClusterWeightOffset = vClusterIndiceOffset + vClusterIndiceSize; + auto vClusterWeightSize = clusterWeightsSize / numVerts; + + auto vStride = vClusterWeightOffset + vClusterWeightSize; + + std::vector dest; + dest.resize(totalAttribBufferSize); + auto vDest = dest.data(); + + auto source = vertBuffer->getData(); + + for (int i = 0; i < numVerts; i++) { + + if (vPositionSize) memcpy(vDest + vPositionOffset, source + positionsOffset + i * vPositionSize, vPositionSize); + if (vNormalsAndTangentsSize) memcpy(vDest + vNormalsAndTangentsOffset, source + normalsAndTangentsOffset + i * vNormalsAndTangentsSize, vNormalsAndTangentsSize); + if (vColorSize) memcpy(vDest + vColorOffset, source + colorsOffset + i * vColorSize, vColorSize); + if (vTexcoord0Size) memcpy(vDest + vTexcoord0Offset, source + texCoordsOffset + i * vTexcoord0Size, vTexcoord0Size); + if (vTexcoord1Size) memcpy(vDest + vTexcoord1Offset, source + texCoords1Offset + i * vTexcoord1Size, vTexcoord1Size); + if (vClusterIndiceSize) memcpy(vDest + vClusterIndiceOffset, source + clusterIndicesOffset + i * vClusterIndiceSize, vClusterIndiceSize); + if (vClusterWeightSize) memcpy(vDest + vClusterWeightOffset, source + clusterWeightsOffset + i * vClusterWeightSize, vClusterWeightSize); + + vDest += vStride; + } + + auto attribBuffer = std::make_shared(); + attribBuffer->setData(totalAttribBufferSize, dest.data()); + vertexBufferStream->addBuffer(attribBuffer, 0, vStride); + } + + // Mesh vertex format and vertex stream is ready + mesh->setVertexFormatAndStream(vertexFormat, vertexBufferStream); + + // Index and Part Buffers unsigned int totalIndices = 0; foreach(const FBXMeshPart& part, extractedMesh.parts) { totalIndices += (part.quadTrianglesIndices.size() + part.triangleIndices.size()); diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp index c1848d99b1..4fea4f2dc5 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp @@ -201,9 +201,10 @@ void GLBackend::renderPassTransfer(const Batch& batch) { { Vec2u outputSize{ 1,1 }; - if (_output._framebuffer) { - outputSize.x = _output._framebuffer->getWidth(); - outputSize.y = _output._framebuffer->getHeight(); + auto framebuffer = acquire(_output._framebuffer); + if (framebuffer) { + outputSize.x = framebuffer->getWidth(); + outputSize.y = framebuffer->getHeight(); } else if (glm::dot(_transform._projectionJitter, _transform._projectionJitter)>0.0f) { qCWarning(gpugllogging) << "Jittering needs to have a frame buffer to be set"; } @@ -220,6 +221,7 @@ void GLBackend::renderPassTransfer(const Batch& batch) { _stereo._contextDisable = false; break; + case Batch::COMMAND_setFramebuffer: case Batch::COMMAND_setViewportTransform: case Batch::COMMAND_setViewTransform: case Batch::COMMAND_setProjectionTransform: diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h index cadcec7a56..0b76ef17de 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h @@ -30,7 +30,6 @@ #include "GLShared.h" - // Different versions for the stereo drawcall // Current preferred is "instanced" which draw the shape twice but instanced and rely on clipping plane to draw left/right side only #if defined(USE_GLES) @@ -40,7 +39,6 @@ #define GPU_STEREO_TECHNIQUE_INSTANCED #endif - // Let these be configured by the one define picked above #ifdef GPU_STEREO_TECHNIQUE_DOUBLED_SIMPLE #define GPU_STEREO_DRAWCALL_DOUBLED @@ -56,8 +54,153 @@ #define GPU_STEREO_CAMERA_BUFFER #endif +// +// GL Backend pointer storage mechanism +// One of the following three defines must be defined. +// GPU_POINTER_STORAGE_SHARED + +// The platonic ideal, use references to smart pointers. +// However, this produces artifacts because there are too many places in the code right now that +// create temporary values (undesirable smart pointer duplications) and then those temp variables +// get passed on and have their reference taken, and then invalidated +// GPU_POINTER_STORAGE_REF + +// Raw pointer manipulation. Seems more dangerous than the reference wrappers, +// but in practice, the danger of grabbing a reference to a temporary variable +// is causing issues +// GPU_POINTER_STORAGE_RAW + +#if defined(USE_GLES) +#define GPU_POINTER_STORAGE_SHARED +#else +#define GPU_POINTER_STORAGE_RAW +#endif + namespace gpu { namespace gl { +#if defined(GPU_POINTER_STORAGE_SHARED) +template +static inline bool compare(const std::shared_ptr& a, const std::shared_ptr& b) { + return a == b; +} + +template +static inline T* acquire(const std::shared_ptr& pointer) { + return pointer.get(); +} + +template +static inline void reset(std::shared_ptr& pointer) { + return pointer.reset(); +} + +template +static inline bool valid(const std::shared_ptr& pointer) { + return pointer.operator bool(); +} + +template +static inline void assign(std::shared_ptr& pointer, const std::shared_ptr& source) { + pointer = source; +} + +using BufferReference = BufferPointer; +using TextureReference = TexturePointer; +using FramebufferReference = FramebufferPointer; +using FormatReference = Stream::FormatPointer; +using PipelineReference = PipelinePointer; + +#define GPU_REFERENCE_INIT_VALUE nullptr + +#elif defined(GPU_POINTER_STORAGE_REF) + +template +class PointerReferenceWrapper : public std::reference_wrapper> { + using Parent = std::reference_wrapper>; + +public: + using Pointer = std::shared_ptr; + PointerReferenceWrapper() : Parent(EMPTY()) {} + PointerReferenceWrapper(const Pointer& pointer) : Parent(pointer) {} + void clear() { *this = EMPTY(); } + +private: + static const Pointer& EMPTY() { + static const Pointer EMPTY_VALUE; + return EMPTY_VALUE; + }; +}; + +template +static bool compare(const PointerReferenceWrapper& reference, const std::shared_ptr& pointer) { + return reference.get() == pointer; +} + +template +static inline T* acquire(const PointerReferenceWrapper& reference) { + return reference.get().get(); +} + +template +static void assign(PointerReferenceWrapper& reference, const std::shared_ptr& pointer) { + reference = pointer; +} + +template +static bool valid(const PointerReferenceWrapper& reference) { + return reference.get().operator bool(); +} + +template +static inline void reset(PointerReferenceWrapper& reference) { + return reference.clear(); +} + +using BufferReference = PointerReferenceWrapper; +using TextureReference = PointerReferenceWrapper; +using FramebufferReference = PointerReferenceWrapper; +using FormatReference = PointerReferenceWrapper; +using PipelineReference = PointerReferenceWrapper; + +#define GPU_REFERENCE_INIT_VALUE + +#elif defined(GPU_POINTER_STORAGE_RAW) + +template +static bool compare(const T* const& rawPointer, const std::shared_ptr& pointer) { + return rawPointer == pointer.get(); +} + +template +static inline T* acquire(T*& rawPointer) { + return rawPointer; +} + +template +static inline bool valid(const T* const& rawPointer) { + return rawPointer; +} + +template +static inline void reset(T*& rawPointer) { + rawPointer = nullptr; +} + +template +static inline void assign(T*& rawPointer, const std::shared_ptr& pointer) { + rawPointer = pointer.get(); +} + +using BufferReference = Buffer*; +using TextureReference = Texture*; +using FramebufferReference = Framebuffer*; +using FormatReference = Stream::Format*; +using PipelineReference = Pipeline*; + +#define GPU_REFERENCE_INIT_VALUE nullptr + +#endif + class GLBackend : public Backend, public std::enable_shared_from_this { // Context Backend static interface required friend class gpu::Context; @@ -67,8 +210,8 @@ class GLBackend : public Backend, public std::enable_shared_from_this protected: explicit GLBackend(bool syncCache); GLBackend(); -public: +public: #if defined(USE_GLES) // https://www.khronos.org/registry/OpenGL-Refpages/es3/html/glGet.xhtml static const GLint MIN_REQUIRED_TEXTURE_IMAGE_UNITS = 16; @@ -109,8 +252,8 @@ public: // This is the ugly "download the pixels to sysmem for taking a snapshot" // Just avoid using it, it's ugly and will break performances virtual void downloadFramebuffer(const FramebufferPointer& srcFramebuffer, - const Vec4i& region, QImage& destImage) final override; - + const Vec4i& region, + QImage& destImage) final override; // this is the maximum numeber of available input buffers size_t getNumInputBuffers() const { return _input._invalidBuffers.size(); } @@ -131,7 +274,6 @@ public: static const int MAX_NUM_RESOURCE_TABLE_TEXTURES = 2; size_t getMaxNumResourceTextureTables() const { return MAX_NUM_RESOURCE_TABLE_TEXTURES; } - // Draw Stage virtual void do_draw(const Batch& batch, size_t paramOffset) = 0; virtual void do_drawIndexed(const Batch& batch, size_t paramOffset) = 0; @@ -183,7 +325,6 @@ public: // Reset stages virtual void do_resetStages(const Batch& batch, size_t paramOffset) final; - virtual void do_disableContextViewCorrection(const Batch& batch, size_t paramOffset) final; virtual void do_restoreContextViewCorrection(const Batch& batch, size_t paramOffset) final; @@ -203,7 +344,7 @@ public: virtual void do_popProfileRange(const Batch& batch, size_t paramOffset) final; // TODO: As long as we have gl calls explicitely issued from interface - // code, we need to be able to record and batch these calls. THe long + // code, we need to be able to record and batch these calls. THe long // term strategy is to get rid of any GL calls in favor of the HIFI GPU API virtual void do_glUniform1i(const Batch& batch, size_t paramOffset) final; virtual void do_glUniform1f(const Batch& batch, size_t paramOffset) final; @@ -228,7 +369,9 @@ public: virtual void do_setStateAntialiasedLineEnable(bool enable) final; virtual void do_setStateDepthBias(Vec2 bias) final; virtual void do_setStateDepthTest(State::DepthTest test) final; - virtual void do_setStateStencil(State::StencilActivation activation, State::StencilTest frontTest, State::StencilTest backTest) final; + virtual void do_setStateStencil(State::StencilActivation activation, + State::StencilTest frontTest, + State::StencilTest backTest) final; virtual void do_setStateAlphaToCoverageEnable(bool enable) final; virtual void do_setStateSampleMask(uint32 mask) final; virtual void do_setStateBlend(State::BlendFunction blendFunction) final; @@ -239,6 +382,7 @@ public: virtual GLuint getFramebufferID(const FramebufferPointer& framebuffer) = 0; virtual GLuint getTextureID(const TexturePointer& texture) final; virtual GLuint getBufferID(const Buffer& buffer) = 0; + virtual GLuint getBufferIDUnsynced(const Buffer& buffer) = 0; virtual GLuint getQueryID(const QueryPointer& query) = 0; virtual GLFramebuffer* syncGPUObject(const Framebuffer& framebuffer) = 0; @@ -256,7 +400,9 @@ public: virtual void releaseQuery(GLuint id) const; virtual void queueLambda(const std::function lambda) const; - bool isTextureManagementSparseEnabled() const override { return (_textureManagement._sparseCapable && Texture::getEnableSparseTextures()); } + bool isTextureManagementSparseEnabled() const override { + return (_textureManagement._sparseCapable && Texture::getEnableSparseTextures()); + } protected: virtual GLint getRealUniformLocation(GLint location) const; @@ -265,11 +411,11 @@ protected: // FIXME instead of a single flag, create a features struct similar to // https://www.khronos.org/registry/vulkan/specs/1.0/man/html/VkPhysicalDeviceFeatures.html - virtual bool supportsBindless() const { return false; } + virtual bool supportsBindless() const { return false; } static const size_t INVALID_OFFSET = (size_t)-1; - bool _inRenderTransferPass { false }; - int _currentDraw { -1 }; + bool _inRenderTransferPass{ false }; + int _currentDraw{ -1 }; std::list profileRanges; mutable Mutex _trashMutex; @@ -298,46 +444,36 @@ protected: virtual void updateInput() = 0; struct InputStageState { - bool _invalidFormat { true }; - bool _lastUpdateStereoState{ false }; + bool _invalidFormat{ true }; + bool _lastUpdateStereoState{ false }; bool _hadColorAttribute{ true }; - Stream::FormatPointer _format; + FormatReference _format{ GPU_REFERENCE_INIT_VALUE }; std::string _formatKey; typedef std::bitset ActivationCache; - ActivationCache _attributeActivation { 0 }; + ActivationCache _attributeActivation{ 0 }; typedef std::bitset BuffersState; BuffersState _invalidBuffers{ 0 }; BuffersState _attribBindingBuffers{ 0 }; - Buffers _buffers; - Offsets _bufferOffsets; - Offsets _bufferStrides; - std::vector _bufferVBOs; + std::array _buffers{}; + std::array _bufferOffsets{}; + std::array _bufferStrides{}; + std::array _bufferVBOs{}; glm::vec4 _colorAttribute{ 0.0f }; - BufferPointer _indexBuffer; - Offset _indexBufferOffset { 0 }; - Type _indexBufferType { UINT32 }; - - BufferPointer _indirectBuffer; + BufferReference _indexBuffer{}; + Offset _indexBufferOffset{ 0 }; + Type _indexBufferType{ UINT32 }; + + BufferReference _indirectBuffer{}; Offset _indirectBufferOffset{ 0 }; Offset _indirectBufferStride{ 0 }; - GLuint _defaultVAO { 0 }; - - InputStageState() : - _invalidFormat(true), - _format(0), - _formatKey(), - _attributeActivation(0), - _buffers(_invalidBuffers.size(), BufferPointer(0)), - _bufferOffsets(_invalidBuffers.size(), 0), - _bufferStrides(_invalidBuffers.size(), 0), - _bufferVBOs(_invalidBuffers.size(), 0) {} + GLuint _defaultVAO{ 0 }; } _input; virtual void initTransform() = 0; @@ -348,7 +484,7 @@ protected: virtual void resetTransformStage(); // Allows for correction of the camera pose to account for changes - // between the time when a was recorded and the time(s) when it is + // between the time when a was recorded and the time(s) when it is // executed // Prev is the previous correction used at previous frame struct CameraCorrection { @@ -363,9 +499,12 @@ protected: struct Cameras { TransformCamera _cams[2]; - Cameras() {}; + Cameras(){}; Cameras(const TransformCamera& cam) { memcpy(_cams, &cam, sizeof(TransformCamera)); }; - Cameras(const TransformCamera& camL, const TransformCamera& camR) { memcpy(_cams, &camL, sizeof(TransformCamera)); memcpy(_cams + 1, &camR, sizeof(TransformCamera)); }; + Cameras(const TransformCamera& camL, const TransformCamera& camR) { + memcpy(_cams, &camL, sizeof(TransformCamera)); + memcpy(_cams + 1, &camR, sizeof(TransformCamera)); + }; }; using CameraBufferElement = Cameras; @@ -379,25 +518,24 @@ protected: mutable std::map _drawCallInfoOffsets; - GLuint _objectBuffer { 0 }; - GLuint _cameraBuffer { 0 }; - GLuint _drawCallInfoBuffer { 0 }; - GLuint _objectBufferTexture { 0 }; - size_t _cameraUboSize { 0 }; + GLuint _objectBuffer{ 0 }; + GLuint _cameraBuffer{ 0 }; + GLuint _drawCallInfoBuffer{ 0 }; + GLuint _objectBufferTexture{ 0 }; + size_t _cameraUboSize{ 0 }; bool _viewIsCamera{ false }; - bool _skybox { false }; + bool _skybox{ false }; Transform _view; CameraCorrection _correction; bool _viewCorrectionEnabled{ true }; - Mat4 _projection; - Vec4i _viewport { 0, 0, 1, 1 }; - Vec2 _depthRange { 0.0f, 1.0f }; + Vec4i _viewport{ 0, 0, 1, 1 }; + Vec2 _depthRange{ 0.0f, 1.0f }; Vec2 _projectionJitter{ 0.0f, 0.0f }; - bool _invalidView { false }; - bool _invalidProj { false }; - bool _invalidViewport { false }; + bool _invalidView{ false }; + bool _invalidProj{ false }; + bool _invalidViewport{ false }; bool _enabledDrawcallInfoBuffer{ false }; @@ -416,45 +554,54 @@ protected: struct UniformStageState { struct BufferState { - BufferPointer buffer; + BufferReference buffer{}; GLintptr offset{ 0 }; GLsizeiptr size{ 0 }; - BufferState(const BufferPointer& buffer = nullptr, GLintptr offset = 0, GLsizeiptr size = 0); - bool operator ==(BufferState& other) const { - return offset == other.offset && size == other.size && buffer == other.buffer; + + BufferState& operator=(const BufferState& other) = delete; + void reset() { + gpu::gl::reset(buffer); + offset = 0; + size = 0; + } + bool compare(const BufferPointer& buffer, GLintptr offset, GLsizeiptr size) { + const auto& self = *this; + return (self.offset == offset && self.size == size && gpu::gl::compare(self.buffer, buffer)); } }; // MAX_NUM_UNIFORM_BUFFERS-1 is the max uniform index BATCHES are allowed to set, but - // MIN_REQUIRED_UNIFORM_BUFFER_BINDINGS is used here because the backend sets some - // internal UBOs for things like camera correction + // MIN_REQUIRED_UNIFORM_BUFFER_BINDINGS is used here because the backend sets some + // internal UBOs for things like camera correction std::array _buffers; } _uniform; - // Helper function that provides common code + // Helper function that provides common code void bindUniformBuffer(uint32_t slot, const BufferPointer& buffer, GLintptr offset = 0, GLsizeiptr size = 0); void releaseUniformBuffer(uint32_t slot); void resetUniformStage(); // update resource cache and do the gl bind/unbind call with the current gpu::Buffer cached at slot s // This is using different gl object depending on the gl version - virtual bool bindResourceBuffer(uint32_t slot, BufferPointer& buffer) = 0; + virtual bool bindResourceBuffer(uint32_t slot, const BufferPointer& buffer) = 0; virtual void releaseResourceBuffer(uint32_t slot) = 0; - // Helper function that provides common code used by do_setResourceTexture and + // Helper function that provides common code used by do_setResourceTexture and // do_setResourceTextureTable (in non-bindless mode) void bindResourceTexture(uint32_t slot, const TexturePointer& texture); - // update resource cache and do the gl unbind call with the current gpu::Texture cached at slot s void releaseResourceTexture(uint32_t slot); void resetResourceStage(); struct ResourceStageState { - std::array _buffers; - std::array _textures; - //Textures _textures { { MAX_NUM_RESOURCE_TEXTURES } }; + struct TextureState { + TextureReference _texture{}; + GLenum _target; + }; + std::array _buffers{}; + std::array _textures{}; int findEmptyTextureSlot() const; } _resource; @@ -469,21 +616,22 @@ protected: void resetPipelineStage(); struct PipelineStageState { - PipelinePointer _pipeline; + PipelineReference _pipeline{}; - GLuint _program { 0 }; - bool _cameraCorrection { false }; - GLShader* _programShader { nullptr }; - bool _invalidProgram { false }; + GLuint _program{ 0 }; + bool _cameraCorrection{ false }; + GLShader* _programShader{ nullptr }; + bool _invalidProgram{ false }; - BufferView _cameraCorrectionBuffer { gpu::BufferView(std::make_shared(sizeof(CameraCorrection), nullptr )) }; - BufferView _cameraCorrectionBufferIdentity { gpu::BufferView(std::make_shared(sizeof(CameraCorrection), nullptr )) }; + BufferView _cameraCorrectionBuffer{ gpu::BufferView(std::make_shared(sizeof(CameraCorrection), nullptr)) }; + BufferView _cameraCorrectionBufferIdentity{ gpu::BufferView( + std::make_shared(sizeof(CameraCorrection), nullptr)) }; State::Data _stateCache{ State::DEFAULT }; - State::Signature _stateSignatureCache { 0 }; + State::Signature _stateSignatureCache{ 0 }; - GLState* _state { nullptr }; - bool _invalidState { false }; + GLState* _state{ nullptr }; + bool _invalidState{ false }; PipelineStageState() { _cameraCorrectionBuffer.edit() = CameraCorrection(); @@ -497,9 +645,9 @@ protected: virtual GLShader* compileBackendProgram(const Shader& program, const Shader::CompilationHandler& handler); virtual GLShader* compileBackendShader(const Shader& shader, const Shader::CompilationHandler& handler); virtual std::string getBackendShaderHeader() const = 0; - // For a program, this will return a string containing all the source files (without any - // backend headers or defines). For a vertex, fragment or geometry shader, this will - // return the fully customized shader with all the version and backend specific + // For a program, this will return a string containing all the source files (without any + // backend headers or defines). For a vertex, fragment or geometry shader, this will + // return the fully customized shader with all the version and backend specific // preprocessor directives // The program string returned can be used as a key for a cache of shader binaries // The shader strings can be reliably sent to the low level `compileShader` functions @@ -515,22 +663,22 @@ protected: // Synchronize the state cache of this Backend with the actual real state of the GL Context void syncOutputStateCache(); void resetOutputStage(); - + struct OutputStageState { - FramebufferPointer _framebuffer { nullptr }; - GLuint _drawFBO { 0 }; + FramebufferReference _framebuffer{}; + GLuint _drawFBO{ 0 }; } _output; void resetQueryStage(); struct QueryStageState { - uint32_t _rangeQueryDepth { 0 }; + uint32_t _rangeQueryDepth{ 0 }; } _queryStage; void resetStages(); // Stores cached binary versions of the shaders for quicker startup on subsequent runs - // Note that shaders in the cache can still fail to load due to hardware or driver - // changes that invalidate the cached binary, in which case we fall back on compiling + // Note that shaders in the cache can still fail to load due to hardware or driver + // changes that invalidate the cached binary, in which case we fall back on compiling // the source again struct ShaderBinaryCache { std::mutex _mutex; @@ -542,7 +690,7 @@ protected: virtual void killShaderBinaryCache(); struct TextureManagementStageState { - bool _sparseCapable { false }; + bool _sparseCapable{ false }; GLTextureTransferEnginePointer _transferEngine; } _textureManagement; virtual void initTextureManagementStage(); @@ -555,6 +703,6 @@ protected: friend class GLShader; }; -} } +}} // namespace gpu::gl #endif diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendInput.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendInput.cpp index 77e1f90f66..85e6ba5382 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendInput.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendInput.cpp @@ -11,15 +11,16 @@ #include "GLBackend.h" #include "GLShared.h" #include "GLInputFormat.h" +#include "GLBuffer.h" using namespace gpu; using namespace gpu::gl; void GLBackend::do_setInputFormat(const Batch& batch, size_t paramOffset) { - Stream::FormatPointer format = batch._streamFormats.get(batch._params[paramOffset]._uint); - if (format != _input._format) { - _input._format = format; + const auto& format = batch._streamFormats.get(batch._params[paramOffset]._uint); + if (!compare(_input._format, format)) { if (format) { + assign(_input._format, format); auto inputFormat = GLInputFormat::sync((*format)); assert(inputFormat); if (_input._formatKey != inputFormat->key) { @@ -27,6 +28,7 @@ void GLBackend::do_setInputFormat(const Batch& batch, size_t paramOffset) { _input._invalidFormat = true; } } else { + reset(_input._format); _input._formatKey.clear(); _input._invalidFormat = true; } @@ -36,20 +38,14 @@ void GLBackend::do_setInputFormat(const Batch& batch, size_t paramOffset) { void GLBackend::do_setInputBuffer(const Batch& batch, size_t paramOffset) { Offset stride = batch._params[paramOffset + 0]._uint; Offset offset = batch._params[paramOffset + 1]._uint; - BufferPointer buffer = batch._buffers.get(batch._params[paramOffset + 2]._uint); + const auto& buffer = batch._buffers.get(batch._params[paramOffset + 2]._uint); uint32 channel = batch._params[paramOffset + 3]._uint; if (channel < getNumInputBuffers()) { bool isModified = false; - if (_input._buffers[channel] != buffer) { - _input._buffers[channel] = buffer; - - GLuint vbo = 0; - if (buffer) { - vbo = getBufferID((*buffer)); - } - _input._bufferVBOs[channel] = vbo; - + if (!compare(_input._buffers[channel], buffer)) { + assign(_input._buffers[channel], buffer); + _input._bufferVBOs[channel] = getBufferIDUnsynced((*buffer)); isModified = true; } @@ -99,18 +95,18 @@ void GLBackend::resetInputStage() { // Reset index buffer _input._indexBufferType = UINT32; _input._indexBufferOffset = 0; - _input._indexBuffer.reset(); + reset(_input._indexBuffer); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); (void) CHECK_GL_ERROR(); // Reset vertex buffer and format - _input._format.reset(); + reset(_input._format); _input._formatKey.clear(); _input._invalidFormat = false; _input._attributeActivation.reset(); for (uint32_t i = 0; i < _input._buffers.size(); i++) { - _input._buffers[i].reset(); + reset(_input._buffers[i]); _input._bufferOffsets[i] = 0; _input._bufferStrides[i] = 0; _input._bufferVBOs[i] = 0; @@ -124,11 +120,11 @@ void GLBackend::do_setIndexBuffer(const Batch& batch, size_t paramOffset) { _input._indexBufferType = (Type)batch._params[paramOffset + 2]._uint; _input._indexBufferOffset = batch._params[paramOffset + 0]._uint; - BufferPointer indexBuffer = batch._buffers.get(batch._params[paramOffset + 1]._uint); - if (indexBuffer != _input._indexBuffer) { - _input._indexBuffer = indexBuffer; + const auto& indexBuffer = batch._buffers.get(batch._params[paramOffset + 1]._uint); + if (!compare(_input._indexBuffer, indexBuffer)) { + assign(_input._indexBuffer, indexBuffer); if (indexBuffer) { - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, getBufferID(*indexBuffer)); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, getBufferIDUnsynced(*indexBuffer)); } else { // FIXME do we really need this? Is there ever a draw call where we care that the element buffer is null? glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); @@ -141,11 +137,11 @@ void GLBackend::do_setIndirectBuffer(const Batch& batch, size_t paramOffset) { _input._indirectBufferOffset = batch._params[paramOffset + 1]._uint; _input._indirectBufferStride = batch._params[paramOffset + 2]._uint; - BufferPointer buffer = batch._buffers.get(batch._params[paramOffset]._uint); - if (buffer != _input._indirectBuffer) { - _input._indirectBuffer = buffer; + const auto& buffer = batch._buffers.get(batch._params[paramOffset]._uint); + if (!compare(_input._indirectBuffer, buffer)) { + assign(_input._indirectBuffer, buffer); if (buffer) { - glBindBuffer(GL_DRAW_INDIRECT_BUFFER, getBufferID(*buffer)); + glBindBuffer(GL_DRAW_INDIRECT_BUFFER, getBufferIDUnsynced(*buffer)); } else { // FIXME do we really need this? Is there ever a draw call where we care that the element buffer is null? glBindBuffer(GL_DRAW_INDIRECT_BUFFER, 0); @@ -157,7 +153,7 @@ void GLBackend::do_setIndirectBuffer(const Batch& batch, size_t paramOffset) { void GLBackend::updateInput() { bool isStereoNow = isStereo(); - // track stereo state change potentially happening wihtout changing the input format + // track stereo state change potentially happening without changing the input format // this is a rare case requesting to invalid the format #ifdef GPU_STEREO_DRAWCALL_INSTANCED _input._invalidFormat |= (isStereoNow != _input._lastUpdateStereoState); @@ -168,13 +164,14 @@ void GLBackend::updateInput() { InputStageState::ActivationCache newActivation; // Assign the vertex format required - if (_input._format) { + auto format = acquire(_input._format); + if (format) { bool hasColorAttribute{ false }; _input._attribBindingBuffers.reset(); - const Stream::Format::AttributeMap& attributes = _input._format->getAttributes(); - auto& inputChannels = _input._format->getChannels(); + const auto& attributes = format->getAttributes(); + const auto& inputChannels = format->getChannels(); for (auto& channelIt : inputChannels) { auto bufferChannelNum = (channelIt).first; const Stream::Format::ChannelMap::value_type::second_type& channel = (channelIt).second; @@ -261,9 +258,17 @@ void GLBackend::updateInput() { auto offset = _input._bufferOffsets.data(); auto stride = _input._bufferStrides.data(); + // Profile the count of buffers to update and use it to short cut the for loop + int numInvalids = (int) _input._invalidBuffers.count(); + _stats._ISNumInputBufferChanges += numInvalids; + for (GLuint buffer = 0; buffer < _input._buffers.size(); buffer++, vbo++, offset++, stride++) { if (_input._invalidBuffers.test(buffer)) { glBindVertexBuffer(buffer, (*vbo), (*offset), (GLsizei)(*stride)); + numInvalids--; + if (numInvalids <= 0) { + break; + } } } diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendOutput.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendOutput.cpp index d1ab34da90..411e16b18e 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendOutput.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendOutput.cpp @@ -27,12 +27,12 @@ void GLBackend::syncOutputStateCache() { glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤tFBO); _output._drawFBO = currentFBO; - _output._framebuffer.reset(); + reset(_output._framebuffer); } void GLBackend::resetOutputStage() { - if (_output._framebuffer) { - _output._framebuffer.reset(); + if (valid(_output._framebuffer)) { + reset(_output._framebuffer); _output._drawFBO = 0; glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); } @@ -41,7 +41,7 @@ void GLBackend::resetOutputStage() { } void GLBackend::do_setFramebuffer(const Batch& batch, size_t paramOffset) { - auto framebuffer = batch._framebuffers.get(batch._params[paramOffset]._uint); + const auto& framebuffer = batch._framebuffers.get(batch._params[paramOffset]._uint); setFramebuffer(framebuffer); } @@ -55,13 +55,13 @@ void GLBackend::do_setFramebufferSwapChain(const Batch& batch, size_t paramOffse } void GLBackend::setFramebuffer(const FramebufferPointer& framebuffer) { - if (_output._framebuffer != framebuffer) { + if (!compare(_output._framebuffer, framebuffer)) { auto newFBO = getFramebufferID(framebuffer); if (_output._drawFBO != newFBO) { _output._drawFBO = newFBO; glBindFramebuffer(GL_DRAW_FRAMEBUFFER, newFBO); } - _output._framebuffer = framebuffer; + assign(_output._framebuffer, framebuffer); } } @@ -114,8 +114,9 @@ void GLBackend::do_clearFramebuffer(const Batch& batch, size_t paramOffset) { } std::vector drawBuffers; + auto framebuffer = acquire(_output._framebuffer); if (masks & Framebuffer::BUFFER_COLORS) { - if (_output._framebuffer) { + if (framebuffer) { for (unsigned int i = 0; i < Framebuffer::MAX_NUM_RENDER_BUFFERS; i++) { if (masks & (1 << i)) { drawBuffers.push_back(GL_COLOR_ATTACHMENT0 + i); @@ -163,8 +164,8 @@ void GLBackend::do_clearFramebuffer(const Batch& batch, size_t paramOffset) { } // Restore the color draw buffers only if a frmaebuffer is bound - if (_output._framebuffer && !drawBuffers.empty()) { - auto glFramebuffer = syncGPUObject(*_output._framebuffer); + if (framebuffer && !drawBuffers.empty()) { + auto glFramebuffer = syncGPUObject(*framebuffer); if (glFramebuffer) { glDrawBuffers((GLsizei)glFramebuffer->_colorBuffers.size(), glFramebuffer->_colorBuffers.data()); } diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp index d3d2bc0938..7a06b3af86 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp @@ -23,9 +23,9 @@ using namespace gpu; using namespace gpu::gl; void GLBackend::do_setPipeline(const Batch& batch, size_t paramOffset) { - PipelinePointer pipeline = batch._pipelines.get(batch._params[paramOffset + 0]._uint); + const auto& pipeline = batch._pipelines.get(batch._params[paramOffset + 0]._uint); - if (_pipeline._pipeline == pipeline) { + if (compare(_pipeline._pipeline, pipeline)) { return; } @@ -34,7 +34,7 @@ void GLBackend::do_setPipeline(const Batch& batch, size_t paramOffset) { // null pipeline == reset if (!pipeline) { - _pipeline._pipeline.reset(); + reset(_pipeline._pipeline); _pipeline._program = 0; _pipeline._cameraCorrection = false; @@ -73,7 +73,7 @@ void GLBackend::do_setPipeline(const Batch& batch, size_t paramOffset) { } // Remember the new pipeline - _pipeline._pipeline = pipeline; + assign(_pipeline._pipeline, pipeline); } // THis should be done on Pipeline::update... @@ -81,10 +81,12 @@ void GLBackend::do_setPipeline(const Batch& batch, size_t paramOffset) { glUseProgram(_pipeline._program); if (_pipeline._cameraCorrection) { // Invalidate uniform buffer cache slot - _uniform._buffers[gpu::slot::buffer::CameraCorrection] = {}; + _uniform._buffers[gpu::slot::buffer::CameraCorrection].reset(); auto& cameraCorrectionBuffer = _transform._viewCorrectionEnabled ? _pipeline._cameraCorrectionBuffer._buffer : _pipeline._cameraCorrectionBufferIdentity._buffer; + // Because we don't sync Buffers in the bindUniformBuffer, let s force this buffer synced + getBufferID(*cameraCorrectionBuffer); bindUniformBuffer(gpu::slot::buffer::CameraCorrection, cameraCorrectionBuffer, 0, sizeof(CameraCorrection)); } (void)CHECK_GL_ERROR(); @@ -110,7 +112,7 @@ void GLBackend::updatePipeline() { _pipeline._stateSignatureCache |= _pipeline._state->_signature; // And perform - for (auto command : _pipeline._state->_commands) { + for (const auto& command : _pipeline._state->_commands) { command->run(this); } } else { @@ -132,23 +134,17 @@ void GLBackend::resetPipelineStage() { _pipeline._invalidProgram = false; _pipeline._program = 0; _pipeline._programShader = nullptr; - _pipeline._pipeline.reset(); + reset(_pipeline._pipeline); glUseProgram(0); } -GLBackend::UniformStageState::BufferState::BufferState(const BufferPointer& buffer, GLintptr offset, GLsizeiptr size) - : buffer(buffer), offset(offset), size(size) {} - void GLBackend::releaseUniformBuffer(uint32_t slot) { - auto& buf = _uniform._buffers[slot]; - if (buf.buffer) { - auto* object = Backend::getGPUObject(*buf.buffer); - if (object) { - glBindBufferBase(GL_UNIFORM_BUFFER, slot, 0); // RELEASE - (void)CHECK_GL_ERROR(); - } - buf = UniformStageState::BufferState(); + auto& bufferState = _uniform._buffers[slot]; + if (valid(bufferState.buffer)) { + glBindBufferBase(GL_UNIFORM_BUFFER, slot, 0); // RELEASE + (void)CHECK_GL_ERROR(); } + bufferState.reset(); } void GLBackend::resetUniformStage() { @@ -163,19 +159,20 @@ void GLBackend::bindUniformBuffer(uint32_t slot, const BufferPointer& buffer, GL return; } - UniformStageState::BufferState bufferState{ buffer, offset, size }; + auto& currentBufferState = _uniform._buffers[slot]; // check cache before thinking - if (_uniform._buffers[slot] == bufferState) { + if (currentBufferState.compare(buffer, offset, size)) { return; } - // Sync BufferObject - auto* object = syncGPUObject(*bufferState.buffer); - if (object) { - glBindBufferRange(GL_UNIFORM_BUFFER, slot, object->_buffer, bufferState.offset, bufferState.size); - - _uniform._buffers[slot] = bufferState; + // Grab the true gl Buffer object + auto glBO = getBufferIDUnsynced(*buffer); + if (glBO) { + glBindBufferRange(GL_UNIFORM_BUFFER, slot, glBO, offset, size); + assign(currentBufferState.buffer, buffer); + currentBufferState.offset = offset; + currentBufferState.size = size; (void)CHECK_GL_ERROR(); } else { releaseUniformBuffer(slot); @@ -192,7 +189,7 @@ void GLBackend::do_setUniformBuffer(const Batch& batch, size_t paramOffset) { return; } - BufferPointer uniformBuffer = batch._buffers.get(batch._params[paramOffset + 2]._uint); + const auto& uniformBuffer = batch._buffers.get(batch._params[paramOffset + 2]._uint); GLintptr rangeStart = batch._params[paramOffset + 1]._uint; GLsizeiptr rangeSize = batch._params[paramOffset + 0]._uint; @@ -200,16 +197,12 @@ void GLBackend::do_setUniformBuffer(const Batch& batch, size_t paramOffset) { } void GLBackend::releaseResourceTexture(uint32_t slot) { - auto& tex = _resource._textures[slot]; - if (tex) { - auto* object = Backend::getGPUObject(*tex); - if (object) { - GLuint target = object->_target; - glActiveTexture(GL_TEXTURE0 + slot); - glBindTexture(target, 0); // RELEASE - (void)CHECK_GL_ERROR(); - } - tex.reset(); + auto& textureState = _resource._textures[slot]; + if (valid(textureState._texture)) { + glActiveTexture(GL_TEXTURE0 + slot); + glBindTexture(textureState._target, 0); // RELEASE + (void)CHECK_GL_ERROR(); + reset(textureState._texture); } } @@ -231,14 +224,14 @@ void GLBackend::do_setResourceBuffer(const Batch& batch, size_t paramOffset) { return; } - auto resourceBuffer = batch._buffers.get(batch._params[paramOffset + 0]._uint); + const auto& resourceBuffer = batch._buffers.get(batch._params[paramOffset + 0]._uint); if (!resourceBuffer) { releaseResourceBuffer(slot); return; } // check cache before thinking - if (_resource._buffers[slot] == resourceBuffer) { + if (compare(_resource._buffers[slot], resourceBuffer)) { return; } @@ -247,7 +240,7 @@ void GLBackend::do_setResourceBuffer(const Batch& batch, size_t paramOffset) { // If successful bind then cache it if (bindResourceBuffer(slot, resourceBuffer)) { - _resource._buffers[slot] = resourceBuffer; + assign(_resource._buffers[slot], resourceBuffer); } else { // else clear slot and cache releaseResourceBuffer(slot); return; @@ -292,14 +285,15 @@ void GLBackend::do_setResourceFramebufferSwapChainTexture(const Batch& batch, si } auto index = batch._params[paramOffset + 2]._uint; auto renderBufferSlot = batch._params[paramOffset + 3]._uint; - auto resourceFramebuffer = swapChain->get(index); - auto resourceTexture = resourceFramebuffer->getRenderBuffer(renderBufferSlot); + const auto& resourceFramebuffer = swapChain->get(index); + const auto& resourceTexture = resourceFramebuffer->getRenderBuffer(renderBufferSlot); setResourceTexture(slot, resourceTexture); } void GLBackend::setResourceTexture(unsigned int slot, const TexturePointer& resourceTexture) { + auto& textureState = _resource._textures[slot]; // check cache before thinking - if (_resource._textures[slot] == resourceTexture) { + if (compare(textureState._texture, resourceTexture)) { return; } @@ -309,15 +303,12 @@ void GLBackend::setResourceTexture(unsigned int slot, const TexturePointer& reso // Always make sure the GLObject is in sync GLTexture* object = syncGPUObject(resourceTexture); if (object) { + assign(textureState._texture, resourceTexture); GLuint to = object->_texture; - GLuint target = object->_target; + textureState._target = object->_target; glActiveTexture(GL_TEXTURE0 + slot); - glBindTexture(target, to); - + glBindTexture(textureState._target, to); (void)CHECK_GL_ERROR(); - - _resource._textures[slot] = resourceTexture; - _stats._RSAmountTextureMemoryBounded += (int)object->size(); } else { @@ -342,7 +333,7 @@ void GLBackend::do_setResourceTextureTable(const Batch& batch, size_t paramOffse int GLBackend::ResourceStageState::findEmptyTextureSlot() const { // start from the end of the slots, try to find an empty one that can be used for (auto i = MAX_NUM_RESOURCE_TEXTURES - 1; i > 0; i--) { - if (!_textures[i]) { + if (!valid(_textures[i]._texture)) { return i; } } diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendQuery.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendQuery.cpp index b64804fe7c..61423bf970 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendQuery.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendQuery.cpp @@ -25,15 +25,15 @@ static bool timeElapsed = false; #endif void GLBackend::do_beginQuery(const Batch& batch, size_t paramOffset) { -#if !defined(USE_GLES) auto query = batch._queries.get(batch._params[paramOffset]._uint); GLQuery* glquery = syncGPUObject(*query); if (glquery) { PROFILE_RANGE_BEGIN(render_gpu_gl_detail, glquery->_profileRangeId, query->getName().c_str(), 0xFFFF7F00); ++_queryStage._rangeQueryDepth; - glGetInteger64v(GL_TIMESTAMP, (GLint64*)&glquery->_batchElapsedTime); + glquery->_batchElapsedTimeBegin = std::chrono::high_resolution_clock::now(); +#if !defined(USE_GLES) if (timeElapsed) { if (_queryStage._rangeQueryDepth <= MAX_RANGE_QUERY_DEPTH) { glBeginQuery(GL_TIME_ELAPSED, glquery->_endqo); @@ -41,17 +41,18 @@ void GLBackend::do_beginQuery(const Batch& batch, size_t paramOffset) { } else { glQueryCounter(glquery->_beginqo, GL_TIMESTAMP); } +#endif + glquery->_rangeQueryDepth = _queryStage._rangeQueryDepth; (void)CHECK_GL_ERROR(); } -#endif } void GLBackend::do_endQuery(const Batch& batch, size_t paramOffset) { -#if !defined(USE_GLES) auto query = batch._queries.get(batch._params[paramOffset]._uint); GLQuery* glquery = syncGPUObject(*query); if (glquery) { +#if !defined(USE_GLES) if (timeElapsed) { if (_queryStage._rangeQueryDepth <= MAX_RANGE_QUERY_DEPTH) { glEndQuery(GL_TIME_ELAPSED); @@ -59,27 +60,26 @@ void GLBackend::do_endQuery(const Batch& batch, size_t paramOffset) { } else { glQueryCounter(glquery->_endqo, GL_TIMESTAMP); } +#endif --_queryStage._rangeQueryDepth; - GLint64 now; - glGetInteger64v(GL_TIMESTAMP, &now); - glquery->_batchElapsedTime = now - glquery->_batchElapsedTime; + auto duration_ns = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - glquery->_batchElapsedTimeBegin); + glquery->_batchElapsedTime = duration_ns.count(); PROFILE_RANGE_END(render_gpu_gl_detail, glquery->_profileRangeId); (void)CHECK_GL_ERROR(); } -#endif } void GLBackend::do_getQuery(const Batch& batch, size_t paramOffset) { -#if !defined(USE_GLES) auto query = batch._queries.get(batch._params[paramOffset]._uint); GLQuery* glquery = syncGPUObject(*query); if (glquery) { if (glquery->_rangeQueryDepth > MAX_RANGE_QUERY_DEPTH) { query->triggerReturnHandler(glquery->_result, glquery->_batchElapsedTime); } else { +#if !defined(USE_GLES) glGetQueryObjectui64v(glquery->_endqo, GL_QUERY_RESULT_AVAILABLE, &glquery->_result); if (glquery->_result == GL_TRUE) { if (timeElapsed) { @@ -92,10 +92,13 @@ void GLBackend::do_getQuery(const Batch& batch, size_t paramOffset) { } query->triggerReturnHandler(glquery->_result, glquery->_batchElapsedTime); } +#else + // gles3 is not supporting true time query returns just the batch elapsed time + query->triggerReturnHandler(0, glquery->_batchElapsedTime); +#endif (void)CHECK_GL_ERROR(); } } -#endif } void GLBackend::resetQueryStage() { diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendTexture.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendTexture.cpp index ca4e328612..f4fb3fcf2c 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendTexture.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendTexture.cpp @@ -66,7 +66,7 @@ GLTexture* GLBackend::syncGPUObject(const TexturePointer& texturePointer) { } void GLBackend::do_generateTextureMips(const Batch& batch, size_t paramOffset) { - TexturePointer resourceTexture = batch._textures.get(batch._params[paramOffset + 0]._uint); + const auto& resourceTexture = batch._textures.get(batch._params[paramOffset + 0]._uint); if (!resourceTexture) { return; } diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBuffer.h b/libraries/gpu-gl-common/src/gpu/gl/GLBuffer.h index 182014e764..6b7d3405f0 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBuffer.h +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBuffer.h @@ -49,6 +49,16 @@ public: } } + template + static GLuint getIdUnsynced(GLBackend& backend, const Buffer& buffer) { + GLBufferType* object = Backend::getGPUObject(buffer); + if (object) { + return object->_buffer; + } else { + return 0; + } + } + const GLuint& _buffer { _id }; const GLuint _size; const Stamp _stamp; diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLPipeline.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLPipeline.cpp index 1b479dceb8..a099e6e66a 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLPipeline.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLPipeline.cpp @@ -24,7 +24,7 @@ GLPipeline* GLPipeline::sync(GLBackend& backend, const Pipeline& pipeline) { } // No object allocated yet, let's see if it's worth it... - ShaderPointer shader = pipeline.getProgram(); + const auto& shader = pipeline.getProgram(); // If this pipeline's shader has already failed to compile, don't try again if (shader->compilationHasFailed()) { @@ -37,7 +37,7 @@ GLPipeline* GLPipeline::sync(GLBackend& backend, const Pipeline& pipeline) { return nullptr; } - StatePointer state = pipeline.getState(); + const auto& state = pipeline.getState(); GLState* stateObject = GLState::sync(*state); if (stateObject == nullptr) { return nullptr; diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLQuery.h b/libraries/gpu-gl-common/src/gpu/gl/GLQuery.h index 23b1f38621..11a4ef0c0a 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLQuery.h +++ b/libraries/gpu-gl-common/src/gpu/gl/GLQuery.h @@ -47,8 +47,9 @@ public: const GLuint& _endqo = { _id }; const GLuint _beginqo = { 0 }; - GLuint64 _result { (GLuint64)-1 }; - GLuint64 _batchElapsedTime { (GLuint64) 0 }; + GLuint64 _result { (GLuint64)0 }; + GLuint64 _batchElapsedTime{ (GLuint64)0 }; + std::chrono::high_resolution_clock::time_point _batchElapsedTimeBegin; uint64_t _profileRangeId { 0 }; uint32_t _rangeQueryDepth { 0 }; diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h index c6fbc43ae5..e5f7415107 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h +++ b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h @@ -134,6 +134,7 @@ protected: GLFramebuffer* syncGPUObject(const Framebuffer& framebuffer) override; GLuint getBufferID(const Buffer& buffer) override; + GLuint getBufferIDUnsynced(const Buffer& buffer) override; GLuint getResourceBufferID(const Buffer& buffer); GLBuffer* syncGPUObject(const Buffer& buffer) override; @@ -160,7 +161,7 @@ protected: void updateTransform(const Batch& batch) override; // Resource Stage - bool bindResourceBuffer(uint32_t slot, BufferPointer& buffer) override; + bool bindResourceBuffer(uint32_t slot, const BufferPointer& buffer) override; void releaseResourceBuffer(uint32_t slot) override; // Output stage diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendBuffer.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendBuffer.cpp index 62ade673b4..d5193f892a 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendBuffer.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendBuffer.cpp @@ -83,6 +83,10 @@ GLuint GL41Backend::getBufferID(const Buffer& buffer) { return GL41Buffer::getId(*this, buffer); } +GLuint GL41Backend::getBufferIDUnsynced(const Buffer& buffer) { + return GL41Buffer::getIdUnsynced(*this, buffer); +} + GLuint GL41Backend::getResourceBufferID(const Buffer& buffer) { auto* object = GL41Buffer::sync(*this, buffer); if (object) { @@ -96,7 +100,7 @@ GLBuffer* GL41Backend::syncGPUObject(const Buffer& buffer) { return GL41Buffer::sync(*this, buffer); } -bool GL41Backend::bindResourceBuffer(uint32_t slot, BufferPointer& buffer) { +bool GL41Backend::bindResourceBuffer(uint32_t slot, const BufferPointer& buffer) { GLuint texBuffer = GL41Backend::getResourceBufferID((*buffer)); if (texBuffer) { glActiveTexture(GL_TEXTURE0 + GL41Backend::RESOURCE_BUFFER_SLOT0_TEX_UNIT + slot); @@ -104,7 +108,7 @@ bool GL41Backend::bindResourceBuffer(uint32_t slot, BufferPointer& buffer) { (void)CHECK_GL_ERROR(); - _resource._buffers[slot] = buffer; + assign(_resource._buffers[slot], buffer); return true; } @@ -113,10 +117,11 @@ bool GL41Backend::bindResourceBuffer(uint32_t slot, BufferPointer& buffer) { } void GL41Backend::releaseResourceBuffer(uint32_t slot) { - auto& buf = _resource._buffers[slot]; - if (buf) { + auto& bufferReference = _resource._buffers[slot]; + auto buffer = acquire(bufferReference); + if (buffer) { glActiveTexture(GL_TEXTURE0 + GL41Backend::RESOURCE_BUFFER_SLOT0_TEX_UNIT + slot); glBindTexture(GL_TEXTURE_BUFFER, 0); - buf.reset(); + reset(bufferReference); } } diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendInput.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendInput.cpp index 9dcb08f0b7..2b985c122e 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendInput.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendInput.cpp @@ -35,14 +35,15 @@ void GL41Backend::updateInput() { if (_input._invalidFormat || _input._invalidBuffers.any()) { + auto format = acquire(_input._format); if (_input._invalidFormat) { InputStageState::ActivationCache newActivation; _stats._ISNumFormatChanges++; // Check expected activation - if (_input._format) { - for (auto& it : _input._format->getAttributes()) { + if (format) { + for (auto& it : format->getAttributes()) { const Stream::Attribute& attrib = (it).second; uint8_t locationCount = attrib._element.getLocationCount(); for (int i = 0; i < locationCount; ++i) { @@ -69,17 +70,18 @@ void GL41Backend::updateInput() { } // now we need to bind the buffers and assign the attrib pointers - if (_input._format) { + if (format) { bool hasColorAttribute{ false }; - const Buffers& buffers = _input._buffers; - const Offsets& offsets = _input._bufferOffsets; - const Offsets& strides = _input._bufferStrides; - - const Stream::Format::AttributeMap& attributes = _input._format->getAttributes(); - auto& inputChannels = _input._format->getChannels(); - _stats._ISNumInputBufferChanges++; + const auto& buffers = _input._buffers; + const auto& offsets = _input._bufferOffsets; + const auto& strides = _input._bufferStrides; + const auto& attributes = format->getAttributes(); + const auto& inputChannels = format->getChannels(); + int numInvalids = (int)_input._invalidBuffers.count(); + _stats._ISNumInputBufferChanges += numInvalids; + GLuint boundVBO = 0; for (auto& channelIt : inputChannels) { const Stream::Format::ChannelMap::value_type::second_type& channel = (channelIt).second; diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendShader.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendShader.cpp index 8a67ff9619..46f91fdc15 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendShader.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendShader.cpp @@ -57,7 +57,7 @@ void GL41Backend::postLinkProgram(ShaderObject& programObject, const Shader& pro const auto resourceBufferUniforms = ::gl::Uniform::loadByName(glprogram, program.getResourceBuffers().getNames()); for (const auto& resourceBuffer : resourceBufferUniforms) { const auto& targetBinding = expectedResourceBuffers.at(resourceBuffer.name); - glProgramUniform1i(glprogram, resourceBuffer.binding, targetBinding); + glProgramUniform1i(glprogram, resourceBuffer.binding, targetBinding + GL41Backend::RESOURCE_BUFFER_SLOT0_TEX_UNIT); } } diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h index 658bea2a3e..30656b47c7 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h +++ b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h @@ -235,6 +235,7 @@ protected: GLFramebuffer* syncGPUObject(const Framebuffer& framebuffer) override; GLuint getBufferID(const Buffer& buffer) override; + GLuint getBufferIDUnsynced(const Buffer& buffer) override; GLBuffer* syncGPUObject(const Buffer& buffer) override; GLTexture* syncGPUObject(const TexturePointer& texture) override; @@ -261,7 +262,7 @@ protected: void updateTransform(const Batch& batch) override; // Resource Stage - bool bindResourceBuffer(uint32_t slot, BufferPointer& buffer) override; + bool bindResourceBuffer(uint32_t slot, const BufferPointer& buffer) override; void releaseResourceBuffer(uint32_t slot) override; // Output stage diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendBuffer.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendBuffer.cpp index 2afbea3876..cb0591c31c 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendBuffer.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendBuffer.cpp @@ -51,19 +51,23 @@ GLuint GL45Backend::getBufferID(const Buffer& buffer) { return GL45Buffer::getId(*this, buffer); } +GLuint GL45Backend::getBufferIDUnsynced(const Buffer& buffer) { + return GL45Buffer::getIdUnsynced(*this, buffer); +} + GLBuffer* GL45Backend::syncGPUObject(const Buffer& buffer) { return GL45Buffer::sync(*this, buffer); } -bool GL45Backend::bindResourceBuffer(uint32_t slot, BufferPointer& buffer) { +bool GL45Backend::bindResourceBuffer(uint32_t slot, const BufferPointer& buffer) { GLBuffer* object = syncGPUObject((*buffer)); if (object) { glBindBufferBase(GL_SHADER_STORAGE_BUFFER, slot, object->_id); (void)CHECK_GL_ERROR(); - _resource._buffers[slot] = buffer; + assign(_resource._buffers[slot], buffer); return true; } @@ -72,11 +76,10 @@ bool GL45Backend::bindResourceBuffer(uint32_t slot, BufferPointer& buffer) { } void GL45Backend::releaseResourceBuffer(uint32_t slot) { - auto& buf = _resource._buffers[slot]; - if (buf) { + auto& bufferReference = _resource._buffers[slot]; + auto buffer = acquire(bufferReference); + if (buffer) { glBindBufferBase(GL_SHADER_STORAGE_BUFFER, slot, 0); - buf.reset(); + reset(bufferReference); } } - - diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp index cc3e609bda..5285e62d3e 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp @@ -28,7 +28,7 @@ void GL45Backend::resetInputStage() { void GL45Backend::updateInput() { bool isStereoNow = isStereo(); - // track stereo state change potentially happening wihtout changing the input format + // track stereo state change potentially happening without changing the input format // this is a rare case requesting to invalid the format #ifdef GPU_STEREO_DRAWCALL_INSTANCED _input._invalidFormat |= (isStereoNow != _input._lastUpdateStereoState); @@ -39,13 +39,14 @@ void GL45Backend::updateInput() { InputStageState::ActivationCache newActivation; // Assign the vertex format required - if (_input._format) { + auto format = acquire(_input._format); + if (format) { bool hasColorAttribute{ false }; _input._attribBindingBuffers.reset(); - const Stream::Format::AttributeMap& attributes = _input._format->getAttributes(); - auto& inputChannels = _input._format->getChannels(); + const auto& attributes = format->getAttributes(); + const auto& inputChannels = format->getChannels(); for (auto& channelIt : inputChannels) { auto bufferChannelNum = (channelIt).first; const Stream::Format::ChannelMap::value_type::second_type& channel = (channelIt).second; @@ -132,9 +133,18 @@ void GL45Backend::updateInput() { auto offset = _input._bufferOffsets.data(); auto stride = _input._bufferStrides.data(); - for (GLuint buffer = 0; buffer < _input._buffers.size(); buffer++, vbo++, offset++, stride++) { + // Profile the count of buffers to update and use it to short cut the for loop + int numInvalids = (int) _input._invalidBuffers.count(); + _stats._ISNumInputBufferChanges += numInvalids; + + auto numBuffers = _input._buffers.size(); + for (GLuint buffer = 0; buffer < numBuffers; buffer++, vbo++, offset++, stride++) { if (_input._invalidBuffers.test(buffer)) { glBindVertexBuffer(buffer, (*vbo), (*offset), (GLsizei)(*stride)); + numInvalids--; + if (numInvalids <= 0) { + break; + } } } diff --git a/libraries/gpu-gles/src/gpu/gles/GLESBackend.h b/libraries/gpu-gles/src/gpu/gles/GLESBackend.h index 785f4c3ef9..56ae41da31 100644 --- a/libraries/gpu-gles/src/gpu/gles/GLESBackend.h +++ b/libraries/gpu-gles/src/gpu/gles/GLESBackend.h @@ -130,6 +130,7 @@ protected: GLFramebuffer* syncGPUObject(const Framebuffer& framebuffer) override; GLuint getBufferID(const Buffer& buffer) override; + GLuint getBufferIDUnsynced(const Buffer& buffer) override; GLuint getResourceBufferID(const Buffer& buffer); GLBuffer* syncGPUObject(const Buffer& buffer) override; @@ -156,7 +157,7 @@ protected: void updateTransform(const Batch& batch) override; // Resource Stage - bool bindResourceBuffer(uint32_t slot, BufferPointer& buffer) override; + bool bindResourceBuffer(uint32_t slot, const BufferPointer& buffer) override; void releaseResourceBuffer(uint32_t slot) override; // Output stage diff --git a/libraries/gpu-gles/src/gpu/gles/GLESBackendBuffer.cpp b/libraries/gpu-gles/src/gpu/gles/GLESBackendBuffer.cpp index 17fdad8377..5e4da4d1fe 100644 --- a/libraries/gpu-gles/src/gpu/gles/GLESBackendBuffer.cpp +++ b/libraries/gpu-gles/src/gpu/gles/GLESBackendBuffer.cpp @@ -64,18 +64,22 @@ GLuint GLESBackend::getBufferID(const Buffer& buffer) { return GLESBuffer::getId(*this, buffer); } +GLuint GLESBackend::getBufferIDUnsynced(const Buffer& buffer) { + return GLESBuffer::getIdUnsynced(*this, buffer); +} + GLBuffer* GLESBackend::syncGPUObject(const Buffer& buffer) { return GLESBuffer::sync(*this, buffer); } -bool GLESBackend::bindResourceBuffer(uint32_t slot, BufferPointer& buffer) { +bool GLESBackend::bindResourceBuffer(uint32_t slot, const BufferPointer& buffer) { GLBuffer* object = syncGPUObject((*buffer)); if (object) { glBindBufferBase(GL_SHADER_STORAGE_BUFFER, slot, object->_id); (void)CHECK_GL_ERROR(); - _resource._buffers[slot] = buffer; + assign(_resource._buffers[slot], buffer); return true; } @@ -84,10 +88,10 @@ bool GLESBackend::bindResourceBuffer(uint32_t slot, BufferPointer& buffer) { } void GLESBackend::releaseResourceBuffer(uint32_t slot) { - auto& buf = _resource._buffers[slot]; - if (buf) { + auto& bufferReference = _resource._buffers[slot]; + if (valid(bufferReference)) { glBindBufferBase(GL_SHADER_STORAGE_BUFFER, slot, 0); - buf.reset(); + reset(bufferReference); } } diff --git a/libraries/gpu/src/gpu/Batch.cpp b/libraries/gpu/src/gpu/Batch.cpp index b6714e2f1a..745f1d1845 100644 --- a/libraries/gpu/src/gpu/Batch.cpp +++ b/libraries/gpu/src/gpu/Batch.cpp @@ -498,7 +498,7 @@ void Batch::setupNamedCalls(const std::string& instanceName, NamedBatchData::Fun captureNamedDrawCallInfo(instanceName); } -BufferPointer Batch::getNamedBuffer(const std::string& instanceName, uint8_t index) { +const BufferPointer& Batch::getNamedBuffer(const std::string& instanceName, uint8_t index) { NamedBatchData& instance = _namedData[instanceName]; if (instance.buffers.size() <= index) { instance.buffers.resize(index + 1); diff --git a/libraries/gpu/src/gpu/Batch.h b/libraries/gpu/src/gpu/Batch.h index bcbfe0616d..8e607a189e 100644 --- a/libraries/gpu/src/gpu/Batch.h +++ b/libraries/gpu/src/gpu/Batch.h @@ -119,7 +119,7 @@ public: void multiDrawIndexedIndirect(uint32 numCommands, Primitive primitiveType); void setupNamedCalls(const std::string& instanceName, NamedBatchData::Function function); - BufferPointer getNamedBuffer(const std::string& instanceName, uint8_t index = 0); + const BufferPointer& getNamedBuffer(const std::string& instanceName, uint8_t index = 0); // Input Stage // InputFormat @@ -417,10 +417,7 @@ public: } const Data& get(uint32 offset) const { - if (offset >= _items.size()) { - static const Data EMPTY; - return EMPTY; - } + assert((offset < _items.size())); return (_items.data() + offset)->_data; } diff --git a/libraries/gpu/src/gpu/Color.slh b/libraries/gpu/src/gpu/Color.slh index 384b8bd329..16eb7487ed 100644 --- a/libraries/gpu/src/gpu/Color.slh +++ b/libraries/gpu/src/gpu/Color.slh @@ -31,29 +31,29 @@ vec4 color_sRGBAToLinear(vec4 srgba) { } vec3 color_LinearToYCoCg(vec3 rgb) { - // Y = R/4 + G/2 + B/4 - // Co = R/2 - B/2 - // Cg = -R/4 + G/2 - B/4 - return vec3( - rgb.x/4.0 + rgb.y/2.0 + rgb.z/4.0, - rgb.x/2.0 - rgb.z/2.0, - -rgb.x/4.0 + rgb.y/2.0 - rgb.z/4.0 - ); + // Y = R/4 + G/2 + B/4 + // Co = R/2 - B/2 + // Cg = -R/4 + G/2 - B/4 + return vec3( + rgb.x/4.0 + rgb.y/2.0 + rgb.z/4.0, + rgb.x/2.0 - rgb.z/2.0, + -rgb.x/4.0 + rgb.y/2.0 - rgb.z/4.0 + ); } vec3 color_YCoCgToUnclampedLinear(vec3 ycocg) { - // R = Y + Co - Cg - // G = Y + Cg - // B = Y - Co - Cg - return vec3( - ycocg.x + ycocg.y - ycocg.z, - ycocg.x + ycocg.z, - ycocg.x - ycocg.y - ycocg.z - ); + // R = Y + Co - Cg + // G = Y + Cg + // B = Y - Co - Cg + return vec3( + ycocg.x + ycocg.y - ycocg.z, + ycocg.x + ycocg.z, + ycocg.x - ycocg.y - ycocg.z + ); } vec3 color_YCoCgToLinear(vec3 ycocg) { - return clamp(color_YCoCgToUnclampedLinear(ycocg), vec3(0.0), vec3(1.0)); + return clamp(color_YCoCgToUnclampedLinear(ycocg), vec3(0.0), vec3(1.0)); } <@func declareColorWheel()@> diff --git a/libraries/gpu/src/gpu/DrawColor.slf b/libraries/gpu/src/gpu/DrawColor.slf index 2540d56d69..fdea26fa68 100644 --- a/libraries/gpu/src/gpu/DrawColor.slf +++ b/libraries/gpu/src/gpu/DrawColor.slf @@ -13,12 +13,16 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -<@include gpu/ShaderConstants.h@> +struct DrawColorParams { + vec4 color; +}; -layout(location=GPU_UNIFORM_COLOR) uniform vec4 color; +layout(binding=0) uniform drawColorParamsBuffer { + DrawColorParams params; +}; layout(location=0) out vec4 outFragColor; void main(void) { - outFragColor = color; + outFragColor = params.color; } diff --git a/libraries/gpu/src/gpu/DrawColoredTexture.slf b/libraries/gpu/src/gpu/DrawColoredTexture.slf index 632bf18391..0fe3707b1c 100755 --- a/libraries/gpu/src/gpu/DrawColoredTexture.slf +++ b/libraries/gpu/src/gpu/DrawColoredTexture.slf @@ -13,14 +13,19 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -<@include gpu/ShaderConstants.h@> - layout(binding=0) uniform sampler2D colorMap; -layout(location=GPU_UNIFORM_COLOR) uniform vec4 color; + +struct DrawColorParams { + vec4 color; +}; + +layout(binding=0) uniform drawColorParams { + DrawColorParams params; +}; layout(location=0) in vec2 varTexCoord0; layout(location=0) out vec4 outFragColor; void main(void) { - outFragColor = texture(colorMap, varTexCoord0) * color; + outFragColor = texture(colorMap, varTexCoord0) * params.color; } diff --git a/libraries/gpu/src/gpu/DrawTexcoordRectTransformUnitQuad.slv b/libraries/gpu/src/gpu/DrawTexcoordRectTransformUnitQuad.slv index d401fc40c2..8849cb494a 100755 --- a/libraries/gpu/src/gpu/DrawTexcoordRectTransformUnitQuad.slv +++ b/libraries/gpu/src/gpu/DrawTexcoordRectTransformUnitQuad.slv @@ -21,7 +21,13 @@ <$declareStandardTransform()$> -layout(location=GPU_UNIFORM_TEXCOORD_RECT) uniform vec4 texcoordRect; +struct TexCoordRectParams { + vec4 texcoordRect; +}; + +layout(binding=0) uniform texcoordRectBuffer { + TexCoordRectParams params; +}; layout(location=0) out vec2 varTexCoord0; @@ -39,5 +45,5 @@ void main(void) { TransformObject obj = getTransformObject(); <$transformModelToClipPos(cam, obj, pos, gl_Position)$> - varTexCoord0 = ((pos.xy + 1.0) * 0.5) * texcoordRect.zw + texcoordRect.xy; + varTexCoord0 = ((pos.xy + 1.0) * 0.5) * params.texcoordRect.zw + params.texcoordRect.xy; } diff --git a/libraries/gpu/src/gpu/Framebuffer.cpp b/libraries/gpu/src/gpu/Framebuffer.cpp index 8bb9be4a76..e88d986da6 100755 --- a/libraries/gpu/src/gpu/Framebuffer.cpp +++ b/libraries/gpu/src/gpu/Framebuffer.cpp @@ -203,11 +203,12 @@ uint32 Framebuffer::getNumRenderBuffers() const { return nb; } -TexturePointer Framebuffer::getRenderBuffer(uint32 slot) const { +const TexturePointer& Framebuffer::getRenderBuffer(uint32 slot) const { + static const TexturePointer EMPTY; if (!isSwapchain() && (slot < getMaxNumRenderBuffers())) { return _renderBuffers[slot]._texture; } else { - return TexturePointer(); + return EMPTY; } } @@ -297,9 +298,10 @@ bool Framebuffer::setDepthStencilBuffer(const TexturePointer& texture, const For return false; } -TexturePointer Framebuffer::getDepthStencilBuffer() const { +const TexturePointer& Framebuffer::getDepthStencilBuffer() const { + static const TexturePointer EMPTY; if (isSwapchain()) { - return TexturePointer(); + return EMPTY; } else { return _depthStencilBuffer._texture; } diff --git a/libraries/gpu/src/gpu/Framebuffer.h b/libraries/gpu/src/gpu/Framebuffer.h index fbbec50a28..44e945883f 100755 --- a/libraries/gpu/src/gpu/Framebuffer.h +++ b/libraries/gpu/src/gpu/Framebuffer.h @@ -95,7 +95,7 @@ public: static Framebuffer* createShadowmap(uint16 width); bool isSwapchain() const; - SwapchainPointer getSwapchain() const { return _swapchain; } + const SwapchainPointer& getSwapchain() const { return _swapchain; } uint32 getFrameCount() const; @@ -105,13 +105,13 @@ public: const TextureViews& getRenderBuffers() const { return _renderBuffers; } int32 setRenderBuffer(uint32 slot, const TexturePointer& texture, uint32 subresource = 0); - TexturePointer getRenderBuffer(uint32 slot) const; + const TexturePointer& getRenderBuffer(uint32 slot) const; uint32 getRenderBufferSubresource(uint32 slot) const; bool setDepthBuffer(const TexturePointer& texture, const Format& format, uint32 subresource = 0); bool setStencilBuffer(const TexturePointer& texture, const Format& format, uint32 subresource = 0); bool setDepthStencilBuffer(const TexturePointer& texture, const Format& format, uint32 subresource = 0); - TexturePointer getDepthStencilBuffer() const; + const TexturePointer& getDepthStencilBuffer() const; uint32 getDepthStencilBufferSubresource() const; Format getDepthStencilBufferFormat() const; diff --git a/libraries/gpu/src/gpu/Noise.slh b/libraries/gpu/src/gpu/Noise.slh index b390945957..d300e71ba9 100644 --- a/libraries/gpu/src/gpu/Noise.slh +++ b/libraries/gpu/src/gpu/Noise.slh @@ -284,14 +284,14 @@ float hifi_noise(in vec2 x) { // https://www.shadertoy.com/view/MdX3Rr // https://en.wikipedia.org/wiki/Fractional_Brownian_motion float hifi_fbm(in vec2 p) { - const mat2 m2 = mat2(0.8, -0.6, 0.6, 0.8); - float f = 0.0; - f += 0.5000 * hifi_noise(p); p = m2 * p * 2.02; - f += 0.2500 * hifi_noise(p); p = m2 * p * 2.03; - f += 0.1250 * hifi_noise(p); p = m2 * p * 2.01; - f += 0.0625 * hifi_noise(p); + const mat2 m2 = mat2(0.8, -0.6, 0.6, 0.8); + float f = 0.0; + f += 0.5000 * hifi_noise(p); p = m2 * p * 2.02; + f += 0.2500 * hifi_noise(p); p = m2 * p * 2.03; + f += 0.1250 * hifi_noise(p); p = m2 * p * 2.01; + f += 0.0625 * hifi_noise(p); - return f / 0.9375; + return f / 0.9375; } <@endif@> \ No newline at end of file diff --git a/libraries/gpu/src/gpu/Paint.slh b/libraries/gpu/src/gpu/Paint.slh index 5f49b20b30..54a1214e12 100644 --- a/libraries/gpu/src/gpu/Paint.slh +++ b/libraries/gpu/src/gpu/Paint.slh @@ -17,7 +17,7 @@ float paintStripe(float value, float offset, float scale, float edge) { float width = fwidth(value); float normalizedWidth = width * scale; - float x0 = (value + offset) * scale - normalizedWidth / 2; + float x0 = (value + offset) * scale - normalizedWidth / 2.0; float x1 = x0 + normalizedWidth; float balance = 1.0 - edge; diff --git a/libraries/gpu/src/gpu/ShaderConstants.h b/libraries/gpu/src/gpu/ShaderConstants.h index dc5879e7ad..13dfd1be9c 100644 --- a/libraries/gpu/src/gpu/ShaderConstants.h +++ b/libraries/gpu/src/gpu/ShaderConstants.h @@ -40,8 +40,6 @@ // OSX seems to have an issue using 14 as an attribute location for passing from the vertex to the fragment shader #define GPU_ATTR_V2F_STEREO_SIDE 8 -#define GPU_UNIFORM_COLOR 101 -#define GPU_UNIFORM_TEXCOORD_RECT 102 #define GPU_UNIFORM_EXTRA0 110 #define GPU_UNIFORM_EXTRA1 111 #define GPU_UNIFORM_EXTRA2 112 @@ -98,8 +96,6 @@ enum Attribute { namespace uniform { enum Uniform { - Color = GPU_UNIFORM_COLOR, - TexCoordRect = GPU_UNIFORM_TEXCOORD_RECT, Extra0 = GPU_UNIFORM_EXTRA0, Extra1 = GPU_UNIFORM_EXTRA1, Extra2 = GPU_UNIFORM_EXTRA2, diff --git a/libraries/gpu/src/gpu/Stream.h b/libraries/gpu/src/gpu/Stream.h index 0def1ab201..2e1ed1d83c 100644 --- a/libraries/gpu/src/gpu/Stream.h +++ b/libraries/gpu/src/gpu/Stream.h @@ -152,6 +152,8 @@ public: BufferStream makeRangedStream(uint32 offset, uint32 count = -1) const; + BufferStream& operator = (const BufferStream& src) = default; + protected: Buffers _buffers; Offsets _offsets; diff --git a/libraries/gpu/src/gpu/TransformCamera_shared.slh b/libraries/gpu/src/gpu/TransformCamera_shared.slh index 37521d8201..e4a0f8c2cc 100644 --- a/libraries/gpu/src/gpu/TransformCamera_shared.slh +++ b/libraries/gpu/src/gpu/TransformCamera_shared.slh @@ -2,11 +2,11 @@ #ifdef __cplusplus # define _MAT4 Mat4 # define _VEC4 Vec4 -# define _MUTABLE mutable +# define _MUTABLE mutable #else # define _MAT4 mat4 # define _VEC4 vec4 -# define _MUTABLE +# define _MUTABLE #endif struct _TransformCamera { diff --git a/libraries/graphics/src/graphics/Geometry.cpp b/libraries/graphics/src/graphics/Geometry.cpp index d43c773249..a983ba07b4 100755 --- a/libraries/graphics/src/graphics/Geometry.cpp +++ b/libraries/graphics/src/graphics/Geometry.cpp @@ -32,6 +32,15 @@ Mesh::Mesh(const Mesh& mesh) : Mesh::~Mesh() { } +void Mesh::setVertexFormatAndStream(const gpu::Stream::FormatPointer& vf, const gpu::BufferStreamPointer& vbs) { + _vertexFormat = vf; + _vertexStream = (*vbs); + + auto attrib = _vertexFormat->getAttribute(gpu::Stream::POSITION); + _vertexBuffer = BufferView(vbs->getBuffers()[attrib._channel], vbs->getOffsets()[attrib._channel], vbs->getBuffers()[attrib._channel]->getSize(), + (gpu::uint16) vbs->getStrides()[attrib._channel], attrib._element); +} + void Mesh::setVertexBuffer(const BufferView& buffer) { _vertexBuffer = buffer; evalVertexFormat(); @@ -107,11 +116,10 @@ Box Mesh::evalPartBound(int partNum) const { index += part._startIndex; auto endIndex = index; endIndex += part._numIndices; - auto vertices = &_vertexBuffer.get(part._baseVertex); for (;index != endIndex; index++) { // skip primitive restart indices if ((*index) != PRIMITIVE_RESTART_INDEX) { - box += vertices[(*index)]; + box += _vertexBuffer.get(part._baseVertex + (*index)); } } } @@ -128,11 +136,10 @@ Box Mesh::evalPartsBound(int partStart, int partEnd) const { Box partBound; auto index = _indexBuffer.cbegin() + (*part)._startIndex; auto endIndex = index + (*part)._numIndices; - auto vertices = &_vertexBuffer.get((*part)._baseVertex); for (;index != endIndex; index++) { // skip primitive restart indices if ((*index) != (uint) PRIMITIVE_RESTART_INDEX) { - partBound += vertices[(*index)]; + partBound += _vertexBuffer.get((*part)._baseVertex + (*index)); } } diff --git a/libraries/graphics/src/graphics/Geometry.h b/libraries/graphics/src/graphics/Geometry.h index eddfdbd1b6..20018ba71b 100755 --- a/libraries/graphics/src/graphics/Geometry.h +++ b/libraries/graphics/src/graphics/Geometry.h @@ -59,6 +59,9 @@ public: void removeAttribute(Slot slot); const BufferView getAttributeBuffer(int attrib) const; + // Force vertex stream and Vertex format + void setVertexFormatAndStream(const gpu::Stream::FormatPointer& vf, const gpu::BufferStreamPointer& vbs); + // Stream format const gpu::Stream::FormatPointer getVertexFormat() const { return _vertexFormat; } diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 5b3196a2bf..d99c0020da 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -538,7 +538,6 @@ void AccountManager::requestAccessToken(const QString& login, const QString& pas QNetworkReply* requestReply = networkAccessManager.post(request, postData); connect(requestReply, &QNetworkReply::finished, this, &AccountManager::requestAccessTokenFinished); - connect(requestReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestAccessTokenError(QNetworkReply::NetworkError))); } void AccountManager::requestAccessTokenWithSteam(QByteArray authSessionTicket) { @@ -633,12 +632,6 @@ void AccountManager::requestAccessTokenFinished() { } } -void AccountManager::requestAccessTokenError(QNetworkReply::NetworkError error) { - // TODO: error handling - qCDebug(networking) << "AccountManager: failed to fetch access token - " << error; - emit loginFailed(); -} - void AccountManager::refreshAccessTokenFinished() { QNetworkReply* requestReply = reinterpret_cast(sender()); diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index b122115dd0..f3b81cf1c9 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -106,7 +106,6 @@ public slots: void requestAccessTokenFinished(); void refreshAccessTokenFinished(); void requestProfileFinished(); - void requestAccessTokenError(QNetworkReply::NetworkError error); void refreshAccessTokenError(QNetworkReply::NetworkError error); void requestProfileError(QNetworkReply::NetworkError error); void logout(); diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 00e552af89..8085039b02 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -31,6 +31,7 @@ #include "udt/PacketHeaders.h" const QString DEFAULT_HIFI_ADDRESS = "file:///~/serverless/tutorial.json"; +const QString REDIRECT_HIFI_ADDRESS = "file:///~/serverless/redirect.json"; const QString ADDRESS_MANAGER_SETTINGS_GROUP = "AddressManager"; const QString SETTINGS_CURRENT_ADDRESS_KEY = "address"; @@ -111,6 +112,9 @@ QUrl AddressManager::currentFacingPublicAddress() const { return shareableAddress; } +QUrl AddressManager::lastAddress() const { + return _lastVisitedURL; +} void AddressManager::loadSettings(const QString& lookupString) { #if defined(USE_GLES) && defined(Q_OS_WIN) @@ -247,9 +251,12 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { UserActivityLogger::getInstance().wentTo(trigger, URL_TYPE_USER, lookupUrl.toString()); + // save the last visited domain URL. + _lastVisitedURL = lookupUrl; + // in case we're failing to connect to where we thought this user was // store their username as previous lookup so we can refresh their location via API - _previousLookup = lookupUrl; + _previousAPILookup = lookupUrl; } else { // we're assuming this is either a network address or global place name // check if it is a network address first @@ -259,8 +266,11 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { UserActivityLogger::getInstance().wentTo(trigger, URL_TYPE_NETWORK_ADDRESS, lookupUrl.toString()); + // save the last visited domain URL. + _lastVisitedURL = lookupUrl; + // a network address lookup clears the previous lookup since we don't expect to re-attempt it - _previousLookup.clear(); + _previousAPILookup.clear(); // If the host changed then we have already saved to history if (hostChanged) { @@ -278,8 +288,11 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { } else if (handleDomainID(lookupUrl.host())){ UserActivityLogger::getInstance().wentTo(trigger, URL_TYPE_DOMAIN_ID, lookupUrl.toString()); + // save the last visited domain URL. + _lastVisitedURL = lookupUrl; + // store this domain ID as the previous lookup in case we're failing to connect and want to refresh API info - _previousLookup = lookupUrl; + _previousAPILookup = lookupUrl; // no place name - this is probably a domain ID // try to look up the domain ID on the metaverse API @@ -287,8 +300,11 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { } else { UserActivityLogger::getInstance().wentTo(trigger, URL_TYPE_PLACE, lookupUrl.toString()); + // save the last visited domain URL. + _lastVisitedURL = lookupUrl; + // store this place name as the previous lookup in case we fail to connect and want to refresh API info - _previousLookup = lookupUrl; + _previousAPILookup = lookupUrl; // wasn't an address - lookup the place name // we may have a path that defines a relative viewpoint - pass that through the lookup so we can go to it after @@ -302,7 +318,7 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { qCDebug(networking) << "Going to relative path" << lookupUrl.path(); // a path lookup clears the previous lookup since we don't expect to re-attempt it - _previousLookup.clear(); + _previousAPILookup.clear(); // if this is a relative path then handle it as a relative viewpoint handlePath(lookupUrl.path(), trigger, true); @@ -314,7 +330,10 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { // be loaded over http(s) // lookupUrl.scheme() == URL_SCHEME_HTTP || // lookupUrl.scheme() == URL_SCHEME_HTTPS || - _previousLookup.clear(); + // TODO once a file can return a connection refusal if there were to be some kind of load error, we'd + // need to store the previous domain tried in _lastVisitedURL. For now , do not store it. + + _previousAPILookup.clear(); _shareablePlaceName.clear(); setDomainInfo(lookupUrl, trigger); emit lookupResultsFinished(); @@ -381,7 +400,7 @@ void AddressManager::handleAPIResponse(QNetworkReply* requestReply) { QJsonObject dataObject = responseObject["data"].toObject(); // Lookup succeeded, don't keep re-trying it (especially on server restarts) - _previousLookup.clear(); + _previousAPILookup.clear(); if (!dataObject.isEmpty()) { goToAddressFromObject(dataObject.toVariantMap(), requestReply); @@ -547,7 +566,7 @@ void AddressManager::handleAPIError(QNetworkReply* errorReply) { if (errorReply->error() == QNetworkReply::ContentNotFoundError) { // if this is a lookup that has no result, don't keep re-trying it - _previousLookup.clear(); + _previousAPILookup.clear(); emit lookupResultIsNotFound(); } @@ -709,7 +728,6 @@ bool AddressManager::handleViewpoint(const QString& viewpointString, bool should // We use _newHostLookupPath to determine if the client has already stored its last address // before moving to a new host thanks to the information in the same lookup URL. - if (definitelyPathOnly || (!pathString.isEmpty() && pathString != _newHostLookupPath) || trigger == Back || trigger == Forward) { addCurrentAddressToHistory(trigger); @@ -798,8 +816,10 @@ bool AddressManager::setDomainInfo(const QUrl& domainURL, LookupTrigger trigger) const QString hostname = domainURL.host(); quint16 port = domainURL.port(); bool emitHostChanged { false }; + // Check if domain handler is in error state. always emit host changed if true. + bool isInErrorState = DependencyManager::get()->getDomainHandler().isInErrorState(); - if (domainURL != _domainURL) { + if (domainURL != _domainURL || isInErrorState) { addCurrentAddressToHistory(trigger); emitHostChanged = true; } @@ -843,8 +863,8 @@ void AddressManager::goToUser(const QString& username, bool shouldMatchOrientati void AddressManager::refreshPreviousLookup() { // if we have a non-empty previous lookup, fire it again now (but don't re-store it in the history) - if (!_previousLookup.isEmpty()) { - handleUrl(_previousLookup, LookupTrigger::AttemptedRefresh); + if (!_previousAPILookup.isEmpty()) { + handleUrl(_previousAPILookup, LookupTrigger::AttemptedRefresh); } else { handleUrl(currentAddress(), LookupTrigger::AttemptedRefresh); } diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index 37b85a9acd..c7cdf8f4ea 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -23,6 +23,7 @@ #include "AccountManager.h" extern const QString DEFAULT_HIFI_ADDRESS; +extern const QString REDIRECT_HIFI_ADDRESS; const QString SANDBOX_HIFI_ADDRESS = "hifi://localhost"; const QString INDEX_PATH = "/"; @@ -55,7 +56,6 @@ const QString GET_PLACE = "/api/v1/places/%1"; * Read-only. * @property {boolean} isConnected - true if you're connected to the domain in your current href * metaverse address, otherwise false. - * Read-only. * @property {string} pathname - The location and orientation in your current href metaverse address * (e.g., "/15,-10,26/0,0,0,1"). * Read-only. @@ -140,7 +140,8 @@ public: * * @typedef {number} location.LookupTrigger */ - enum LookupTrigger { + enum LookupTrigger + { UserInput, Back, Forward, @@ -164,6 +165,8 @@ public: QString currentPath(bool withOrientation = true) const; QString currentFacingPath() const; + QUrl lastAddress() const; + const QUuid& getRootPlaceID() const { return _rootPlaceID; } QString getPlaceName() const; QString getDomainID() const; @@ -191,7 +194,7 @@ public slots: * Helps ensure that user's location history is correctly maintained. */ void handleLookupString(const QString& lookupString, bool fromSuggestions = false); - + /**jsdoc * Go to a position and orientation resulting from a lookup for a named path in the domain (set in the domain server's * settings). @@ -204,8 +207,9 @@ public slots: // functions and signals that should be exposed are moved to a scripting interface class. // // we currently expect this to be called from NodeList once handleLookupString has been called with a path - bool goToViewpointForPath(const QString& viewpointString, const QString& pathString) - { return handleViewpoint(viewpointString, false, DomainPathResponse, false, pathString); } + bool goToViewpointForPath(const QString& viewpointString, const QString& pathString) { + return handleViewpoint(viewpointString, false, DomainPathResponse, false, pathString); + } /**jsdoc * Go back to the previous location in your navigation history, if there is one. @@ -226,8 +230,10 @@ public slots: * @param {location.LookupTrigger} trigger=StartupFromSettings - The reason for the function call. Helps ensure that user's * location history is correctly maintained. */ - void goToLocalSandbox(QString path = "", LookupTrigger trigger = LookupTrigger::StartupFromSettings) { handleUrl(SANDBOX_HIFI_ADDRESS + path, trigger); } - + void goToLocalSandbox(QString path = "", LookupTrigger trigger = LookupTrigger::StartupFromSettings) { + handleUrl(SANDBOX_HIFI_ADDRESS + path, trigger); + } + /**jsdoc * Go to the default "welcome" metaverse address. * @function location.goToEntry @@ -245,6 +251,12 @@ public slots: */ void goToUser(const QString& username, bool shouldMatchOrientation = true); + /**jsdoc + * Go to the last address tried. This will be the last URL tried from location.handleLookupString + * @function location.goToLastAddress + */ + void goToLastAddress() { handleUrl(_lastVisitedURL, LookupTrigger::AttemptedRefresh); } + /**jsdoc * Refresh the current address, e.g., after connecting to a domain in order to position the user to the desired location. * @function location.refreshPreviousLookup @@ -352,7 +364,8 @@ signals: * location.locationChangeRequired.connect(onLocationChangeRequired); */ void locationChangeRequired(const glm::vec3& newPosition, - bool hasOrientationChange, const glm::quat& newOrientation, + bool hasOrientationChange, + const glm::quat& newOrientation, bool shouldFaceLocation); /**jsdoc @@ -423,7 +436,7 @@ private slots: void handleShareableNameAPIResponse(QNetworkReply* requestReply); private: - void goToAddressFromObject(const QVariantMap& addressMap, const QNetworkReply* reply); + void goToAddressFromObject(const QVariantMap& addressMap, const QNetworkReply* reply); // Set host and port, and return `true` if it was changed. bool setHost(const QString& host, LookupTrigger trigger, quint16 port = 0); @@ -435,8 +448,11 @@ private: bool handleNetworkAddress(const QString& lookupString, LookupTrigger trigger, bool& hostChanged); void handlePath(const QString& path, LookupTrigger trigger, bool wasPathOnly = false); - bool handleViewpoint(const QString& viewpointString, bool shouldFace, LookupTrigger trigger, - bool definitelyPathOnly = false, const QString& pathString = QString()); + bool handleViewpoint(const QString& viewpointString, + bool shouldFace, + LookupTrigger trigger, + bool definitelyPathOnly = false, + const QString& pathString = QString()); bool handleUsername(const QString& lookupString); bool handleDomainID(const QString& host); @@ -446,6 +462,7 @@ private: void addCurrentAddressToHistory(LookupTrigger trigger); QUrl _domainURL; + QUrl _lastVisitedURL; QUuid _rootPlaceID; PositionGetter _positionGetter; @@ -459,7 +476,7 @@ private: QString _newHostLookupPath; - QUrl _previousLookup; + QUrl _previousAPILookup; }; -#endif // hifi_AddressManager_h +#endif // hifi_AddressManager_h diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index 39c8b5b1a1..f34a93de96 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -55,6 +55,9 @@ DomainHandler::DomainHandler(QObject* parent) : // stop the refresh timer if we connect to a domain connect(this, &DomainHandler::connectedToDomain, &_apiRefreshTimer, &QTimer::stop); + + // stop the refresh timer if redirected to the error domain + connect(this, &DomainHandler::redirectToErrorDomainURL, &_apiRefreshTimer, &QTimer::stop); } void DomainHandler::disconnect() { @@ -106,13 +109,16 @@ void DomainHandler::softReset() { QMetaObject::invokeMethod(&_settingsTimer, "stop"); // restart the API refresh timer in case we fail to connect and need to refresh information - QMetaObject::invokeMethod(&_apiRefreshTimer, "start"); + if (!_isInErrorState) { + QMetaObject::invokeMethod(&_apiRefreshTimer, "start"); + } } void DomainHandler::hardReset() { emit resetting(); softReset(); + _isInErrorState = false; qCDebug(networking) << "Hard reset in NodeList DomainHandler."; _pendingDomainID = QUuid(); @@ -128,6 +134,11 @@ void DomainHandler::hardReset() { _pendingPath.clear(); } +void DomainHandler::setErrorDomainURL(const QUrl& url) { + _errorDomainURL = url; + return; +} + void DomainHandler::setSockAddr(const HifiSockAddr& sockAddr, const QString& hostname) { if (_sockAddr != sockAddr) { // we should reset on a sockAddr change @@ -171,7 +182,8 @@ void DomainHandler::setURLAndID(QUrl domainURL, QUuid domainID) { domainPort = DEFAULT_DOMAIN_SERVER_PORT; } - if (_domainURL != domainURL || _sockAddr.getPort() != domainPort) { + // if it's in the error state, reset and try again. + if ((_domainURL != domainURL || _sockAddr.getPort() != domainPort) || _isInErrorState) { // re-set the domain info so that auth information is reloaded hardReset(); @@ -206,7 +218,8 @@ void DomainHandler::setURLAndID(QUrl domainURL, QUuid domainID) { void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname, const QUuid& id) { - if (_iceServerSockAddr.getAddress().toString() != iceServerHostname || id != _pendingDomainID) { + // if it's in the error state, reset and try again. + if ((_iceServerSockAddr.getAddress().toString() != iceServerHostname || id != _pendingDomainID) || _isInErrorState) { // re-set the domain info to connect to new domain hardReset(); @@ -316,6 +329,24 @@ void DomainHandler::connectedToServerless(std::map namedPaths) setIsConnected(true); } +void DomainHandler::loadedErrorDomain(std::map namedPaths) { + auto lookup = namedPaths.find("/"); + QString viewpoint; + if (lookup != namedPaths.end()) { + viewpoint = lookup->second; + } else { + viewpoint = DOMAIN_SPAWNING_POINT; + } + DependencyManager::get()->goToViewpointForPath(viewpoint, QString()); +} + +void DomainHandler::setRedirectErrorState(QUrl errorUrl, int reasonCode) { + _errorDomainURL = errorUrl; + _lastDomainConnectionError = reasonCode; + _isInErrorState = true; + emit redirectToErrorDomainURL(_errorDomainURL); +} + void DomainHandler::requestDomainSettings() { qCDebug(networking) << "Requesting settings from domain server"; @@ -451,7 +482,17 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer(); diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index a428110db6..3d361482ac 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -50,6 +50,11 @@ public: QString getHostname() const { return _domainURL.host(); } + QUrl getErrorDomainURL(){ return _errorDomainURL; } + void setErrorDomainURL(const QUrl& url); + + int getLastDomainConnectionError() { return _lastDomainConnectionError; } + const QHostAddress& getIP() const { return _sockAddr.getAddress(); } void setIPToLocalhost() { _sockAddr.setAddress(QHostAddress(QHostAddress::LocalHost)); } @@ -81,6 +86,8 @@ public: void connectedToServerless(std::map namedPaths); + void loadedErrorDomain(std::map namedPaths); + QString getViewPointFromNamedPath(QString namedPath); bool hasSettings() const { return !_settingsObject.isEmpty(); } @@ -135,6 +142,11 @@ public: * 4 * The domain already has its maximum number of users. * + * + * TimedOut + * 5 + * Connecting to the domain timed out. + * * * * @typedef {number} Window.ConnectionRefusedReason @@ -144,7 +156,8 @@ public: ProtocolMismatch, LoginError, NotAuthorized, - TooManyUsers + TooManyUsers, + TimedOut }; public slots: @@ -157,6 +170,11 @@ public slots: void processICEResponsePacket(QSharedPointer icePacket); void processDomainServerConnectionDeniedPacket(QSharedPointer message); + // sets domain handler in error state. + void setRedirectErrorState(QUrl errorUrl, int reasonCode); + + bool isInErrorState() { return _isInErrorState; } + private slots: void completedHostnameLookup(const QHostInfo& hostInfo); void completedIceServerHostnameLookup(); @@ -164,6 +182,8 @@ private slots: signals: void domainURLChanged(QUrl domainURL); + void domainConnectionErrorChanged(int reasonCode); + // NOTE: the emission of completedSocketDiscovery does not mean a connection to DS is established // It means that, either from DNS lookup or ICE, we think we have a socket we can talk to DS on void completedSocketDiscovery(); @@ -179,6 +199,7 @@ signals: void settingsReceiveFail(); void domainConnectionRefused(QString reasonMessage, int reason, const QString& extraInfo); + void redirectToErrorDomainURL(QUrl errorDomainURL); void limitOfSilentDomainCheckInsReached(); @@ -190,6 +211,7 @@ private: QUuid _uuid; Node::LocalID _localID; QUrl _domainURL; + QUrl _errorDomainURL; HifiSockAddr _sockAddr; QUuid _assignmentUUID; QUuid _connectionToken; @@ -198,6 +220,7 @@ private: HifiSockAddr _iceServerSockAddr; NetworkPeer _icePeer; bool _isConnected { false }; + bool _isInErrorState { false }; QJsonObject _settingsObject; QString _pendingPath; QTimer _settingsTimer; @@ -210,6 +233,9 @@ private: QTimer _apiRefreshTimer; std::map _namedPaths; + + // domain connection error upon connection refusal. + int _lastDomainConnectionError{ -1 }; }; const QString DOMAIN_SPAWNING_POINT { "/0, -10, 0" }; diff --git a/libraries/networking/src/UserActivityLogger.cpp b/libraries/networking/src/UserActivityLogger.cpp index a5ee417939..c05daf0217 100644 --- a/libraries/networking/src/UserActivityLogger.cpp +++ b/libraries/networking/src/UserActivityLogger.cpp @@ -38,10 +38,10 @@ void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCall if (_disabled.get()) { return; } - + auto accountManager = DependencyManager::get(); QHttpMultiPart* multipart = new QHttpMultiPart(QHttpMultiPart::FormDataType); - + // Adding the action name QHttpPart actionPart; actionPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"action_name\""); @@ -53,7 +53,7 @@ void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCall elapsedPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"elapsed_ms\""); elapsedPart.setBody(QString::number(_timer.elapsed()).toLocal8Bit()); multipart->append(elapsedPart); - + // If there are action details, add them to the multipart if (!details.isEmpty()) { QHttpPart detailsPart; @@ -62,13 +62,13 @@ void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCall detailsPart.setBody(QJsonDocument(details).toJson(QJsonDocument::Compact)); multipart->append(detailsPart); } - + // if no callbacks specified, call our owns if (params.isEmpty()) { params.callbackReceiver = this; params.errorCallbackMethod = "requestError"; } - + accountManager->sendRequest(USER_ACTIVITY_URL, AccountManagerAuth::Optional, QNetworkAccessManager::PostOperation, @@ -88,7 +88,7 @@ void UserActivityLogger::launch(QString applicationVersion, bool previousSession actionDetails.insert(VERSION_KEY, applicationVersion); actionDetails.insert(CRASH_KEY, previousSessionCrashed); actionDetails.insert(RUNTIME_KEY, previousSessionRuntime); - + logAction(ACTION_NAME, actionDetails); } @@ -105,9 +105,9 @@ void UserActivityLogger::changedDisplayName(QString displayName) { const QString ACTION_NAME = "changed_display_name"; QJsonObject actionDetails; const QString DISPLAY_NAME = "display_name"; - + actionDetails.insert(DISPLAY_NAME, displayName); - + logAction(ACTION_NAME, actionDetails); } @@ -116,10 +116,10 @@ void UserActivityLogger::changedModel(QString typeOfModel, QString modelURL) { QJsonObject actionDetails; const QString TYPE_OF_MODEL = "type_of_model"; const QString MODEL_URL = "model_url"; - + actionDetails.insert(TYPE_OF_MODEL, typeOfModel); actionDetails.insert(MODEL_URL, modelURL); - + logAction(ACTION_NAME, actionDetails); } @@ -127,9 +127,9 @@ void UserActivityLogger::changedDomain(QString domainURL) { const QString ACTION_NAME = "changed_domain"; QJsonObject actionDetails; const QString DOMAIN_URL = "domain_url"; - + actionDetails.insert(DOMAIN_URL, domainURL); - + logAction(ACTION_NAME, actionDetails); } @@ -151,10 +151,10 @@ void UserActivityLogger::connectedDevice(QString typeOfDevice, QString deviceNam QJsonObject actionDetails; const QString TYPE_OF_DEVICE = "type_of_device"; const QString DEVICE_NAME = "device_name"; - + actionDetails.insert(TYPE_OF_DEVICE, typeOfDevice); actionDetails.insert(DEVICE_NAME, deviceName); - + logAction(ACTION_NAME, actionDetails); } @@ -163,9 +163,9 @@ void UserActivityLogger::loadedScript(QString scriptName) { const QString ACTION_NAME = "loaded_script"; QJsonObject actionDetails; const QString SCRIPT_NAME = "script_name"; - + actionDetails.insert(SCRIPT_NAME, scriptName); - + logAction(ACTION_NAME, actionDetails); } @@ -199,10 +199,10 @@ void UserActivityLogger::wentTo(AddressManager::LookupTrigger lookupTrigger, QSt const QString TRIGGER_TYPE_KEY = "trigger"; const QString DESTINATION_TYPE_KEY = "destination_type"; const QString DESTINATION_NAME_KEY = "detination_name"; - + actionDetails.insert(TRIGGER_TYPE_KEY, trigger); actionDetails.insert(DESTINATION_TYPE_KEY, destinationType); actionDetails.insert(DESTINATION_NAME_KEY, destinationName); - + logAction(ACTION_NAME, actionDetails); } diff --git a/libraries/networking/src/udt/Socket.cpp b/libraries/networking/src/udt/Socket.cpp index c378987cd0..44220df8f8 100644 --- a/libraries/networking/src/udt/Socket.cpp +++ b/libraries/networking/src/udt/Socket.cpp @@ -37,6 +37,7 @@ Socket::Socket(QObject* parent, bool shouldChangeSocketOptions) : _shouldChangeSocketOptions(shouldChangeSocketOptions) { connect(&_udpSocket, &QUdpSocket::readyRead, this, &Socket::readPendingDatagrams); + connect(this, &Socket::pendingDatagrams, this, &Socket::processPendingDatagrams, Qt::QueuedConnection); // make sure we hear about errors and state changes from the underlying socket connect(&_udpSocket, SIGNAL(error(QAbstractSocket::SocketError)), @@ -315,55 +316,85 @@ void Socket::checkForReadyReadBackup() { } void Socket::readPendingDatagrams() { + int packetsRead = 0; + int packetSizeWithHeader = -1; - - while (_udpSocket.hasPendingDatagrams() && (packetSizeWithHeader = _udpSocket.pendingDatagramSize()) != -1) { - - // we're reading a packet so re-start the readyRead backup timer - _readyReadBackupTimer->start(); - + // Max datagrams to read before processing: + static const int MAX_DATAGRAMS_CONSECUTIVELY = 10000; + while (_udpSocket.hasPendingDatagrams() + && (packetSizeWithHeader = _udpSocket.pendingDatagramSize()) != -1 + && packetsRead <= MAX_DATAGRAMS_CONSECUTIVELY) { // grab a time point we can mark as the receive time of this packet auto receiveTime = p_high_resolution_clock::now(); - // setup a HifiSockAddr to read into - HifiSockAddr senderSockAddr; // setup a buffer to read the packet into auto buffer = std::unique_ptr(new char[packetSizeWithHeader]); + QHostAddress senderAddress; + quint16 senderPort; + // pull the datagram auto sizeRead = _udpSocket.readDatagram(buffer.get(), packetSizeWithHeader, - senderSockAddr.getAddressPointer(), senderSockAddr.getPortPointer()); + &senderAddress, &senderPort); - // save information for this packet, in case it is the one that sticks readyRead - _lastPacketSizeRead = sizeRead; - _lastPacketSockAddr = senderSockAddr; - - if (sizeRead <= 0) { - // we either didn't pull anything for this packet or there was an error reading (this seems to trigger - // on windows even if there's not a packet available) + // we either didn't pull anything for this packet or there was an error reading (this seems to trigger + // on windows even if there's not a packet available) + if (sizeRead < 0) { continue; } - auto it = _unfilteredHandlers.find(senderSockAddr); + _incomingDatagrams.push_back({ senderAddress, senderPort, packetSizeWithHeader, + std::move(buffer), receiveTime }); + ++packetsRead; + } + + if (packetsRead > _maxDatagramsRead) { + _maxDatagramsRead = packetsRead; + qCDebug(networking) << "readPendingDatagrams: Datagrams read:" << packetsRead; + } + emit pendingDatagrams(packetsRead); +} + +void Socket::processPendingDatagrams(int) { + // setup a HifiSockAddr to read into + HifiSockAddr senderSockAddr; + + while (!_incomingDatagrams.empty()) { + auto& datagram = _incomingDatagrams.front(); + senderSockAddr.setAddress(datagram._senderAddress); + senderSockAddr.setPort(datagram._senderPort); + int datagramSize = datagram._datagramLength; + auto receiveTime = datagram._receiveTime; + + // we're reading a packet so re-start the readyRead backup timer + _readyReadBackupTimer->start(); + + // save information for this packet, in case it is the one that sticks readyRead + _lastPacketSizeRead = datagramSize; + _lastPacketSockAddr = senderSockAddr; + + // Process unfiltered packets first. + auto it = _unfilteredHandlers.find(senderSockAddr); if (it != _unfilteredHandlers.end()) { - // we have a registered unfiltered handler for this HifiSockAddr - call that and return + // we have a registered unfiltered handler for this HifiSockAddr (eg. STUN packet) - call that and return if (it->second) { - auto basePacket = BasePacket::fromReceivedPacket(std::move(buffer), packetSizeWithHeader, senderSockAddr); + auto basePacket = BasePacket::fromReceivedPacket(std::move(datagram._datagram), + datagramSize, senderSockAddr); basePacket->setReceiveTime(receiveTime); it->second(std::move(basePacket)); } - + _incomingDatagrams.pop_front(); continue; } // check if this was a control packet or a data packet - bool isControlPacket = *reinterpret_cast(buffer.get()) & CONTROL_BIT_MASK; + bool isControlPacket = *reinterpret_cast(datagram._datagram.get()) & CONTROL_BIT_MASK; if (isControlPacket) { // setup a control packet from the data we just read - auto controlPacket = ControlPacket::fromReceivedPacket(std::move(buffer), packetSizeWithHeader, senderSockAddr); + auto controlPacket = ControlPacket::fromReceivedPacket(std::move(datagram._datagram), datagramSize, senderSockAddr); controlPacket->setReceiveTime(receiveTime); // move this control packet to the matching connection, if there is one @@ -375,13 +406,13 @@ void Socket::readPendingDatagrams() { } else { // setup a Packet from the data we just read - auto packet = Packet::fromReceivedPacket(std::move(buffer), packetSizeWithHeader, senderSockAddr); + auto packet = Packet::fromReceivedPacket(std::move(datagram._datagram), datagramSize, senderSockAddr); packet->setReceiveTime(receiveTime); // save the sequence number in case this is the packet that sticks readyRead _lastReceivedSequenceNumber = packet->getSequenceNumber(); - // call our verification operator to see if this packet is verified + // call our hash verification operator to see if this packet is verified if (!_packetFilterOperator || _packetFilterOperator(*packet)) { if (packet->isReliable()) { // if this was a reliable packet then signal the matching connection with the sequence number @@ -395,6 +426,7 @@ void Socket::readPendingDatagrams() { qCDebug(networking) << "Can't process packet: version" << (unsigned int)NLPacket::versionInHeader(*packet) << ", type" << NLPacket::typeInHeader(*packet); #endif + _incomingDatagrams.pop_front(); continue; } } @@ -410,6 +442,8 @@ void Socket::readPendingDatagrams() { } } } + + _incomingDatagrams.pop_front(); } } diff --git a/libraries/networking/src/udt/Socket.h b/libraries/networking/src/udt/Socket.h index 1f28592c83..ef4a457116 100644 --- a/libraries/networking/src/udt/Socket.h +++ b/libraries/networking/src/udt/Socket.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -94,6 +95,7 @@ public: signals: void clientHandshakeRequestComplete(const HifiSockAddr& sockAddr); + void pendingDatagrams(int datagramCount); public slots: void cleanupConnection(HifiSockAddr sockAddr); @@ -101,6 +103,7 @@ public slots: private slots: void readPendingDatagrams(); + void processPendingDatagrams(int datagramCount); void checkForReadyReadBackup(); void handleSocketError(QAbstractSocket::SocketError socketError); @@ -144,6 +147,17 @@ private: int _lastPacketSizeRead { 0 }; SequenceNumber _lastReceivedSequenceNumber; HifiSockAddr _lastPacketSockAddr; + + struct Datagram { + QHostAddress _senderAddress; + int _senderPort; + int _datagramLength; + std::unique_ptr _datagram; + p_high_resolution_clock::time_point _receiveTime; + }; + + std::list _incomingDatagrams; + int _maxDatagramsRead { 0 }; friend UDTTest; }; diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 2c130274bc..5478221607 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -446,8 +446,9 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) { void EntityMotionState::updateSendVelocities() { if (!_body->isActive()) { - // make sure all derivatives are zero - clearObjectVelocities(); + if (!_body->isKinematicObject()) { + clearObjectVelocities(); + } // we pretend we sent the inactive update for this object _numInactiveUpdates = 1; } else { diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index 5666e75aa1..2004f8953d 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -138,7 +138,6 @@ void PhysicalEntitySimulation::changeEntityInternal(EntityItemPointer entity) { btRigidBody* body = motionState->getRigidBody(); if (body) { body->forceActivationState(ISLAND_SLEEPING); - motionState->updateSendVelocities(); // has side-effect of zeroing entity velocities for inactive body } // send packet to remove ownership diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index 58c197d6f4..8343919ae1 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -286,6 +286,7 @@ void PhysicsEngine::reinsertObject(ObjectMotionState* object) { void PhysicsEngine::processTransaction(PhysicsEngine::Transaction& transaction) { // removes for (auto object : transaction.objectsToRemove) { + bumpAndPruneContacts(object); btRigidBody* body = object->getRigidBody(); if (body) { removeDynamicsForBody(body); @@ -935,19 +936,22 @@ struct AllContactsCallback : public btCollisionWorld::ContactResultCallback { const btCollisionObject* otherBody; btVector3 penetrationPoint; btVector3 otherPenetrationPoint; + btVector3 normal; if (colObj0->m_collisionObject == &collisionObject) { otherBody = colObj1->m_collisionObject; penetrationPoint = getWorldPoint(cp.m_localPointB, colObj1->getWorldTransform()); otherPenetrationPoint = getWorldPoint(cp.m_localPointA, colObj0->getWorldTransform()); + normal = -cp.m_normalWorldOnB; } else { otherBody = colObj0->m_collisionObject; penetrationPoint = getWorldPoint(cp.m_localPointA, colObj0->getWorldTransform()); otherPenetrationPoint = getWorldPoint(cp.m_localPointB, colObj1->getWorldTransform()); + normal = cp.m_normalWorldOnB; } // TODO: Give MyAvatar a motion state so we don't have to do this if ((m_collisionFilterMask & BULLET_COLLISION_GROUP_MY_AVATAR) && myAvatarCollisionObject && myAvatarCollisionObject == otherBody) { - contacts.emplace_back(Physics::getSessionUUID(), bulletToGLM(penetrationPoint), bulletToGLM(otherPenetrationPoint)); + contacts.emplace_back(Physics::getSessionUUID(), bulletToGLM(penetrationPoint), bulletToGLM(otherPenetrationPoint), bulletToGLM(normal)); return 0; } @@ -963,7 +967,7 @@ struct AllContactsCallback : public btCollisionWorld::ContactResultCallback { } // This is the correct object type. Add it to the list. - contacts.emplace_back(candidate->getObjectID(), bulletToGLM(penetrationPoint), bulletToGLM(otherPenetrationPoint)); + contacts.emplace_back(candidate->getObjectID(), bulletToGLM(penetrationPoint), bulletToGLM(otherPenetrationPoint), bulletToGLM(normal)); return 0; } diff --git a/libraries/physics/src/PhysicsEngine.h b/libraries/physics/src/PhysicsEngine.h index 09b68d9a8b..d10be018b8 100644 --- a/libraries/physics/src/PhysicsEngine.h +++ b/libraries/physics/src/PhysicsEngine.h @@ -49,13 +49,15 @@ struct ContactTestResult { ContactTestResult(const ContactTestResult& contactTestResult) : foundID(contactTestResult.foundID), testCollisionPoint(contactTestResult.testCollisionPoint), - foundCollisionPoint(contactTestResult.foundCollisionPoint) { + foundCollisionPoint(contactTestResult.foundCollisionPoint), + collisionNormal(contactTestResult.collisionNormal) { } - ContactTestResult(QUuid foundID, glm::vec3 testCollisionPoint, glm::vec3 otherCollisionPoint) : + ContactTestResult(const QUuid& foundID, const glm::vec3& testCollisionPoint, const glm::vec3& otherCollisionPoint, const glm::vec3& collisionNormal) : foundID(foundID), testCollisionPoint(testCollisionPoint), - foundCollisionPoint(otherCollisionPoint) { + foundCollisionPoint(otherCollisionPoint), + collisionNormal(collisionNormal) { } QUuid foundID; @@ -63,6 +65,8 @@ struct ContactTestResult { glm::vec3 testCollisionPoint; // The deepest point of an intersection within the volume of the found object, in world space. glm::vec3 foundCollisionPoint; + // The normal vector of this intersection + glm::vec3 collisionNormal; }; using ContactMap = std::map; diff --git a/libraries/procedural/src/procedural/ShaderConstants.h b/libraries/procedural/src/procedural/ShaderConstants.h index 1995996c97..bfbf2a2691 100644 --- a/libraries/procedural/src/procedural/ShaderConstants.h +++ b/libraries/procedural/src/procedural/ShaderConstants.h @@ -14,7 +14,6 @@ #ifndef PROCEDURAL_SHADER_CONSTANTS_H #define PROCEDURAL_SHADER_CONSTANTS_H -// Polyvox #define PROCEDURAL_UNIFORM_TIME 200 #define PROCEDURAL_UNIFORM_DATE 201 #define PROCEDURAL_UNIFORM_FRAME_COUNT 202 diff --git a/libraries/render-utils/src/AntialiasingEffect.cpp b/libraries/render-utils/src/AntialiasingEffect.cpp index 2b17ba3c01..17c13df19a 100644 --- a/libraries/render-utils/src/AntialiasingEffect.cpp +++ b/libraries/render-utils/src/AntialiasingEffect.cpp @@ -29,7 +29,6 @@ namespace ru { - using render_utils::slot::uniform::Uniform; using render_utils::slot::texture::Texture; using render_utils::slot::buffer::Buffer; } @@ -39,13 +38,7 @@ namespace gr { using graphics::slot::buffer::Buffer; } -#define ANTIALIASING_USE_TAA 1 - #if !ANTIALIASING_USE_TAA -#include "fxaa_vert.h" -#include "fxaa_frag.h" -#include "fxaa_blend_frag.h" - Antialiasing::Antialiasing() { _geometryId = DependencyManager::get()->allocateID(); @@ -58,30 +51,9 @@ Antialiasing::~Antialiasing() { } } -const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline(RenderArgs* args) { - int width = args->_viewport.z; - int height = args->_viewport.w; - - if (_antialiasingBuffer && _antialiasingBuffer->getSize() != uvec2(width, height)) { - _antialiasingBuffer.reset(); - } - - if (!_antialiasingBuffer) { - // Link the antialiasing FBO to texture - _antialiasingBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("antialiasing")); - auto format = gpu::Element::COLOR_SRGBA_32; - auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT); - _antialiasingTexture = gpu::Texture::createRenderBuffer(format, width, height, gpu::Texture::SINGLE_MIP, defaultSampler); - _antialiasingBuffer->setRenderBuffer(0, _antialiasingTexture); - } - +const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline() { if (!_antialiasingPipeline) { - auto vs = fxaa_vert::getShader(); - auto ps = fxaa_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - - _texcoordOffsetLoc = program->getUniforms().findLocation("texcoordOffset"); - + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::fxaa); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); state->setDepthTest(false, false, gpu::LESS_EQUAL); @@ -96,9 +68,7 @@ const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline(RenderArgs* ar const gpu::PipelinePointer& Antialiasing::getBlendPipeline() { if (!_blendPipeline) { - auto vs = fxaa_vert::getShader(); - auto ps = fxaa_blend_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::fxaa_blend); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); state->setDepthTest(false, false, gpu::LESS_EQUAL); PrepareStencil::testNoAA(*state); @@ -119,13 +89,30 @@ void Antialiasing::run(const render::RenderContextPointer& renderContext, const batch.enableStereo(false); batch.setViewportTransform(args->_viewport); - // FIXME: NEED to simplify that code to avoid all the GeometryCahce call, this is purely pixel manipulation - float fbWidth = renderContext->args->_viewport.z; - float fbHeight = renderContext->args->_viewport.w; - // float sMin = args->_viewport.x / fbWidth; - // float sWidth = args->_viewport.z / fbWidth; - // float tMin = args->_viewport.y / fbHeight; - // float tHeight = args->_viewport.w / fbHeight; + if (!_paramsBuffer) { + _paramsBuffer = std::make_shared(sizeof(glm::vec4), nullptr); + } + + { + int width = args->_viewport.z; + int height = args->_viewport.w; + if (_antialiasingBuffer && _antialiasingBuffer->getSize() != uvec2(width, height)) { + _antialiasingBuffer.reset(); + } + + if (!_antialiasingBuffer) { + // Link the antialiasing FBO to texture + _antialiasingBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("antialiasing")); + auto format = gpu::Element::COLOR_SRGBA_32; + auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT); + _antialiasingTexture = gpu::Texture::createRenderBuffer(format, width, height, gpu::Texture::SINGLE_MIP, defaultSampler); + _antialiasingBuffer->setRenderBuffer(0, _antialiasingTexture); + glm::vec2 fbExtent { args->_viewport.z, args->_viewport.w }; + glm::vec2 inverseFbExtent = 1.0f / fbExtent; + _paramsBuffer->setSubData(0, glm::vec4(inverseFbExtent, 0.0, 0.0)); + } + } + glm::mat4 projMat; Transform viewMat; @@ -136,40 +123,18 @@ void Antialiasing::run(const render::RenderContextPointer& renderContext, const batch.setModelTransform(Transform()); // FXAA step - auto pipeline = getAntialiasingPipeline(renderContext->args); + auto pipeline = getAntialiasingPipeline(); batch.setResourceTexture(0, sourceBuffer->getRenderBuffer(0)); batch.setFramebuffer(_antialiasingBuffer); batch.setPipeline(pipeline); - - // initialize the view-space unpacking uniforms using frustum data - float left, right, bottom, top, nearVal, farVal; - glm::vec4 nearClipPlane, farClipPlane; - - args->getViewFrustum().computeOffAxisFrustum(left, right, bottom, top, nearVal, farVal, nearClipPlane, farClipPlane); - - // float depthScale = (farVal - nearVal) / farVal; - // float nearScale = -1.0f / nearVal; - // float depthTexCoordScaleS = (right - left) * nearScale / sWidth; - // float depthTexCoordScaleT = (top - bottom) * nearScale / tHeight; - // float depthTexCoordOffsetS = left * nearScale - sMin * depthTexCoordScaleS; - // float depthTexCoordOffsetT = bottom * nearScale - tMin * depthTexCoordScaleT; - - batch._glUniform2f(_texcoordOffsetLoc, 1.0f / fbWidth, 1.0f / fbHeight); - - glm::vec4 color(0.0f, 0.0f, 0.0f, 1.0f); - glm::vec2 bottomLeft(-1.0f, -1.0f); - glm::vec2 topRight(1.0f, 1.0f); - glm::vec2 texCoordTopLeft(0.0f, 0.0f); - glm::vec2 texCoordBottomRight(1.0f, 1.0f); - DependencyManager::get()->renderQuad(batch, bottomLeft, topRight, texCoordTopLeft, texCoordBottomRight, color, _geometryId); - + batch.setUniformBuffer(0, _paramsBuffer); + batch.draw(gpu::TRIANGLE_STRIP, 4); // Blend step batch.setResourceTexture(0, _antialiasingTexture); batch.setFramebuffer(sourceBuffer); batch.setPipeline(getBlendPipeline()); - - DependencyManager::get()->renderQuad(batch, bottomLeft, topRight, texCoordTopLeft, texCoordBottomRight, color, _geometryId); + batch.draw(gpu::TRIANGLE_STRIP, 4); }); } #else @@ -314,7 +279,11 @@ void Antialiasing::run(const render::RenderContextPointer& renderContext, const // Must match the bindg point in the fxaa_blend.slf shader batch.setResourceFramebufferSwapChainTexture(0, _antialiasingBuffers, 1); // Disable sharpen if FXAA - batch._glUniform1f(ru::Uniform::TaaSharpenIntensity, _sharpen * _params.get().regionInfo.z); + if (!_blendParamsBuffer) { + _blendParamsBuffer = std::make_shared(sizeof(glm::vec4), nullptr); + } + _blendParamsBuffer->setSubData(0, _sharpen * _params.get().regionInfo.z); + batch.setUniformBuffer(0, _blendParamsBuffer); } batch.draw(gpu::TRIANGLE_STRIP, 4); batch.advance(_antialiasingBuffers); diff --git a/libraries/render-utils/src/AntialiasingEffect.h b/libraries/render-utils/src/AntialiasingEffect.h index 61da352154..936ade043d 100644 --- a/libraries/render-utils/src/AntialiasingEffect.h +++ b/libraries/render-utils/src/AntialiasingEffect.h @@ -134,6 +134,10 @@ signals: #define SET_BIT(bitfield, bitIndex, value) bitfield = ((bitfield) & ~(1 << (bitIndex))) | ((value) << (bitIndex)) #define GET_BIT(bitfield, bitIndex) ((bitfield) & (1 << (bitIndex))) +#define ANTIALIASING_USE_TAA 1 + +#if ANTIALIASING_USE_TAA + struct TAAParams { float nope{ 0.0f }; float blend{ 0.15f }; @@ -186,7 +190,7 @@ private: gpu::FramebufferSwapChainPointer _antialiasingBuffers; gpu::TexturePointer _antialiasingTextures[2]; - + gpu::BufferPointer _blendParamsBuffer; gpu::PipelinePointer _antialiasingPipeline; gpu::PipelinePointer _blendPipeline; gpu::PipelinePointer _debugBlendPipeline; @@ -197,7 +201,7 @@ private: }; -/* +#else class AntiAliasingConfig : public render::Job::Config { Q_OBJECT Q_PROPERTY(bool enabled MEMBER enabled) @@ -219,18 +223,15 @@ public: const gpu::PipelinePointer& getBlendPipeline(); private: - - // Uniforms for AA - gpu::int32 _texcoordOffsetLoc; - gpu::FramebufferPointer _antialiasingBuffer; gpu::TexturePointer _antialiasingTexture; + gpu::BufferPointer _paramsBuffer; gpu::PipelinePointer _antialiasingPipeline; gpu::PipelinePointer _blendPipeline; int _geometryId { 0 }; }; -*/ +#endif #endif // hifi_AntialiasingEffect_h diff --git a/libraries/render-utils/src/BloomEffect.cpp b/libraries/render-utils/src/BloomEffect.cpp index 3a35e736a9..e6217eb825 100644 --- a/libraries/render-utils/src/BloomEffect.cpp +++ b/libraries/render-utils/src/BloomEffect.cpp @@ -184,6 +184,7 @@ void BloomDraw::run(const render::RenderContextPointer& renderContext, const Inp } DebugBloom::DebugBloom() { + _params = std::make_shared(sizeof(glm::vec4), nullptr); } void DebugBloom::configure(const Config& config) { @@ -227,7 +228,8 @@ void DebugBloom::run(const render::RenderContextPointer& renderContext, const In Transform modelTransform; if (_mode == DebugBloomConfig::MODE_ALL_LEVELS) { - batch._glUniform4f(gpu::slot::uniform::TexCoordRect, 0.0f, 0.0f, 1.f, 1.f); + _params->setSubData(0, vec4(0.0f, 0.0f, 1.f, 1.f)); + batch.setUniformBuffer(0, _params); modelTransform = gpu::Framebuffer::evalSubregionTexcoordTransform(framebufferSize, args->_viewport / 2); modelTransform.postTranslate(glm::vec3(-1.0f, 1.0f, 0.0f)); @@ -255,7 +257,8 @@ void DebugBloom::run(const render::RenderContextPointer& renderContext, const In viewport.z /= 2; - batch._glUniform4f(gpu::slot::uniform::TexCoordRect, 0.5f, 0.0f, 0.5f, 1.f); + _params->setSubData(0, vec4(0.5f, 0.0f, 0.5f, 1.f)); + batch.setUniformBuffer(0, _params); modelTransform = gpu::Framebuffer::evalSubregionTexcoordTransform(framebufferSize, viewport); modelTransform.postTranslate(glm::vec3(-1.0f, 0.0f, 0.0f)); diff --git a/libraries/render-utils/src/BloomEffect.h b/libraries/render-utils/src/BloomEffect.h index 7789c70190..12c94bb929 100644 --- a/libraries/render-utils/src/BloomEffect.h +++ b/libraries/render-utils/src/BloomEffect.h @@ -121,6 +121,7 @@ public: private: gpu::PipelinePointer _pipeline; + gpu::BufferPointer _params; DebugBloomConfig::Mode _mode; }; diff --git a/libraries/render-utils/src/BloomThreshold.shared.slh b/libraries/render-utils/src/BloomThreshold.shared.slh index 8aaf8ec311..5ad490a1ca 100644 --- a/libraries/render-utils/src/BloomThreshold.shared.slh +++ b/libraries/render-utils/src/BloomThreshold.shared.slh @@ -8,7 +8,7 @@ struct Parameters { BT_VEC2 _deltaUV; - float _threshold; + float _threshold; int _sampleCount; }; diff --git a/libraries/render-utils/src/BloomThreshold.slf b/libraries/render-utils/src/BloomThreshold.slf index 621aa31622..47a1fb0d9f 100644 --- a/libraries/render-utils/src/BloomThreshold.slf +++ b/libraries/render-utils/src/BloomThreshold.slf @@ -30,7 +30,7 @@ void main(void) { for (int x=0 ; xgetNumParts(); for (int partIndex = 0; partIndex < numParts; partIndex++) { - if (!fbxGeometry.meshes[i].blendshapes.empty()) { + if (!fbxGeometry.meshes[i].blendshapes.empty() && _blendedVertexBuffers.find(i) == _blendedVertexBuffers.end()) { initializeBlendshapes(fbxGeometry.meshes[i], i); } auto ptr = std::make_shared(shared_from_this(), i, partIndex, shapeID, transform, offset); @@ -97,6 +97,7 @@ void CauterizedModel::createRenderItemSet() { shapeID++; } } + _blendedVertexBuffersInitialized = true; } else { Model::createRenderItemSet(); } diff --git a/libraries/render-utils/src/DeferredBufferRead.slh b/libraries/render-utils/src/DeferredBufferRead.slh index 8c6c76b24a..e5a7c39d54 100644 --- a/libraries/render-utils/src/DeferredBufferRead.slh +++ b/libraries/render-utils/src/DeferredBufferRead.slh @@ -137,7 +137,7 @@ vec4 unpackDeferredPosition(float depthValue, vec2 texcoord) { // This method to unpack position is fastesst vec4 unpackDeferredPositionFromZdb(vec2 texcoord) { float Zdb = texture(depthMap, texcoord).x; - return unpackDeferredPosition(Zdb, texcoord); + return unpackDeferredPosition(Zdb, texcoord); } vec4 unpackDeferredPositionFromZeye(vec2 texcoord) { diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index af5a3f3e63..3b23711a64 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -32,7 +32,6 @@ namespace ru { using render_utils::slot::texture::Texture; using render_utils::slot::buffer::Buffer; - using render_utils::slot::uniform::Uniform; } namespace gr { diff --git a/libraries/render-utils/src/DeferredTransform.slh b/libraries/render-utils/src/DeferredTransform.slh index 6dd92c651b..19986805f6 100644 --- a/libraries/render-utils/src/DeferredTransform.slh +++ b/libraries/render-utils/src/DeferredTransform.slh @@ -38,8 +38,8 @@ struct DeferredFrameTransform { mat4 _projectionMono; mat4 _viewInverse; mat4 _view; - mat4 _projectionUnJittered[2]; - mat4 _invProjectionUnJittered[2]; + mat4 _projectionUnJittered[2]; + mat4 _invProjectionUnJittered[2]; }; layout(binding=RENDER_UTILS_BUFFER_DEFERRED_FRAME_TRANSFORM) uniform deferredFrameTransformBuffer { @@ -68,10 +68,10 @@ mat4 getProjectionMono() { return frameTransform._projectionMono; } mat4 getUnjitteredProjection(int side) { - return frameTransform._projectionUnJittered[side]; + return frameTransform._projectionUnJittered[side]; } mat4 getUnjitteredInvProjection(int side) { - return frameTransform._invProjectionUnJittered[side]; + return frameTransform._invProjectionUnJittered[side]; } // positive near distance of the projection @@ -158,7 +158,7 @@ vec3 evalUnjitteredEyePositionFromZdb(int side, float Zdb, vec2 texcoord) { } vec3 evalEyePositionFromZeye(int side, float Zeye, vec2 texcoord) { - float Zdb = evalZdbFromZeye(Zeye); + float Zdb = evalZdbFromZeye(Zeye); return evalEyePositionFromZdb(side, Zdb, texcoord); } diff --git a/libraries/render-utils/src/Fade_shared.slh b/libraries/render-utils/src/Fade_shared.slh index 7d1c0fb8bc..7ede92b807 100644 --- a/libraries/render-utils/src/Fade_shared.slh +++ b/libraries/render-utils/src/Fade_shared.slh @@ -13,12 +13,12 @@ struct FadeParameters { - VEC4 _noiseInvSizeAndLevel; - VEC4 _innerEdgeColor; - VEC4 _outerEdgeColor; - VEC2 _edgeWidthInvWidth; - FLOAT32 _baseLevel; - INT32 _isInverted; + VEC4 _noiseInvSizeAndLevel; + VEC4 _innerEdgeColor; + VEC4 _outerEdgeColor; + VEC2 _edgeWidthInvWidth; + FLOAT32 _baseLevel; + INT32 _isInverted; }; // <@if 1@> diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index cbc9b40129..2fe811c97b 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -2104,9 +2104,7 @@ void GeometryCache::useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend) { auto stateNoBlend = std::make_shared(); PrepareStencil::testMaskDrawShape(*stateNoBlend); - auto noBlendPS = gpu::Shader::createVertex(shader::gpu::fragment::DrawTextureOpaque); auto programNoBlend = gpu::Shader::createProgram(shader::render_utils::program::standardDrawTextureNoBlend); - _standardDrawPipelineNoBlend = gpu::Pipeline::create(programNoBlend, stateNoBlend); }); diff --git a/libraries/render-utils/src/Highlight.slh b/libraries/render-utils/src/Highlight.slh index 56a999f508..b26337676f 100644 --- a/libraries/render-utils/src/Highlight.slh +++ b/libraries/render-utils/src/Highlight.slh @@ -15,15 +15,15 @@ <@include Highlight_shared.slh@> -layout(binding=RENDER_UTILS_BUFFER_HIGHLIGHT_PARAMS) uniform highlightParamsBuffer { +layout(std140, binding=RENDER_UTILS_BUFFER_HIGHLIGHT_PARAMS) uniform highlightParamsBuffer { HighlightParameters params; }; layout(binding=RENDER_UTILS_TEXTURE_HIGHLIGHT_SCENE_DEPTH) uniform sampler2D sceneDepthMap; layout(binding=RENDER_UTILS_TEXTURE_HIGHLIGHT_DEPTH) uniform sampler2D highlightedDepthMap; -in vec2 varTexCoord0; -out vec4 outFragColor; +layout(location=0) in vec2 varTexCoord0; +layout(location=0) out vec4 outFragColor; const float FAR_Z = 1.0; const float LINEAR_DEPTH_BIAS = 5e-3; @@ -55,10 +55,10 @@ void main(void) { discard; <@endif@> } else { - vec2 halfTexel = getInvWidthHeight() / 2; + vec2 halfTexel = getInvWidthHeight() / 2.0; vec2 texCoord0 = varTexCoord0+halfTexel; float weight = 0.0; - vec2 deltaUv = params._size / params._blurKernelSize; + vec2 deltaUv = params._size / float(params._blurKernelSize); vec2 lineStartUv = texCoord0 - params._size / 2.0; vec2 uv; int x; @@ -87,7 +87,7 @@ void main(void) { } } - if (intensity > 0) { + if (intensity > 0.0) { // sumOutlineDepth /= intensity; } else { sumOutlineDepth = FAR_Z; diff --git a/libraries/render-utils/src/HighlightEffect.cpp b/libraries/render-utils/src/HighlightEffect.cpp index 7e2e55c768..11326b1120 100644 --- a/libraries/render-utils/src/HighlightEffect.cpp +++ b/libraries/render-utils/src/HighlightEffect.cpp @@ -28,7 +28,6 @@ using namespace render; namespace ru { using render_utils::slot::texture::Texture; using render_utils::slot::buffer::Buffer; - using render_utils::slot::uniform::Uniform; } namespace gr { diff --git a/libraries/render-utils/src/Highlight_aabox.slv b/libraries/render-utils/src/Highlight_aabox.slv index 5130d5e7ff..2ecebdea51 100644 --- a/libraries/render-utils/src/Highlight_aabox.slv +++ b/libraries/render-utils/src/Highlight_aabox.slv @@ -45,7 +45,7 @@ struct HighlightParameters { vec2 outlineWidth; }; -layout(binding=0) uniform parametersBuffer { +layout(std140, binding=0) uniform parametersBuffer { HighlightParameters _parameters; }; diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index ba2bd28852..b9ed43c339 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -302,15 +302,22 @@ bool Model::updateGeometry() { assert(_meshStates.empty()); const FBXGeometry& fbxGeometry = getFBXGeometry(); + int i = 0; foreach (const FBXMesh& mesh, fbxGeometry.meshes) { MeshState state; state.clusterDualQuaternions.resize(mesh.clusters.size()); state.clusterMatrices.resize(mesh.clusters.size()); _meshStates.push_back(state); + if (!mesh.blendshapes.empty() && _blendedVertexBuffers.find(i) == _blendedVertexBuffers.end()) { + initializeBlendshapes(mesh, i); + } + i++; } + _blendedVertexBuffersInitialized = true; needFullUpdate = true; emit rigReady(); } + return needFullUpdate; } @@ -1027,6 +1034,10 @@ void Model::removeFromScene(const render::ScenePointer& scene, render::Transacti _modelMeshMaterialNames.clear(); _modelMeshRenderItemShapes.clear(); + _blendedVertexBuffers.clear(); + _normalsAndTangents.clear(); + _blendedVertexBuffersInitialized = false; + _addedToScene = false; _renderInfoVertexCount = 0; @@ -1273,8 +1284,7 @@ QStringList Model::getJointNames() const { class Blender : public QRunnable { public: - Blender(ModelPointer model, int blendNumber, const Geometry::WeakPointer& geometry, - const QVector& meshes, const QVector& blendshapeCoefficients); + Blender(ModelPointer model, int blendNumber, const Geometry::WeakPointer& geometry, const QVector& blendshapeCoefficients); virtual void run() override; @@ -1283,37 +1293,37 @@ private: ModelPointer _model; int _blendNumber; Geometry::WeakPointer _geometry; - QVector _meshes; QVector _blendshapeCoefficients; }; -Blender::Blender(ModelPointer model, int blendNumber, const Geometry::WeakPointer& geometry, - const QVector& meshes, const QVector& blendshapeCoefficients) : +Blender::Blender(ModelPointer model, int blendNumber, const Geometry::WeakPointer& geometry, const QVector& blendshapeCoefficients) : _model(model), _blendNumber(blendNumber), _geometry(geometry), - _meshes(meshes), _blendshapeCoefficients(blendshapeCoefficients) { } void Blender::run() { - DETAILED_PROFILE_RANGE_EX(simulation_animation, __FUNCTION__, 0xFFFF0000, 0, { { "url", _model->getURL().toString() } }); QVector vertices; QVector normalsAndTangents; - if (_model) { + if (_model && _model->isLoaded()) { + DETAILED_PROFILE_RANGE_EX(simulation_animation, __FUNCTION__, 0xFFFF0000, 0, { { "url", _model->getURL().toString() } }); int offset = 0; int normalsAndTangentsOffset = 0; - foreach (const FBXMesh& mesh, _meshes) { - if (mesh.blendshapes.isEmpty()) { + auto meshes = _model->getFBXGeometry().meshes; + int meshIndex = 0; + foreach (const FBXMesh& mesh, meshes) { + auto modelMeshNormalsAndTangents = _model->_normalsAndTangents.find(meshIndex++); + if (mesh.blendshapes.isEmpty() || modelMeshNormalsAndTangents == _model->_normalsAndTangents.end()) { continue; } vertices += mesh.vertices; - normalsAndTangents += mesh.normalsAndTangents; + normalsAndTangents += modelMeshNormalsAndTangents->second; glm::vec3* meshVertices = vertices.data() + offset; NormalType* meshNormalsAndTangents = normalsAndTangents.data() + normalsAndTangentsOffset; offset += mesh.vertices.size(); - normalsAndTangentsOffset += mesh.normalsAndTangents.size(); + normalsAndTangentsOffset += modelMeshNormalsAndTangents->second.size(); const float NORMAL_COEFFICIENT_SCALE = 0.01f; for (int i = 0, n = qMin(_blendshapeCoefficients.size(), mesh.blendshapes.size()); i < n; i++) { float vertexCoefficient = _blendshapeCoefficients.at(i); @@ -1353,9 +1363,8 @@ void Blender::run() { } // post the result to the ModelBlender, which will dispatch to the model if still alive QMetaObject::invokeMethod(DependencyManager::get().data(), "setBlendedVertices", - Q_ARG(ModelPointer, _model), Q_ARG(int, _blendNumber), - Q_ARG(const Geometry::WeakPointer&, _geometry), Q_ARG(const QVector&, vertices), - Q_ARG(const QVector&, normalsAndTangents)); + Q_ARG(ModelPointer, _model), Q_ARG(int, _blendNumber), Q_ARG(QVector, vertices), + Q_ARG(QVector, normalsAndTangents)); } void Model::setScaleToFit(bool scaleToFit, const glm::vec3& dimensions, bool forceRescale) { @@ -1526,18 +1535,15 @@ bool Model::maybeStartBlender() { if (isLoaded()) { const FBXGeometry& fbxGeometry = getFBXGeometry(); if (fbxGeometry.hasBlendedMeshes()) { - QThreadPool::globalInstance()->start(new Blender(getThisPointer(), ++_blendNumber, _renderGeometry, - fbxGeometry.meshes, _blendshapeCoefficients)); + QThreadPool::globalInstance()->start(new Blender(getThisPointer(), ++_blendNumber, _renderGeometry, _blendshapeCoefficients)); return true; } } return false; } -void Model::setBlendedVertices(int blendNumber, const Geometry::WeakPointer& geometry, - const QVector& vertices, const QVector& normalsAndTangents) { - auto geometryRef = geometry.lock(); - if (!geometryRef || _renderGeometry != geometryRef || blendNumber < _appliedBlendNumber) { +void Model::setBlendedVertices(int blendNumber, const QVector& vertices, const QVector& normalsAndTangents) { + if (!isLoaded() || blendNumber < _appliedBlendNumber || !_blendedVertexBuffersInitialized) { return; } _appliedBlendNumber = blendNumber; @@ -1546,26 +1552,28 @@ void Model::setBlendedVertices(int blendNumber, const Geometry::WeakPointer& geo int normalAndTangentIndex = 0; for (int i = 0; i < fbxGeometry.meshes.size(); i++) { const FBXMesh& mesh = fbxGeometry.meshes.at(i); - if (mesh.blendshapes.isEmpty()) { + auto meshNormalsAndTangents = _normalsAndTangents.find(i); + if (mesh.blendshapes.isEmpty() || meshNormalsAndTangents == _normalsAndTangents.end()) { continue; } const auto vertexCount = mesh.vertices.size(); const auto verticesSize = vertexCount * sizeof(glm::vec3); - const auto& buffer = _blendedVertexBuffers[i]; - assert(buffer && _blendedVertexBuffersInitialized); - buffer->resize(mesh.vertices.size() * sizeof(glm::vec3) + mesh.normalsAndTangents.size() * sizeof(NormalType)); - buffer->setSubData(0, verticesSize, (gpu::Byte*) vertices.constData() + index * sizeof(glm::vec3)); - buffer->setSubData(verticesSize, mesh.normalsAndTangents.size() * sizeof(NormalType), (const gpu::Byte*) normalsAndTangents.data() + normalAndTangentIndex * sizeof(NormalType)); + const auto& buffer = _blendedVertexBuffers.find(i); + assert(buffer != _blendedVertexBuffers.end()); + buffer->second->resize(mesh.vertices.size() * sizeof(glm::vec3) + meshNormalsAndTangents->second.size() * sizeof(NormalType)); + buffer->second->setSubData(0, verticesSize, (gpu::Byte*) vertices.constData() + index * sizeof(glm::vec3)); + buffer->second->setSubData(verticesSize, meshNormalsAndTangents->second.size() * sizeof(NormalType), (const gpu::Byte*) normalsAndTangents.data() + normalAndTangentIndex * sizeof(NormalType)); index += vertexCount; - normalAndTangentIndex += mesh.normalsAndTangents.size(); + normalAndTangentIndex += meshNormalsAndTangents->second.size(); } } void Model::deleteGeometry() { _deleteGeometryCounter++; _blendedVertexBuffers.clear(); + _normalsAndTangents.clear(); _blendedVertexBuffersInitialized = false; _meshStates.clear(); _rig.destroyAnimGraph(); @@ -1599,6 +1607,7 @@ const render::ItemIDs& Model::fetchRenderItemIDs() const { } void Model::initializeBlendshapes(const FBXMesh& mesh, int index) { + _blendedVertexBuffers[index] = std::make_shared(); QVector normalsAndTangents; normalsAndTangents.resize(2 * mesh.normals.size()); @@ -1627,12 +1636,10 @@ void Model::initializeBlendshapes(const FBXMesh& mesh, int index) { } }); const auto verticesSize = mesh.vertices.size() * sizeof(glm::vec3); - _blendedVertexBuffers[index] = std::make_shared(); _blendedVertexBuffers[index]->resize(mesh.vertices.size() * sizeof(glm::vec3) + normalsAndTangents.size() * sizeof(NormalType)); _blendedVertexBuffers[index]->setSubData(0, verticesSize, (const gpu::Byte*) mesh.vertices.constData()); _blendedVertexBuffers[index]->setSubData(verticesSize, normalsAndTangents.size() * sizeof(NormalType), (const gpu::Byte*) normalsAndTangents.data()); - mesh.normalsAndTangents = normalsAndTangents; - _blendedVertexBuffersInitialized = true; + _normalsAndTangents[index] = normalsAndTangents; } void Model::createRenderItemSet() { @@ -1673,7 +1680,7 @@ void Model::createRenderItemSet() { // Create the render payloads int numParts = (int)mesh->getNumParts(); for (int partIndex = 0; partIndex < numParts; partIndex++) { - if (!fbxGeometry.meshes[i].blendshapes.empty()) { + if (!fbxGeometry.meshes[i].blendshapes.empty() && _blendedVertexBuffers.find(i) == _blendedVertexBuffers.end()) { initializeBlendshapes(fbxGeometry.meshes[i], i); } _modelMeshRenderItems << std::make_shared(shared_from_this(), i, partIndex, shapeID, transform, offset); @@ -1683,6 +1690,7 @@ void Model::createRenderItemSet() { shapeID++; } } + _blendedVertexBuffersInitialized = true; } bool Model::isRenderable() const { @@ -1775,35 +1783,38 @@ ModelBlender::~ModelBlender() { } void ModelBlender::noteRequiresBlend(ModelPointer model) { + Lock lock(_mutex); if (_pendingBlenders < QThread::idealThreadCount()) { if (model->maybeStartBlender()) { _pendingBlenders++; + return; } - return; } - { - Lock lock(_mutex); - _modelsRequiringBlends.insert(model); - } + _modelsRequiringBlends.insert(model); } -void ModelBlender::setBlendedVertices(ModelPointer model, int blendNumber, const Geometry::WeakPointer& geometry, - const QVector& vertices, const QVector& normalsAndTangents) { +void ModelBlender::setBlendedVertices(ModelPointer model, int blendNumber, QVector vertices, QVector normalsAndTangents) { if (model) { - model->setBlendedVertices(blendNumber, geometry, vertices, normalsAndTangents); + model->setBlendedVertices(blendNumber, vertices, normalsAndTangents); } - _pendingBlenders--; { Lock lock(_mutex); - for (auto i = _modelsRequiringBlends.begin(); i != _modelsRequiringBlends.end();) { + _pendingBlenders--; + _modelsRequiringBlends.erase(model); + std::set> modelsToErase; + for (auto i = _modelsRequiringBlends.begin(); i != _modelsRequiringBlends.end(); i++) { auto weakPtr = *i; - _modelsRequiringBlends.erase(i++); // remove front of the set ModelPointer nextModel = weakPtr.lock(); if (nextModel && nextModel->maybeStartBlender()) { _pendingBlenders++; - return; + break; + } else { + modelsToErase.insert(weakPtr); } } + for (auto& weakPtr : modelsToErase) { + _modelsRequiringBlends.erase(weakPtr); + } } } diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index e7534f5b89..447f75dd9d 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -144,8 +144,7 @@ public: bool maybeStartBlender(); /// Sets blended vertices computed in a separate thread. - void setBlendedVertices(int blendNumber, const Geometry::WeakPointer& geometry, - const QVector& vertices, const QVector& normalsAndTangents); + void setBlendedVertices(int blendNumber, const QVector& vertices, const QVector& normalsAndTangents); bool isLoaded() const { return (bool)_renderGeometry && _renderGeometry->isGeometryLoaded(); } bool isAddedToScene() const { return _addedToScene; } @@ -345,7 +344,7 @@ public: void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName); void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName); - bool areBlendedVertexBuffersInitialized(int index) { return _blendedVertexBuffersInitialized; } + std::unordered_map> _normalsAndTangents; public slots: void loadURLFinished(bool success); @@ -521,8 +520,7 @@ public: bool shouldComputeBlendshapes() { return _computeBlendshapes; } public slots: - void setBlendedVertices(ModelPointer model, int blendNumber, const Geometry::WeakPointer& geometry, - const QVector& vertices, const QVector& normalsAndTangents); + void setBlendedVertices(ModelPointer model, int blendNumber, QVector vertices, QVector normalsAndTangents); void setComputeBlendshapes(bool computeBlendshapes) { _computeBlendshapes = computeBlendshapes; } private: diff --git a/libraries/render-utils/src/Shadow.slh b/libraries/render-utils/src/Shadow.slh index 85a30d5889..5115a876fe 100644 --- a/libraries/render-utils/src/Shadow.slh +++ b/libraries/render-utils/src/Shadow.slh @@ -45,7 +45,7 @@ struct ShadowSampleOffsets { }; ShadowSampleOffsets evalShadowFilterOffsets(vec4 position) { - float shadowScale = getShadowScale(); + float shadowScale = getShadowScale(); ShadowSampleOffsets offsets; #if SHADOW_SCREEN_SPACE_DITHER @@ -67,10 +67,10 @@ ShadowSampleOffsets evalShadowFilterOffsets(vec4 position) { ivec2 offset = coords & ivec2(1,1); offset.y = (offset.x+offset.y) & 1; - offsets.points[0] = shadowScale * vec3(offset + PCFkernel[0], 0.0); - offsets.points[1] = shadowScale * vec3(offset + PCFkernel[1], 0.0); - offsets.points[2] = shadowScale * vec3(offset + PCFkernel[2], 0.0); - offsets.points[3] = shadowScale * vec3(offset + PCFkernel[3], 0.0); + offsets.points[0] = shadowScale * vec3(vec2(offset) + PCFkernel[0], 0.0); + offsets.points[1] = shadowScale * vec3(vec2(offset) + PCFkernel[1], 0.0); + offsets.points[2] = shadowScale * vec3(vec2(offset) + PCFkernel[2], 0.0); + offsets.points[3] = shadowScale * vec3(vec2(offset) + PCFkernel[3], 0.0); return offsets; } @@ -104,7 +104,7 @@ float evalShadowAttenuation(vec3 worldLightDir, vec4 worldPosition, float viewDe vec3 cascadeMix; bvec4 isPixelOnCascade; int cascadeIndex; - float oneMinusNdotL = 1.0 - clamp(dot(worldLightDir, worldNormal), 0, 1); + float oneMinusNdotL = 1.0 - clamp(dot(worldLightDir, worldNormal), 0.0, 1.0); for (cascadeIndex=0 ; cascadeIndex layout(std140, binding=RENDER_UTILS_BUFFER_SHADOW_PARAMS) uniform shadowTransformBuffer { - ShadowParameters shadow; + ShadowParameters shadow; }; int getShadowCascadeCount() { @@ -30,26 +30,26 @@ float evalShadowFalloff(float depth) { } mat4 getShadowReprojection(int cascadeIndex) { - return shadow.cascades[cascadeIndex].reprojection; + return shadow.cascades[cascadeIndex].reprojection; } float getShadowScale() { - return shadow.invMapSize; + return shadow.invMapSize; } float getShadowFixedBias(int cascadeIndex) { - return shadow.cascades[cascadeIndex].fixedBias; + return shadow.cascades[cascadeIndex].fixedBias; } float getShadowSlopeBias(int cascadeIndex) { - return shadow.cascades[cascadeIndex].slopeBias; + return shadow.cascades[cascadeIndex].slopeBias; } // Compute the texture coordinates from world coordinates vec4 evalShadowTexcoord(int cascadeIndex, vec4 position) { - vec4 shadowCoord = getShadowReprojection(cascadeIndex) * position; - return vec4(shadowCoord.xyz, 1.0); + vec4 shadowCoord = getShadowReprojection(cascadeIndex) * position; + return vec4(shadowCoord.xyz, 1.0); } bool isShadowCascadeProjectedOnPixel(vec4 cascadeTexCoords) { diff --git a/libraries/render-utils/src/Shadows_shared.slh b/libraries/render-utils/src/Shadows_shared.slh index bc8063e018..9cb46587a2 100644 --- a/libraries/render-utils/src/Shadows_shared.slh +++ b/libraries/render-utils/src/Shadows_shared.slh @@ -8,8 +8,8 @@ #define SHADOW_CASCADE_MAX_COUNT 4 struct ShadowTransform { - MAT4 reprojection; - float fixedBias; + MAT4 reprojection; + float fixedBias; float slopeBias; float _padding1; float _padding2; diff --git a/libraries/render-utils/src/TextRenderer3D.cpp b/libraries/render-utils/src/TextRenderer3D.cpp index dd9167d248..8ef0dc0d73 100644 --- a/libraries/render-utils/src/TextRenderer3D.cpp +++ b/libraries/render-utils/src/TextRenderer3D.cpp @@ -70,9 +70,8 @@ void TextRenderer3D::draw(gpu::Batch& batch, float x, float y, const QString& st const glm::vec2& bounds, bool layered) { // The font does all the OpenGL work if (_font) { - // Cache color so that the pointer stays valid. _color = color; - _font->drawString(batch, x, y, str, &_color, _effectType, bounds, layered); + _font->drawString(batch, _drawInfo, str, _color, _effectType, { x, y }, bounds, layered); } } diff --git a/libraries/render-utils/src/TextRenderer3D.h b/libraries/render-utils/src/TextRenderer3D.h index 175802ef6e..6c91411e1d 100644 --- a/libraries/render-utils/src/TextRenderer3D.h +++ b/libraries/render-utils/src/TextRenderer3D.h @@ -15,12 +15,14 @@ #include #include #include +#include namespace gpu { class Batch; } class Font; +#include "text/Font.h" #include "text/EffectType.h" #include "text/FontFamilies.h" @@ -51,7 +53,7 @@ private: // text color glm::vec4 _color; - + Font::DrawInfo _drawInfo; std::shared_ptr _font; }; diff --git a/libraries/render-utils/src/debug_deferred_buffer_shared.slh b/libraries/render-utils/src/debug_deferred_buffer_shared.slh index 2d11a66d61..f5d6797c50 100644 --- a/libraries/render-utils/src/debug_deferred_buffer_shared.slh +++ b/libraries/render-utils/src/debug_deferred_buffer_shared.slh @@ -7,7 +7,7 @@ struct DebugParameters { - INT32 _shadowCascadeIndex; + INT32 _shadowCascadeIndex; }; // <@if 1@> diff --git a/libraries/render-utils/src/deferred_light_limited.slv b/libraries/render-utils/src/deferred_light_limited.slv index fb59b8e78f..1c835aacf7 100644 --- a/libraries/render-utils/src/deferred_light_limited.slv +++ b/libraries/render-utils/src/deferred_light_limited.slv @@ -18,7 +18,8 @@ <$declareStandardTransform()$> -uniform vec4 sphereParam; +// FIXME make into a uniform buffer or push constant if this shader ever comes into use +vec4 sphereParam = vec4(0.0); layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01; @@ -41,7 +42,7 @@ void main(void) { } #endif #endif - _texCoord01.xy = vec4(projected.xy, 0.0, 1.0) * gl_Position.w; + _texCoord01 = vec4(projected.xy, 0.0, 1.0) * gl_Position.w; } else { const float depth = -1.0; //Draw at near plane const vec4 UNIT_QUAD[4] = vec4[4]( @@ -60,7 +61,7 @@ void main(void) { #endif #endif - _texCoord01.xy = vec4((pos.xy + 1.0) * 0.5, 0.0, 1.0); + _texCoord01 = vec4((pos.xy + 1.0) * 0.5, 0.0, 1.0); #ifdef GPU_TRANSFORM_IS_STEREO #ifdef GPU_TRANSFORM_STEREO_SPLIT_SCREEN diff --git a/libraries/render-utils/src/drawWorkloadProxy.slf b/libraries/render-utils/src/drawWorkloadProxy.slf index 32dceab00a..f0bd9d474c 100644 --- a/libraries/render-utils/src/drawWorkloadProxy.slf +++ b/libraries/render-utils/src/drawWorkloadProxy.slf @@ -13,9 +13,9 @@ <@include DeferredBufferWrite.slh@> <@include gpu/Paint.slh@> -in vec4 varColor; -in vec3 varTexcoord; -in vec3 varEyePos; +layout(location=0) in vec4 varColor; +layout(location=1) in vec3 varTexcoord; +layout(location=2) in vec3 varEyePos; void main(void) { if (varColor.w > 0.0) { diff --git a/libraries/render-utils/src/drawWorkloadProxy.slv b/libraries/render-utils/src/drawWorkloadProxy.slv index 28a62070f9..7a01702107 100644 --- a/libraries/render-utils/src/drawWorkloadProxy.slv +++ b/libraries/render-utils/src/drawWorkloadProxy.slv @@ -19,9 +19,9 @@ <$declareWorkloadProxies()$> -out vec4 varColor; -out vec3 varTexcoord; -out vec3 varEyePos; +layout(location=0) out vec4 varColor; +layout(location=1) out vec3 varTexcoord; +layout(location=2) out vec3 varEyePos; void main(void) { const vec4 UNIT_SPRITE[3] = vec4[3]( diff --git a/libraries/render-utils/src/drawWorkloadView.slf b/libraries/render-utils/src/drawWorkloadView.slf index c44dae4a24..b638824204 100644 --- a/libraries/render-utils/src/drawWorkloadView.slf +++ b/libraries/render-utils/src/drawWorkloadView.slf @@ -14,9 +14,9 @@ <@include DeferredBufferWrite.slh@> <@include gpu/Paint.slh@> -in vec4 varColor; -in vec3 varTexcoord; -in vec3 varEyePos; +layout(location=0) in vec4 varColor; +layout(location=1) in vec3 varTexcoord; +layout(location=2) in vec3 varEyePos; void main(void) { if (varColor.w > 0.0) { diff --git a/libraries/render-utils/src/drawWorkloadView.slv b/libraries/render-utils/src/drawWorkloadView.slv index 291a8c86cd..db4a33c450 100644 --- a/libraries/render-utils/src/drawWorkloadView.slv +++ b/libraries/render-utils/src/drawWorkloadView.slv @@ -18,9 +18,9 @@ <@include WorkloadResource.slh@> <$declareWorkloadViews()$> -out vec4 varColor; -out vec3 varTexcoord; -out vec3 varEyePos; +layout(location=0) out vec4 varColor; +layout(location=1) out vec3 varTexcoord; +layout(location=2) out vec3 varEyePos; const int NUM_VERTICES_PER_SEGMENT = 2; const int NUM_SEGMENT_PER_VIEW_REGION = 65; @@ -79,7 +79,7 @@ void main(void) { <$transformModelToEyeDir(cam, obj, originSpaceTan, tanEye)$> lateralDir = normalize(cross(vec3(0.0, 0.0, 1.0), normalize(tanEye))); - posEye.xyz += (0.005 * abs(posEye.z) * (regionID + 1)) * (-1.0 + 2.0 * float(segmentVertexID)) * lateralDir; + posEye.xyz += (0.005 * abs(posEye.z) * float(regionID + 1)) * (-1.0 + 2.0 * float(segmentVertexID)) * lateralDir; varEyePos = posEye.xyz; <$transformEyeToClipPos(cam, posEye, gl_Position)$> diff --git a/libraries/render-utils/src/fxaa.slf b/libraries/render-utils/src/fxaa.slf index 2dddcc795b..f1096a3054 100644 --- a/libraries/render-utils/src/fxaa.slf +++ b/libraries/render-utils/src/fxaa.slf @@ -24,7 +24,9 @@ precision mediump int; layout(binding=0) uniform sampler2D colorTexture; //uniform sampler2D historyTexture; -layout(location=0) uniform vec2 texcoordOffset; + +// FIXME make into a uniform buffer or push constant if this shader ever comes into use +vec2 texcoordOffset = vec2(0.0); layout(location=0) in vec2 varTexCoord0; layout(location=0) out vec4 outFragColor; @@ -66,7 +68,7 @@ void main() { outFragColor.w = 1.0; }*/ - if (gl_FragCoord.x > 800) { + if (gl_FragCoord.x > 800.0) { /* // filter width limit for dependent "two-tap" texture samples float FXAA_SPAN_MAX = 8.0; diff --git a/libraries/render-utils/src/fxaa_blend.slf b/libraries/render-utils/src/fxaa_blend.slf index aca050f047..c051801659 100644 --- a/libraries/render-utils/src/fxaa_blend.slf +++ b/libraries/render-utils/src/fxaa_blend.slf @@ -18,7 +18,14 @@ layout(location=0) in vec2 varTexCoord0; layout(location=0) out vec4 outFragColor; layout(binding=0) uniform sampler2D colorTexture; -layout(location=GPU_UNIFORM_EXTRA0) uniform float sharpenIntensity; + +struct FxaaBlendParams { + vec4 sharpenIntensity; +}; + +layout(binding=0) uniform fxaaBlendParamsBuffer { + FxaaBlendParams params; +}; void main(void) { vec4 pixels[9]; @@ -37,7 +44,7 @@ void main(void) { sharpenedPixel = pixels[4]*6.8 - (pixels[1]+pixels[3]+pixels[5]+pixels[7]) - (pixels[0]+pixels[2]+pixels[6]+pixels[8])*0.7; - vec4 minColor = max(vec4(0), pixels[4]-vec4(0.5)); - vec4 maxColor = pixels[4]+vec4(0.5); - outFragColor = clamp(pixels[4] + sharpenedPixel * sharpenIntensity, minColor, maxColor); + vec4 minColor = max(vec4(0), pixels[4]-vec4(0.5)); + vec4 maxColor = pixels[4]+vec4(0.5); + outFragColor = clamp(pixels[4] + sharpenedPixel * params.sharpenIntensity.x, minColor, maxColor); } diff --git a/libraries/render-utils/src/glowLine.slf b/libraries/render-utils/src/glowLine.slf index 4e80b3358a..c65d8d6488 100644 --- a/libraries/render-utils/src/glowLine.slf +++ b/libraries/render-utils/src/glowLine.slf @@ -22,9 +22,9 @@ void main(void) { float alpha = 1.0 - abs(distanceFromCenter); // Convert from a linear alpha curve to a sharp peaked one - alpha = _color.a * pow(alpha, 10.0); - - // Drop everything where the curve falls off to nearly nothing + alpha = _color.a * pow(alpha, 10.0); + + // Drop everything where the curve falls off to nearly nothing if (alpha <= 0.05) { discard; } diff --git a/libraries/render-utils/src/lightClusters_drawClusterContent.slf b/libraries/render-utils/src/lightClusters_drawClusterContent.slf index 65ae8f423e..27cb838566 100644 --- a/libraries/render-utils/src/lightClusters_drawClusterContent.slf +++ b/libraries/render-utils/src/lightClusters_drawClusterContent.slf @@ -42,7 +42,7 @@ void main(void) { ivec3 cluster = clusterGrid_getCluster(clusterIndex); int numLights = cluster.x + cluster.y; - float numLightsScale = clamp(numLights * 0.05, 0.01, 1.0); + float numLightsScale = clamp(float(numLights) * 0.05, 0.01, 1.0); int clusterOffset = cluster.z; @@ -90,6 +90,6 @@ void main(void) { numLightTouching++; } - _fragColor = vec4(colorRamp(1.0 - (numLightTouching / 12.0f)), (numLightTouching > 0 ? 0.5 + 0.5 * numLightsScale : 0.0)); + _fragColor = vec4(colorRamp(1.0 - (float(numLightTouching) / 12.0f)), (numLightTouching > 0 ? 0.5 + 0.5 * numLightsScale : 0.0)); } diff --git a/libraries/render-utils/src/lightClusters_drawClusterContent.slv b/libraries/render-utils/src/lightClusters_drawClusterContent.slv index d7e4a66a6a..1303cf3926 100644 --- a/libraries/render-utils/src/lightClusters_drawClusterContent.slv +++ b/libraries/render-utils/src/lightClusters_drawClusterContent.slv @@ -56,7 +56,7 @@ void main(void) { ivec3 cluster = clusterGrid_getCluster(gpu_InstanceID()); int numLights = cluster.x + cluster.y; - float numLightsScale = clamp(numLights * 0.1, 0.0, 1.0); + float numLightsScale = clamp(float(numLights) * 0.1, 0.0, 1.0); ivec3 clusterPos = frustumGrid_indexToCluster(gpu_InstanceID()); diff --git a/libraries/render-utils/src/lightClusters_drawClusterFromDepth.slf b/libraries/render-utils/src/lightClusters_drawClusterFromDepth.slf index 4efb60a259..abbd86dd70 100644 --- a/libraries/render-utils/src/lightClusters_drawClusterFromDepth.slf +++ b/libraries/render-utils/src/lightClusters_drawClusterFromDepth.slf @@ -39,7 +39,7 @@ void main(void) { ivec3 cluster = clusterGrid_getCluster(frustumGrid_clusterToIndex(clusterPos)); int numLights = cluster.x + cluster.y; - float numLightsScale = clamp(numLights * 0.05, 0.01, 1.0); + float numLightsScale = clamp(float(numLights) * 0.05, 0.01, 1.0); ivec3 dims = frustumGrid.dims.xyz; diff --git a/libraries/render-utils/src/model_translucent_normal_map.slf b/libraries/render-utils/src/model_translucent_normal_map.slf index 45eee9d160..7ac6982cfa 100644 --- a/libraries/render-utils/src/model_translucent_normal_map.slf +++ b/libraries/render-utils/src/model_translucent_normal_map.slf @@ -87,7 +87,7 @@ void main(void) { 1.0, occlusionTex, fragPositionES, - fragPositionWS, + fragPositionWS, albedo, fresnel, metallic, diff --git a/libraries/render-utils/src/model_translucent_normal_map_fade.slf b/libraries/render-utils/src/model_translucent_normal_map_fade.slf index 2ede2bfbaa..2c182aeb19 100644 --- a/libraries/render-utils/src/model_translucent_normal_map_fade.slf +++ b/libraries/render-utils/src/model_translucent_normal_map_fade.slf @@ -97,7 +97,7 @@ void main(void) { 1.0, occlusionTex, fragPositionES, - fragPositionWS, + fragPositionWS, albedo, fresnel, metallic, diff --git a/libraries/render-utils/src/parabola_translucent.slf b/libraries/render-utils/src/parabola_translucent.slf new file mode 100644 index 0000000000..62b5b15193 --- /dev/null +++ b/libraries/render-utils/src/parabola_translucent.slf @@ -0,0 +1,18 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// Created by Sam Gondelman on 9/10/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +<@include DeferredBufferWrite.slh@> + +layout(location=0) in vec4 _color; + +void main(void) { + packDeferredFragmentTranslucent(vec3(1.0, 0.0, 0.0), _color.a, _color.rgb, DEFAULT_FRESNEL, DEFAULT_ROUGHNESS); +} diff --git a/libraries/render-utils/src/render-utils/ShaderConstants.h b/libraries/render-utils/src/render-utils/ShaderConstants.h index 6a88a62287..ccf6314a39 100644 --- a/libraries/render-utils/src/render-utils/ShaderConstants.h +++ b/libraries/render-utils/src/render-utils/ShaderConstants.h @@ -43,9 +43,6 @@ #define RENDER_UTILS_BUFFER_AMBIENT_LIGHT 6 #define RENDER_UTILS_BUFFER_LIGHT_INDEX 7 -#define RENDER_UTILS_UNIFORM_LIGHT_RADIUS 0 -#define RENDER_UTILS_UNIFORM_LIGHT_TEXCOORD_TRANSFORM 1 - // Deferred lighting resolution #define RENDER_UTILS_TEXTURE_DEFERRRED_COLOR 0 #define RENDER_UTILS_TEXTURE_DEFERRRED_NORMAL 1 @@ -140,8 +137,6 @@ enum Uniform { TextOutline = RENDER_UTILS_UNIFORM_TEXT_OUTLINE, TaaSharpenIntensity = GPU_UNIFORM_EXTRA0, HighlightOutlineWidth = GPU_UNIFORM_EXTRA0, - LightRadius = RENDER_UTILS_UNIFORM_LIGHT_RADIUS, - TexcoordTransform = RENDER_UTILS_UNIFORM_LIGHT_TEXCOORD_TRANSFORM, }; } diff --git a/libraries/render-utils/src/render-utils/fxaa.slp b/libraries/render-utils/src/render-utils/fxaa.slp new file mode 100644 index 0000000000..c2c4bfbebd --- /dev/null +++ b/libraries/render-utils/src/render-utils/fxaa.slp @@ -0,0 +1 @@ +VERTEX gpu::vertex::DrawUnitQuadTexcoord diff --git a/libraries/render-utils/src/render-utils/parabola_translucent.slp b/libraries/render-utils/src/render-utils/parabola_translucent.slp new file mode 100644 index 0000000000..ab3f1d4126 --- /dev/null +++ b/libraries/render-utils/src/render-utils/parabola_translucent.slp @@ -0,0 +1 @@ +VERTEX parabola diff --git a/libraries/render-utils/src/sdf_text3D.slf b/libraries/render-utils/src/sdf_text3D.slf index 2fbaa03900..d35396e469 100644 --- a/libraries/render-utils/src/sdf_text3D.slf +++ b/libraries/render-utils/src/sdf_text3D.slf @@ -14,8 +14,15 @@ <@include render-utils/ShaderConstants.h@> layout(binding=0) uniform sampler2D Font; -layout(location=RENDER_UTILS_UNIFORM_TEXT_OUTLINE) uniform bool Outline; -layout(location=RENDER_UTILS_UNIFORM_TEXT_COLOR) uniform vec4 Color; + +struct TextParams { + vec4 color; + vec4 outline; +}; + +layout(binding=0) uniform textParamsBuffer { + TextParams params; +}; // the interpolated normal layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; @@ -32,7 +39,7 @@ const float taaBias = pow(2.0, TAA_TEXTURE_LOD_BIAS); float evalSDF(vec2 texCoord) { // retrieve signed distance float sdf = textureLod(Font, texCoord, TAA_TEXTURE_LOD_BIAS).g; - if (Outline) { + if (params.outline.x > 0.0) { if (sdf > interiorCutoff) { sdf = 1.0 - sdf; } else { @@ -47,12 +54,12 @@ void main() { vec2 dxTexCoord = dFdx(_texCoord0) * 0.5 * taaBias; vec2 dyTexCoord = dFdy(_texCoord0) * 0.5 * taaBias; - // Perform 4x supersampling for anisotropic filtering + // Perform 4x supersampling for anisotropic filtering float a; - a = evalSDF(_texCoord0); - a += evalSDF(_texCoord0+dxTexCoord); - a += evalSDF(_texCoord0+dyTexCoord); - a += evalSDF(_texCoord0+dxTexCoord+dyTexCoord); + a = evalSDF(_texCoord0); + a += evalSDF(_texCoord0+dxTexCoord); + a += evalSDF(_texCoord0+dyTexCoord); + a += evalSDF(_texCoord0+dxTexCoord+dyTexCoord); a *= 0.25; // discard if invisible @@ -62,8 +69,8 @@ void main() { packDeferredFragment( normalize(_normalWS), - a * Color.a, - Color.rgb, + a * params.color.a, + params.color.rgb, DEFAULT_ROUGHNESS, DEFAULT_METALLIC, DEFAULT_EMISSIVE, diff --git a/libraries/render-utils/src/sdf_text3D_transparent.slf b/libraries/render-utils/src/sdf_text3D_transparent.slf index 218236c26b..9dffca2038 100644 --- a/libraries/render-utils/src/sdf_text3D_transparent.slf +++ b/libraries/render-utils/src/sdf_text3D_transparent.slf @@ -14,8 +14,15 @@ <@include render-utils/ShaderConstants.h@> layout(binding=0) uniform sampler2D Font; -layout(location=RENDER_UTILS_UNIFORM_TEXT_OUTLINE) uniform bool Outline; -layout(location=RENDER_UTILS_UNIFORM_TEXT_COLOR) uniform vec4 Color; + +struct TextParams { + vec4 color; + vec4 outline; +}; + +layout(binding=0) uniform textParamsBuffer { + TextParams params; +}; // the interpolated normal layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; @@ -31,7 +38,7 @@ const float outlineExpansion = 0.2; void main() { // retrieve signed distance float sdf = texture(Font, _texCoord0).g; - if (Outline) { + if (params.outline.x > 0.0) { if (sdf > interiorCutoff) { sdf = 1.0 - sdf; } else { @@ -51,8 +58,8 @@ void main() { packDeferredFragmentTranslucent( normalize(_normalWS), - a * Color.a, - Color.rgb, + a * params.color.a, + params.color.rgb, DEFAULT_FRESNEL, DEFAULT_ROUGHNESS); } \ No newline at end of file diff --git a/libraries/render-utils/src/simple_fade.slf b/libraries/render-utils/src/simple_fade.slf index e9f94c29bc..97ed0c570c 100644 --- a/libraries/render-utils/src/simple_fade.slf +++ b/libraries/render-utils/src/simple_fade.slf @@ -91,7 +91,7 @@ void main(void) { normal, 1.0, diffuse+fadeEmissive, - max(0, 1.0 - shininess / 128.0), + max(0.0, 1.0 - shininess / 128.0), DEFAULT_METALLIC, specular, specular); @@ -100,7 +100,7 @@ void main(void) { normal, 1.0, diffuse, - max(0, 1.0 - shininess / 128.0), + max(0.0, 1.0 - shininess / 128.0), length(specular), DEFAULT_EMISSIVE+fadeEmissive, DEFAULT_OCCLUSION, diff --git a/libraries/render-utils/src/simple_transparent_textured.slf b/libraries/render-utils/src/simple_transparent_textured.slf index 0e6198de68..5573a7aa22 100644 --- a/libraries/render-utils/src/simple_transparent_textured.slf +++ b/libraries/render-utils/src/simple_transparent_textured.slf @@ -17,7 +17,7 @@ <@include render-utils/ShaderConstants.h@> // the albedo texture -layout(location=0) uniform sampler2D originalTexture; +layout(binding=0) uniform sampler2D originalTexture; // the interpolated normal layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; diff --git a/libraries/render-utils/src/ssao.slh b/libraries/render-utils/src/ssao.slh index 5341a1682a..b149d8f912 100644 --- a/libraries/render-utils/src/ssao.slh +++ b/libraries/render-utils/src/ssao.slh @@ -137,14 +137,14 @@ float getBlurCoef(int c) { float getAngleDitheringWorldPos(in vec3 pixelWorldPos) { vec3 worldPosFract = fract(pixelWorldPos * 1.0); - ivec3 pixelPos = ivec3(worldPosFract * 256); + ivec3 pixelPos = ivec3(worldPosFract * 256.0); - return isDitheringEnabled() * ((3 * pixelPos.x ^ pixelPos.y + pixelPos.x * pixelPos.y) + (3 * pixelPos.y ^ pixelPos.z + pixelPos.x * pixelPos.z)) * 10 + getFrameDithering(); + return isDitheringEnabled() * float(((3 * pixelPos.x ^ pixelPos.y + pixelPos.x * pixelPos.y) + (3 * pixelPos.y ^ pixelPos.z + pixelPos.x * pixelPos.z)) * 10) + getFrameDithering(); } float getAngleDithering(in ivec2 pixelPos) { // Hash function used in the AlchemyAO paper - return isDitheringEnabled() * (3 * pixelPos.x ^ pixelPos.y + pixelPos.x * pixelPos.y) * 10 + getFrameDithering(); + return isDitheringEnabled() * float((3 * pixelPos.x ^ pixelPos.y + pixelPos.x * pixelPos.y) * 10) + getFrameDithering(); } float evalDiskRadius(float Zeye, vec2 imageSize) { @@ -162,7 +162,7 @@ const float TWO_PI = 6.28; vec3 getUnitTapLocation(int sampleNumber, float spinAngle){ // Radius relative to ssR - float alpha = float(sampleNumber + 0.5) * getInvNumSamples(); + float alpha = (float(sampleNumber) + 0.5) * getInvNumSamples(); float angle = alpha * (getNumSpiralTurns() * TWO_PI) + spinAngle; return vec3(cos(angle), sin(angle), alpha); } @@ -219,7 +219,7 @@ vec3 getTapLocationClamped(int sampleNumber, float spinAngle, float outerRadius, if (redoTap) { tap.xy = tapPos - pixelPos; tap.z = length(tap.xy); - tap.z = 0; + tap.z = 0.0; } return tap; @@ -253,7 +253,7 @@ vec3 fetchTapUnfiltered(ivec4 side, ivec2 ssC, vec3 tap, vec2 imageSize) { vec2 tapUV = (vec2(ssP) + vec2(0.5)) / imageSize; - vec2 fetchUV = vec2(tapUV.x + side.w * 0.5 * (side.x - tapUV.x), tapUV.y); + vec2 fetchUV = vec2(tapUV.x + float(side.w) * 0.5 * (float(side.x) - tapUV.x), tapUV.y); vec3 P; P.xy = tapUV; @@ -263,7 +263,7 @@ vec3 fetchTapUnfiltered(ivec4 side, ivec2 ssC, vec3 tap, vec2 imageSize) { } vec3 fetchTap(ivec4 side, ivec2 ssC, vec3 tap, vec2 imageSize) { - int mipLevel = evalMipFromRadius(tap.z * doFetchMips()); + int mipLevel = evalMipFromRadius(tap.z * float(doFetchMips())); ivec2 ssP = ivec2(tap.xy) + ssC; ivec2 ssPFull = ivec2(ssP.x + side.y, ssP.y); @@ -276,7 +276,7 @@ vec3 fetchTap(ivec4 side, ivec2 ssC, vec3 tap, vec2 imageSize) { ivec2 mipP = clamp(ssPFull >> mipLevel, ivec2(0), mipSize - ivec2(1)); vec2 tapUV = (vec2(ssP) + vec2(0.5)) / imageSize; - vec2 fetchUV = vec2(tapUV.x + side.w * 0.5 * (side.x - tapUV.x), tapUV.y); + vec2 fetchUV = vec2(tapUV.x + float(side.w) * 0.5 * (float(side.x) - tapUV.x), tapUV.y); // vec2 tapUV = (vec2(mipP) + vec2(0.5)) / vec2(mipSize); vec3 P; diff --git a/libraries/render-utils/src/ssao_debugOcclusion.slf b/libraries/render-utils/src/ssao_debugOcclusion.slf index 007fd0cd7b..ab7989e35e 100644 --- a/libraries/render-utils/src/ssao_debugOcclusion.slf +++ b/libraries/render-utils/src/ssao_debugOcclusion.slf @@ -84,15 +84,15 @@ void main(void) { float keepTapRadius = 1.0; int keepedMip = -1; bool keep = false; - - for (int i = 0; i < getNumSamples(); ++i) { + int sampleCount = int(getNumSamples()); + for (int i = 0; i < sampleCount; ++i) { vec3 tap = getTapLocationClamped(i, randomPatternRotationAngle, ssDiskRadius, cursorPixelPos, imageSize); // The occluding point in camera space vec2 fragToTap = vec2(ssC) + tap.xy - fragCoord.xy; if (dot(fragToTap,fragToTap) < keepTapRadius) { keep = true; - keepedMip = evalMipFromRadius(tap.z * doFetchMips()); + keepedMip = evalMipFromRadius(tap.z * float(doFetchMips())); } vec3 tapUVZ = fetchTap(side, ssC, tap, imageSize); diff --git a/libraries/render-utils/src/ssao_makeOcclusion.slf b/libraries/render-utils/src/ssao_makeOcclusion.slf index 1b638d4270..3934b9eddc 100644 --- a/libraries/render-utils/src/ssao_makeOcclusion.slf +++ b/libraries/render-utils/src/ssao_makeOcclusion.slf @@ -52,8 +52,9 @@ void main(void) { // Accumulate the Obscurance for each samples float sum = 0.0; - for (int i = 0; i < getNumSamples(); ++i) { - vec3 tap = getTapLocationClamped(i, randomPatternRotationAngle, ssDiskRadius, ssC, imageSize); + int sampleCount = int(getNumSamples()); + for (int i = 0; i < sampleCount; ++i) { + vec3 tap = getTapLocationClamped(i, randomPatternRotationAngle, ssDiskRadius, vec2(ssC), imageSize); vec3 tapUVZ = fetchTap(side, ssC, tap, imageSize); @@ -68,10 +69,10 @@ void main(void) { // Bilateral box-filter over a quad for free, respecting depth edges // (the difference that this makes is subtle) if (abs(dFdx(Cp.z)) < 0.02) { - A -= dFdx(A) * ((ssC.x & 1) - 0.5); + A -= dFdx(A) * (float(ssC.x & 1) - 0.5); } if (abs(dFdy(Cp.z)) < 0.02) { - A -= dFdy(A) * ((ssC.y & 1) - 0.5); + A -= dFdy(A) * (float(ssC.y & 1) - 0.5); } diff --git a/libraries/render-utils/src/subsurfaceScattering_drawScattering.slf b/libraries/render-utils/src/subsurfaceScattering_drawScattering.slf index 00b60d6fd4..8664fa16fd 100644 --- a/libraries/render-utils/src/subsurfaceScattering_drawScattering.slf +++ b/libraries/render-utils/src/subsurfaceScattering_drawScattering.slf @@ -26,7 +26,8 @@ layout(location=0) in vec2 varTexCoord0; layout(location=0) out vec4 _fragColor; -layout(location=GPU_UNIFORM_EXTRA0) uniform vec2 uniformCursorTexcoord = vec2(0.5); +// FIXME make into a uniform buffer or push constant if this shader ever comes into use +vec2 uniformCursorTexcoord = vec2(0.5); //uniform vec3 uniformLightVector = vec3(1.0); @@ -79,7 +80,7 @@ vec3 drawScatteringTableUV(vec2 cursor, vec2 texcoord) { vec3 distance = vec3(0.0); for (int c = 0; c < 3; c++) { - vec2 BRDFuv = vec2(clamp(bentNdotL[c] * 0.5 + 0.5, 0.0, 1.0), clamp(2 * curvature, 0.0, 1.0)); + vec2 BRDFuv = vec2(clamp(bentNdotL[c] * 0.5 + 0.5, 0.0, 1.0), clamp(2.0 * curvature, 0.0, 1.0)); vec2 delta = BRDFuv - texcoord; distance[c] = 1.0 - dot(delta, delta); } diff --git a/libraries/render-utils/src/surfaceGeometry_copyDepth.slf b/libraries/render-utils/src/surfaceGeometry_copyDepth.slf index 7eb097224e..f018ee1105 100644 --- a/libraries/render-utils/src/surfaceGeometry_copyDepth.slf +++ b/libraries/render-utils/src/surfaceGeometry_copyDepth.slf @@ -16,7 +16,7 @@ layout(binding=0) uniform sampler2D depthMap; layout(location=0) out vec4 outFragColor; void main(void) { - float Zdb = texelFetch(depthMap, ivec2(gl_FragCoord.xy), 0).x; - outFragColor = vec4(Zdb, 0.0, 0.0, 1.0); + float Zdb = texelFetch(depthMap, ivec2(gl_FragCoord.xy), 0).x; + outFragColor = vec4(Zdb, 0.0, 0.0, 1.0); } diff --git a/libraries/render-utils/src/taa.slh b/libraries/render-utils/src/taa.slh index 26ffe55263..2161ad9524 100644 --- a/libraries/render-utils/src/taa.slh +++ b/libraries/render-utils/src/taa.slh @@ -24,10 +24,10 @@ layout(binding=RENDER_UTILS_TEXTURE_TAA_NEXT) uniform sampler2D nextMap; struct TAAParams { - float none; - float blend; - float covarianceGamma; - float debugShowVelocityThreshold; + float none; + float blend; + float covarianceGamma; + float debugShowVelocityThreshold; ivec4 flags; vec4 pixelInfo_orbZoom; vec4 regionInfo; @@ -77,47 +77,47 @@ vec2 taa_getRegionFXAA() { #define USE_YCOCG 1 vec4 taa_fetchColor(sampler2D map, vec2 uv) { - vec4 c = texture(map, uv); - // Apply rapid pseudo tonemapping as TAA is applied to a tonemapped image, using luminance as weight, as proposed in - // https://de45xmedrsdbp.cloudfront.net/Resources/files/TemporalAA_small-59732822.pdf - float lum = dot(vec3(0.3,0.5,0.2),c.rgb); - c.rgb = c.rgb / (1.0+lum); + vec4 c = texture(map, uv); + // Apply rapid pseudo tonemapping as TAA is applied to a tonemapped image, using luminance as weight, as proposed in + // https://de45xmedrsdbp.cloudfront.net/Resources/files/TemporalAA_small-59732822.pdf + float lum = dot(vec3(0.3,0.5,0.2),c.rgb); + c.rgb = c.rgb / (1.0+lum); #if USE_YCOCG - return vec4(color_LinearToYCoCg(c.rgb), c.a); + return vec4(color_LinearToYCoCg(c.rgb), c.a); #else - return c; + return c; #endif } vec3 taa_resolveColor(vec3 color) { #if USE_YCOCG - color = max(vec3(0), color_YCoCgToUnclampedLinear(color)); + color = max(vec3(0), color_YCoCgToUnclampedLinear(color)); #endif - // Apply rapid inverse tonemapping, using luminance as weight, as proposed in - // https://de45xmedrsdbp.cloudfront.net/Resources/files/TemporalAA_small-59732822.pdf - float lum = dot(vec3(0.3,0.5,0.2),color.rgb); - color = color / (1.0-lum); - return color; + // Apply rapid inverse tonemapping, using luminance as weight, as proposed in + // https://de45xmedrsdbp.cloudfront.net/Resources/files/TemporalAA_small-59732822.pdf + float lum = dot(vec3(0.3,0.5,0.2),color.rgb); + color = color / (1.0-lum); + return color; } vec4 taa_fetchSourceMap(vec2 uv) { - return taa_fetchColor(sourceMap, uv); + return taa_fetchColor(sourceMap, uv); } vec4 taa_fetchHistoryMap(vec2 uv) { - return taa_fetchColor(historyMap, uv); + return taa_fetchColor(historyMap, uv); } vec4 taa_fetchNextMap(vec2 uv) { - return taa_fetchColor(nextMap, uv); + return taa_fetchColor(nextMap, uv); } vec2 taa_fetchVelocityMap(vec2 uv) { - return texture(velocityMap, uv).xy; + return texture(velocityMap, uv).xy; } float taa_fetchDepth(vec2 uv) { - return -texture(depthMap, vec2(uv), 0).x; + return -texture(depthMap, vec2(uv), 0.0).x; } @@ -141,35 +141,35 @@ vec2 taa_getTexelSize() { vec3 taa_findClosestFragment3x3(vec2 uv) { - vec2 dd = abs(taa_getTexelSize()); - vec2 du = vec2(dd.x, 0.0); - vec2 dv = vec2(0.0, dd.y); + vec2 dd = abs(taa_getTexelSize()); + vec2 du = vec2(dd.x, 0.0); + vec2 dv = vec2(0.0, dd.y); - vec3 dtl = vec3(-1, -1, taa_fetchDepth(uv - dv - du)); - vec3 dtc = vec3( 0, -1, taa_fetchDepth(uv - dv)); - vec3 dtr = vec3( 1, -1, taa_fetchDepth(uv - dv + du)); + vec3 dtl = vec3(-1, -1, taa_fetchDepth(uv - dv - du)); + vec3 dtc = vec3( 0, -1, taa_fetchDepth(uv - dv)); + vec3 dtr = vec3( 1, -1, taa_fetchDepth(uv - dv + du)); - vec3 dml = vec3(-1, 0, taa_fetchDepth(uv - du)); - vec3 dmc = vec3( 0, 0, taa_fetchDepth(uv)); - vec3 dmr = vec3( 1, 0, taa_fetchDepth(uv + du)); + vec3 dml = vec3(-1, 0, taa_fetchDepth(uv - du)); + vec3 dmc = vec3( 0, 0, taa_fetchDepth(uv)); + vec3 dmr = vec3( 1, 0, taa_fetchDepth(uv + du)); - vec3 dbl = vec3(-1, 1, taa_fetchDepth(uv + dv - du)); - vec3 dbc = vec3( 0, 1, taa_fetchDepth(uv + dv)); - vec3 dbr = vec3( 1, 1, taa_fetchDepth(uv + dv + du)); + vec3 dbl = vec3(-1, 1, taa_fetchDepth(uv + dv - du)); + vec3 dbc = vec3( 0, 1, taa_fetchDepth(uv + dv)); + vec3 dbr = vec3( 1, 1, taa_fetchDepth(uv + dv + du)); - vec3 dmin = dtl; - if (ZCMP_GT(dmin.z, dtc.z)) dmin = dtc; - if (ZCMP_GT(dmin.z, dtr.z)) dmin = dtr; + vec3 dmin = dtl; + if (ZCMP_GT(dmin.z, dtc.z)) dmin = dtc; + if (ZCMP_GT(dmin.z, dtr.z)) dmin = dtr; - if (ZCMP_GT(dmin.z, dml.z)) dmin = dml; - if (ZCMP_GT(dmin.z, dmc.z)) dmin = dmc; - if (ZCMP_GT(dmin.z, dmr.z)) dmin = dmr; + if (ZCMP_GT(dmin.z, dml.z)) dmin = dml; + if (ZCMP_GT(dmin.z, dmc.z)) dmin = dmc; + if (ZCMP_GT(dmin.z, dmr.z)) dmin = dmr; - if (ZCMP_GT(dmin.z, dbl.z)) dmin = dbl; - if (ZCMP_GT(dmin.z, dbc.z)) dmin = dbc; - if (ZCMP_GT(dmin.z, dbr.z)) dmin = dbr; + if (ZCMP_GT(dmin.z, dbl.z)) dmin = dbl; + if (ZCMP_GT(dmin.z, dbc.z)) dmin = dbc; + if (ZCMP_GT(dmin.z, dbr.z)) dmin = dbr; - return vec3(uv + dd.xy * dmin.xy, dmin.z); + return vec3(uv + dd.xy * dmin.xy, dmin.z); } vec2 taa_fetchVelocityMapBest(vec2 uv) { @@ -230,7 +230,7 @@ vec2 taa_fromEyeUVToFragUV(vec2 eyeUV, int stereoSide) { vec2 fragUV = eyeUV; if (isStereo()) { fragUV.x *= 0.5; - fragUV.x += stereoSide*0.5; + fragUV.x += float(stereoSide)*0.5; } return fragUV; } @@ -264,8 +264,8 @@ mat3 taa_evalNeighbourColorVariance(vec3 sourceColor, vec2 fragUV, vec2 fragVelo vec2 texelSize = taa_getTexelSize(); - vec2 du = vec2(texelSize.x, 0.0); - vec2 dv = vec2(0.0, texelSize.y); + vec2 du = vec2(texelSize.x, 0.0); + vec2 dv = vec2(0.0, texelSize.y); vec3 sampleColor = taa_fetchSourceMap(fragUV - dv - du).rgb; vec3 sumSamples = sampleColor; @@ -320,72 +320,72 @@ mat3 taa_evalNeighbourColorRegion(vec3 sourceColor, vec2 fragUV, vec2 fragVeloci vec3 cmin, cmax, cavg; #if MINMAX_3X3_ROUNDED - vec2 du = vec2(texelSize.x, 0.0); - vec2 dv = vec2(0.0, texelSize.y); + vec2 du = vec2(texelSize.x, 0.0); + vec2 dv = vec2(0.0, texelSize.y); - vec3 ctl = taa_fetchSourceMap(fragUV - dv - du).rgb; - vec3 ctc = taa_fetchSourceMap(fragUV - dv).rgb; - vec3 ctr = taa_fetchSourceMap(fragUV - dv + du).rgb; - vec3 cml = taa_fetchSourceMap(fragUV - du).rgb; - vec3 cmc = sourceColor; //taa_fetchSourceMap(fragUV).rgb; // could resuse the same osurce sample isn't it ? - vec3 cmr = taa_fetchSourceMap(fragUV + du).rgb; - vec3 cbl = taa_fetchSourceMap(fragUV + dv - du).rgb; - vec3 cbc = taa_fetchSourceMap(fragUV + dv).rgb; - vec3 cbr = taa_fetchSourceMap(fragUV + dv + du).rgb; + vec3 ctl = taa_fetchSourceMap(fragUV - dv - du).rgb; + vec3 ctc = taa_fetchSourceMap(fragUV - dv).rgb; + vec3 ctr = taa_fetchSourceMap(fragUV - dv + du).rgb; + vec3 cml = taa_fetchSourceMap(fragUV - du).rgb; + vec3 cmc = sourceColor; //taa_fetchSourceMap(fragUV).rgb; // could resuse the same osurce sample isn't it ? + vec3 cmr = taa_fetchSourceMap(fragUV + du).rgb; + vec3 cbl = taa_fetchSourceMap(fragUV + dv - du).rgb; + vec3 cbc = taa_fetchSourceMap(fragUV + dv).rgb; + vec3 cbr = taa_fetchSourceMap(fragUV + dv + du).rgb; - cmin = min(ctl, min(ctc, min(ctr, min(cml, min(cmc, min(cmr, min(cbl, min(cbc, cbr)))))))); - cmax = max(ctl, max(ctc, max(ctr, max(cml, max(cmc, max(cmr, max(cbl, max(cbc, cbr)))))))); + cmin = min(ctl, min(ctc, min(ctr, min(cml, min(cmc, min(cmr, min(cbl, min(cbc, cbr)))))))); + cmax = max(ctl, max(ctc, max(ctr, max(cml, max(cmc, max(cmr, max(cbl, max(cbc, cbr)))))))); - #if MINMAX_3X3_ROUNDED || USE_YCOCG || USE_CLIPPING - cavg = (ctl + ctc + ctr + cml + cmc + cmr + cbl + cbc + cbr) / 9.0; + #if MINMAX_3X3_ROUNDED || USE_YCOCG || USE_CLIPPING + cavg = (ctl + ctc + ctr + cml + cmc + cmr + cbl + cbc + cbr) / 9.0; #elif cavg = (cmin + cmax ) * 0.5; - #endif + #endif - #if MINMAX_3X3_ROUNDED - vec3 cmin5 = min(ctc, min(cml, min(cmc, min(cmr, cbc)))); - vec3 cmax5 = max(ctc, max(cml, max(cmc, max(cmr, cbc)))); - vec3 cavg5 = (ctc + cml + cmc + cmr + cbc) / 5.0; - cmin = 0.5 * (cmin + cmin5); - cmax = 0.5 * (cmax + cmax5); - cavg = 0.5 * (cavg + cavg5); - #endif + #if MINMAX_3X3_ROUNDED + vec3 cmin5 = min(ctc, min(cml, min(cmc, min(cmr, cbc)))); + vec3 cmax5 = max(ctc, max(cml, max(cmc, max(cmr, cbc)))); + vec3 cavg5 = (ctc + cml + cmc + cmr + cbc) / 5.0; + cmin = 0.5 * (cmin + cmin5); + cmax = 0.5 * (cmax + cmax5); + cavg = 0.5 * (cavg + cavg5); + #endif #else - const float _SubpixelThreshold = 0.5; - const float _GatherBase = 0.5; - const float _GatherSubpixelMotion = 0.1666; + const float _SubpixelThreshold = 0.5; + const float _GatherBase = 0.5; + const float _GatherSubpixelMotion = 0.1666; - vec2 texel_vel = fragVelocity * imageSize; - float texel_vel_mag = length(texel_vel) * -fragZe; - float k_subpixel_motion = clamp(_SubpixelThreshold / (0.0001 + texel_vel_mag), 0.0, 1.0); - float k_min_max_support = _GatherBase + _GatherSubpixelMotion * k_subpixel_motion; + vec2 texel_vel = fragVelocity * imageSize; + float texel_vel_mag = length(texel_vel) * -fragZe; + float k_subpixel_motion = clamp(_SubpixelThreshold / (0.0001 + texel_vel_mag), 0.0, 1.0); + float k_min_max_support = _GatherBase + _GatherSubpixelMotion * k_subpixel_motion; - vec2 ss_offset01 = k_min_max_support * vec2(-texelSize.x, texelSize.y); - vec2 ss_offset11 = k_min_max_support * vec2(texelSize.x, texelSize.y); - vec3 c00 = taa_fetchSourceMap(fragUV - ss_offset11).rgb; - vec3 c10 = taa_fetchSourceMap(fragUV - ss_offset01).rgb; - vec3 c01 = taa_fetchSourceMap(fragUV + ss_offset01).rgb; - vec3 c11 = taa_fetchSourceMap(fragUV + ss_offset11).rgb; + vec2 ss_offset01 = k_min_max_support * vec2(-texelSize.x, texelSize.y); + vec2 ss_offset11 = k_min_max_support * vec2(texelSize.x, texelSize.y); + vec3 c00 = taa_fetchSourceMap(fragUV - ss_offset11).rgb; + vec3 c10 = taa_fetchSourceMap(fragUV - ss_offset01).rgb; + vec3 c01 = taa_fetchSourceMap(fragUV + ss_offset01).rgb; + vec3 c11 = taa_fetchSourceMap(fragUV + ss_offset11).rgb; - cmin = min(c00, min(c10, min(c01, c11))); - cmax = max(c00, max(c10, max(c01, c11))); + cmin = min(c00, min(c10, min(c01, c11))); + cmax = max(c00, max(c10, max(c01, c11))); cavg = (cmin + cmax ) * 0.5; - #if USE_YCOCG || USE_CLIPPING - cavg = (c00 + c10 + c01 + c11) / 4.0; + #if USE_YCOCG || USE_CLIPPING + cavg = (c00 + c10 + c01 + c11) / 4.0; #elif cavg = (cmin + cmax ) * 0.5; - #endif + #endif #endif - // shrink chroma min-max - #if USE_YCOCG - vec2 chroma_extent = vec2(0.25 * 0.5 * (cmax.r - cmin.r)); - vec2 chroma_center = sourceColor.gb; - cmin.yz = chroma_center - chroma_extent; - cmax.yz = chroma_center + chroma_extent; - cavg.yz = chroma_center; - #endif + // shrink chroma min-max + #if USE_YCOCG + vec2 chroma_extent = vec2(0.25 * 0.5 * (cmax.r - cmin.r)); + vec2 chroma_center = sourceColor.gb; + cmin.yz = chroma_center - chroma_extent; + cmax.yz = chroma_center + chroma_extent; + cavg.yz = chroma_center; + #endif return mat3(cmin, cmax, cavg); } @@ -393,22 +393,22 @@ mat3 taa_evalNeighbourColorRegion(vec3 sourceColor, vec2 fragUV, vec2 fragVeloci //#define USE_OPTIMIZATIONS 0 vec3 taa_clampColor(vec3 colorMin, vec3 colorMax, vec3 colorSource, vec3 color) { - const float eps = 0.00001; + const float eps = 0.00001; vec3 p = colorSource; vec3 q = color; - // note: only clips towards aabb center (but fast!) - vec3 p_clip = 0.5 * (colorMax + colorMin); - vec3 e_clip = 0.5 * (colorMax - colorMin) + vec3(eps); + // note: only clips towards aabb center (but fast!) + vec3 p_clip = 0.5 * (colorMax + colorMin); + vec3 e_clip = 0.5 * (colorMax - colorMin) + vec3(eps); - vec3 v_clip = q - p_clip; - vec3 v_unit = v_clip.xyz / e_clip; - vec3 a_unit = abs(v_unit); - float ma_unit = max(a_unit.x, max(a_unit.y, a_unit.z)); + vec3 v_clip = q - p_clip; + vec3 v_unit = v_clip.xyz / e_clip; + vec3 a_unit = abs(v_unit); + float ma_unit = max(a_unit.x, max(a_unit.y, a_unit.z)); - if (ma_unit > 1.0) - return p_clip + v_clip / ma_unit; - else - return q;// point inside aabb + if (ma_unit > 1.0) + return p_clip + v_clip / ma_unit; + else + return q;// point inside aabb } vec3 taa_evalConstrainColor(vec3 sourceColor, vec2 sourceUV, vec2 sourceVel, vec3 candidateColor) { @@ -416,25 +416,25 @@ vec3 taa_evalConstrainColor(vec3 sourceColor, vec2 sourceUV, vec2 sourceVel, vec colorMinMaxAvg = taa_evalNeighbourColorVariance(sourceColor, sourceUV, sourceVel); - // clamp history to neighbourhood of current sample + // clamp history to neighbourhood of current sample return taa_clampColor(colorMinMaxAvg[0], colorMinMaxAvg[1], sourceColor, candidateColor); } vec3 taa_evalFeedbackColor(vec3 sourceColor, vec3 historyColor, float blendFactor) { const float _FeedbackMin = 0.1; const float _FeedbackMax = 0.9; - // feedback weight from unbiased luminance diff (t.lottes) - #if USE_YCOCG - float lum0 = sourceColor.r; - float lum1 = historyColor.r; - #else - float lum0 = Luminance(sourceColor.rgb); - float lum1 = Luminance(historyColor.rgb); - #endif - float unbiased_diff = abs(lum0 - lum1) / max(lum0, max(lum1, 0.2)); - float unbiased_weight = 1.0 - unbiased_diff; - float unbiased_weight_sqr = unbiased_weight * unbiased_weight; - float k_feedback = mix(_FeedbackMin, _FeedbackMax, unbiased_weight_sqr); + // feedback weight from unbiased luminance diff (t.lottes) + #if USE_YCOCG + float lum0 = sourceColor.r; + float lum1 = historyColor.r; + #else + float lum0 = Luminance(sourceColor.rgb); + float lum1 = Luminance(historyColor.rgb); + #endif + float unbiased_diff = abs(lum0 - lum1) / max(lum0, max(lum1, 0.2)); + float unbiased_weight = 1.0 - unbiased_diff; + float unbiased_weight_sqr = unbiased_weight * unbiased_weight; + float k_feedback = mix(_FeedbackMin, _FeedbackMax, unbiased_weight_sqr); vec3 nextColor = mix(historyColor, sourceColor, k_feedback * blendFactor).xyz; @@ -478,7 +478,7 @@ vec3 taa_evalFXAA(vec2 fragUV) { vec3 rgbSW = texture(sourceMap, fragUV + (vec2(-1.0, +1.0) * texelSize)).xyz; vec3 rgbSE = texture(sourceMap, fragUV + (vec2(+1.0, +1.0) * texelSize)).xyz; vec3 rgbM = texture(sourceMap, fragUV).xyz; - + // convert RGB values to luminance vec3 luma = vec3(0.299, 0.587, 0.114); float lumaNW = dot(rgbNW, luma); @@ -486,11 +486,11 @@ vec3 taa_evalFXAA(vec2 fragUV) { float lumaSW = dot(rgbSW, luma); float lumaSE = dot(rgbSE, luma); float lumaM = dot( rgbM, luma); - + // luma range of local neighborhood float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE))); float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE))); - + // direction perpendicular to local luma gradient vec2 dir; dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE)); @@ -502,7 +502,7 @@ vec3 taa_evalFXAA(vec2 fragUV) { float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce); dir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX), max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX), dir * rcpDirMin)) * texelSize; - + // perform additional texture sampling perpendicular to gradient vec3 rgbA = (1.0 / 2.0) * ( texture(sourceMap, fragUV + dir * (1.0 / 3.0 - 0.5)).xyz + diff --git a/libraries/render-utils/src/taa_blend.slf b/libraries/render-utils/src/taa_blend.slf index d2e23b590f..50575a6a07 100644 --- a/libraries/render-utils/src/taa_blend.slf +++ b/libraries/render-utils/src/taa_blend.slf @@ -23,7 +23,7 @@ void main(void) { // Pixel being shaded - vec3 sourceColor = texture(sourceMap, varTexCoord0).xyz; + vec3 sourceColor = texture(sourceMap, varTexCoord0).xyz; vec2 imageSize = getWidthHeight(0); vec2 texelSize = getInvWidthHeight(); @@ -57,7 +57,7 @@ void main(void) { vec2 cursorVelocityDir = cursorVelocity / cursorVelocityLength; vec2 cursorVelocityNor = vec2(cursorVelocityDir.y, -cursorVelocityDir.x); - if ((dot(cursorVelocityDir, cursorToFragVec) < 0) && abs(dot(cursorVelocityNor, cursorToFragVec)) < 1.0) { + if ((dot(cursorVelocityDir, cursorToFragVec) < 0.0) && abs(dot(cursorVelocityNor, cursorToFragVec)) < 1.0) { vec3 speedColor = taa_getVelocityColorRelative(cursorToFragLength); @@ -69,7 +69,7 @@ void main(void) { float tenPercentHeight = 0.1 * imageSize.y; float centerWidth = imageSize.x * 0.5; - //vec2 nextOrbPos = vec2(centerWidth, imageSize.y - 3 * tenPercentHeight); + //vec2 nextOrbPos = vec2(centerWidth, imageSize.y - 3.0 * tenPercentHeight); vec2 nextOrbPos = cursorPos; vec2 nextOrbPosToPix = pixPos - nextOrbPos; float nextOrbPosToPixLength = length(nextOrbPosToPix); @@ -124,9 +124,9 @@ void main(void) { // draw region splitter if ((abs(distToRegionDebug) < getInvWidthHeight().x) || (abs(distToRegionFXAA) < getInvWidthHeight().x)) { - outFragColor.rgb = vec3(1.0, 1.0, 0.0); - return; - } + outFragColor.rgb = vec3(1.0, 1.0, 0.0); + return; + } if (distToRegionFXAA > 0.0) { return; @@ -138,7 +138,7 @@ void main(void) { return; } - outFragColor = vec4(nextColor, 1.0); + outFragColor = vec4(nextColor, 1.0); vec3 prevColor = nextColor; @@ -146,7 +146,7 @@ void main(void) { prevColor = texture(historyMap, prevTexCoord).xyz; } - outFragColor.xyz = mix(prevColor, vec3(1,0,1), clamp(distance(prevColor, nextColor) - 0.01, 0, 1)); + outFragColor.xyz = mix(prevColor, vec3(1,0,1), clamp(distance(prevColor, nextColor) - 0.01, 0.0, 1.0)); if (pixVelocityLength > params.debugShowVelocityThreshold) { vec3 speedColor = taa_getVelocityColorAboveThreshold(pixVelocityLength); diff --git a/libraries/render-utils/src/text/Font.cpp b/libraries/render-utils/src/text/Font.cpp index aca6ddb79f..68a1e59dc3 100644 --- a/libraries/render-utils/src/text/Font.cpp +++ b/libraries/render-utils/src/text/Font.cpp @@ -256,29 +256,30 @@ void Font::setupGPU() { } } -void Font::rebuildVertices(float x, float y, const QString& str, const glm::vec2& bounds) { - _verticesBuffer = std::make_shared(); - _numVertices = 0; - _indicesBuffer = std::make_shared(); - _numIndices = 0; +void Font::buildVertices(Font::DrawInfo& drawInfo, const QString& str, const glm::vec2& origin, const glm::vec2& bounds) { + drawInfo.verticesBuffer = std::make_shared(); + drawInfo.indicesBuffer = std::make_shared(); + drawInfo.indexCount = 0; + int numVertices = 0; - _lastStringRendered = str; - _lastBounds = bounds; + drawInfo.string = str; + drawInfo.bounds = bounds; + drawInfo.origin = origin; // Top left of text - glm::vec2 advance = glm::vec2(x, y); + glm::vec2 advance = origin; foreach(const QString& token, tokenizeForWrapping(str)) { bool isNewLine = (token == QString('\n')); bool forceNewLine = false; // Handle wrapping - if (!isNewLine && (bounds.x != -1) && (advance.x + computeExtent(token).x > x + bounds.x)) { + if (!isNewLine && (bounds.x != -1) && (advance.x + computeExtent(token).x > origin.x + bounds.x)) { // We are out of the x bound, force new line forceNewLine = true; } if (isNewLine || forceNewLine) { // Character return, move the advance to a new line - advance = glm::vec2(x, advance.y - _leading); + advance = glm::vec2(origin.x, advance.y - _leading); if (isNewLine) { // No need to draw anything, go directly to next token @@ -288,7 +289,7 @@ void Font::rebuildVertices(float x, float y, const QString& str, const glm::vec2 break; } } - if ((bounds.y != -1) && (advance.y - _fontSize < -y - bounds.y)) { + if ((bounds.y != -1) && (advance.y - _fontSize < -origin.y - bounds.y)) { // We are out of the y bound, stop drawing break; } @@ -297,11 +298,11 @@ void Font::rebuildVertices(float x, float y, const QString& str, const glm::vec2 if (!isNewLine) { for (auto c : token) { auto glyph = _glyphs[c]; - quint16 verticesOffset = _numVertices; + quint16 verticesOffset = numVertices; QuadBuilder qd(glyph, advance - glm::vec2(0.0f, _ascent)); - _verticesBuffer->append(sizeof(QuadBuilder), (const gpu::Byte*)&qd); - _numVertices += 4; + drawInfo.verticesBuffer->append(qd); + numVertices += 4; // Sam's recommended triangle slices // Triangle tri1 = { v0, v1, v3 }; @@ -327,8 +328,8 @@ void Font::rebuildVertices(float x, float y, const QString& str, const glm::vec2 indices[3] = verticesOffset + 2; indices[4] = verticesOffset + 1; indices[5] = verticesOffset + 3; - _indicesBuffer->append(sizeof(indices), (const gpu::Byte*)indices); - _numIndices += NUMBER_OF_INDICES_PER_QUAD; + drawInfo.indicesBuffer->append(sizeof(indices), (const gpu::Byte*)indices); + drawInfo.indexCount += NUMBER_OF_INDICES_PER_QUAD; // Advance by glyph size @@ -341,26 +342,39 @@ void Font::rebuildVertices(float x, float y, const QString& str, const glm::vec2 } } -void Font::drawString(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4* color, - EffectType effectType, const glm::vec2& bounds, bool layered) { +void Font::drawString(gpu::Batch& batch, Font::DrawInfo& drawInfo, const QString& str, const glm::vec4& color, + EffectType effectType, const glm::vec2& origin, const glm::vec2& bounds, bool layered) { if (str == "") { return; } - if (str != _lastStringRendered || bounds != _lastBounds) { - rebuildVertices(x, y, str, bounds); + if (str != drawInfo.string || bounds != drawInfo.bounds || origin != drawInfo.origin) { + buildVertices(drawInfo, str, origin, bounds); } setupGPU(); - batch.setPipeline(((*color).a < 1.0f || layered) ? _transparentPipeline : _pipeline); - batch.setResourceTexture(render_utils::slot::texture::TextFont, _texture); - batch._glUniform1i(render_utils::slot::uniform::TextOutline, (effectType == OUTLINE_EFFECT)); + struct GpuDrawParams { + glm::vec4 color; + glm::vec4 outline; + }; + + if (!drawInfo.paramsBuffer || drawInfo.params.color != color || drawInfo.params.effect != effectType) { + drawInfo.params.color = color; + drawInfo.params.effect = effectType; + GpuDrawParams gpuDrawParams; + gpuDrawParams.color = ColorUtils::sRGBToLinearVec4(drawInfo.params.color); + gpuDrawParams.outline.x = (drawInfo.params.effect == OUTLINE_EFFECT) ? 1 : 0; + drawInfo.paramsBuffer = std::make_shared(sizeof(GpuDrawParams), nullptr); + drawInfo.paramsBuffer->setSubData(0, sizeof(GpuDrawParams), (const gpu::Byte*)&gpuDrawParams); + } // need the gamma corrected color here - glm::vec4 lrgba = ColorUtils::sRGBToLinearVec4(*color); - batch._glUniform4fv(render_utils::slot::uniform::TextColor, 1, (const float*)&lrgba); + + batch.setPipeline((color.a < 1.0f || layered) ? _transparentPipeline : _pipeline); batch.setInputFormat(_format); - batch.setInputBuffer(0, _verticesBuffer, 0, _format->getChannels().at(0)._stride); - batch.setIndexBuffer(gpu::UINT16, _indicesBuffer, 0); - batch.drawIndexed(gpu::TRIANGLES, _numIndices, 0); + batch.setInputBuffer(0, drawInfo.verticesBuffer, 0, _format->getChannels().at(0)._stride); + batch.setResourceTexture(render_utils::slot::texture::TextFont, _texture); + batch.setUniformBuffer(0, drawInfo.paramsBuffer, 0, sizeof(GpuDrawParams)); + batch.setIndexBuffer(gpu::UINT16, drawInfo.indicesBuffer, 0); + batch.drawIndexed(gpu::TRIANGLES, drawInfo.indexCount, 0); } diff --git a/libraries/render-utils/src/text/Font.h b/libraries/render-utils/src/text/Font.h index 2fa2b65fa5..9a0a9b4734 100644 --- a/libraries/render-utils/src/text/Font.h +++ b/libraries/render-utils/src/text/Font.h @@ -23,16 +23,34 @@ public: void read(QIODevice& path); + struct DrawParams { + vec4 color{ -1 }; + EffectType effect; + }; + + struct DrawInfo { + gpu::BufferPointer verticesBuffer; + gpu::BufferPointer indicesBuffer; + gpu::BufferPointer paramsBuffer; + uint32_t indexCount; + + QString string; + glm::vec2 origin; + glm::vec2 bounds; + DrawParams params; + }; + glm::vec2 computeExtent(const QString& str) const; float getFontSize() const { return _fontSize; } // Render string to batch - void drawString(gpu::Batch& batch, float x, float y, const QString& str, - const glm::vec4* color, EffectType effectType, - const glm::vec2& bound, bool layered = false); + void drawString(gpu::Batch& batch, DrawInfo& drawInfo, const QString& str, + const glm::vec4& color, EffectType effectType, + const glm::vec2& origin, const glm::vec2& bound, bool layered = false); static Pointer load(const QString& family); + private: static Pointer load(QIODevice& fontFile); QStringList tokenizeForWrapping(const QString& str) const; @@ -40,7 +58,7 @@ private: glm::vec2 computeTokenExtent(const QString& str) const; const Glyph& getGlyph(const QChar& c) const; - void rebuildVertices(float x, float y, const QString& str, const glm::vec2& bounds); + void buildVertices(DrawInfo& drawInfo, const QString& str, const glm::vec2& origin, const glm::vec2& bounds); void setupGPU(); @@ -66,15 +84,7 @@ private: gpu::PipelinePointer _transparentPipeline; gpu::TexturePointer _texture; gpu::Stream::FormatPointer _format; - gpu::BufferPointer _verticesBuffer; - gpu::BufferPointer _indicesBuffer; gpu::BufferStreamPointer _stream; - unsigned int _numVertices = 0; - unsigned int _numIndices = 0; - - // last string render characteristics - QString _lastStringRendered; - glm::vec2 _lastBounds; }; #endif diff --git a/libraries/render-utils/src/velocityBuffer_cameraMotion.slf b/libraries/render-utils/src/velocityBuffer_cameraMotion.slf index 6932766d63..083440dbf8 100644 --- a/libraries/render-utils/src/velocityBuffer_cameraMotion.slf +++ b/libraries/render-utils/src/velocityBuffer_cameraMotion.slf @@ -27,11 +27,11 @@ void main(void) { ivec4 stereoSide; ivec2 framePixelPos = getPixelPosTexcoordPosAndSide(gl_FragCoord.xy, pixelPos, texcoordPos, stereoSide); - float Zdb = texelFetch(depthMap, ivec2(gl_FragCoord.xy), 0).x; + float Zdb = texelFetch(depthMap, ivec2(gl_FragCoord.xy), 0).x; - // The position of the pixel fragment in Eye space then in world space + // The position of the pixel fragment in Eye space then in world space vec3 eyePos = evalUnjitteredEyePositionFromZdb(stereoSide.x, Zdb, texcoordPos); - vec3 worldPos = (getViewInverse() * vec4(eyePos, 1.0)).xyz; + vec3 worldPos = (getViewInverse() * vec4(eyePos, 1.0)).xyz; vec3 prevEyePos = (getPreviousView() * vec4(worldPos, 1.0)).xyz; vec4 prevClipPos = (getUnjitteredProjection(stereoSide.x) * vec4(prevEyePos, 1.0)); diff --git a/libraries/render-utils/src/zone_drawAmbient.slf b/libraries/render-utils/src/zone_drawAmbient.slf index e560d91308..f20d83e913 100644 --- a/libraries/render-utils/src/zone_drawAmbient.slf +++ b/libraries/render-utils/src/zone_drawAmbient.slf @@ -44,7 +44,7 @@ void main(void) { // vec3 ambient = sphericalHarmonics_evalSphericalLight(getLightAmbientSphere(lightAmbient), fragNormal).xyz; // _fragColor = vec4( 0.5 * (fragNormal + vec3(1.0)), 1.0); - vec3 color = (sphereUV.x > 0 ? ambientMap : ambientSH); + vec3 color = (sphereUV.x > 0.0 ? ambientMap : ambientSH); color = color * 1.0 - base.w + base.xyz * base.w; const float INV_GAMMA_22 = 1.0 / 2.2; diff --git a/libraries/render-utils/src/zone_drawKeyLight.slf b/libraries/render-utils/src/zone_drawKeyLight.slf index 99e9357cb0..7174914ed8 100644 --- a/libraries/render-utils/src/zone_drawKeyLight.slf +++ b/libraries/render-utils/src/zone_drawKeyLight.slf @@ -36,7 +36,7 @@ void main(void) { vec3 outSpherePos = normalize(vec3(sphereUV, -sqrt(1.0 - sphereR2))); vec3 outNormal = vec3(getViewInverse() * vec4(outSpherePos, 0.0)); - float val = step(SUN_THRESHOLD, dot(-lightDirection, outNormal)); + float val = step(SUN_THRESHOLD, dot(-lightDirection, outNormal)); color = lightIrradiance * vec3(val); @@ -45,7 +45,7 @@ void main(void) { vec3 inSpherePos = normalize(vec3(inSphereUV, sqrt(1.0 - dot(inSphereUV.xy, inSphereUV.xy)))); vec3 inNormal = vec3(getViewInverse() * vec4(inSpherePos, 0.0)); - vec3 marbleColor = max(lightIrradiance * vec3(dot(-lightDirection, inNormal)), vec3(0.01)); + vec3 marbleColor = max(lightIrradiance * vec3(dot(-lightDirection, inNormal)), vec3(0.01)); color += marbleColor; } diff --git a/libraries/render/src/render/DrawSceneOctree.cpp b/libraries/render/src/render/DrawSceneOctree.cpp index 2cb8a5d8ef..6c87d2f56b 100644 --- a/libraries/render/src/render/DrawSceneOctree.cpp +++ b/libraries/render/src/render/DrawSceneOctree.cpp @@ -36,6 +36,9 @@ const gpu::PipelinePointer DrawSceneOctree::getDrawCellBoundsPipeline() { // Good to go add the brand new pipeline _drawCellBoundsPipeline = gpu::Pipeline::create(program, state); + _cellBoundsFormat = std::make_shared(); + _cellBoundsFormat->setAttribute(0, 0, gpu::Element(gpu::VEC4, gpu::INT32, gpu::XYZW), 0, gpu::Stream::PER_INSTANCE); + _cellBoundsBuffer = std::make_shared(); } return _drawCellBoundsPipeline; } @@ -82,8 +85,11 @@ void DrawSceneOctree::run(const RenderContextPointer& renderContext, const ItemS // bind the one gpu::Pipeline we need batch.setPipeline(getDrawCellBoundsPipeline()); + batch.setInputFormat(_cellBoundsFormat); - auto drawCellBounds = [this, &scene, &batch](const std::vector& cells) { + std::vector cellBounds; + auto drawCellBounds = [this, &cellBounds, &scene, &batch](const std::vector& cells) { + cellBounds.reserve(cellBounds.size() + cells.size()); for (const auto& cellID : cells) { auto cell = scene->getSpatialTree().getConcreteCell(cellID); auto cellLoc = cell.getlocation(); @@ -98,14 +104,19 @@ void DrawSceneOctree::run(const RenderContextPointer& renderContext, const ItemS } else if (!empty && !_showVisibleCells) { continue; } - - batch._glUniform4iv(gpu::slot::uniform::Extra0, 1, ((const int*)(&cellLocation))); - batch.draw(gpu::LINES, 24, 0); + cellBounds.push_back(cellLocation); } }; drawCellBounds(inSelection.cellSelection.insideCells); drawCellBounds(inSelection.cellSelection.partialCells); + auto size = cellBounds.size() * sizeof(ivec4); + if (size > _cellBoundsBuffer->getSize()) { + _cellBoundsBuffer->resize(size); + } + _cellBoundsBuffer->setSubData(0, cellBounds); + batch.setInputBuffer(0, _cellBoundsBuffer, 0, sizeof(ivec4)); + batch.drawInstanced((uint32_t)cellBounds.size(), gpu::LINES, 24); // Draw the LOD Reticle { diff --git a/libraries/render/src/render/DrawSceneOctree.h b/libraries/render/src/render/DrawSceneOctree.h index 0b2cd6f685..3f7c07ded3 100644 --- a/libraries/render/src/render/DrawSceneOctree.h +++ b/libraries/render/src/render/DrawSceneOctree.h @@ -54,6 +54,8 @@ namespace render { gpu::PipelinePointer _drawCellBoundsPipeline; gpu::PipelinePointer _drawLODReticlePipeline; gpu::PipelinePointer _drawItemBoundPipeline; + gpu::BufferPointer _cellBoundsBuffer; + gpu::Stream::FormatPointer _cellBoundsFormat; bool _showVisibleCells; // initialized by Config bool _showEmptyCells; // initialized by Config diff --git a/libraries/render/src/render/DrawStatus.cpp b/libraries/render/src/render/DrawStatus.cpp index 9b7d4ace2b..a1b61a4e77 100644 --- a/libraries/render/src/render/DrawStatus.cpp +++ b/libraries/render/src/render/DrawStatus.cpp @@ -170,13 +170,45 @@ void DrawStatus::run(const RenderContextPointer& renderContext, const Input& inp batch.setPipeline(getDrawItemStatusPipeline()); if (_showNetwork) { - for (size_t i = 0; i < itemBounds.size(); i++) { - batch._glUniform3fv(gpu::slot::uniform::Extra0, 1, (const float*)&itemBounds[i].bound.getCorner()); - batch._glUniform3fv(gpu::slot::uniform::Extra1, 1, ((const float*)&itemBounds[i].bound.getScale())); - batch._glUniform4iv(gpu::slot::uniform::Extra2, 1, (const int*)&(itemStatus[i].first)); - batch._glUniform4iv(gpu::slot::uniform::Extra3, 1, (const int*)&(itemStatus[i].second)); - batch.draw(gpu::TRIANGLES, 24 * NUM_STATUS_VEC4_PER_ITEM, 0); + if (!_instanceBuffer) { + _instanceBuffer = std::make_shared(); } + + struct InstanceData { + vec4 boundPos; + vec4 boundDim; + ivec4 status0; + ivec4 status1; + }; + + if (!_vertexFormat) { + _vertexFormat = std::make_shared(); + _vertexFormat->setAttribute(0, 0, gpu::Element(gpu::VEC4, gpu::FLOAT, gpu::XYZW), offsetof(InstanceData, boundPos), gpu::Stream::PER_INSTANCE); + _vertexFormat->setAttribute(1, 0, gpu::Element(gpu::VEC4, gpu::FLOAT, gpu::XYZW), offsetof(InstanceData, boundDim), gpu::Stream::PER_INSTANCE); + _vertexFormat->setAttribute(2, 0, gpu::Element(gpu::VEC4, gpu::INT32, gpu::XYZW), offsetof(InstanceData, status0), gpu::Stream::PER_INSTANCE); + _vertexFormat->setAttribute(3, 0, gpu::Element(gpu::VEC4, gpu::INT32, gpu::XYZW), offsetof(InstanceData, status1), gpu::Stream::PER_INSTANCE); + } + + batch.setInputFormat(_vertexFormat); + std::vector instanceData; + instanceData.resize(itemBounds.size()); + for (size_t i = 0; i < itemBounds.size(); i++) { + InstanceData& item = instanceData[i]; + const auto& bound = itemBounds[i].bound; + const auto& status = itemStatus[i]; + item.boundPos = vec4(bound.getCorner(), 1.0f); + item.boundDim = vec4(bound.getScale(), 1.0f); + item.status0 = status.first; + item.status1 = status.second; + } + + auto instanceBufferSize = sizeof(InstanceData) * instanceData.size(); + if (_instanceBuffer->getSize() < instanceBufferSize) { + _instanceBuffer->resize(instanceBufferSize); + } + _instanceBuffer->setSubData(0, instanceData); + batch.setInputBuffer(0, _instanceBuffer, 0, sizeof(InstanceData)); + batch.drawInstanced((uint32_t)instanceData.size(), gpu::TRIANGLES, 24 * NUM_STATUS_VEC4_PER_ITEM); } batch.setResourceTexture(0, 0); }); diff --git a/libraries/render/src/render/DrawStatus.h b/libraries/render/src/render/DrawStatus.h index 96269fda4d..2959ca59c5 100644 --- a/libraries/render/src/render/DrawStatus.h +++ b/libraries/render/src/render/DrawStatus.h @@ -63,6 +63,8 @@ namespace render { gpu::PipelinePointer _drawItemStatusPipeline; gpu::BufferPointer _boundsBuffer; + gpu::BufferPointer _instanceBuffer; + gpu::Stream::FormatPointer _vertexFormat; gpu::TexturePointer _statusIconMap; }; } diff --git a/libraries/render/src/render/DrawTask.cpp b/libraries/render/src/render/DrawTask.cpp index 8fb800291e..12f75049b2 100755 --- a/libraries/render/src/render/DrawTask.cpp +++ b/libraries/render/src/render/DrawTask.cpp @@ -184,8 +184,13 @@ void DrawBounds::run(const RenderContextPointer& renderContext, _drawBuffer = std::make_shared(sizeOfItemBound); } - _drawBuffer->setData(numItems * sizeOfItemBound, (const gpu::Byte*) items.data()); + if (!_paramsBuffer) { + _paramsBuffer = std::make_shared(sizeof(vec4), nullptr); + } + _drawBuffer->setData(numItems * sizeOfItemBound, (const gpu::Byte*) items.data()); + glm::vec4 color(glm::vec3(0.0f), -(float) numItems); + _paramsBuffer->setSubData(0, color); gpu::doInBatch("DrawBounds::run", args->_context, [&](gpu::Batch& batch) { args->_batch = &batch; @@ -202,7 +207,7 @@ void DrawBounds::run(const RenderContextPointer& renderContext, batch.setPipeline(getPipeline()); glm::vec4 color(glm::vec3(0.0f), -(float) numItems); - batch._glUniform4fv(gpu::slot::uniform::Color, 1, (const float*)(&color)); + batch.setUniformBuffer(0, _paramsBuffer); batch.setResourceBuffer(0, _drawBuffer); static const int NUM_VERTICES_PER_CUBE = 24; @@ -212,9 +217,10 @@ void DrawBounds::run(const RenderContextPointer& renderContext, gpu::Stream::FormatPointer DrawQuadVolume::_format; -DrawQuadVolume::DrawQuadVolume(const glm::vec3& color) : - _color{ color } { +DrawQuadVolume::DrawQuadVolume(const glm::vec3& color) { _meshVertices = gpu::BufferView(std::make_shared(sizeof(glm::vec3) * 8, nullptr), gpu::Element::VEC3F_XYZ); + _params = std::make_shared(sizeof(glm::vec4), nullptr); + _params->setSubData(0, vec4(color, 1.0)); if (!_format) { _format = std::make_shared(); _format->setAttribute(gpu::Stream::POSITION, gpu::Stream::POSITION, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0); @@ -248,8 +254,7 @@ void DrawQuadVolume::run(const render::RenderContextPointer& renderContext, cons batch.setProjectionTransform(projMat); batch.setViewTransform(viewMat); batch.setPipeline(getPipeline()); - - batch._glUniform4f(0, _color.x, _color.y, _color.z, 1.0f); + batch.setUniformBuffer(0, _params); batch.setInputFormat(_format); batch.setInputBuffer(gpu::Stream::POSITION, _meshVertices); batch.setIndexBuffer(indices); diff --git a/libraries/render/src/render/DrawTask.h b/libraries/render/src/render/DrawTask.h index 6b98fb2977..1ef4b8caf1 100755 --- a/libraries/render/src/render/DrawTask.h +++ b/libraries/render/src/render/DrawTask.h @@ -66,6 +66,7 @@ private: const gpu::PipelinePointer getPipeline(); gpu::PipelinePointer _boundsPipeline; gpu::BufferPointer _drawBuffer; + gpu::BufferPointer _paramsBuffer; }; class DrawQuadVolumeConfig : public render::JobConfig { @@ -95,7 +96,7 @@ protected: const gpu::BufferView& indices, int indexCount); gpu::BufferView _meshVertices; - glm::vec3 _color; + gpu::BufferPointer _params; bool _isUpdateEnabled{ true }; static gpu::Stream::FormatPointer _format; diff --git a/libraries/render/src/render/drawCellBounds.slv b/libraries/render/src/render/drawCellBounds.slv index f3cc4c6411..24cc6254fd 100644 --- a/libraries/render/src/render/drawCellBounds.slv +++ b/libraries/render/src/render/drawCellBounds.slv @@ -20,8 +20,7 @@ <$declareColorWheel()$> <@include SceneOctree.slh@> -layout(location=GPU_UNIFORM_EXTRA0) uniform ivec4 inCellLocation; - +layout(location=0) in ivec4 inCellLocation; layout(location=0) out vec4 varColor; void main(void) { @@ -62,5 +61,5 @@ void main(void) { TransformObject obj = getTransformObject(); <$transformModelToClipPos(cam, obj, pos, gl_Position)$> - varColor = vec4(colorWheel(fract(float(inCellLocation.w) / 5.0)), 0.8 + 0.2 * cellIsEmpty); + varColor = vec4(colorWheel(fract(float(inCellLocation.w) / 5.0)), 0.8 + 0.2 * float(cellIsEmpty)); } \ No newline at end of file diff --git a/libraries/render/src/render/drawItemBounds.slv b/libraries/render/src/render/drawItemBounds.slv index 8925009160..ea4d0f24e6 100644 --- a/libraries/render/src/render/drawItemBounds.slv +++ b/libraries/render/src/render/drawItemBounds.slv @@ -20,7 +20,14 @@ <@include gpu/Color.slh@> <$declareColorWheel()$> -layout(location=GPU_UNIFORM_COLOR) uniform vec4 inColor; +struct DrawItemBoundsParams { + vec4 color; +}; + +layout(binding=0) uniform drawItemBoundsParamsBuffer { + DrawItemBoundsParams params; +}; + struct ItemBound { vec4 id_boundPos; @@ -91,10 +98,10 @@ void main(void) { TransformObject obj = getTransformObject(); <$transformModelToClipPos(cam, obj, pos, gl_Position)$> - if (inColor.w < 0.0) { - varColor = vec4(colorWheel(float(boundID)/(-inColor.w)), 1.0); + if (params.color.w < 0.0) { + varColor = vec4(colorWheel(float(boundID)/(-params.color.w)), 1.0); } else { - varColor = vec4(colorWheel(float(inColor.w)), 1.0); + varColor = vec4(colorWheel(float(params.color.w)), 1.0); } varTexcoord = vec2(cubeVec.w, length(boundDim)); diff --git a/libraries/render/src/render/drawItemStatus.slf b/libraries/render/src/render/drawItemStatus.slf index 309a73ae15..9409ee6171 100644 --- a/libraries/render/src/render/drawItemStatus.slf +++ b/libraries/render/src/render/drawItemStatus.slf @@ -18,7 +18,7 @@ layout(location=0) out vec4 outFragColor; layout(binding=0) uniform sampler2D _icons; vec2 getIconTexcoord(float icon, vec2 uv) { const vec2 ICON_COORD_SIZE = vec2(0.0625, 1.0); - return vec2((uv.x + icon) * ICON_COORD_SIZE.x, uv.y * ICON_COORD_SIZE.y); + return vec2((uv.x + icon) * ICON_COORD_SIZE.x, uv.y * ICON_COORD_SIZE.y); } void main(void) { diff --git a/libraries/render/src/render/drawItemStatus.slv b/libraries/render/src/render/drawItemStatus.slv index e92bdda248..7aac26fe2e 100644 --- a/libraries/render/src/render/drawItemStatus.slv +++ b/libraries/render/src/render/drawItemStatus.slv @@ -20,10 +20,10 @@ layout(location=0) out vec4 varColor; layout(location=1) out vec3 varTexcoord; -layout(location=GPU_UNIFORM_EXTRA0) uniform vec3 inBoundPos; -layout(location=GPU_UNIFORM_EXTRA1) uniform vec3 inBoundDim; -layout(location=GPU_UNIFORM_EXTRA2) uniform ivec4 inStatus0; -layout(location=GPU_UNIFORM_EXTRA3) uniform ivec4 inStatus1; +layout(location=0) in vec3 inBoundPos; +layout(location=1) in vec3 inBoundDim; +layout(location=2) in ivec4 inStatus0; +layout(location=3) in ivec4 inStatus1; vec3 paintRainbow(float normalizedHue) { float v = normalizedHue * 6.f; diff --git a/libraries/script-engine/src/AssetScriptingInterface.h b/libraries/script-engine/src/AssetScriptingInterface.h index 72d6901fb5..0e05a563b2 100644 --- a/libraries/script-engine/src/AssetScriptingInterface.h +++ b/libraries/script-engine/src/AssetScriptingInterface.h @@ -108,6 +108,10 @@ public: Q_INVOKABLE void setBakingEnabled(QString path, bool enabled, QScriptValue callback); #if (PR_BUILD || DEV_BUILD) + /** + * This function is purely for development purposes, and not meant for use in a + * production context. It is not a public-facing API, so it should not contain jsdoc. + */ Q_INVOKABLE void sendFakedHandshake(); #endif diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index ce4ec89950..cfd155e14b 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -271,7 +271,7 @@ void ScriptEngine::disconnectNonEssentialSignals() { // Ensure the thread should be running, and does exist if (_isRunning && _isThreaded && (workerThread = thread())) { connect(this, &ScriptEngine::doneRunning, workerThread, &QThread::quit); - connect(this, &QObject::destroyed, workerThread, &QObject::deleteLater); + connect(workerThread, &QThread::finished, workerThread, &QObject::deleteLater); } } @@ -386,7 +386,7 @@ void ScriptEngine::runInThread() { // disconnectNonEssentialSignals() method connect(workerThread, &QThread::started, this, &ScriptEngine::run); connect(this, &ScriptEngine::doneRunning, workerThread, &QThread::quit); - connect(this, &QObject::destroyed, workerThread, &QObject::deleteLater); + connect(workerThread, &QThread::finished, workerThread, &QObject::deleteLater); workerThread->start(); } @@ -1974,8 +1974,9 @@ void ScriptEngine::forwardHandlerCall(const EntityItemID& entityID, const QStrin } int ScriptEngine::getNumRunningEntityScripts() const { + QReadLocker locker { &_entityScriptsLock }; int sum = 0; - for (auto& st : _entityScripts) { + for (const auto& st : _entityScripts) { if (st.status == EntityScriptStatus::RUNNING) { ++sum; } @@ -1984,14 +1985,20 @@ int ScriptEngine::getNumRunningEntityScripts() const { } void ScriptEngine::setEntityScriptDetails(const EntityItemID& entityID, const EntityScriptDetails& details) { - _entityScripts[entityID] = details; + { + QWriteLocker locker { &_entityScriptsLock }; + _entityScripts[entityID] = details; + } emit entityScriptDetailsUpdated(); } void ScriptEngine::updateEntityScriptStatus(const EntityItemID& entityID, const EntityScriptStatus &status, const QString& errorInfo) { - EntityScriptDetails &details = _entityScripts[entityID]; - details.status = status; - details.errorInfo = errorInfo; + { + QWriteLocker locker { &_entityScriptsLock }; + EntityScriptDetails& details = _entityScripts[entityID]; + details.status = status; + details.errorInfo = errorInfo; + } emit entityScriptDetailsUpdated(); } @@ -2042,6 +2049,7 @@ QFuture ScriptEngine::getLocalEntityScriptDetails(const EntityItemID& } bool ScriptEngine::getEntityScriptDetails(const EntityItemID& entityID, EntityScriptDetails &details) const { + QReadLocker locker { &_entityScriptsLock }; auto it = _entityScripts.constFind(entityID); if (it == _entityScripts.constEnd()) { return false; @@ -2050,6 +2058,11 @@ bool ScriptEngine::getEntityScriptDetails(const EntityItemID& entityID, EntitySc return true; } +bool ScriptEngine::hasEntityScriptDetails(const EntityItemID& entityID) const { + QReadLocker locker { &_entityScriptsLock }; + return _entityScripts.contains(entityID); +} + const static EntityItemID BAD_SCRIPT_UUID_PLACEHOLDER { "{20170224-dead-face-0000-cee000021114}" }; void ScriptEngine::processDeferredEntityLoads(const QString& entityScript, const EntityItemID& leaderID) { @@ -2064,14 +2077,15 @@ void ScriptEngine::processDeferredEntityLoads(const QString& entityScript, const } foreach(DeferredLoadEntity retry, retryLoads) { // check whether entity was since been deleted - if (!_entityScripts.contains(retry.entityID)) { + + EntityScriptDetails details; + if (!getEntityScriptDetails(retry.entityID, details)) { qCDebug(scriptengine) << "processDeferredEntityLoads -- entity details gone (entity deleted?)" << retry.entityID; continue; } // check whether entity has since been unloaded or otherwise errored-out - auto details = _entityScripts[retry.entityID]; if (details.status != EntityScriptStatus::PENDING) { qCDebug(scriptengine) << "processDeferredEntityLoads -- entity status no longer PENDING; " << retry.entityID << details.status; @@ -2079,7 +2093,11 @@ void ScriptEngine::processDeferredEntityLoads(const QString& entityScript, const } // propagate leader's failure reasons to the pending entity - const auto leaderDetails = _entityScripts[leaderID]; + EntityScriptDetails leaderDetails; + { + QWriteLocker locker { &_entityScriptsLock }; + leaderDetails = _entityScripts[leaderID]; + } if (leaderDetails.status != EntityScriptStatus::RUNNING) { qCDebug(scriptengine) << QString("... pending load of %1 cancelled (leader: %2 status: %3)") .arg(retry.entityID.toString()).arg(leaderID.toString()).arg(leaderDetails.status); @@ -2125,7 +2143,7 @@ void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString& return; } - if (!_entityScripts.contains(entityID)) { + if (!hasEntityScriptDetails(entityID)) { // make sure EntityScriptDetails has an entry for this UUID right away // (which allows bailing from the loading/provisioning process early if the Entity gets deleted mid-flight) updateEntityScriptStatus(entityID, EntityScriptStatus::PENDING, "...pending..."); @@ -2166,9 +2184,12 @@ void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString& _occupiedScriptURLs[entityScript] = entityID; #ifdef DEBUG_ENTITY_STATES - auto previousStatus = _entityScripts.contains(entityID) ? _entityScripts[entityID].status : EntityScriptStatus::PENDING; - qCDebug(scriptengine) << "loadEntityScript.LOADING: " << entityScript << entityID.toString() - << "(previous: " << previousStatus << ")"; + { + EntityScriptDetails details; + bool hasEntityScript = getEntityScriptDetails(entityID, details); + qCDebug(scriptengine) << "loadEntityScript.LOADING: " << entityScript << entityID.toString() + << "(previous: " << (hasEntityScript ? details.status : EntityScriptStatus::PENDING) << ")"; + } #endif EntityScriptDetails newDetails; @@ -2197,7 +2218,7 @@ void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString& #ifdef DEBUG_ENTITY_STATES qCDebug(scriptengine) << "loadEntityScript.contentAvailable" << status << QUrl(url).fileName() << entityID.toString(); #endif - if (!isStopping() && _entityScripts.contains(entityID)) { + if (!isStopping() && hasEntityScriptDetails(entityID)) { _contentAvailableQueue[entityID] = { entityID, url, contents, isURL, success, status }; } else { #ifdef DEBUG_ENTITY_STATES @@ -2267,8 +2288,11 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co bool isFileUrl = isURL && scriptOrURL.startsWith("file://"); auto fileName = isURL ? scriptOrURL : "about:EmbeddedEntityScript"; - const EntityScriptDetails &oldDetails = _entityScripts[entityID]; - const QString entityScript = oldDetails.scriptText; + QString entityScript; + { + QWriteLocker locker { &_entityScriptsLock }; + entityScript = _entityScripts[entityID].scriptText; + } EntityScriptDetails newDetails; newDetails.scriptText = scriptOrURL; @@ -2446,8 +2470,8 @@ void ScriptEngine::unloadEntityScript(const EntityItemID& entityID, bool shouldR "entityID:" << entityID; #endif - if (_entityScripts.contains(entityID)) { - const EntityScriptDetails &oldDetails = _entityScripts[entityID]; + EntityScriptDetails oldDetails; + if (getEntityScriptDetails(entityID, oldDetails)) { auto scriptText = oldDetails.scriptText; if (isEntityScriptRunning(entityID)) { @@ -2460,7 +2484,10 @@ void ScriptEngine::unloadEntityScript(const EntityItemID& entityID, bool shouldR #endif if (shouldRemoveFromMap) { // this was a deleted entity, we've been asked to remove it from the map - _entityScripts.remove(entityID); + { + QWriteLocker locker { &_entityScriptsLock }; + _entityScripts.remove(entityID); + } emit entityScriptDetailsUpdated(); } else if (oldDetails.status != EntityScriptStatus::UNLOADED) { EntityScriptDetails newDetails; @@ -2491,10 +2518,19 @@ void ScriptEngine::unloadAllEntityScripts() { #ifdef THREAD_DEBUGGING qCDebug(scriptengine) << "ScriptEngine::unloadAllEntityScripts() called on correct thread [" << thread() << "]"; #endif - foreach(const EntityItemID& entityID, _entityScripts.keys()) { + + QList keys; + { + QReadLocker locker{ &_entityScriptsLock }; + keys = _entityScripts.keys(); + } + foreach(const EntityItemID& entityID, keys) { unloadEntityScript(entityID); } - _entityScripts.clear(); + { + QWriteLocker locker{ &_entityScriptsLock }; + _entityScripts.clear(); + } emit entityScriptDetailsUpdated(); _occupiedScriptURLs.clear(); @@ -2508,7 +2544,7 @@ void ScriptEngine::unloadAllEntityScripts() { } void ScriptEngine::refreshFileScript(const EntityItemID& entityID) { - if (!HIFI_AUTOREFRESH_FILE_SCRIPTS || !_entityScripts.contains(entityID)) { + if (!HIFI_AUTOREFRESH_FILE_SCRIPTS || !hasEntityScriptDetails(entityID)) { return; } @@ -2518,7 +2554,11 @@ void ScriptEngine::refreshFileScript(const EntityItemID& entityID) { } recurseGuard = true; - EntityScriptDetails details = _entityScripts[entityID]; + EntityScriptDetails details; + { + QWriteLocker locker { &_entityScriptsLock }; + details = _entityScripts[entityID]; + } // Check to see if a file based script needs to be reloaded (easier debugging) if (details.lastModified > 0) { QString filePath = QUrl(details.scriptText).toLocalFile(); @@ -2584,7 +2624,11 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS refreshFileScript(entityID); } if (isEntityScriptRunning(entityID)) { - EntityScriptDetails details = _entityScripts[entityID]; + EntityScriptDetails details; + { + QWriteLocker locker { &_entityScriptsLock }; + details = _entityScripts[entityID]; + } QScriptValue entityScript = details.scriptObject; // previously loaded // If this is a remote call, we need to check to see if the function is remotely callable @@ -2646,7 +2690,11 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS refreshFileScript(entityID); } if (isEntityScriptRunning(entityID)) { - EntityScriptDetails details = _entityScripts[entityID]; + EntityScriptDetails details; + { + QWriteLocker locker { &_entityScriptsLock }; + details = _entityScripts[entityID]; + } QScriptValue entityScript = details.scriptObject; // previously loaded if (entityScript.property(methodName).isFunction()) { QScriptValueList args; @@ -2680,7 +2728,11 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS refreshFileScript(entityID); } if (isEntityScriptRunning(entityID)) { - EntityScriptDetails details = _entityScripts[entityID]; + EntityScriptDetails details; + { + QWriteLocker locker { &_entityScriptsLock }; + details = _entityScripts[entityID]; + } QScriptValue entityScript = details.scriptObject; // previously loaded if (entityScript.property(methodName).isFunction()) { QScriptValueList args; diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 94b50bfd2c..08e2c492e8 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -461,7 +461,9 @@ public: * @returns {boolean} */ Q_INVOKABLE bool isEntityScriptRunning(const EntityItemID& entityID) { - return _entityScripts.contains(entityID) && _entityScripts[entityID].status == EntityScriptStatus::RUNNING; + QReadLocker locker { &_entityScriptsLock }; + auto it = _entityScripts.constFind(entityID); + return it != _entityScripts.constEnd() && it->status == EntityScriptStatus::RUNNING; } QVariant cloneEntityScriptDetails(const EntityItemID& entityID); QFuture getLocalEntityScriptDetails(const EntityItemID& entityID) override; @@ -559,6 +561,7 @@ public: void clearDebugLogWindow(); int getNumRunningEntityScripts() const; bool getEntityScriptDetails(const EntityItemID& entityID, EntityScriptDetails &details) const; + bool hasEntityScriptDetails(const EntityItemID& entityID) const; public slots: @@ -771,6 +774,7 @@ protected: bool _isInitialized { false }; QHash _timerFunctionMap; QSet _includedURLs; + mutable QReadWriteLock _entityScriptsLock { QReadWriteLock::Recursive }; QHash _entityScripts; QHash _occupiedScriptURLs; QList _deferredEntityLoads; diff --git a/libraries/shared/src/PrioritySortUtil.h b/libraries/shared/src/PrioritySortUtil.h index 8ded047212..27f6b193ba 100644 --- a/libraries/shared/src/PrioritySortUtil.h +++ b/libraries/shared/src/PrioritySortUtil.h @@ -16,55 +16,16 @@ #include "NumericalConstants.h" #include "shared/ConicalViewFrustum.h" -/* PrioritySortUtil is a helper for sorting 3D things relative to a ViewFrustum. To use: +// PrioritySortUtil is a helper for sorting 3D things relative to a ViewFrustum. -(1) Derive a class from pure-virtual PrioritySortUtil::Sortable that wraps a copy of - the Thing you want to prioritize and sort: - - class SortableWrapper: public PrioritySortUtil::Sortable { - public: - SortableWrapper(const Thing& thing) : _thing(thing) { } - glm::vec3 getPosition() const override { return _thing->getPosition(); } - float getRadius() const override { return 0.5f * _thing->getBoundingRadius(); } - uint64_t getTimestamp() const override { return _thing->getLastTime(); } - Thing getThing() const { return _thing; } - private: - Thing _thing; - }; - -(2) Make a PrioritySortUtil::PriorityQueue and add them to the queue: - - PrioritySortUtil::PriorityQueue sortedThings(viewFrustum); - std::priority_queue< PrioritySortUtil::Sortable > sortedThings; - for (thing in things) { - sortedThings.push(SortableWrapper(thing)); - } - -(3) Loop over your priority queue and do timeboxed work: - - NOTE: Be careful using references to members of instances of T from std::priority_queue. - Under the hood std::priority_queue may re-use instances of T. - For example, after a pop() or a push() the top T may have the same memory address - as the top T before the pop() or push() (but point to a swapped instance of T). - This causes a reference to member variable of T to point to a different value - when operations taken on std::priority_queue shuffle around the instances of T. - - uint64_t cutoffTime = usecTimestampNow() + TIME_BUDGET; - while (!sortedThings.empty()) { - const Thing& thing = sortedThings.top(); - // ...do work on thing... - sortedThings.pop(); - if (usecTimestampNow() > cutoffTime) { - break; - } - } -*/ +const float OUT_OF_VIEW_PENALTY = -10.0f; +const float OUT_OF_VIEW_THRESHOLD = 0.5f * OUT_OF_VIEW_PENALTY; namespace PrioritySortUtil { constexpr float DEFAULT_ANGULAR_COEF { 1.0f }; constexpr float DEFAULT_CENTER_COEF { 0.5f }; - constexpr float DEFAULT_AGE_COEF { 0.25f / (float)(USECS_PER_SECOND) }; + constexpr float DEFAULT_AGE_COEF { 0.25f }; class Sortable { public: @@ -84,8 +45,9 @@ namespace PrioritySortUtil { PriorityQueue() = delete; PriorityQueue(const ConicalViewFrustums& views) : _views(views) { } PriorityQueue(const ConicalViewFrustums& views, float angularWeight, float centerWeight, float ageWeight) - : _views(views), _angularWeight(angularWeight), _centerWeight(centerWeight), _ageWeight(ageWeight) - { } + : _views(views), _angularWeight(angularWeight), _centerWeight(centerWeight), _ageWeight(ageWeight) + , _usecCurrentTime(usecTimestampNow()) { + } void setViews(const ConicalViewFrustums& views) { _views = views; } @@ -93,6 +55,7 @@ namespace PrioritySortUtil { _angularWeight = angularWeight; _centerWeight = centerWeight; _ageWeight = ageWeight; + _usecCurrentTime = usecTimestampNow(); } size_t size() const { return _vector.size(); } @@ -131,23 +94,18 @@ namespace PrioritySortUtil { glm::vec3 offset = position - view.getPosition(); float distance = glm::length(offset) + 0.001f; // add 1mm to avoid divide by zero const float MIN_RADIUS = 0.1f; // WORKAROUND for zero size objects (we still want them to sort by distance) - float radius = glm::min(thing.getRadius(), MIN_RADIUS); - float cosineAngle = (glm::dot(offset, view.getDirection()) / distance); - float age = (float)(usecTimestampNow() - thing.getTimestamp()); + float radius = glm::max(thing.getRadius(), MIN_RADIUS); + // Other item's angle from view centre: + float cosineAngle = glm::dot(offset, view.getDirection()) / distance; + float age = float((_usecCurrentTime - thing.getTimestamp()) / USECS_PER_SECOND); - // we modulatate "age" drift rate by the cosineAngle term to make periphrial objects sort forward - // at a reduced rate but we don't want the "age" term to go zero or negative so we clamp it - const float MIN_COSINE_ANGLE_FACTOR = 0.1f; - float cosineAngleFactor = glm::max(cosineAngle, MIN_COSINE_ANGLE_FACTOR); - - float priority = _angularWeight * glm::max(radius, MIN_RADIUS) / distance - + _centerWeight * cosineAngle - + _ageWeight * cosineAngleFactor * age; + // the "age" term accumulates at the sum of all weights + float angularSize = radius / distance; + float priority = (_angularWeight * angularSize + _centerWeight * cosineAngle) * (age + 1.0f) + _ageWeight * age; // decrement priority of things outside keyhole if (distance - radius > view.getRadius()) { if (!view.intersects(offset, distance, radius)) { - constexpr float OUT_OF_VIEW_PENALTY = -10.0f; priority += OUT_OF_VIEW_PENALTY; } } @@ -159,12 +117,13 @@ namespace PrioritySortUtil { float _angularWeight { DEFAULT_ANGULAR_COEF }; float _centerWeight { DEFAULT_CENTER_COEF }; float _ageWeight { DEFAULT_AGE_COEF }; + quint64 _usecCurrentTime { 0 }; }; } // namespace PrioritySortUtil -// for now we're keeping hard-coded sorted time budgets in one spot + // for now we're keeping hard-coded sorted time budgets in one spot const uint64_t MAX_UPDATE_RENDERABLES_TIME_BUDGET = 2000; // usec const uint64_t MIN_SORTED_UPDATE_RENDERABLES_TIME_BUDGET = 1000; // usec +const uint64_t MAX_UPDATE_AVATARS_TIME_BUDGET = 2000; // usec #endif // hifi_PrioritySortUtil_h - diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index d59c58def8..6ecf9faca7 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -24,6 +24,7 @@ #include "SharedUtil.h" #include "shared/Bilateral.h" #include "Transform.h" +#include "PhysicsCollisionGroups.h" class QColor; class QUrl; @@ -264,6 +265,8 @@ public: * @property {Quat} orientation - The orientation of the collision region, relative to a parent if defined. * @property {float} threshold - The approximate minimum penetration depth for a test object to be considered in contact with the collision region. * The depth is measured in world space, but will scale with the parent if defined. +* @property {CollisionMask} [collisionGroup=8] - The type of object this collision pick collides as. Objects whose collision masks overlap with the pick's collision group +* will be considered colliding with the pick. * @property {Uuid} parentID - The ID of the parent, either an avatar, an entity, or an overlay. * @property {number} parentJointIndex - The joint of the parent to parent to, for example, the joints on the model of an avatar. (default = 0, no joint) * @property {string} joint - If "Mouse," parents the pick to the mouse. If "Avatar," parents the pick to MyAvatar's head. Otherwise, parents to the joint of the given name on MyAvatar. @@ -277,7 +280,8 @@ public: modelURL(collisionRegion.modelURL), shapeInfo(std::make_shared()), transform(collisionRegion.transform), - threshold(collisionRegion.threshold) + threshold(collisionRegion.threshold), + collisionGroup(collisionRegion.collisionGroup) { shapeInfo->setParams(collisionRegion.shapeInfo->getType(), collisionRegion.shapeInfo->getHalfExtents(), collisionRegion.modelURL.toString()); } @@ -316,6 +320,9 @@ public: if (pickVariant["orientation"].isValid()) { transform.setRotation(quatFromVariant(pickVariant["orientation"])); } + if (pickVariant["collisionGroup"].isValid()) { + collisionGroup = pickVariant["collisionGroup"].toUInt(); + } } QVariantMap toVariantMap() const override { @@ -330,6 +337,7 @@ public: collisionRegion["loaded"] = loaded; collisionRegion["threshold"] = threshold; + collisionRegion["collisionGroup"] = collisionGroup; collisionRegion["position"] = vec3toVariant(transform.getTranslation()); collisionRegion["orientation"] = quatToVariant(transform.getRotation()); @@ -341,12 +349,14 @@ public: return !std::isnan(threshold) && !(glm::any(glm::isnan(transform.getTranslation())) || glm::any(glm::isnan(transform.getRotation())) || - shapeInfo->getType() == SHAPE_TYPE_NONE); + shapeInfo->getType() == SHAPE_TYPE_NONE || + collisionGroup == 0); } bool operator==(const CollisionRegion& other) const { return loaded == other.loaded && threshold == other.threshold && + collisionGroup == other.collisionGroup && glm::all(glm::equal(transform.getTranslation(), other.transform.getTranslation())) && glm::all(glm::equal(transform.getRotation(), other.transform.getRotation())) && glm::all(glm::equal(transform.getScale(), other.transform.getScale())) && @@ -362,6 +372,10 @@ public: return false; } + if (collisionGroup == 0) { + return false; + } + return !shapeInfo->getPointCollection().size(); } @@ -372,7 +386,8 @@ public: // We can't compute the shapeInfo here without loading the model first, so we delegate that responsibility to the owning CollisionPick std::shared_ptr shapeInfo = std::make_shared(); Transform transform; - float threshold; + float threshold { 0.0f }; + uint16_t collisionGroup { USER_COLLISION_GROUP_MY_AVATAR }; }; namespace std { diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index b275660c0f..aaf5ca7260 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -38,6 +38,10 @@ var DEFAULT_SCRIPTS_SEPARATE = [ //"system/chat.js" ]; +if (Settings.getValue("enableInterstitialMode", false)) { + DEFAULT_SCRIPTS_SEPARATE.push("system/interstitialPage.js"); +} + // add a menu item for debugging var MENU_CATEGORY = "Developer"; var MENU_ITEM = "Debug defaultScripts.js"; diff --git a/scripts/developer/utilities/audio/Stats.qml b/scripts/developer/utilities/audio/Stats.qml index 7f559ea664..f359e9b04c 100644 --- a/scripts/developer/utilities/audio/Stats.qml +++ b/scripts/developer/utilities/audio/Stats.qml @@ -12,12 +12,12 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 import QtQuick.Layouts 1.3 -import "../../../../resources/qml/controls-uit" as HifiControls +import "qrc:////qml//controls-uit" as HifiControls Column { id: stats width: parent.width - property bool showGraphs: toggleGraphs.checked + property alias showGraphs: toggleGraphs.toggle Item { width: parent.width @@ -25,10 +25,10 @@ Column { HifiControls.Button { id: toggleGraphs - property bool checked: false + property bool toggle: false anchors.horizontalCenter: parent.horizontalCenter - text: checked ? "Hide graphs" : "Show graphs" - onClicked: function() { checked = !checked; } + text: toggle ? "Hide graphs" : "Show graphs" + onClicked: { toggle = !toggle;} } } @@ -40,7 +40,8 @@ Column { Section { label: "Latency" - description: "Audio pipeline latency, broken out and summed" + // description: "Audio pipeline latency, broken out and summed" + description: label control: ColumnLayout { MovingValue { label: "Input Read"; source: AudioStats.inputReadMsMax; showGraphs: stats.showGraphs } MovingValue { label: "Input Ring"; source: AudioStats.inputUnplayedMsMax; showGraphs: stats.showGraphs } @@ -62,7 +63,8 @@ Column { Section { label: "Upstream Jitter" - description: "Timegaps in packets sent to the mixer" + // description: "Timegaps in packets sent to the mixer" + description: label control: Jitter { max: AudioStats.sentTimegapMsMaxWindow avg: AudioStats.sentTimegapMsAvgWindow @@ -76,13 +78,15 @@ Column { Section { label: "Mixer (upstream)" - description: "This client's remote audio stream, as seen by the server's mixer" + // description: "This client's remote audio stream, as seen by the server's mixer" + description: label control: Stream { stream: AudioStats.mixerStream; showGraphs: stats.showGraphs } } Section { label: "Client (downstream)" - description: "This client's received audio stream, between the network and the OS" + // description: "This client's received audio stream, between the network and the OS" + description: label control: Stream { stream: AudioStats.clientStream; showGraphs: stats.showGraphs } } } diff --git a/scripts/developer/utilities/audio/TabletStats.qml b/scripts/developer/utilities/audio/TabletStats.qml index 130b90f032..2f8d212a2a 100644 --- a/scripts/developer/utilities/audio/TabletStats.qml +++ b/scripts/developer/utilities/audio/TabletStats.qml @@ -12,7 +12,7 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 import QtQuick.Layouts 1.3 -import "../../../../resources/qml/styles-uit" +import "qrc:////qml//styles-uit" Item { id: dialog diff --git a/scripts/modules/appUi.js b/scripts/modules/appUi.js index 6d2986768a..dab377911b 100644 --- a/scripts/modules/appUi.js +++ b/scripts/modules/appUi.js @@ -1,5 +1,5 @@ "use strict"; -/*global Tablet, Script*/ +/* global Tablet, Script */ // // libraries/appUi.js // @@ -11,6 +11,7 @@ // function AppUi(properties) { + var request = Script.require('request').request; /* Example development order: 1. var AppUi = Script.require('appUi'); 2. Put appname-i.svg, appname-a.svg in graphicsDirectory (where non-default graphicsDirectory can be added in #3). @@ -31,37 +32,63 @@ function AppUi(properties) { var that = this; function defaultButton(name, suffix) { var base = that[name] || (that.buttonPrefix + suffix); - that[name] = (base.indexOf('/') >= 0) ? base : (that.graphicsDirectory + base); //poor man's merge + that[name] = (base.indexOf('/') >= 0) ? base : (that.graphicsDirectory + base); // poor man's merge } // Defaults: that.tabletName = "com.highfidelity.interface.tablet.system"; that.inject = ""; that.graphicsDirectory = "icons/tablet-icons/"; // Where to look for button svgs. See below. + that.additionalAppScreens = []; that.checkIsOpen = function checkIsOpen(type, tabletUrl) { // Are we active? Value used to set isOpen. - return (type === that.type) && that.currentUrl && (tabletUrl.indexOf(that.currentUrl) >= 0); // Actual url may have prefix or suffix. + // Actual url may have prefix or suffix. + return (type === that.currentVisibleScreenType) && + that.currentVisibleUrl && + ((that.home.indexOf(that.currentVisibleUrl) > -1) || + (that.additionalAppScreens.indexOf(that.currentVisibleUrl) > -1)); }; - that.setCurrentData = function setCurrentData(url) { - that.currentUrl = url; - that.type = /.qml$/.test(url) ? 'QML' : 'Web'; - } - that.open = function open(optionalUrl) { // How to open the app. + that.setCurrentVisibleScreenMetadata = function setCurrentVisibleScreenMetadata(type, url) { + that.currentVisibleScreenType = type; + that.currentVisibleUrl = url; + }; + that.open = function open(optionalUrl, optionalInject) { // How to open the app. var url = optionalUrl || that.home; - that.setCurrentData(url); - if (that.isQML()) { + var inject = optionalInject || that.inject; + + if (that.isQMLUrl(url)) { that.tablet.loadQMLSource(url); } else { - that.tablet.gotoWebScreen(url, that.inject); + that.tablet.gotoWebScreen(url, inject); + } + }; + // Opens some app on top of the current app (on desktop, opens new window) + that.openNewAppOnTop = function openNewAppOnTop(url, optionalInject) { + var inject = optionalInject || ""; + if (that.isQMLUrl(url)) { + that.tablet.loadQMLOnTop(url); + } else { + that.tablet.loadWebScreenOnTop(url, inject); } }; that.close = function close() { // How to close the app. - that.currentUrl = ""; + that.currentVisibleUrl = ""; // for toolbar-mode: go back to home screen, this will close the window. that.tablet.gotoHomeScreen(); }; that.buttonActive = function buttonActive(isActive) { // How to make the button active (white). that.button.editProperties({isActive: isActive}); }; + that.isQMLUrl = function isQMLUrl(url) { + var type = /.qml$/.test(url) ? 'QML' : 'Web'; + return type === 'QML'; + }; + that.isCurrentlyOnQMLScreen = function isCurrentlyOnQMLScreen() { + return that.currentVisibleScreenType === 'QML'; + }; + + // + // START Notification Handling Defaults + // that.messagesWaiting = function messagesWaiting(isWaiting) { // How to indicate a message light on button. // Note that waitingButton doesn't have to exist unless someone explicitly calls this with isWaiting true. that.button.editProperties({ @@ -69,16 +96,124 @@ function AppUi(properties) { activeIcon: isWaiting ? that.activeMessagesButton : that.activeButton }); }; - that.isQML = function isQML() { // We set type property in onClick. - return that.type === 'QML'; + that.notificationPollTimeout = false; + that.notificationPollTimeoutMs = 60000; + that.notificationPollEndpoint = false; + that.notificationPollStopPaginatingConditionMet = false; + that.notificationDataProcessPage = function (data) { + return data; }; - that.eventSignal = function eventSignal() { // What signal to hook onMessage to. - return that.isQML() ? that.tablet.fromQml : that.tablet.webEventReceived; + that.notificationPollCallback = that.ignore; + that.notificationPollCaresAboutSince = false; + that.notificationInitialCallbackMade = false; + that.notificationDisplayBanner = function (message) { + Window.displayAnnouncement(message); + }; + // + // END Notification Handling Defaults + // + + // Handlers + that.onScreenChanged = function onScreenChanged(type, url) { + // Set isOpen, wireEventBridge, set buttonActive as appropriate, + // and finally call onOpened() or onClosed() IFF defined. + that.setCurrentVisibleScreenMetadata(type, url); + if (that.checkIsOpen(type, url)) { + that.wireEventBridge(true); + if (!that.isOpen) { + that.buttonActive(true); + if (that.onOpened) { + that.onOpened(); + } + that.isOpen = true; + } + } else { // Not us. Should we do something for type Home, Menu, and particularly Closed (meaning tablet hidden? + that.wireEventBridge(false); + if (that.isOpen) { + that.buttonActive(false); + if (that.onClosed) { + that.onClosed(); + } + that.isOpen = false; + } + } + console.debug(that.buttonName + " app reports: Tablet screen changed.\nNew screen type: " + type + + "\nNew screen URL: " + url + "\nCurrent app open status: " + that.isOpen + "\n"); }; // Overwrite with the given properties: Object.keys(properties).forEach(function (key) { that[key] = properties[key]; }); + // + // START Notification Handling + // + var METAVERSE_BASE = Account.metaverseServerURL; + var currentDataPageToRetrieve = 1; + var concatenatedServerResponse = new Array(); + that.notificationPoll = function () { + if (!that.notificationPollEndpoint) { + return; + } + + // User is "appearing offline" + if (GlobalServices.findableBy === "none") { + that.notificationPollTimeout = Script.setTimeout(that.notificationPoll, that.notificationPollTimeoutMs); + return; + } + + var url = METAVERSE_BASE + that.notificationPollEndpoint; + + if (that.notificationPollCaresAboutSince) { + url = url + "&since=" + (new Date().getTime()); + } + + console.debug(that.buttonName, 'polling for notifications at endpoint', url); + + function requestCallback(error, response) { + if (error || (response.status !== 'success')) { + print("Error: unable to get", url, error || response.status); + that.notificationPollTimeout = Script.setTimeout(that.notificationPoll, that.notificationPollTimeoutMs); + return; + } + + if (!that.notificationPollStopPaginatingConditionMet || that.notificationPollStopPaginatingConditionMet(response)) { + that.notificationPollTimeout = Script.setTimeout(that.notificationPoll, that.notificationPollTimeoutMs); + + var notificationData; + if (concatenatedServerResponse.length) { + notificationData = concatenatedServerResponse; + } else { + notificationData = that.notificationDataProcessPage(response); + } + console.debug(that.buttonName, 'notification data for processing:', JSON.stringify(notificationData)); + that.notificationPollCallback(notificationData); + that.notificationInitialCallbackMade = true; + currentDataPageToRetrieve = 1; + concatenatedServerResponse = new Array(); + } else { + concatenatedServerResponse = concatenatedServerResponse.concat(that.notificationDataProcessPage(response)); + currentDataPageToRetrieve++; + request({ uri: (url + "&page=" + currentDataPageToRetrieve) }, requestCallback); + } + } + + request({ uri: url }, requestCallback); + }; + + // This won't do anything if there isn't a notification endpoint set + that.notificationPoll(); + + function availabilityChanged() { + if (that.notificationPollTimeout) { + Script.clearTimeout(that.notificationPollTimeout); + that.notificationPollTimeout = false; + } + that.notificationPoll(); + } + // + // END Notification Handling + // + // Properties: that.tablet = Tablet.getTablet(that.tabletName); // Must be after we gather properties. @@ -100,65 +235,58 @@ function AppUi(properties) { } that.button = that.tablet.addButton(buttonOptions); that.ignore = function ignore() { }; - - // Handlers - that.onScreenChanged = function onScreenChanged(type, url) { - // Set isOpen, wireEventBridge, set buttonActive as appropriate, - // and finally call onOpened() or onClosed() IFF defined. - console.debug(that.buttonName, 'onScreenChanged', type, url, that.isOpen); - if (that.checkIsOpen(type, url)) { - if (!that.isOpen) { - that.wireEventBridge(true); - that.buttonActive(true); - if (that.onOpened) { - that.onOpened(); - } - that.isOpen = true; - } - - } else { // Not us. Should we do something for type Home, Menu, and particularly Closed (meaning tablet hidden? - if (that.isOpen) { - that.wireEventBridge(false); - that.buttonActive(false); - if (that.onClosed) { - that.onClosed(); - } - that.isOpen = false; - } - } - }; - that.hasEventBridge = false; + that.hasOutboundEventBridge = false; + that.hasInboundQmlEventBridge = false; + that.hasInboundHtmlEventBridge = false; // HTML event bridge uses strings, not objects. Here we abstract over that. // (Although injected javascript still has to use JSON.stringify/JSON.parse.) - that.sendToHtml = function (messageObject) { that.tablet.emitScriptEvent(JSON.stringify(messageObject)); }; - that.fromHtml = function (messageString) { that.onMessage(JSON.parse(messageString)); }; + that.sendToHtml = function (messageObject) { + that.tablet.emitScriptEvent(JSON.stringify(messageObject)); + }; + that.fromHtml = function (messageString) { + var parsedMessage = JSON.parse(messageString); + parsedMessage.messageSrc = "HTML"; + that.onMessage(parsedMessage); + }; that.sendMessage = that.ignore; that.wireEventBridge = function wireEventBridge(on) { // Uniquivocally sets that.sendMessage(messageObject) to do the right thing. - // Sets hasEventBridge and wires onMessage to eventSignal as appropriate, IFF onMessage defined. - var handler, isQml = that.isQML(); + // Sets has*EventBridge and wires onMessage to the proper event bridge as appropriate, IFF onMessage defined. + var isCurrentlyOnQMLScreen = that.isCurrentlyOnQMLScreen(); // Outbound (always, regardless of whether there is an inbound handler). if (on) { - that.sendMessage = isQml ? that.tablet.sendToQml : that.sendToHtml; + that.sendMessage = isCurrentlyOnQMLScreen ? that.tablet.sendToQml : that.sendToHtml; + that.hasOutboundEventBridge = true; } else { that.sendMessage = that.ignore; + that.hasOutboundEventBridge = false; } - if (!that.onMessage) { return; } + if (!that.onMessage) { + return; + } // Inbound - handler = isQml ? that.onMessage : that.fromHtml; if (on) { - if (!that.hasEventBridge) { - console.debug(that.buttonName, 'connecting', that.eventSignal()); - that.eventSignal().connect(handler); - that.hasEventBridge = true; + if (isCurrentlyOnQMLScreen && !that.hasInboundQmlEventBridge) { + console.debug(that.buttonName, 'connecting', that.tablet.fromQml); + that.tablet.fromQml.connect(that.onMessage); + that.hasInboundQmlEventBridge = true; + } else if (!isCurrentlyOnQMLScreen && !that.hasInboundHtmlEventBridge) { + console.debug(that.buttonName, 'connecting', that.tablet.webEventReceived); + that.tablet.webEventReceived.connect(that.fromHtml); + that.hasInboundHtmlEventBridge = true; } } else { - if (that.hasEventBridge) { - console.debug(that.buttonName, 'disconnecting', that.eventSignal()); - that.eventSignal().disconnect(handler); - that.hasEventBridge = false; + if (that.hasInboundQmlEventBridge) { + console.debug(that.buttonName, 'disconnecting', that.tablet.fromQml); + that.tablet.fromQml.disconnect(that.onMessage); + that.hasInboundQmlEventBridge = false; + } + if (that.hasInboundHtmlEventBridge) { + console.debug(that.buttonName, 'disconnecting', that.tablet.webEventReceived); + that.tablet.webEventReceived.disconnect(that.fromHtml); + that.hasInboundHtmlEventBridge = false; } } }; @@ -175,6 +303,7 @@ function AppUi(properties) { } : that.ignore; that.onScriptEnding = function onScriptEnding() { // Close if necessary, clean up any remaining handlers, and remove the button. + GlobalServices.findableByChanged.disconnect(availabilityChanged); if (that.isOpen) { that.close(); } @@ -185,10 +314,15 @@ function AppUi(properties) { } that.tablet.removeButton(that.button); } + if (that.notificationPollTimeout) { + Script.clearInterval(that.notificationPollTimeout); + that.notificationPollTimeout = false; + } }; // Set up the handlers. that.tablet.screenChanged.connect(that.onScreenChanged); that.button.clicked.connect(that.onClicked); Script.scriptEnding.connect(that.onScriptEnding); + GlobalServices.findableByChanged.connect(availabilityChanged); } module.exports = AppUi; diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index 5939b36438..993ea30c2e 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -1,5 +1,5 @@ "use strict"; -/*jslint vars:true, plusplus:true, forin:true*/ +/* jslint vars:true, plusplus:true, forin:true */ /* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ // // wallet.js @@ -14,629 +14,529 @@ /* global getConnectionData */ (function () { // BEGIN LOCAL_SCOPE - Script.include("/~/system/libraries/accountUtils.js"); - Script.include("/~/system/libraries/connectionUtils.js"); +Script.include("/~/system/libraries/accountUtils.js"); +Script.include("/~/system/libraries/connectionUtils.js"); +var AppUi = Script.require('appUi'); - var MARKETPLACE_URL = Account.metaverseServerURL + "/marketplace"; +var MARKETPLACE_URL = Account.metaverseServerURL + "/marketplace"; - // BEGIN AVATAR SELECTOR LOGIC - var UNSELECTED_COLOR = { red: 0x1F, green: 0xC6, blue: 0xA6 }; - var SELECTED_COLOR = { red: 0xF3, green: 0x91, blue: 0x29 }; - var HOVER_COLOR = { red: 0xD0, green: 0xD0, blue: 0xD0 }; +// BEGIN AVATAR SELECTOR LOGIC +var UNSELECTED_COLOR = { red: 0x1F, green: 0xC6, blue: 0xA6 }; +var SELECTED_COLOR = { red: 0xF3, green: 0x91, blue: 0x29 }; +var HOVER_COLOR = { red: 0xD0, green: 0xD0, blue: 0xD0 }; - var overlays = {}; // Keeps track of all our extended overlay data objects, keyed by target identifier. +var overlays = {}; // Keeps track of all our extended overlay data objects, keyed by target identifier. - function ExtendedOverlay(key, type, properties) { // A wrapper around overlays to store the key it is associated with. - overlays[key] = this; - this.key = key; - this.selected = false; - this.hovering = false; - this.activeOverlay = Overlays.addOverlay(type, properties); // We could use different overlays for (un)selected... +function ExtendedOverlay(key, type, properties) { // A wrapper around overlays to store the key it is associated with. + overlays[key] = this; + this.key = key; + this.selected = false; + this.hovering = false; + this.activeOverlay = Overlays.addOverlay(type, properties); // We could use different overlays for (un)selected... +} +// Instance methods: +ExtendedOverlay.prototype.deleteOverlay = function () { // remove display and data of this overlay + Overlays.deleteOverlay(this.activeOverlay); + delete overlays[this.key]; +}; + +ExtendedOverlay.prototype.editOverlay = function (properties) { // change display of this overlay + Overlays.editOverlay(this.activeOverlay, properties); +}; + +function color(selected, hovering) { + var base = hovering ? HOVER_COLOR : selected ? SELECTED_COLOR : UNSELECTED_COLOR; + function scale(component) { + var delta = 0xFF - component; + return component; } - // Instance methods: - ExtendedOverlay.prototype.deleteOverlay = function () { // remove display and data of this overlay - Overlays.deleteOverlay(this.activeOverlay); - delete overlays[this.key]; - }; - - ExtendedOverlay.prototype.editOverlay = function (properties) { // change display of this overlay - Overlays.editOverlay(this.activeOverlay, properties); - }; - - function color(selected, hovering) { - var base = hovering ? HOVER_COLOR : selected ? SELECTED_COLOR : UNSELECTED_COLOR; - function scale(component) { - var delta = 0xFF - component; - return component; - } - return { red: scale(base.red), green: scale(base.green), blue: scale(base.blue) }; - } - // so we don't have to traverse the overlays to get the last one - var lastHoveringId = 0; - ExtendedOverlay.prototype.hover = function (hovering) { - this.hovering = hovering; - if (this.key === lastHoveringId) { - if (hovering) { - return; - } - lastHoveringId = 0; - } - this.editOverlay({ color: color(this.selected, hovering) }); + return { red: scale(base.red), green: scale(base.green), blue: scale(base.blue) }; +} +// so we don't have to traverse the overlays to get the last one +var lastHoveringId = 0; +ExtendedOverlay.prototype.hover = function (hovering) { + this.hovering = hovering; + if (this.key === lastHoveringId) { if (hovering) { - // un-hover the last hovering overlay - if (lastHoveringId && lastHoveringId !== this.key) { - ExtendedOverlay.get(lastHoveringId).hover(false); - } - lastHoveringId = this.key; - } - }; - ExtendedOverlay.prototype.select = function (selected) { - if (this.selected === selected) { return; } - - this.editOverlay({ color: color(selected, this.hovering) }); - this.selected = selected; - }; - // Class methods: - var selectedId = false; - ExtendedOverlay.isSelected = function (id) { - return selectedId === id; - }; - ExtendedOverlay.get = function (key) { // answer the extended overlay data object associated with the given avatar identifier - return overlays[key]; - }; - ExtendedOverlay.some = function (iterator) { // Bails early as soon as iterator returns truthy. - var key; - for (key in overlays) { - if (iterator(ExtendedOverlay.get(key))) { - return; - } - } - }; - ExtendedOverlay.unHover = function () { // calls hover(false) on lastHoveringId (if any) - if (lastHoveringId) { + lastHoveringId = 0; + } + this.editOverlay({ color: color(this.selected, hovering) }); + if (hovering) { + // un-hover the last hovering overlay + if (lastHoveringId && lastHoveringId !== this.key) { ExtendedOverlay.get(lastHoveringId).hover(false); } - }; + lastHoveringId = this.key; + } +}; +ExtendedOverlay.prototype.select = function (selected) { + if (this.selected === selected) { + return; + } - // hit(overlay) on the one overlay intersected by pickRay, if any. - // noHit() if no ExtendedOverlay was intersected (helps with hover) - ExtendedOverlay.applyPickRay = function (pickRay, hit, noHit) { - var pickedOverlay = Overlays.findRayIntersection(pickRay); // Depends on nearer coverOverlays to extend closer to us than farther ones. - if (!pickedOverlay.intersects) { - if (noHit) { - return noHit(); - } + this.editOverlay({ color: color(selected, this.hovering) }); + this.selected = selected; +}; +// Class methods: +var selectedId = false; +ExtendedOverlay.isSelected = function (id) { + return selectedId === id; +}; +ExtendedOverlay.get = function (key) { // answer the extended overlay data object associated with the given avatar identifier + return overlays[key]; +}; +ExtendedOverlay.some = function (iterator) { // Bails early as soon as iterator returns truthy. + var key; + for (key in overlays) { + if (iterator(ExtendedOverlay.get(key))) { return; } - ExtendedOverlay.some(function (overlay) { // See if pickedOverlay is one of ours. - if ((overlay.activeOverlay) === pickedOverlay.overlayID) { - hit(overlay); - return true; - } - }); - }; - - function addAvatarNode(id) { - return new ExtendedOverlay(id, "sphere", { - drawInFront: true, - solid: true, - alpha: 0.8, - color: color(false, false), - ignoreRayIntersection: false - }); } - - var pingPong = true; - function updateOverlays() { - var eye = Camera.position; - AvatarList.getAvatarIdentifiers().forEach(function (id) { - if (!id) { - return; // don't update ourself, or avatars we're not interested in - } - var avatar = AvatarList.getAvatar(id); - if (!avatar) { - return; // will be deleted below if there had been an overlay. - } - var overlay = ExtendedOverlay.get(id); - if (!overlay) { // For now, we're treating this as a temporary loss, as from the personal space bubble. Add it back. - overlay = addAvatarNode(id); - } - var target = avatar.position; - var distance = Vec3.distance(target, eye); - var offset = 0.2; - var diff = Vec3.subtract(target, eye); // get diff between target and eye (a vector pointing to the eye from avatar position) - var headIndex = avatar.getJointIndex("Head"); // base offset on 1/2 distance from hips to head if we can - if (headIndex > 0) { - offset = avatar.getAbsoluteJointTranslationInObjectFrame(headIndex).y / 2; - } - - // move a bit in front, towards the camera - target = Vec3.subtract(target, Vec3.multiply(Vec3.normalize(diff), offset)); - - // now bump it up a bit - target.y = target.y + offset; - - overlay.ping = pingPong; - overlay.editOverlay({ - color: color(ExtendedOverlay.isSelected(id), overlay.hovering), - position: target, - dimensions: 0.032 * distance - }); - }); - pingPong = !pingPong; - ExtendedOverlay.some(function (overlay) { // Remove any that weren't updated. (User is gone.) - if (overlay.ping === pingPong) { - overlay.deleteOverlay(); - } - }); - } - function removeOverlays() { - selectedId = false; - lastHoveringId = 0; - ExtendedOverlay.some(function (overlay) { - overlay.deleteOverlay(); - }); +}; +ExtendedOverlay.unHover = function () { // calls hover(false) on lastHoveringId (if any) + if (lastHoveringId) { + ExtendedOverlay.get(lastHoveringId).hover(false); } +}; - // - // Clicks. - // - function usernameFromIDReply(id, username, machineFingerprint, isAdmin) { - if (selectedId === id) { - var message = { - method: 'updateSelectedRecipientUsername', - userName: username === "" ? "unknown username" : username - }; - sendToQml(message); +// hit(overlay) on the one overlay intersected by pickRay, if any. +// noHit() if no ExtendedOverlay was intersected (helps with hover) +ExtendedOverlay.applyPickRay = function (pickRay, hit, noHit) { + var pickedOverlay = Overlays.findRayIntersection(pickRay); // Depends on nearer coverOverlays to extend closer to us than farther ones. + if (!pickedOverlay.intersects) { + if (noHit) { + return noHit(); } + return; } - function handleClick(pickRay) { - ExtendedOverlay.applyPickRay(pickRay, function (overlay) { - var nextSelectedStatus = !overlay.selected; - var avatarId = overlay.key; - selectedId = nextSelectedStatus ? avatarId : false; - if (nextSelectedStatus) { - Users.requestUsernameFromID(avatarId); - } - var message = { - method: 'selectRecipient', - id: avatarId, - isSelected: nextSelectedStatus, - displayName: '"' + AvatarList.getAvatar(avatarId).sessionDisplayName + '"', - userName: '' - }; - sendToQml(message); - - ExtendedOverlay.some(function (overlay) { - var id = overlay.key; - var selected = ExtendedOverlay.isSelected(id); - overlay.select(selected); - }); - + ExtendedOverlay.some(function (overlay) { // See if pickedOverlay is one of ours. + if ((overlay.activeOverlay) === pickedOverlay.overlayID) { + hit(overlay); return true; - }); - } - function handleMouseEvent(mousePressEvent) { // handleClick if we get one. - if (!mousePressEvent.isLeftButton) { - return; } - handleClick(Camera.computePickRay(mousePressEvent.x, mousePressEvent.y)); + }); +}; + +function addAvatarNode(id) { + return new ExtendedOverlay(id, "sphere", { + drawInFront: true, + solid: true, + alpha: 0.8, + color: color(false, false), + ignoreRayIntersection: false + }); +} + +var pingPong = true; +function updateOverlays() { + var eye = Camera.position; + AvatarList.getAvatarIdentifiers().forEach(function (id) { + if (!id) { + return; // don't update ourself, or avatars we're not interested in + } + var avatar = AvatarList.getAvatar(id); + if (!avatar) { + return; // will be deleted below if there had been an overlay. + } + var overlay = ExtendedOverlay.get(id); + if (!overlay) { // For now, we're treating this as a temporary loss, as from the personal space bubble. Add it back. + overlay = addAvatarNode(id); + } + var target = avatar.position; + var distance = Vec3.distance(target, eye); + var offset = 0.2; + var diff = Vec3.subtract(target, eye); // get diff between target and eye (a vector pointing to the eye from avatar position) + var headIndex = avatar.getJointIndex("Head"); // base offset on 1/2 distance from hips to head if we can + if (headIndex > 0) { + offset = avatar.getAbsoluteJointTranslationInObjectFrame(headIndex).y / 2; + } + + // move a bit in front, towards the camera + target = Vec3.subtract(target, Vec3.multiply(Vec3.normalize(diff), offset)); + + // now bump it up a bit + target.y = target.y + offset; + + overlay.ping = pingPong; + overlay.editOverlay({ + color: color(ExtendedOverlay.isSelected(id), overlay.hovering), + position: target, + dimensions: 0.032 * distance + }); + }); + pingPong = !pingPong; + ExtendedOverlay.some(function (overlay) { // Remove any that weren't updated. (User is gone.) + if (overlay.ping === pingPong) { + overlay.deleteOverlay(); + } + }); +} +function removeOverlays() { + selectedId = false; + lastHoveringId = 0; + ExtendedOverlay.some(function (overlay) { + overlay.deleteOverlay(); + }); +} + +// +// Clicks. +// +function usernameFromIDReply(id, username, machineFingerprint, isAdmin) { + if (selectedId === id) { + var message = { + method: 'updateSelectedRecipientUsername', + userName: username === "" ? "unknown username" : username + }; + ui.sendMessage(message); } - function handleMouseMove(pickRay) { // given the pickRay, just do the hover logic - ExtendedOverlay.applyPickRay(pickRay, function (overlay) { - overlay.hover(true); - }, function () { +} +function handleClick(pickRay) { + ExtendedOverlay.applyPickRay(pickRay, function (overlay) { + var nextSelectedStatus = !overlay.selected; + var avatarId = overlay.key; + selectedId = nextSelectedStatus ? avatarId : false; + if (nextSelectedStatus) { + Users.requestUsernameFromID(avatarId); + } + var message = { + method: 'selectRecipient', + id: avatarId, + isSelected: nextSelectedStatus, + displayName: '"' + AvatarList.getAvatar(avatarId).sessionDisplayName + '"', + userName: '' + }; + ui.sendMessage(message); + + ExtendedOverlay.some(function (overlay) { + var id = overlay.key; + var selected = ExtendedOverlay.isSelected(id); + overlay.select(selected); + }); + + return true; + }); +} +function handleMouseEvent(mousePressEvent) { // handleClick if we get one. + if (!mousePressEvent.isLeftButton) { + return; + } + handleClick(Camera.computePickRay(mousePressEvent.x, mousePressEvent.y)); +} +function handleMouseMove(pickRay) { // given the pickRay, just do the hover logic + ExtendedOverlay.applyPickRay(pickRay, function (overlay) { + overlay.hover(true); + }, function () { + ExtendedOverlay.unHover(); + }); +} + +// handy global to keep track of which hand is the mouse (if any) +var currentHandPressed = 0; +var TRIGGER_CLICK_THRESHOLD = 0.85; +var TRIGGER_PRESS_THRESHOLD = 0.05; + +function handleMouseMoveEvent(event) { // find out which overlay (if any) is over the mouse position + var pickRay; + if (HMD.active) { + if (currentHandPressed !== 0) { + pickRay = controllerComputePickRay(currentHandPressed); + } else { + // nothing should hover, so ExtendedOverlay.unHover(); + return; + } + } else { + pickRay = Camera.computePickRay(event.x, event.y); + } + handleMouseMove(pickRay); +} +function handleTriggerPressed(hand, value) { + // The idea is if you press one trigger, it is the one + // we will consider the mouse. Even if the other is pressed, + // we ignore it until this one is no longer pressed. + var isPressed = value > TRIGGER_PRESS_THRESHOLD; + if (currentHandPressed === 0) { + currentHandPressed = isPressed ? hand : 0; + return; + } + if (currentHandPressed === hand) { + currentHandPressed = isPressed ? hand : 0; + return; + } + // otherwise, the other hand is still triggered + // so do nothing. +} + +// We get mouseMoveEvents from the handControllers, via handControllerPointer. +// But we don't get mousePressEvents. +var triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click'); +var triggerPressMapping = Controller.newMapping(Script.resolvePath('') + '-press'); +function controllerComputePickRay(hand) { + var controllerPose = getControllerWorldLocation(hand, true); + if (controllerPose.valid) { + return { origin: controllerPose.position, direction: Quat.getUp(controllerPose.orientation) }; + } +} +function makeClickHandler(hand) { + return function (clicked) { + if (clicked > TRIGGER_CLICK_THRESHOLD) { + var pickRay = controllerComputePickRay(hand); + handleClick(pickRay); + } + }; +} +function makePressHandler(hand) { + return function (value) { + handleTriggerPressed(hand, value); + }; +} +triggerMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand)); +triggerMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand)); +triggerPressMapping.from(Controller.Standard.RT).peek().to(makePressHandler(Controller.Standard.RightHand)); +triggerPressMapping.from(Controller.Standard.LT).peek().to(makePressHandler(Controller.Standard.LeftHand)); +// END AVATAR SELECTOR LOGIC + +var sendMoneyRecipient; +var sendMoneyParticleEffectUpdateTimer; +var particleEffectTimestamp; +var sendMoneyParticleEffect; +var SEND_MONEY_PARTICLE_TIMER_UPDATE = 250; +var SEND_MONEY_PARTICLE_EMITTING_DURATION = 3000; +var SEND_MONEY_PARTICLE_LIFETIME_SECONDS = 8; +var SEND_MONEY_PARTICLE_PROPERTIES = { + accelerationSpread: { x: 0, y: 0, z: 0 }, + alpha: 1, + alphaFinish: 1, + alphaSpread: 0, + alphaStart: 1, + azimuthFinish: 0, + azimuthStart: -6, + color: { red: 143, green: 5, blue: 255 }, + colorFinish: { red: 255, green: 0, blue: 204 }, + colorSpread: { red: 0, green: 0, blue: 0 }, + colorStart: { red: 0, green: 136, blue: 255 }, + emitAcceleration: { x: 0, y: 0, z: 0 }, // Immediately gets updated to be accurate + emitDimensions: { x: 0, y: 0, z: 0 }, + emitOrientation: { x: 0, y: 0, z: 0 }, + emitRate: 4, + emitSpeed: 2.1, + emitterShouldTrail: true, + isEmitting: 1, + lifespan: SEND_MONEY_PARTICLE_LIFETIME_SECONDS + 1, // Immediately gets updated to be accurate + lifetime: SEND_MONEY_PARTICLE_LIFETIME_SECONDS + 1, + maxParticles: 20, + name: 'hfc-particles', + particleRadius: 0.2, + polarFinish: 0, + polarStart: 0, + radiusFinish: 0.05, + radiusSpread: 0, + radiusStart: 0.2, + speedSpread: 0, + textures: "http://hifi-content.s3.amazonaws.com/alan/dev/Particles/Bokeh-Particle-HFC.png", + type: 'ParticleEffect' +}; + +var MS_PER_SEC = 1000; +function updateSendMoneyParticleEffect() { + var timestampNow = Date.now(); + if ((timestampNow - particleEffectTimestamp) > (SEND_MONEY_PARTICLE_LIFETIME_SECONDS * MS_PER_SEC)) { + deleteSendMoneyParticleEffect(); + return; + } else if ((timestampNow - particleEffectTimestamp) > SEND_MONEY_PARTICLE_EMITTING_DURATION) { + Entities.editEntity(sendMoneyParticleEffect, { + isEmitting: 0 + }); + } else if (sendMoneyParticleEffect) { + var recipientPosition = AvatarList.getAvatar(sendMoneyRecipient).position; + var distance = Vec3.distance(recipientPosition, MyAvatar.position); + var accel = Vec3.subtract(recipientPosition, MyAvatar.position); + accel.y -= 3.0; + var life = Math.sqrt(2 * distance / Vec3.length(accel)); + Entities.editEntity(sendMoneyParticleEffect, { + emitAcceleration: accel, + lifespan: life }); } +} - // handy global to keep track of which hand is the mouse (if any) - var currentHandPressed = 0; - var TRIGGER_CLICK_THRESHOLD = 0.85; - var TRIGGER_PRESS_THRESHOLD = 0.05; - - function handleMouseMoveEvent(event) { // find out which overlay (if any) is over the mouse position - var pickRay; - if (HMD.active) { - if (currentHandPressed !== 0) { - pickRay = controllerComputePickRay(currentHandPressed); - } else { - // nothing should hover, so - ExtendedOverlay.unHover(); - return; - } - } else { - pickRay = Camera.computePickRay(event.x, event.y); - } - handleMouseMove(pickRay); +function deleteSendMoneyParticleEffect() { + if (sendMoneyParticleEffectUpdateTimer) { + Script.clearInterval(sendMoneyParticleEffectUpdateTimer); + sendMoneyParticleEffectUpdateTimer = null; } - function handleTriggerPressed(hand, value) { - // The idea is if you press one trigger, it is the one - // we will consider the mouse. Even if the other is pressed, - // we ignore it until this one is no longer pressed. - var isPressed = value > TRIGGER_PRESS_THRESHOLD; - if (currentHandPressed === 0) { - currentHandPressed = isPressed ? hand : 0; - return; - } - if (currentHandPressed === hand) { - currentHandPressed = isPressed ? hand : 0; - return; - } - // otherwise, the other hand is still triggered - // so do nothing. + if (sendMoneyParticleEffect) { + sendMoneyParticleEffect = Entities.deleteEntity(sendMoneyParticleEffect); } + sendMoneyRecipient = null; +} - // We get mouseMoveEvents from the handControllers, via handControllerPointer. - // But we don't get mousePressEvents. - var triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click'); - var triggerPressMapping = Controller.newMapping(Script.resolvePath('') + '-press'); - function controllerComputePickRay(hand) { - var controllerPose = getControllerWorldLocation(hand, true); - if (controllerPose.valid) { - return { origin: controllerPose.position, direction: Quat.getUp(controllerPose.orientation) }; - } +function onUsernameChanged() { + if (Account.username !== Settings.getValue("wallet/savedUsername")) { + Settings.setValue("wallet/autoLogout", false); + Settings.setValue("wallet/savedUsername", ""); } - function makeClickHandler(hand) { - return function (clicked) { - if (clicked > TRIGGER_CLICK_THRESHOLD) { - var pickRay = controllerComputePickRay(hand); - handleClick(pickRay); - } - }; - } - function makePressHandler(hand) { - return function (value) { - handleTriggerPressed(hand, value); - }; - } - triggerMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand)); - triggerMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand)); - triggerPressMapping.from(Controller.Standard.RT).peek().to(makePressHandler(Controller.Standard.RightHand)); - triggerPressMapping.from(Controller.Standard.LT).peek().to(makePressHandler(Controller.Standard.LeftHand)); - // END AVATAR SELECTOR LOGIC +} - // Function Name: onButtonClicked() - // - // Description: - // -Fired when the app button is pressed. - // - // Relevant Variables: - // -WALLET_QML_SOURCE: The path to the Wallet QML - // -onWalletScreen: true/false depending on whether we're looking at the app. - var WALLET_QML_SOURCE = "hifi/commerce/wallet/Wallet.qml"; - var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml"; - var onWalletScreen = false; - function onButtonClicked() { - if (!tablet) { - print("Warning in buttonClicked(): 'tablet' undefined!"); - return; +// Function Name: fromQml() +// +// Description: +// -Called when a message is received from SpectatorCamera.qml. The "message" argument is what is sent from the QML +// in the format "{method, params}", like json-rpc. See also sendToQml(). +var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml"; +var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js"); +function fromQml(message) { + switch (message.method) { + case 'passphrasePopup_cancelClicked': + case 'needsLogIn_cancelClicked': + ui.close(); + break; + case 'walletSetup_cancelClicked': + switch (message.referrer) { + case '': // User clicked "Wallet" app + case undefined: + case null: + ui.close(); + break; + case 'purchases': + case 'marketplace cta': + case 'mainPage': + ui.open(MARKETPLACE_URL, MARKETPLACES_INJECT_SCRIPT_URL); + break; + default: // User needs to return to an individual marketplace item URL + ui.open(MARKETPLACE_URL + '/items/' + message.referrer, MARKETPLACES_INJECT_SCRIPT_URL); + break; } - if (onWalletScreen) { - // for toolbar-mode: go back to home screen, this will close the window. - tablet.gotoHomeScreen(); - } else { - tablet.loadQMLSource(WALLET_QML_SOURCE); - } - } - - // Function Name: sendToQml() - // - // Description: - // -Use this function to send a message to the QML (i.e. to change appearances). The "message" argument is what is sent to - // the QML in the format "{method, params}", like json-rpc. See also fromQml(). - function sendToQml(message) { - tablet.sendToQml(message); - } - - var sendMoneyRecipient; - var sendMoneyParticleEffectUpdateTimer; - var particleEffectTimestamp; - var sendMoneyParticleEffect; - var SEND_MONEY_PARTICLE_TIMER_UPDATE = 250; - var SEND_MONEY_PARTICLE_EMITTING_DURATION = 3000; - var SEND_MONEY_PARTICLE_LIFETIME_SECONDS = 8; - var SEND_MONEY_PARTICLE_PROPERTIES = { - accelerationSpread: { x: 0, y: 0, z: 0 }, - alpha: 1, - alphaFinish: 1, - alphaSpread: 0, - alphaStart: 1, - azimuthFinish: 0, - azimuthStart: -6, - color: { red: 143, green: 5, blue: 255 }, - colorFinish: { red: 255, green: 0, blue: 204 }, - colorSpread: { red: 0, green: 0, blue: 0 }, - colorStart: { red: 0, green: 136, blue: 255 }, - emitAcceleration: { x: 0, y: 0, z: 0 }, // Immediately gets updated to be accurate - emitDimensions: { x: 0, y: 0, z: 0 }, - emitOrientation: { x: 0, y: 0, z: 0 }, - emitRate: 4, - emitSpeed: 2.1, - emitterShouldTrail: true, - isEmitting: 1, - lifespan: SEND_MONEY_PARTICLE_LIFETIME_SECONDS + 1, // Immediately gets updated to be accurate - lifetime: SEND_MONEY_PARTICLE_LIFETIME_SECONDS + 1, - maxParticles: 20, - name: 'hfc-particles', - particleRadius: 0.2, - polarFinish: 0, - polarStart: 0, - radiusFinish: 0.05, - radiusSpread: 0, - radiusStart: 0.2, - speedSpread: 0, - textures: "http://hifi-content.s3.amazonaws.com/alan/dev/Particles/Bokeh-Particle-HFC.png", - type: 'ParticleEffect' - }; - - function updateSendMoneyParticleEffect() { - var timestampNow = Date.now(); - if ((timestampNow - particleEffectTimestamp) > (SEND_MONEY_PARTICLE_LIFETIME_SECONDS * 1000)) { - deleteSendMoneyParticleEffect(); - return; - } else if ((timestampNow - particleEffectTimestamp) > SEND_MONEY_PARTICLE_EMITTING_DURATION) { - Entities.editEntity(sendMoneyParticleEffect, { - isEmitting: 0 - }); - } else if (sendMoneyParticleEffect) { - var recipientPosition = AvatarList.getAvatar(sendMoneyRecipient).position; - var distance = Vec3.distance(recipientPosition, MyAvatar.position); - var accel = Vec3.subtract(recipientPosition, MyAvatar.position); - accel.y -= 3.0; - var life = Math.sqrt(2 * distance / Vec3.length(accel)); - Entities.editEntity(sendMoneyParticleEffect, { - emitAcceleration: accel, - lifespan: life - }); - } - } - - function deleteSendMoneyParticleEffect() { - if (sendMoneyParticleEffectUpdateTimer) { - Script.clearInterval(sendMoneyParticleEffectUpdateTimer); - sendMoneyParticleEffectUpdateTimer = null; - } - if (sendMoneyParticleEffect) { - sendMoneyParticleEffect = Entities.deleteEntity(sendMoneyParticleEffect); - } - sendMoneyRecipient = null; - } - - function onUsernameChanged() { - if (Account.username !== Settings.getValue("wallet/savedUsername")) { - Settings.setValue("wallet/autoLogout", false); - Settings.setValue("wallet/savedUsername", ""); - } - } - - // Function Name: fromQml() - // - // Description: - // -Called when a message is received from SpectatorCamera.qml. The "message" argument is what is sent from the QML - // in the format "{method, params}", like json-rpc. See also sendToQml(). - var isHmdPreviewDisabled = true; - var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js"); - - function fromQml(message) { - switch (message.method) { - case 'passphrasePopup_cancelClicked': - case 'needsLogIn_cancelClicked': - tablet.gotoHomeScreen(); - break; - case 'walletSetup_cancelClicked': - switch (message.referrer) { - case '': // User clicked "Wallet" app - case undefined: - case null: - tablet.gotoHomeScreen(); - break; - case 'purchases': - case 'marketplace cta': - case 'mainPage': - tablet.gotoWebScreen(MARKETPLACE_URL, MARKETPLACES_INJECT_SCRIPT_URL); - break; - default: // User needs to return to an individual marketplace item URL - tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.referrer, MARKETPLACES_INJECT_SCRIPT_URL); - break; - } - break; - case 'needsLogIn_loginClicked': - openLoginWindow(); - break; - case 'disableHmdPreview': - break; // do nothing here, handled in marketplaces.js - case 'maybeEnableHmdPreview': - break; // do nothing here, handled in marketplaces.js - case 'passphraseReset': - onButtonClicked(); - onButtonClicked(); - break; - case 'walletReset': - Settings.setValue("isFirstUseOfPurchases", true); - onButtonClicked(); - onButtonClicked(); - break; - case 'transactionHistory_linkClicked': - tablet.gotoWebScreen(message.marketplaceLink, MARKETPLACES_INJECT_SCRIPT_URL); - break; - case 'goToPurchases_fromWalletHome': - case 'goToPurchases': - tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH); - break; - case 'goToMarketplaceMainPage': - tablet.gotoWebScreen(MARKETPLACE_URL, MARKETPLACES_INJECT_SCRIPT_URL); - break; - case 'goToMarketplaceItemPage': - tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.itemId, MARKETPLACES_INJECT_SCRIPT_URL); - break; - case 'refreshConnections': - print('Refreshing Connections...'); - getConnectionData(false); - break; - case 'enable_ChooseRecipientNearbyMode': - if (!isUpdateOverlaysWired) { - Script.update.connect(updateOverlays); - isUpdateOverlaysWired = true; - } - break; - case 'disable_ChooseRecipientNearbyMode': - if (isUpdateOverlaysWired) { - Script.update.disconnect(updateOverlays); - isUpdateOverlaysWired = false; - } - removeOverlays(); - break; - case 'sendAsset_sendPublicly': - if (message.assetName === "") { - deleteSendMoneyParticleEffect(); - sendMoneyRecipient = message.recipient; - var amount = message.amount; - var props = SEND_MONEY_PARTICLE_PROPERTIES; - props.parentID = MyAvatar.sessionUUID; - props.position = MyAvatar.position; - props.position.y += 0.2; - if (message.effectImage) { - props.textures = message.effectImage; - } - sendMoneyParticleEffect = Entities.addEntity(props, true); - particleEffectTimestamp = Date.now(); - updateSendMoneyParticleEffect(); - sendMoneyParticleEffectUpdateTimer = Script.setInterval(updateSendMoneyParticleEffect, SEND_MONEY_PARTICLE_TIMER_UPDATE); - } - break; - case 'transactionHistory_goToBank': - if (Account.metaverseServerURL.indexOf("staging") >= 0) { - Window.location = "hifi://hifiqa-master-metaverse-staging"; // So that we can test in staging. - } else { - Window.location = "hifi://BankOfHighFidelity"; - } - break; - case 'wallet_availableUpdatesReceived': - // NOP - break; - case 'http.request': - // Handled elsewhere, don't log. - break; - default: - print('Unrecognized message from QML:', JSON.stringify(message)); - } - } - - // Function Name: wireEventBridge() - // - // Description: - // -Used to connect/disconnect the script's response to the tablet's "fromQml" signal. Set the "on" argument to enable or - // disable to event bridge. - // - // Relevant Variables: - // -hasEventBridge: true/false depending on whether we've already connected the event bridge. - var hasEventBridge = false; - function wireEventBridge(on) { - if (!tablet) { - print("Warning in wireEventBridge(): 'tablet' undefined!"); - return; - } - if (on) { - if (!hasEventBridge) { - tablet.fromQml.connect(fromQml); - hasEventBridge = true; - } - } else { - if (hasEventBridge) { - tablet.fromQml.disconnect(fromQml); - hasEventBridge = false; - } - } - } - - // Function Name: onTabletScreenChanged() - // - // Description: - // -Called when the TabletScriptingInterface::screenChanged() signal is emitted. The "type" argument can be either the string - // value of "Home", "Web", "Menu", "QML", or "Closed". The "url" argument is only valid for Web and QML. - function onTabletScreenChanged(type, url) { - onWalletScreen = (type === "QML" && url === WALLET_QML_SOURCE); - wireEventBridge(onWalletScreen); - // Change button to active when window is first openend, false otherwise. - if (button) { - button.editProperties({ isActive: onWalletScreen }); - } - - if (onWalletScreen) { - if (!isWired) { - Users.usernameFromIDReply.connect(usernameFromIDReply); - Controller.mousePressEvent.connect(handleMouseEvent); - Controller.mouseMoveEvent.connect(handleMouseMoveEvent); - triggerMapping.enable(); - triggerPressMapping.enable(); - } - isWired = true; - } else { - off(); - } - } - - // - // Manage the connection between the button and the window. - // - var button; - var buttonName = "WALLET"; - var tablet = null; - var walletEnabled = Settings.getValue("commerce", true); - function startup() { - GlobalServices.myUsernameChanged.connect(onUsernameChanged); - if (walletEnabled) { - tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - button = tablet.addButton({ - text: buttonName, - icon: "icons/tablet-icons/wallet-i.svg", - activeIcon: "icons/tablet-icons/wallet-a.svg", - sortOrder: 10 - }); - button.clicked.connect(onButtonClicked); - tablet.screenChanged.connect(onTabletScreenChanged); - } - } - var isWired = false; - var isUpdateOverlaysWired = false; - function off() { - if (isWired) { - Users.usernameFromIDReply.disconnect(usernameFromIDReply); - Controller.mousePressEvent.disconnect(handleMouseEvent); - Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent); - triggerMapping.disable(); - triggerPressMapping.disable(); - - isWired = false; + break; + case 'needsLogIn_loginClicked': + openLoginWindow(); + break; + case 'disableHmdPreview': + break; // do nothing here, handled in marketplaces.js + case 'maybeEnableHmdPreview': + break; // do nothing here, handled in marketplaces.js + case 'transactionHistory_linkClicked': + ui.open(message.marketplaceLink, MARKETPLACES_INJECT_SCRIPT_URL); + break; + case 'goToPurchases_fromWalletHome': + case 'goToPurchases': + ui.open(MARKETPLACE_PURCHASES_QML_PATH); + break; + case 'goToMarketplaceMainPage': + ui.open(MARKETPLACE_URL, MARKETPLACES_INJECT_SCRIPT_URL); + break; + case 'goToMarketplaceItemPage': + ui.open(MARKETPLACE_URL + '/items/' + message.itemId, MARKETPLACES_INJECT_SCRIPT_URL); + break; + case 'refreshConnections': + print('Refreshing Connections...'); + getConnectionData(false); + break; + case 'enable_ChooseRecipientNearbyMode': + if (!isUpdateOverlaysWired) { + Script.update.connect(updateOverlays); + isUpdateOverlaysWired = true; } + break; + case 'disable_ChooseRecipientNearbyMode': if (isUpdateOverlaysWired) { Script.update.disconnect(updateOverlays); isUpdateOverlaysWired = false; } removeOverlays(); - } - function shutdown() { - GlobalServices.myUsernameChanged.disconnect(onUsernameChanged); - button.clicked.disconnect(onButtonClicked); - tablet.removeButton(button); - deleteSendMoneyParticleEffect(); - if (tablet) { - tablet.screenChanged.disconnect(onTabletScreenChanged); - if (onWalletScreen) { - tablet.gotoHomeScreen(); + break; + case 'sendAsset_sendPublicly': + if (message.assetName === "") { + deleteSendMoneyParticleEffect(); + sendMoneyRecipient = message.recipient; + var amount = message.amount; + var props = SEND_MONEY_PARTICLE_PROPERTIES; + props.parentID = MyAvatar.sessionUUID; + props.position = MyAvatar.position; + props.position.y += 0.2; + if (message.effectImage) { + props.textures = message.effectImage; } + sendMoneyParticleEffect = Entities.addEntity(props, true); + particleEffectTimestamp = Date.now(); + updateSendMoneyParticleEffect(); + sendMoneyParticleEffectUpdateTimer = Script.setInterval(updateSendMoneyParticleEffect, SEND_MONEY_PARTICLE_TIMER_UPDATE); } - off(); + break; + case 'transactionHistory_goToBank': + if (Account.metaverseServerURL.indexOf("staging") >= 0) { + Window.location = "hifi://hifiqa-master-metaverse-staging"; // So that we can test in staging. + } else { + Window.location = "hifi://BankOfHighFidelity"; + } + break; + case 'wallet_availableUpdatesReceived': + // NOP + break; + case 'http.request': + // Handled elsewhere, don't log. + break; + default: + print('Unrecognized message from QML:', JSON.stringify(message)); } +} - // - // Run the functions. - // - startup(); - Script.scriptEnding.connect(shutdown); +function walletOpened() { + Users.usernameFromIDReply.connect(usernameFromIDReply); + Controller.mousePressEvent.connect(handleMouseEvent); + Controller.mouseMoveEvent.connect(handleMouseMoveEvent); + triggerMapping.enable(); + triggerPressMapping.enable(); +} +function walletClosed() { + off(); +} + +// +// Manage the connection between the button and the window. +// +var BUTTON_NAME = "WALLET"; +var WALLET_QML_SOURCE = "hifi/commerce/wallet/Wallet.qml"; +var ui; +function startup() { + ui = new AppUi({ + buttonName: BUTTON_NAME, + sortOrder: 10, + home: WALLET_QML_SOURCE, + onOpened: walletOpened, + onClosed: walletClosed, + onMessage: fromQml + }); + GlobalServices.myUsernameChanged.connect(onUsernameChanged); +} +var isUpdateOverlaysWired = false; +function off() { + Users.usernameFromIDReply.disconnect(usernameFromIDReply); + Controller.mousePressEvent.disconnect(handleMouseEvent); + Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent); + triggerMapping.disable(); + triggerPressMapping.disable(); + + if (isUpdateOverlaysWired) { + Script.update.disconnect(updateOverlays); + isUpdateOverlaysWired = false; + } + removeOverlays(); +} +function shutdown() { + GlobalServices.myUsernameChanged.disconnect(onUsernameChanged); + deleteSendMoneyParticleEffect(); + off(); +} + +// +// Run the functions. +// +startup(); +Script.scriptEnding.connect(shutdown); }()); // END LOCAL_SCOPE diff --git a/scripts/system/controllers/controllerModules/highlightNearbyEntities.js b/scripts/system/controllers/controllerModules/highlightNearbyEntities.js index bc09ebee7a..3a33082f64 100644 --- a/scripts/system/controllers/controllerModules/highlightNearbyEntities.js +++ b/scripts/system/controllers/controllerModules/highlightNearbyEntities.js @@ -37,7 +37,7 @@ this.highlightedEntities = []; this.parameters = dispatcherUtils.makeDispatcherModuleParameters( - 480, + 120, this.hand === dispatcherUtils.RIGHT_HAND ? ["rightHand"] : ["leftHand"], [], 100); diff --git a/scripts/system/controllers/controllerModules/inEditMode.js b/scripts/system/controllers/controllerModules/inEditMode.js index d590545532..2bdd89f141 100644 --- a/scripts/system/controllers/controllerModules/inEditMode.js +++ b/scripts/system/controllers/controllerModules/inEditMode.js @@ -29,7 +29,7 @@ Script.include("/~/system/libraries/utils.js"); this.reticleMaxY; this.parameters = makeDispatcherModuleParameters( - 160, + 200, this.hand === RIGHT_HAND ? ["rightHand", "rightHandEquip", "rightHandTrigger"] : ["leftHand", "leftHandEquip", "leftHandTrigger"], [], 100, diff --git a/scripts/system/controllers/controllerModules/inVREditMode.js b/scripts/system/controllers/controllerModules/inVREditMode.js index 7b78d5e1c4..02863cf935 100644 --- a/scripts/system/controllers/controllerModules/inVREditMode.js +++ b/scripts/system/controllers/controllerModules/inVREditMode.js @@ -21,7 +21,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); this.disableModules = false; var NO_HAND_LASER = -1; // Invalid hand parameter so that default laser is not displayed. this.parameters = makeDispatcherModuleParameters( - 200, // Not too high otherwise the tablet laser doesn't work. + 240, // Not too high otherwise the tablet laser doesn't work. this.hand === RIGHT_HAND ? ["rightHand", "rightHandEquip", "rightHandTrigger"] : ["leftHand", "leftHandEquip", "leftHandTrigger"], diff --git a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js index a8de76aebd..27c1b458b8 100644 --- a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js @@ -26,7 +26,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); this.hapticTargetID = null; this.parameters = makeDispatcherModuleParameters( - 500, + 140, this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], [], 100); diff --git a/scripts/system/controllers/controllerModules/nearGrabHyperLinkEntity.js b/scripts/system/controllers/controllerModules/nearGrabHyperLinkEntity.js index 962ae89bb9..366fcd3032 100644 --- a/scripts/system/controllers/controllerModules/nearGrabHyperLinkEntity.js +++ b/scripts/system/controllers/controllerModules/nearGrabHyperLinkEntity.js @@ -21,7 +21,7 @@ this.hyperlink = ""; this.parameters = makeDispatcherModuleParameters( - 485, + 125, this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], [], 100); diff --git a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js index 035c150a5d..f805dbf60e 100644 --- a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js @@ -24,6 +24,9 @@ Script.include("/~/system/libraries/controllers.js"); // XXX this.ignoreIK = (grabbableData.ignoreIK !== undefined) ? grabbableData.ignoreIK : true; // XXX this.kinematicGrab = (grabbableData.kinematic !== undefined) ? grabbableData.kinematic : NEAR_GRABBING_KINEMATIC; + // this offset needs to match the one in libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp:378 + var GRAB_POINT_SPHERE_OFFSET = { x: 0.04, y: 0.13, z: 0.039 }; // x = upward, y = forward, z = lateral + function getGrabOffset(handController) { var offset = GRAB_POINT_SPHERE_OFFSET; if (handController === Controller.Standard.LeftHand) { @@ -54,7 +57,7 @@ Script.include("/~/system/libraries/controllers.js"); this.cloneAllowed = true; this.parameters = makeDispatcherModuleParameters( - 500, + 140, this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], [], 100); diff --git a/scripts/system/controllers/controllerModules/nearTrigger.js b/scripts/system/controllers/controllerModules/nearTrigger.js index 6a9cd9fbcd..f1126dedc3 100644 --- a/scripts/system/controllers/controllerModules/nearTrigger.js +++ b/scripts/system/controllers/controllerModules/nearTrigger.js @@ -29,7 +29,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); this.startSent = false; this.parameters = makeDispatcherModuleParameters( - 480, + 120, this.hand === RIGHT_HAND ? ["rightHandTrigger", "rightHand"] : ["leftHandTrigger", "leftHand"], [], 100); diff --git a/scripts/system/controllers/controllerModules/teleport.js b/scripts/system/controllers/controllerModules/teleport.js index e4dd1c43fa..deaa934f99 100644 --- a/scripts/system/controllers/controllerModules/teleport.js +++ b/scripts/system/controllers/controllerModules/teleport.js @@ -62,40 +62,51 @@ Script.include("/~/system/libraries/controllers.js"); alpha: 1, width: 0.025 }; + var teleportPath = { color: COLORS_TELEPORT_CAN_TELEPORT, alpha: 1, width: 0.025 }; + var seatPath = { color: COLORS_TELEPORT_SEAT, alpha: 1, width: 0.025 }; + var teleportEnd = { type: "model", url: TARGET_MODEL_URL, dimensions: TARGET_MODEL_DIMENSIONS, ignorePickIntersection: true }; + var seatEnd = { type: "model", url: SEAT_MODEL_URL, dimensions: TARGET_MODEL_DIMENSIONS, ignorePickIntersection: true }; - - + + var collisionEnd = { + type: "shape", + shape: "box", + dimensions: { x: 1.0, y: 0.001, z: 1.0 }, + alpha: 0.0, + ignorePickIntersection: true + }; + var teleportRenderStates = [{name: "cancel", path: cancelPath}, {name: "teleport", path: teleportPath, end: teleportEnd}, - {name: "seat", path: seatPath, end: seatEnd}]; + {name: "seat", path: seatPath, end: seatEnd}, + {name: "collision", end: collisionEnd}]; var DEFAULT_DISTANCE = 8.0; var teleportDefaultRenderStates = [{name: "cancel", distance: DEFAULT_DISTANCE, path: cancelPath}]; var ignoredEntities = []; - var TELEPORTER_STATES = { IDLE: 'idle', TARGETTING: 'targetting', @@ -104,8 +115,9 @@ Script.include("/~/system/libraries/controllers.js"); var TARGET = { NONE: 'none', // Not currently targetting anything - INVISIBLE: 'invisible', // The current target is an invvsible surface INVALID: 'invalid', // The current target is invalid (wall, ceiling, etc.) + COLLIDES: 'collides', // Insufficient space to accommodate the avatar capsule + DISCREPANCY: 'discrepancy', // We are not 100% sure the avatar will fit so we trigger safe landing SURFACE: 'surface', // The current target is a valid surface SEAT: 'seat' // The current target is a seat }; @@ -115,6 +127,7 @@ Script.include("/~/system/libraries/controllers.js"); function Teleporter(hand) { var _this = this; + this.init = false; this.hand = hand; this.buttonValue = 0; this.disabled = false; // used by the 'Hifi-Teleport-Disabler' message handler @@ -122,74 +135,138 @@ Script.include("/~/system/libraries/controllers.js"); this.state = TELEPORTER_STATES.IDLE; this.currentTarget = TARGET.INVALID; this.currentResult = null; + this.capsuleThreshold = 0.05; + this.pickHeightOffset = 0.05; this.getOtherModule = function() { var otherModule = this.hand === RIGHT_HAND ? leftTeleporter : rightTeleporter; return otherModule; }; - this.teleportParabolaHandVisible = Pointers.createPointer(PickType.Parabola, { - joint: (_this.hand === RIGHT_HAND) ? "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", - dirOffset: { x: 0, y: 1, z: 0.1 }, - posOffset: { x: (_this.hand === RIGHT_HAND) ? 0.03 : -0.03, y: 0.2, z: 0.02 }, - filter: Picks.PICK_ENTITIES, - faceAvatar: true, - scaleWithAvatar: true, - centerEndY: false, - speed: speed, - accelerationAxis: accelerationAxis, - rotateAccelerationWithAvatar: true, - renderStates: teleportRenderStates, - defaultRenderStates: teleportDefaultRenderStates, - maxDistance: 8.0 - }); - this.teleportParabolaHandInvisible = Pointers.createPointer(PickType.Parabola, { - joint: (_this.hand === RIGHT_HAND) ? "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", - dirOffset: { x: 0, y: 1, z: 0.1 }, - posOffset: { x: (_this.hand === RIGHT_HAND) ? 0.03 : -0.03, y: 0.2, z: 0.02 }, - filter: Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_INVISIBLE, - faceAvatar: true, - scaleWithAvatar: true, - centerEndY: false, - speed: speed, - accelerationAxis: accelerationAxis, - rotateAccelerationWithAvatar: true, - renderStates: teleportRenderStates, - maxDistance: 8.0 - }); - this.teleportParabolaHeadVisible = Pointers.createPointer(PickType.Parabola, { - joint: "Avatar", - filter: Picks.PICK_ENTITIES, - faceAvatar: true, - scaleWithAvatar: true, - centerEndY: false, - speed: speed, - accelerationAxis: accelerationAxis, - rotateAccelerationWithAvatar: true, - renderStates: teleportRenderStates, - defaultRenderStates: teleportDefaultRenderStates, - maxDistance: 8.0 - }); - this.teleportParabolaHeadInvisible = Pointers.createPointer(PickType.Parabola, { - joint: "Avatar", - filter: Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_INVISIBLE, - faceAvatar: true, - scaleWithAvatar: true, - centerEndY: false, - speed: speed, - accelerationAxis: accelerationAxis, - rotateAccelerationWithAvatar: true, - renderStates: teleportRenderStates, - maxDistance: 8.0 - }); + this.teleportHeadCollisionPick; + this.teleportHandCollisionPick; + this.teleportParabolaHandVisuals; + this.teleportParabolaHandCollisions; + this.teleportParabolaHeadVisuals; + this.teleportParabolaHeadCollisions; this.cleanup = function() { - Pointers.removePointer(this.teleportParabolaHandVisible); - Pointers.removePointer(this.teleportParabolaHandInvisible); - Pointers.removePointer(this.teleportParabolaHeadVisible); - Pointers.removePointer(this.teleportParabolaHeadInvisible); + Pointers.removePointer(_this.teleportParabolaHandVisuals); + Pointers.removePointer(_this.teleportParabolaHandCollisions); + Pointers.removePointer(_this.teleportParabolaHeadVisuals); + Pointers.removePointer(_this.teleportParabolaHeadCollisions); + Picks.removePick(_this.teleportHandCollisionPick); + Picks.removePick(_this.teleportHeadCollisionPick); }; + this.initPointers = function () { + if (_this.init) { + _this.cleanup(); + } + _this.teleportParabolaHandVisuals = Pointers.createPointer(PickType.Parabola, { + joint: (_this.hand === RIGHT_HAND) ? "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", + dirOffset: { x: 0, y: 1, z: 0.1 }, + posOffset: { x: (_this.hand === RIGHT_HAND) ? 0.03 : -0.03, y: 0.2, z: 0.02 }, + filter: Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_INVISIBLE, + faceAvatar: true, + scaleWithAvatar: true, + centerEndY: false, + speed: speed, + accelerationAxis: accelerationAxis, + rotateAccelerationWithAvatar: true, + renderStates: teleportRenderStates, + defaultRenderStates: teleportDefaultRenderStates, + maxDistance: 8.0 + }); + + _this.teleportParabolaHandCollisions = Pointers.createPointer(PickType.Parabola, { + joint: (_this.hand === RIGHT_HAND) ? "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", + dirOffset: { x: 0, y: 1, z: 0.1 }, + posOffset: { x: (_this.hand === RIGHT_HAND) ? 0.03 : -0.03, y: 0.2, z: 0.02 }, + filter: Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_INVISIBLE, + faceAvatar: true, + scaleWithAvatar: true, + centerEndY: false, + speed: speed, + accelerationAxis: accelerationAxis, + rotateAccelerationWithAvatar: true, + renderStates: teleportRenderStates, + maxDistance: 8.0 + }); + + _this.teleportParabolaHeadVisuals = Pointers.createPointer(PickType.Parabola, { + joint: "Avatar", + filter: Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_INVISIBLE, + faceAvatar: true, + scaleWithAvatar: true, + centerEndY: false, + speed: speed, + accelerationAxis: accelerationAxis, + rotateAccelerationWithAvatar: true, + renderStates: teleportRenderStates, + defaultRenderStates: teleportDefaultRenderStates, + maxDistance: 8.0 + }); + + _this.teleportParabolaHeadCollisions = Pointers.createPointer(PickType.Parabola, { + joint: "Avatar", + filter: Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_INVISIBLE, + faceAvatar: true, + scaleWithAvatar: true, + centerEndY: false, + speed: speed, + accelerationAxis: accelerationAxis, + rotateAccelerationWithAvatar: true, + renderStates: teleportRenderStates, + maxDistance: 8.0 + }); + + + var capsuleData = MyAvatar.getCollisionCapsule(); + + var sensorToWorldScale = MyAvatar.getSensorToWorldScale(); + + var radius = capsuleData.radius / sensorToWorldScale; + var height = (Vec3.distance(capsuleData.start, capsuleData.end) + (capsuleData.radius * 2.0)) / sensorToWorldScale; + var capsuleRatio = 10.0 * radius / height; + var offset = _this.pickHeightOffset * capsuleRatio; + + _this.teleportHandCollisionPick = Picks.createPick(PickType.Collision, { + enabled: true, + parentID: Pointers.getPointerProperties(_this.teleportParabolaHandCollisions).renderStates["collision"].end, + filter: Picks.PICK_ENTITIES | Picks.PICK_AVATARS, + shape: { + shapeType: "capsule-y", + dimensions: { + x: radius * 2.0, + y: height - (radius * 2.0), + z: radius * 2.0 + } + }, + position: { x: 0, y: offset + height * 0.5, z: 0 }, + threshold: _this.capsuleThreshold + }); + + _this.teleportHeadCollisionPick = Picks.createPick(PickType.Collision, { + enabled: true, + parentID: Pointers.getPointerProperties(_this.teleportParabolaHeadCollisions).renderStates["collision"].end, + filter: Picks.PICK_ENTITIES | Picks.PICK_AVATARS, + shape: { + shapeType: "capsule-y", + dimensions: { + x: radius * 2.0, + y: height - (radius * 2.0), + z: radius * 2.0 + } + }, + position: { x: 0, y: offset + height * 0.5, z: 0 }, + threshold: _this.capsuleThreshold + }); + _this.init = true; + } + + _this.initPointers(); + this.axisButtonStateX = 0; // Left/right axis button pressed. this.axisButtonStateY = 0; // Up/down axis button pressed. this.BUTTON_TRANSITION_DELAY = 100; // Allow time for transition from direction buttons to touch-pad. @@ -254,52 +331,56 @@ Script.include("/~/system/libraries/controllers.js"); var pose = Controller.getPoseValue(handInfo[(_this.hand === RIGHT_HAND) ? 'right' : 'left'].controllerInput); var mode = pose.valid ? _this.hand : 'head'; if (!pose.valid) { - Pointers.disablePointer(_this.teleportParabolaHandVisible); - Pointers.disablePointer(_this.teleportParabolaHandInvisible); - Pointers.enablePointer(_this.teleportParabolaHeadVisible); - Pointers.enablePointer(_this.teleportParabolaHeadInvisible); + Pointers.disablePointer(_this.teleportParabolaHandVisuals); + Pointers.disablePointer(_this.teleportParabolaHandCollisions); + Picks.disablePick(_this.teleportHandCollisionPick); + Pointers.enablePointer(_this.teleportParabolaHeadVisuals); + Pointers.enablePointer(_this.teleportParabolaHeadCollisions); + Picks.enablePick(_this.teleportHeadCollisionPick); } else { - Pointers.enablePointer(_this.teleportParabolaHandVisible); - Pointers.enablePointer(_this.teleportParabolaHandInvisible); - Pointers.disablePointer(_this.teleportParabolaHeadVisible); - Pointers.disablePointer(_this.teleportParabolaHeadInvisible); + Pointers.enablePointer(_this.teleportParabolaHandVisuals); + Pointers.enablePointer(_this.teleportParabolaHandCollisions); + Picks.enablePick(_this.teleportHandCollisionPick); + Pointers.disablePointer(_this.teleportParabolaHeadVisuals); + Pointers.disablePointer(_this.teleportParabolaHeadCollisions); + Picks.disablePick(_this.teleportHeadCollisionPick); } // We do up to 2 picks to find a teleport location. // There are 2 types of teleport locations we are interested in: - // 1. A visible floor. This can be any entity surface that points within some degree of "up" + // + // 1. A visible floor. This can be any entity surface that points within some degree of "up" + // and where the avatar capsule can be positioned without colliding + // // 2. A seat. The seat can be visible or invisible. // - // * In the first pass we pick against visible and invisible entities so that we can find invisible seats. - // We might hit an invisible entity that is not a seat, so we need to do a second pass. - // * In the second pass we pick against visible entities only. + // The Collision Pick is currently parented to the end overlay on teleportParabolaXXXXCollisions // - var result; + // TODO + // Parent the collision Pick directly to the teleportParabolaXXXXVisuals and get rid of teleportParabolaXXXXCollisions + // + var result, collisionResult; if (mode === 'head') { - result = Pointers.getPrevPickResult(_this.teleportParabolaHeadInvisible); + result = Pointers.getPrevPickResult(_this.teleportParabolaHeadCollisions); + collisionResult = Picks.getPrevPickResult(_this.teleportHeadCollisionPick); } else { - result = Pointers.getPrevPickResult(_this.teleportParabolaHandInvisible); - } - - var teleportLocationType = getTeleportTargetType(result); - if (teleportLocationType === TARGET.INVISIBLE) { - if (mode === 'head') { - result = Pointers.getPrevPickResult(_this.teleportParabolaHeadVisible); - } else { - result = Pointers.getPrevPickResult(_this.teleportParabolaHandVisible); - } - teleportLocationType = getTeleportTargetType(result); + result = Pointers.getPrevPickResult(_this.teleportParabolaHandCollisions); + collisionResult = Picks.getPrevPickResult(_this.teleportHandCollisionPick); } + + var teleportLocationType = getTeleportTargetType(result, collisionResult); if (teleportLocationType === TARGET.NONE) { // Use the cancel default state this.setTeleportState(mode, "cancel", ""); - } else if (teleportLocationType === TARGET.INVALID || teleportLocationType === TARGET.INVISIBLE) { + } else if (teleportLocationType === TARGET.INVALID) { this.setTeleportState(mode, "", "cancel"); - } else if (teleportLocationType === TARGET.SURFACE) { - this.setTeleportState(mode, "teleport", ""); + } else if (teleportLocationType === TARGET.COLLIDES) { + this.setTeleportState(mode, "cancel", "collision"); + } else if (teleportLocationType === TARGET.SURFACE || teleportLocationType === TARGET.DISCREPANCY) { + this.setTeleportState(mode, "teleport", "collision"); } else if (teleportLocationType === TARGET.SEAT) { - this.setTeleportState(mode, "", "seat"); + this.setTeleportState(mode, "collision", "seat"); } return this.teleport(result, teleportLocationType); }; @@ -314,10 +395,11 @@ Script.include("/~/system/libraries/controllers.js"); // Do nothing } else if (target === TARGET.SEAT) { Entities.callEntityMethod(result.objectID, 'sit'); - } else if (target === TARGET.SURFACE) { + } else if (target === TARGET.SURFACE || target === TARGET.DISCREPANCY) { var offset = getAvatarFootOffset(); result.intersection.y += offset; - MyAvatar.goToLocation(result.intersection, true, HMD.orientation, false); + var shouldLandSafe = target === TARGET.DISCREPANCY; + MyAvatar.goToLocation(result.intersection, true, HMD.orientation, false, shouldLandSafe); HMD.centerUI(); MyAvatar.centerBody(); } @@ -328,33 +410,38 @@ Script.include("/~/system/libraries/controllers.js"); }; this.disableLasers = function() { - Pointers.disablePointer(_this.teleportParabolaHandVisible); - Pointers.disablePointer(_this.teleportParabolaHandInvisible); - Pointers.disablePointer(_this.teleportParabolaHeadVisible); - Pointers.disablePointer(_this.teleportParabolaHeadInvisible); + Pointers.disablePointer(_this.teleportParabolaHandVisuals); + Pointers.disablePointer(_this.teleportParabolaHandCollisions); + Pointers.disablePointer(_this.teleportParabolaHeadVisuals); + Pointers.disablePointer(_this.teleportParabolaHeadCollisions); + Picks.disablePick(_this.teleportHeadCollisionPick); + Picks.disablePick(_this.teleportHandCollisionPick); }; this.setTeleportState = function(mode, visibleState, invisibleState) { if (mode === 'head') { - Pointers.setRenderState(_this.teleportParabolaHeadVisible, visibleState); - Pointers.setRenderState(_this.teleportParabolaHeadInvisible, invisibleState); + Pointers.setRenderState(_this.teleportParabolaHeadVisuals, visibleState); + Pointers.setRenderState(_this.teleportParabolaHeadCollisions, invisibleState); } else { - Pointers.setRenderState(_this.teleportParabolaHandVisible, visibleState); - Pointers.setRenderState(_this.teleportParabolaHandInvisible, invisibleState); + Pointers.setRenderState(_this.teleportParabolaHandVisuals, visibleState); + Pointers.setRenderState(_this.teleportParabolaHandCollisions, invisibleState); } }; this.setIgnoreEntities = function(entitiesToIgnore) { - Pointers.setIgnoreItems(this.teleportParabolaHandVisible, entitiesToIgnore); - Pointers.setIgnoreItems(this.teleportParabolaHandInvisible, entitiesToIgnore); - Pointers.setIgnoreItems(this.teleportParabolaHeadVisible, entitiesToIgnore); - Pointers.setIgnoreItems(this.teleportParabolaHeadInvisible, entitiesToIgnore); + Pointers.setIgnoreItems(this.teleportParabolaHandVisuals, entitiesToIgnore); + Pointers.setIgnoreItems(this.teleportParabolaHandCollisions, entitiesToIgnore); + Pointers.setIgnoreItems(this.teleportParabolaHeadVisuals, entitiesToIgnore); + Pointers.setIgnoreItems(this.teleportParabolaHeadCollisions, entitiesToIgnore); + Picks.setIgnoreItems(_this.teleportHeadCollisionPick, entitiesToIgnore); + Picks.setIgnoreItems(_this.teleportHandCollisionPick, entitiesToIgnore); }; } // related to repositioning the avatar after you teleport var FOOT_JOINT_NAMES = ["RightToe_End", "RightToeBase", "RightFoot"]; var DEFAULT_ROOT_TO_FOOT_OFFSET = 0.5; + function getAvatarFootOffset() { // find a valid foot jointIndex @@ -395,7 +482,29 @@ Script.include("/~/system/libraries/controllers.js"); // than MAX_ANGLE_FROM_UP_TO_TELEPORT degrees from your avatar's up, then // you can't teleport there. var MAX_ANGLE_FROM_UP_TO_TELEPORT = 70; - function getTeleportTargetType(result) { + var MAX_DISCREPANCY_DISTANCE = 1.0; + var MAX_DOT_SIGN = -0.6; + + function checkForMeshDiscrepancy(result, collisionResult) { + var intersectingObjects = collisionResult.intersectingObjects; + if (intersectingObjects.length > 0 && intersectingObjects.length < 3) { + for (var j = 0; j < collisionResult.intersectingObjects.length; j++) { + var intersectingObject = collisionResult.intersectingObjects[j]; + for (var i = 0; i < intersectingObject.collisionContacts.length; i++) { + var normal = intersectingObject.collisionContacts[i].normalOnPick; + var distanceToPick = Vec3.distance(intersectingObject.collisionContacts[i].pointOnPick, result.intersection); + var normalSign = Vec3.dot(normal, Quat.getUp(MyAvatar.orientation)); + if ((distanceToPick > MAX_DISCREPANCY_DISTANCE) || (normalSign > MAX_DOT_SIGN)) { + return false; + } + } + } + return true; + } + return false; + } + + function getTeleportTargetType(result, collisionResult) { if (result.type === Picks.INTERSECTED_NONE) { return TARGET.NONE; } @@ -410,9 +519,14 @@ Script.include("/~/system/libraries/controllers.js"); return TARGET.INVALID; } } - - if (!props.visible) { - return TARGET.INVISIBLE; + var isDiscrepancy = false; + if (collisionResult.collisionRegion != undefined) { + if (collisionResult.intersects) { + isDiscrepancy = checkForMeshDiscrepancy(result, collisionResult); + if (!isDiscrepancy) { + return TARGET.COLLIDES; + } + } } var surfaceNormal = result.surfaceNormal; @@ -420,6 +534,8 @@ Script.include("/~/system/libraries/controllers.js"); if (angle > MAX_ANGLE_FROM_UP_TO_TELEPORT) { return TARGET.INVALID; + } else if (isDiscrepancy) { + return TARGET.DISCREPANCY; } else { return TARGET.SURFACE; } @@ -513,7 +629,14 @@ Script.include("/~/system/libraries/controllers.js"); } } }; - + + MyAvatar.onLoadComplete.connect(function () { + Script.setTimeout(function () { + leftTeleporter.initPointers(); + rightTeleporter.initPointers(); + }, 500); + }); + Messages.subscribe('Hifi-Teleport-Disabler'); Messages.subscribe('Hifi-Teleport-Ignore-Add'); Messages.subscribe('Hifi-Teleport-Ignore-Remove'); diff --git a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js index a2fe0bfcd4..4e36355621 100644 --- a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js +++ b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js @@ -22,14 +22,14 @@ Script.include("/~/system/libraries/controllers.js"); this.running = false; this.parameters = makeDispatcherModuleParameters( - 120, + 160, this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], [], 100, makeLaserParams(hand, true)); this.grabModuleWantsNearbyOverlay = function(controllerData) { - if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) { + if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE || controllerData.secondaryValues[this.hand] > BUMPER_ON_VALUE) { var nearGrabName = this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay"; var nearGrabModule = getEnabledModuleByName(nearGrabName); if (nearGrabModule) { @@ -42,6 +42,23 @@ Script.include("/~/system/libraries/controllers.js"); return true; } } + nearGrabName = this.hand === RIGHT_HAND ? "RightNearParentingGrabEntity" : "LeftNearParentingGrabEntity"; + nearGrabModule = getEnabledModuleByName(nearGrabName); + if (nearGrabModule && nearGrabModule.isReady(controllerData)) { + // check for if near parent module is active. + var isNearGrabModuleActive = nearGrabModule.isReady(controllerData).active; + if (isNearGrabModuleActive) { + // if true, return true. + return isNearGrabModuleActive; + } else { + // check near action grab entity as a second pass. + nearGrabName = this.hand === RIGHT_HAND ? "RightNearActionGrabEntity" : "LeftNearActionGrabEntity"; + nearGrabModule = getEnabledModuleByName(nearGrabName); + if (nearGrabModule && nearGrabModule.isReady(controllerData)) { + return nearGrabModule.isReady(controllerData).active; + } + } + } } return false; }; @@ -50,14 +67,14 @@ Script.include("/~/system/libraries/controllers.js"); return this.hand === RIGHT_HAND ? leftOverlayLaserInput : rightOverlayLaserInput; }; - this.isPointingAtTriggerable = function(controllerData, triggerPressed) { + this.isPointingAtTriggerable = function(controllerData, triggerPressed, checkEntitiesOnly) { // allow pointing at tablet, unlocked web entities, or web overlays automatically without pressing trigger, // but for pointing at locked web entities or non-web overlays user must be pressing trigger var intersection = controllerData.rayPicks[this.hand]; - if (intersection.type === Picks.INTERSECTED_OVERLAY) { - var objectID = intersection.objectID; + var objectID = intersection.objectID; + if (intersection.type === Picks.INTERSECTED_OVERLAY && !checkEntitiesOnly) { if ((HMD.tabletID && objectID === HMD.tabletID) || - (HMD.tabletScreenID && objectID === HMD.tabletScreenID) || + (HMD.tabletScreenID && objectID === HMD.tabletScreenID) || (HMD.homeButtonID && objectID === HMD.homeButtonID)) { return true; } else { @@ -65,9 +82,9 @@ Script.include("/~/system/libraries/controllers.js"); return overlayType === "web3d" || triggerPressed; } } else if (intersection.type === Picks.INTERSECTED_ENTITY) { - var entityProperty = Entities.getEntityProperties(intersection.objectID); - var entityType = entityProperty.type; - var isLocked = entityProperty.locked; + var entityProperties = Entities.getEntityProperties(objectID); + var entityType = entityProperties.type; + var isLocked = entityProperties.locked; return entityType === "Web" && (!isLocked || triggerPressed); } return false; @@ -103,7 +120,8 @@ Script.include("/~/system/libraries/controllers.js"); var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE && controllerData.triggerValues[this.otherHand] <= TRIGGER_OFF_VALUE; var allowThisModule = !otherModuleRunning || isTriggerPressed; - if (allowThisModule && this.isPointingAtTriggerable(controllerData, isTriggerPressed)) { + + if (allowThisModule && this.isPointingAtTriggerable(controllerData, isTriggerPressed, false)) { this.updateAllwaysOn(); if (isTriggerPressed) { this.dominantHandOverride = true; // Override dominant hand. @@ -121,13 +139,27 @@ Script.include("/~/system/libraries/controllers.js"); otherModuleRunning = otherModuleRunning && this.getDominantHand() !== this.hand; // Auto-swap to dominant hand. otherModuleRunning = otherModuleRunning || this.getOtherModule().dominantHandOverride; // Override dominant hand. var grabModuleNeedsToRun = this.grabModuleWantsNearbyOverlay(controllerData); + // only allow for non-near grab var allowThisModule = !otherModuleRunning && !grabModuleNeedsToRun; var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE; var laserOn = isTriggerPressed || this.parameters.handLaser.allwaysOn; - if (allowThisModule && (laserOn && this.isPointingAtTriggerable(controllerData, isTriggerPressed))) { - this.running = true; - return makeRunningValues(true, [], []); + if (allowThisModule) { + if (isTriggerPressed && !this.isPointingAtTriggerable(controllerData, isTriggerPressed, true)) { + // if trigger is down + not pointing at a web entity, keep running web surface laser + this.running = true; + return makeRunningValues(true, [], []); + } else if (laserOn && this.isPointingAtTriggerable(controllerData, isTriggerPressed, false)) { + // if trigger is down + pointing at a web entity/overlay, keep running web surface laser + this.running = true; + return makeRunningValues(true, [], []); + } else { + this.deleteContextOverlay(); + this.running = false; + this.dominantHandOverride = false; + return makeRunningValues(false, [], []); + } } + // if module needs to stop from near grabs or other modules are running, stop it. this.deleteContextOverlay(); this.running = false; this.dominantHandOverride = false; diff --git a/scripts/system/edit.js b/scripts/system/edit.js index e340c75a8b..88ae2852be 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -860,7 +860,7 @@ var toolBar = (function () { propertiesTool.setVisible(false); selectionManager.clearSelections(); cameraManager.disable(); - selectionDisplay.triggerMapping.disable(); + selectionDisplay.disableTriggerMapping(); tablet.landscape = false; Controller.disableMapping(CONTROLLER_MAPPING_NAME); } else { @@ -876,7 +876,7 @@ var toolBar = (function () { gridTool.setVisible(true); grid.setEnabled(true); propertiesTool.setVisible(true); - selectionDisplay.triggerMapping.enable(); + selectionDisplay.enableTriggerMapping(); print("starting tablet in landscape mode"); tablet.landscape = true; Controller.enableMapping(CONTROLLER_MAPPING_NAME); @@ -1739,7 +1739,6 @@ function getPositionToCreateEntity(extra) { position = Vec3.sum(Camera.position, Vec3.multiply(Quat.getForward(Camera.orientation), CREATE_DISTANCE + delta)); } else { position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getForward(MyAvatar.orientation), CREATE_DISTANCE + delta)); - position.y += 0.5; } if (position.x > HALF_TREE_SCALE || position.y > HALF_TREE_SCALE || position.z > HALF_TREE_SCALE) { diff --git a/scripts/system/help.js b/scripts/system/help.js index aaeb82721c..40bbf6dbe2 100644 --- a/scripts/system/help.js +++ b/scripts/system/help.js @@ -1,5 +1,5 @@ "use strict"; - +/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ // // help.js // scripts/system/ @@ -12,50 +12,18 @@ // /* globals Tablet, Script, HMD, Controller, Menu */ -(function() { // BEGIN LOCAL_SCOPE - - var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; - var HELP_URL = Script.resourcesPath() + "html/tabletHelp.html"; - var buttonName = "HELP"; - var onHelpScreen = false; - var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - var button = tablet.addButton({ - icon: "icons/tablet-icons/help-i.svg", - activeIcon: "icons/tablet-icons/help-a.svg", - text: buttonName, - sortOrder: 6 +(function () { // BEGIN LOCAL_SCOPE +var AppUi = Script.require('appUi'); + +var HELP_URL = Script.resourcesPath() + "html/tabletHelp.html"; +var HELP_BUTTON_NAME = "HELP"; +var ui; +function startup() { + ui = new AppUi({ + buttonName: HELP_BUTTON_NAME, + sortOrder: 6, + home: HELP_URL }); - - var enabled = false; - function onClicked() { - if (onHelpScreen) { - tablet.gotoHomeScreen(); - } else { - if (HMD.tabletID) { - Entities.editEntity(HMD.tabletID, {textures: JSON.stringify({"tex.close" : HOME_BUTTON_TEXTURE})}); - } - Menu.triggerOption('Help...'); - onHelpScreen = true; - } - } - - function onScreenChanged(type, url) { - onHelpScreen = type === "Web" && (url.indexOf(HELP_URL) === 0); - button.editProperties({ isActive: onHelpScreen }); - } - - button.clicked.connect(onClicked); - tablet.screenChanged.connect(onScreenChanged); - - Script.scriptEnding.connect(function () { - if (onHelpScreen) { - tablet.gotoHomeScreen(); - } - button.clicked.disconnect(onClicked); - tablet.screenChanged.disconnect(onScreenChanged); - if (tablet) { - tablet.removeButton(button); - } - }); - +} +startup(); }()); // END LOCAL_SCOPE diff --git a/scripts/system/html/entityList.html b/scripts/system/html/entityList.html index 7906a3c97f..89974f9409 100644 --- a/scripts/system/html/entityList.html +++ b/scripts/system/html/entityList.html @@ -9,9 +9,10 @@ --> + - + diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 5cd5f6d610..23de49b613 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -6,12 +6,8 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -var entities = {}; -var selectedEntities = []; -var currentSortColumn = 'type'; -var currentSortOrder = 'des'; -var entityList = null; -var refreshEntityListTimer = null; +const ASCENDING_SORT = 1; +const DESCENDING_SORT = -1; const ASCENDING_STRING = '▴'; const DESCENDING_STRING = '▾'; const LOCKED_GLYPH = ""; @@ -23,14 +19,54 @@ const DELETE = 46; // Key code for the delete key. const KEY_P = 80; // Key code for letter p used for Parenting hotkey. const MAX_ITEMS = Number.MAX_VALUE; // Used to set the max length of the list of discovered entities. -debugPrint = function (message) { - console.log(message); +const COMPARE_ASCENDING = function(a, b) { + let va = a[currentSortColumn]; + let vb = b[currentSortColumn]; + + if (va < vb) { + return -1; + } else if (va > vb) { + return 1; + } else if (a.id < b.id) { + return -1; + } + + return 1; +} +const COMPARE_DESCENDING = function(a, b) { + return COMPARE_ASCENDING(b, a); +} + + +// List of all entities +let entities = [] +// List of all entities, indexed by Entity ID +var entitiesByID = {}; +// The filtered and sorted list of entities +var visibleEntities = []; + +var selectedEntities = []; +var currentSortColumn = 'type'; +var currentSortOrder = ASCENDING_SORT; + +const ENABLE_PROFILING = false; +var profileIndent = ''; +const PROFILE_NOOP = function(_name, fn, args) { + fn.apply(this, args); +} ; +const PROFILE = !ENABLE_PROFILING ? PROFILE_NOOP : function(name, fn, args) { + console.log("PROFILE-Web " + profileIndent + "(" + name + ") Begin"); + var previousIndent = profileIndent; + profileIndent += ' '; + var before = Date.now(); + fn.apply(this, args); + var delta = Date.now() - before; + profileIndent = previousIndent; + console.log("PROFILE-Web " + profileIndent + "(" + name + ") End " + delta + "ms"); }; function loaded() { openEventBridge(function() { - entityList = new List('entity-list', { valueNames: ['name', 'type', 'url', 'locked', 'visible'], page: MAX_ITEMS}); - entityList.clear(); elEntityTable = document.getElementById("entity-table"); elEntityTableBody = document.getElementById("entity-table-body"); elRefresh = document.getElementById("refresh"); @@ -89,10 +125,10 @@ function loaded() { }; function onRowClicked(clickEvent) { - var id = this.dataset.entityId; - var selection = [this.dataset.entityId]; + let entityID = this.dataset.entityID; + let selection = [entityID]; if (clickEvent.ctrlKey) { - var selectedIndex = selectedEntities.indexOf(id); + let selectedIndex = selectedEntities.indexOf(entityID); if (selectedIndex >= 0) { selection = selectedEntities; selection.splice(selectedIndex, 1) @@ -100,22 +136,23 @@ function loaded() { selection = selection.concat(selectedEntities); } } else if (clickEvent.shiftKey && selectedEntities.length > 0) { - var previousItemFound = -1; - var clickedItemFound = -1; - for (var entity in entityList.visibleItems) { - if (clickedItemFound === -1 && this.dataset.entityId == entityList.visibleItems[entity].values().id) { - clickedItemFound = entity; - } else if(previousItemFound === -1 && selectedEntities[0] == entityList.visibleItems[entity].values().id) { - previousItemFound = entity; + let previousItemFound = -1; + let clickedItemFound = -1; + for (let i = 0, len = visibleEntities.length; i < len; ++i) { + let entity = visibleEntities[i]; + if (clickedItemFound === -1 && entityID === entity.id) { + clickedItemFound = i; + } else if (previousItemFound === -1 && selectedEntities[0] === entity.id) { + previousItemFound = i; } - } + }; if (previousItemFound !== -1 && clickedItemFound !== -1) { - var betweenItems = []; - var toItem = Math.max(previousItemFound, clickedItemFound); + let betweenItems = []; + let toItem = Math.max(previousItemFound, clickedItemFound); // skip first and last item in this loop, we add them to selection after the loop - for (var i = (Math.min(previousItemFound, clickedItemFound) + 1); i < toItem; i++) { - entityList.visibleItems[i].elm.className = 'selected'; - betweenItems.push(entityList.visibleItems[i].values().id); + for (let i = (Math.min(previousItemFound, clickedItemFound) + 1); i < toItem; i++) { + visibleEntities[i].el.className = 'selected'; + betweenItems.push(visibleEntities[i].id); } if (previousItemFound > clickedItemFound) { // always make sure that we add the items in the right order @@ -125,6 +162,12 @@ function loaded() { } } + selectedEntities.forEach(function(entityID) { + if (selection.indexOf(entityID) === -1) { + entitiesByID[entityID].el.className = ''; + } + }); + selectedEntities = selection; this.className = 'selected'; @@ -142,7 +185,7 @@ function loaded() { EventBridge.emitWebEvent(JSON.stringify({ type: "selectionUpdate", focus: true, - entityIds: [this.dataset.entityId], + entityIds: [this.dataset.entityID], })); } @@ -156,64 +199,141 @@ function loaded() { return number ? number : ""; } - function addEntity(id, name, type, url, locked, visible, verticesCount, texturesCount, texturesSize, hasTransparent, - isBaked, drawCalls, hasScript) { + function getFilename(url) { + let urlParts = url.split('/'); + return urlParts[urlParts.length - 1]; + } - var urlParts = url.split('/'); - var filename = urlParts[urlParts.length - 1]; + // Update the entity list with the new set of data sent from edit.js + function updateEntityList(entityData) { + const IMAGE_MODEL_NAME = 'default-image-model.fbx'; - var IMAGE_MODEL_NAME = 'default-image-model.fbx'; + entities = [] + entitiesByID = {}; + visibleEntities = []; - if (filename === IMAGE_MODEL_NAME) { - type = "Image"; - } + PROFILE("map-data", function() { + entityData.forEach(function(entity) { + let type = entity.type; + let filename = getFilename(entity.url); + if (filename === IMAGE_MODEL_NAME) { + type = "Image"; + } - if (entities[id] === undefined) { - entityList.add([{ - id: id, name: name, type: type, url: filename, locked: locked, visible: visible, - verticesCount: displayIfNonZero(verticesCount), texturesCount: displayIfNonZero(texturesCount), - texturesSize: decimalMegabytes(texturesSize), hasTransparent: hasTransparent, - isBaked: isBaked, drawCalls: displayIfNonZero(drawCalls), hasScript: hasScript - }], - function (items) { - var currentElement = items[0].elm; - var id = items[0]._values.id; - entities[id] = { - id: id, - name: name, - el: currentElement, - item: items[0] - }; - currentElement.setAttribute('id', 'entity_' + id); - currentElement.setAttribute('title', url); - currentElement.dataset.entityId = id; - currentElement.onclick = onRowClicked; - currentElement.ondblclick = onRowDoubleClicked; + let entityData = { + id: entity.id, + name: entity.name, + type: type, + url: filename, + fullUrl: entity.url, + locked: entity.locked, + visible: entity.visible, + verticesCount: entity.verticesCount, + texturesCount: entity.texturesCount, + texturesSize: entity.texturesSize, + hasTransparent: entity.hasTransparent, + isBaked: entity.isBaked, + drawCalls: entity.drawCalls, + hasScript: entity.hasScript, + } + + entities.push(entityData); + entitiesByID[entityData.id] = entityData; }); - } else { - var item = entities[id].item; - item.values({ name: name, url: filename, locked: locked, visible: visible }); - } + }); + + PROFILE("create-rows", function() { + entities.forEach(function(entity) { + let row = document.createElement('tr'); + row.dataset.entityID = entity.id; + row.attributes.title = entity.fullUrl; + function addColumn(cls, text) { + let col = document.createElement('td'); + col.className = cls; + col.innerText = text; + row.append(col); + } + function addColumnHTML(cls, text) { + let col = document.createElement('td'); + col.className = cls; + col.innerHTML = text; + row.append(col); + } + addColumn('type', entity.type); + addColumn('name', entity.name); + addColumn('url', entity.url); + addColumnHTML('locked glyph', entity.locked ? LOCKED_GLYPH : null); + addColumnHTML('visible glyph', entity.visible ? VISIBLE_GLYPH : null); + addColumn('verticesCount', displayIfNonZero(entity.verticesCount)); + addColumn('texturesCount', displayIfNonZero(entity.texturesCount)); + addColumn('texturesSize', decimalMegabytes(entity.texturesSize)); + addColumnHTML('hasTransparent glyph', entity.hasTransparent ? TRANSPARENCY_GLYPH : null); + addColumnHTML('isBaked glyph', entity.isBaked ? BAKED_GLYPH : null); + addColumn('drawCalls', displayIfNonZero(entity.drawCalls)); + addColumn('hasScript glyph', entity.hasScript ? SCRIPT_GLYPH : null); + row.addEventListener('click', onRowClicked); + row.addEventListener('dblclick', onRowDoubleClicked); + + entity.el = row; + }); + }); + + refreshEntityList(); + updateSelectedEntities(selectedEntities); + } + + function refreshEntityList() { + PROFILE("refresh-entity-list", function() { + PROFILE("filter", function() { + let searchTerm = elFilter.value.toLowerCase(); + if (searchTerm === '') { + visibleEntities = entities.slice(0); + } else { + visibleEntities = entities.filter(function(e) { + return e.name.toLowerCase().indexOf(searchTerm) > -1 + || e.type.toLowerCase().indexOf(searchTerm) > -1 + || e.fullUrl.toLowerCase().indexOf(searchTerm) > -1 + || e.id.toLowerCase().indexOf(searchTerm) > -1; + }); + } + }); + + PROFILE("sort", function() { + let cmp = currentSortOrder === ASCENDING_SORT ? COMPARE_ASCENDING : COMPARE_DESCENDING; + visibleEntities.sort(cmp); + }); + + PROFILE("update-dom", function() { + elEntityTableBody.innerHTML = ''; + for (let i = 0, len = visibleEntities.length; i < len; ++i) { + elEntityTableBody.append(visibleEntities[i].el); + } + }); + }); } function removeEntities(deletedIDs) { - for (i = 0, length = deletedIDs.length; i < length; i++) { - delete entities[deletedIDs[i]]; - entityList.remove("id", deletedIDs[i]); + // Loop from the back so we can pop items off while iterating + for (let j = entities.length - 1; j >= 0; --j) { + let id = entities[j]; + for (let i = 0, length = deletedIDs.length; i < length; ++i) { + if (id === deletedIDs[i]) { + entities.splice(j, 1); + entitiesByID[id].el.remove(); + delete entitiesByID[id]; + break; + } + } } - } - - function scheduleRefreshEntityList() { - var REFRESH_DELAY = 50; - if (refreshEntityListTimer) { - clearTimeout(refreshEntityListTimer); - } - refreshEntityListTimer = setTimeout(refreshEntityListObject, REFRESH_DELAY); + refreshEntities(); } function clearEntities() { - entities = {}; - entityList.clear(); + entities = [] + entitiesByID = {}; + visibleEntities = []; + elEntityTableBody.innerHTML = ''; + refreshFooter(); } @@ -232,15 +352,17 @@ function loaded() { hasScript: document.querySelector('#entity-hasScript .sort-order'), } function setSortColumn(column) { - if (currentSortColumn == column) { - currentSortOrder = currentSortOrder == "asc" ? "desc" : "asc"; - } else { - elSortOrder[currentSortColumn].innerHTML = ""; - currentSortColumn = column; - currentSortOrder = "asc"; - } - elSortOrder[column].innerHTML = currentSortOrder == "asc" ? ASCENDING_STRING : DESCENDING_STRING; - entityList.sort(currentSortColumn, { order: currentSortOrder }); + PROFILE("set-sort-column", function() { + if (currentSortColumn === column) { + currentSortOrder *= -1; + } else { + elSortOrder[currentSortColumn].innerHTML = ""; + currentSortColumn = column; + currentSortOrder = ASCENDING_SORT; + } + elSortOrder[column].innerHTML = currentSortOrder === ASCENDING_SORT ? ASCENDING_STRING : DESCENDING_STRING; + refreshEntityList(); + }); } setSortColumn('type'); @@ -254,32 +376,27 @@ function loaded() { elFooter.firstChild.nodeValue = selectedEntities.length + " entities selected"; } else if (selectedEntities.length === 1) { elFooter.firstChild.nodeValue = "1 entity selected"; - } else if (entityList.visibleItems.length === 1) { + } else if (visibleEntities.length === 1) { elFooter.firstChild.nodeValue = "1 entity found"; } else { - elFooter.firstChild.nodeValue = entityList.visibleItems.length + " entities found"; + elFooter.firstChild.nodeValue = visibleEntities.length + " entities found"; } } - function refreshEntityListObject() { - refreshEntityListTimer = null; - entityList.sort(currentSortColumn, { order: currentSortOrder }); - entityList.search(elFilter.value); - refreshFooter(); - } function updateSelectedEntities(selectedIDs) { - var notFound = false; - for (var id in entities) { - entities[id].el.className = ''; - } + let notFound = false; + + selectedEntities.forEach(function(id) { + entitiesByID[id].el.className = ''; + }); selectedEntities = []; - for (var i = 0; i < selectedIDs.length; i++) { - var id = selectedIDs[i]; + for (let i = 0; i < selectedIDs.length; i++) { + let id = selectedIDs[i]; selectedEntities.push(id); - if (id in entities) { - var entity = entities[id]; + if (id in entitiesByID) { + let entity = entitiesByID[id]; entity.el.className = 'selected'; } else { notFound = true; @@ -363,31 +480,20 @@ function loaded() { refreshEntities(); } } else if (data.type === "update" && data.selectedIDs !== undefined) { - var newEntities = data.entities; - if (newEntities && newEntities.length == 0) { - elNoEntitiesMessage.style.display = "block"; - elFooter.firstChild.nodeValue = "0 entities found"; - } else if (newEntities) { - elNoEntitiesMessage.style.display = "none"; - for (var i = 0; i < newEntities.length; i++) { - var id = newEntities[i].id; - addEntity(id, newEntities[i].name, newEntities[i].type, newEntities[i].url, - newEntities[i].locked ? LOCKED_GLYPH : null, - newEntities[i].visible ? VISIBLE_GLYPH : null, - newEntities[i].verticesCount, newEntities[i].texturesCount, newEntities[i].texturesSize, - newEntities[i].hasTransparent ? TRANSPARENCY_GLYPH : null, - newEntities[i].isBaked ? BAKED_GLYPH : null, - newEntities[i].drawCalls, - newEntities[i].hasScript ? SCRIPT_GLYPH : null); + PROFILE("update", function() { + var newEntities = data.entities; + if (newEntities && newEntities.length === 0) { + elNoEntitiesMessage.style.display = "block"; + elFooter.firstChild.nodeValue = "0 entities found"; + } else if (newEntities) { + elNoEntitiesMessage.style.display = "none"; + updateEntityList(newEntities); + updateSelectedEntities(data.selectedIDs); } - updateSelectedEntities(data.selectedIDs); - scheduleRefreshEntityList(); - resize(); - } + }); } else if (data.type === "removeEntities" && data.deletedIDs !== undefined && data.selectedIDs !== undefined) { removeEntities(data.deletedIDs); updateSelectedEntities(data.selectedIDs); - scheduleRefreshEntityList(); } else if (data.type === "deleted" && data.ids) { removeEntities(data.ids); refreshFooter(); @@ -434,7 +540,13 @@ function loaded() { }; window.onresize = resize; - elFilter.onchange = resize; + + elFilter.onkeyup = refreshEntityList; + elFilter.onpaste = refreshEntityList; + elFilter.onchange = function() { + refreshEntityList(); + resize(); + }; elFilter.onblur = refreshFooter; diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index 9b91d06d41..3239105254 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -1,3 +1,5 @@ +/* global $, window, MutationObserver */ + // // marketplacesInject.js // @@ -11,7 +13,6 @@ // (function () { - // Event bridge messages. var CLARA_IO_DOWNLOAD = "CLARA.IO DOWNLOAD"; var CLARA_IO_STATUS = "CLARA.IO STATUS"; @@ -24,7 +25,7 @@ var canWriteAssets = false; var xmlHttpRequest = null; - var isPreparing = false; // Explicitly track download request status. + var isPreparing = false; // Explicitly track download request status. var commerceMode = false; var userIsLoggedIn = false; @@ -33,7 +34,6 @@ var messagesWaiting = false; function injectCommonCode(isDirectoryPage) { - // Supporting styles from marketplaces.css. // Glyph font family, size, and spacing adjusted because HiFi-Glyphs cannot be used cross-domain. $("head").append( @@ -74,7 +74,9 @@ (document.referrer !== "") ? window.history.back() : window.location = (marketplaceBaseURL + "/marketplace?"); }); $("#all-markets").on("click", function () { - EventBridge.emitWebEvent(GOTO_DIRECTORY); + EventBridge.emitWebEvent(JSON.stringify({ + type: GOTO_DIRECTORY + })); }); } @@ -94,11 +96,11 @@ }); } - emitWalletSetupEvent = function() { + var emitWalletSetupEvent = function () { EventBridge.emitWebEvent(JSON.stringify({ type: "WALLET_SETUP" })); - } + }; function maybeAddSetupWalletButton() { if (!$('body').hasClass("walletsetup-injected") && userIsLoggedIn && walletNeedsSetup) { @@ -113,7 +115,7 @@ var span = document.createElement('span'); span.style = "margin:10px 5px;color:#1b6420;font-size:15px;"; - span.innerHTML = "Setup your Wallet to get money and shop in Marketplace."; + span.innerHTML = "Activate your Wallet to get money and shop in Marketplace."; var xButton = document.createElement('a'); xButton.id = "xButton"; @@ -285,7 +287,7 @@ $(this).closest('.col-xs-3').prev().attr("class", 'col-xs-6'); $(this).closest('.col-xs-3').attr("class", 'col-xs-6'); - var priceElement = $(this).find('.price') + var priceElement = $(this).find('.price'); priceElement.css({ "padding": "3px 5px", "height": "40px", @@ -355,12 +357,12 @@ function injectAddScrollbarToCategories() { $('#categories-dropdown').on('show.bs.dropdown', function () { $('body > div.container').css('display', 'none') - $('#categories-dropdown > ul.dropdown-menu').css({ 'overflow': 'auto', 'height': 'calc(100vh - 110px)' }) + $('#categories-dropdown > ul.dropdown-menu').css({ 'overflow': 'auto', 'height': 'calc(100vh - 110px)' }); }); $('#categories-dropdown').on('hide.bs.dropdown', function () { - $('body > div.container').css('display', '') - $('#categories-dropdown > ul.dropdown-menu').css({ 'overflow': '', 'height': '' }) + $('body > div.container').css('display', ''); + $('#categories-dropdown > ul.dropdown-menu').css({ 'overflow': '', 'height': '' }); }); } @@ -382,7 +384,6 @@ mutations.forEach(function (mutation) { injectBuyButtonOnMainPage(); }); - //observer.disconnect(); }); var config = { attributes: true, childList: true, characterData: true }; observer.observe(target, config); @@ -451,8 +452,8 @@ "itemPage", urlParams.get('edition'), type); - } - }); + } + }); maybeAddPurchasesButton(); } } @@ -503,127 +504,142 @@ $(".top-title .col-sm-4").append(downloadContainer); downloadContainer.append(downloadFBX); } + } + } - // Automatic download to High Fidelity. - function startAutoDownload() { + // Automatic download to High Fidelity. + function startAutoDownload() { + // One file request at a time. + if (isPreparing) { + console.log("WARNING: Clara.io FBX: Prepare only one download at a time"); + return; + } - // One file request at a time. - if (isPreparing) { - console.log("WARNING: Clara.io FBX: Prepare only one download at a time"); - return; - } + // User must be able to write to Asset Server. + if (!canWriteAssets) { + console.log("ERROR: Clara.io FBX: File download cancelled because no permissions to write to Asset Server"); + EventBridge.emitWebEvent(JSON.stringify({ + type: WARN_USER_NO_PERMISSIONS + })); + return; + } - // User must be able to write to Asset Server. - if (!canWriteAssets) { - console.log("ERROR: Clara.io FBX: File download cancelled because no permissions to write to Asset Server"); - EventBridge.emitWebEvent(WARN_USER_NO_PERMISSIONS); - return; - } + // User must be logged in. + var loginButton = $("#topnav a[href='/signup']"); + if (loginButton.length > 0) { + loginButton[0].click(); + return; + } - // User must be logged in. - var loginButton = $("#topnav a[href='/signup']"); - if (loginButton.length > 0) { - loginButton[0].click(); - return; - } + // Obtain zip file to download for requested asset. + // Reference: https://clara.io/learn/sdk/api/export - // Obtain zip file to download for requested asset. - // Reference: https://clara.io/learn/sdk/api/export + //var XMLHTTPREQUEST_URL = "https://clara.io/api/scenes/{uuid}/export/fbx?zip=true¢erScene=true&alignSceneGround=true&fbxUnit=Meter&fbxVersion=7&fbxEmbedTextures=true&imageFormat=WebGL"; + // 13 Jan 2017: Specify FBX version 5 and remove some options in order to make Clara.io site more likely to + // be successful in generating zip files. + var XMLHTTPREQUEST_URL = "https://clara.io/api/scenes/{uuid}/export/fbx?fbxUnit=Meter&fbxVersion=5&fbxEmbedTextures=true&imageFormat=WebGL"; - //var XMLHTTPREQUEST_URL = "https://clara.io/api/scenes/{uuid}/export/fbx?zip=true¢erScene=true&alignSceneGround=true&fbxUnit=Meter&fbxVersion=7&fbxEmbedTextures=true&imageFormat=WebGL"; - // 13 Jan 2017: Specify FBX version 5 and remove some options in order to make Clara.io site more likely to - // be successful in generating zip files. - var XMLHTTPREQUEST_URL = "https://clara.io/api/scenes/{uuid}/export/fbx?fbxUnit=Meter&fbxVersion=5&fbxEmbedTextures=true&imageFormat=WebGL"; + var uuid = location.href.match(/\/view\/([a-z0-9\-]*)/)[1]; + var url = XMLHTTPREQUEST_URL.replace("{uuid}", uuid); - var uuid = location.href.match(/\/view\/([a-z0-9\-]*)/)[1]; - var url = XMLHTTPREQUEST_URL.replace("{uuid}", uuid); + xmlHttpRequest = new XMLHttpRequest(); + var responseTextIndex = 0; + var zipFileURL = ""; - xmlHttpRequest = new XMLHttpRequest(); - var responseTextIndex = 0; - var zipFileURL = ""; + xmlHttpRequest.onreadystatechange = function () { + // Messages are appended to responseText; process the new ones. + var message = this.responseText.slice(responseTextIndex); + var statusMessage = ""; - xmlHttpRequest.onreadystatechange = function () { - // Messages are appended to responseText; process the new ones. - var message = this.responseText.slice(responseTextIndex); - var statusMessage = ""; + if (isPreparing) { // Ignore messages in flight after finished/cancelled. + var lines = message.split(/[\n\r]+/); - if (isPreparing) { // Ignore messages in flight after finished/cancelled. - var lines = message.split(/[\n\r]+/); + for (var i = 0, length = lines.length; i < length; i++) { + if (lines[i].slice(0, 5) === "data:") { + // Parse line. + var data; + try { + data = JSON.parse(lines[i].slice(5)); + } + catch (e) { + data = {}; + } - for (var i = 0, length = lines.length; i < length; i++) { - if (lines[i].slice(0, 5) === "data:") { - // Parse line. - var data; - try { - data = JSON.parse(lines[i].slice(5)); - } - catch (e) { - data = {}; - } + // Extract status message. + if (data.hasOwnProperty("message") && data.message !== null) { + statusMessage = data.message; + console.log("Clara.io FBX: " + statusMessage); + } - // Extract status message. - if (data.hasOwnProperty("message") && data.message !== null) { - statusMessage = data.message; - console.log("Clara.io FBX: " + statusMessage); - } - - // Extract zip file URL. - if (data.hasOwnProperty("files") && data.files.length > 0) { - zipFileURL = data.files[0].url; - if (zipFileURL.slice(-4) !== ".zip") { - console.log(JSON.stringify(data)); // Data for debugging. - } - } + // Extract zip file URL. + if (data.hasOwnProperty("files") && data.files.length > 0) { + zipFileURL = data.files[0].url; + if (zipFileURL.slice(-4) !== ".zip") { + console.log(JSON.stringify(data)); // Data for debugging. } } - - if (statusMessage !== "") { - // Update the UI with the most recent status message. - EventBridge.emitWebEvent(CLARA_IO_STATUS + " " + statusMessage); - } } - - responseTextIndex = this.responseText.length; - }; - - // Note: onprogress doesn't have computable total length so can't use it to determine % complete. - - xmlHttpRequest.onload = function () { - var statusMessage = ""; - - if (!isPreparing) { - return; - } - - isPreparing = false; - - var HTTP_OK = 200; - if (this.status !== HTTP_OK) { - statusMessage = "Zip file request terminated with " + this.status + " " + this.statusText; - console.log("ERROR: Clara.io FBX: " + statusMessage); - EventBridge.emitWebEvent(CLARA_IO_STATUS + " " + statusMessage); - } else if (zipFileURL.slice(-4) !== ".zip") { - statusMessage = "Error creating zip file for download."; - console.log("ERROR: Clara.io FBX: " + statusMessage + ": " + zipFileURL); - EventBridge.emitWebEvent(CLARA_IO_STATUS + " " + statusMessage); - } else { - EventBridge.emitWebEvent(CLARA_IO_DOWNLOAD + " " + zipFileURL); - console.log("Clara.io FBX: File download initiated for " + zipFileURL); - } - - xmlHttpRequest = null; } - isPreparing = true; - - console.log("Clara.io FBX: Request zip file for " + uuid); - EventBridge.emitWebEvent(CLARA_IO_STATUS + " Initiating download"); - - xmlHttpRequest.open("POST", url, true); - xmlHttpRequest.setRequestHeader("Accept", "text/event-stream"); - xmlHttpRequest.send(); + if (statusMessage !== "") { + // Update the UI with the most recent status message. + EventBridge.emitWebEvent(JSON.stringify({ + type: CLARA_IO_STATUS, + status: statusMessage + })); + } } + + responseTextIndex = this.responseText.length; + }; + + // Note: onprogress doesn't have computable total length so can't use it to determine % complete. + + xmlHttpRequest.onload = function () { + var statusMessage = ""; + + if (!isPreparing) { + return; + } + + isPreparing = false; + + var HTTP_OK = 200; + if (this.status !== HTTP_OK) { + statusMessage = "Zip file request terminated with " + this.status + " " + this.statusText; + console.log("ERROR: Clara.io FBX: " + statusMessage); + EventBridge.emitWebEvent(JSON.stringify({ + type: CLARA_IO_STATUS, + status: statusMessage + })); + } else if (zipFileURL.slice(-4) !== ".zip") { + statusMessage = "Error creating zip file for download."; + console.log("ERROR: Clara.io FBX: " + statusMessage + ": " + zipFileURL); + EventBridge.emitWebEvent(JSON.stringify({ + type: CLARA_IO_STATUS, + status: (statusMessage + ": " + zipFileURL) + })); + } else { + EventBridge.emitWebEvent(JSON.stringify({ + type: CLARA_IO_DOWNLOAD + })); + console.log("Clara.io FBX: File download initiated for " + zipFileURL); + } + + xmlHttpRequest = null; } + + isPreparing = true; + + console.log("Clara.io FBX: Request zip file for " + uuid); + EventBridge.emitWebEvent(JSON.stringify({ + type: CLARA_IO_STATUS, + status: "Initiating download" + })); + + xmlHttpRequest.open("POST", url, true); + xmlHttpRequest.setRequestHeader("Accept", "text/event-stream"); + xmlHttpRequest.send(); } function injectClaraCode() { @@ -663,7 +679,9 @@ updateClaraCodeInterval = undefined; }); - EventBridge.emitWebEvent(QUERY_CAN_WRITE_ASSETS); + EventBridge.emitWebEvent(JSON.stringify({ + type: QUERY_CAN_WRITE_ASSETS + })); } function cancelClaraDownload() { @@ -673,7 +691,9 @@ xmlHttpRequest.abort(); xmlHttpRequest = null; console.log("Clara.io FBX: File download cancelled"); - EventBridge.emitWebEvent(CLARA_IO_CANCELLED_DOWNLOAD); + EventBridge.emitWebEvent(JSON.stringify({ + type: CLARA_IO_CANCELLED_DOWNLOAD + })); } } @@ -708,25 +728,22 @@ function onLoad() { EventBridge.scriptEventReceived.connect(function (message) { - if (message.slice(0, CAN_WRITE_ASSETS.length) === CAN_WRITE_ASSETS) { - canWriteAssets = message.slice(-4) === "true"; - } else if (message.slice(0, CLARA_IO_CANCEL_DOWNLOAD.length) === CLARA_IO_CANCEL_DOWNLOAD) { + message = JSON.parse(message); + if (message.type === CAN_WRITE_ASSETS) { + canWriteAssets = message.canWriteAssets; + } else if (message.type === CLARA_IO_CANCEL_DOWNLOAD) { cancelClaraDownload(); - } else { - var parsedJsonMessage = JSON.parse(message); - - if (parsedJsonMessage.type === "marketplaces") { - if (parsedJsonMessage.action === "commerceSetting") { - commerceMode = !!parsedJsonMessage.data.commerceMode; - userIsLoggedIn = !!parsedJsonMessage.data.userIsLoggedIn; - walletNeedsSetup = !!parsedJsonMessage.data.walletNeedsSetup; - marketplaceBaseURL = parsedJsonMessage.data.metaverseServerURL; - if (marketplaceBaseURL.indexOf('metaverse.') !== -1) { - marketplaceBaseURL = marketplaceBaseURL.replace('metaverse.', ''); - } - messagesWaiting = parsedJsonMessage.data.messagesWaiting; - injectCode(); + } else if (message.type === "marketplaces") { + if (message.action === "commerceSetting") { + commerceMode = !!message.data.commerceMode; + userIsLoggedIn = !!message.data.userIsLoggedIn; + walletNeedsSetup = !!message.data.walletNeedsSetup; + marketplaceBaseURL = message.data.metaverseServerURL; + if (marketplaceBaseURL.indexOf('metaverse.') !== -1) { + marketplaceBaseURL = marketplaceBaseURL.replace('metaverse.', ''); } + messagesWaiting = message.data.messagesWaiting; + injectCode(); } } }); @@ -739,6 +756,6 @@ } // Load / unload. - window.addEventListener("load", onLoad); // More robust to Web site issues than using $(document).ready(). - window.addEventListener("page:change", onLoad); // Triggered after Marketplace HTML is changed + window.addEventListener("load", onLoad); // More robust to Web site issues than using $(document).ready(). + window.addEventListener("page:change", onLoad); // Triggered after Marketplace HTML is changed }()); diff --git a/scripts/system/interstitialPage.js b/scripts/system/interstitialPage.js new file mode 100644 index 0000000000..0c786dad87 --- /dev/null +++ b/scripts/system/interstitialPage.js @@ -0,0 +1,475 @@ +// +// interstitialPage.js +// scripts/system +// +// Created by Dante Ruiz on 08/02/2018. +// Copyright 2012 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +/* global Script, Controller, Overlays, Quat, MyAvatar, Entities, print, Vec3, AddressManager, Render, Window, Toolbars, + Camera, HMD, location, Account, Xform*/ + +(function() { + Script.include("/~/system/libraries/Xform.js"); + var DEBUG = false; + var MIN_LOADING_PROGRESS = 3.6; + var TOTAL_LOADING_PROGRESS = 3.8; + var EPSILON = 0.01; + var isVisible = false; + var VOLUME = 0.4; + var tune = SoundCache.getSound("http://hifi-content.s3.amazonaws.com/alexia/LoadingScreens/crystals_and_voices.wav"); + var sample = null; + var MAX_LEFT_MARGIN = 1.9; + var INNER_CIRCLE_WIDTH = 4.7; + var DEFAULT_Z_OFFSET = 5.45; + var previousCameraMode = Camera.mode; + + var renderViewTask = Render.getConfig("RenderMainView"); + var toolbar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); + var request = Script.require('request').request; + var BUTTON_PROPERTIES = { + text: "Interstitial" + }; + + var tablet = null; + var button = null; + + // Tips have a character limit of 69 + var userTips = [ + "Tip: Visit TheSpot to explore featured domains!", + "Tip: Visit our docs online to learn more about scripting!", + "Tip: Don't want others invading your personal space? Turn on the Bubble!", + "Tip: Want to make a friend? Shake hands with them in VR!", + "Tip: Enjoy live music? Visit Rust to dance your heart out!", + "Tip: Have you visited BodyMart to check out the new avatars recently?", + "Tip: Use the Create app to import models and create custom entities.", + "Tip: We're open source! Feel free to contribute to our code on GitHub!", + "Tip: What emotes have you used in the Emote app?", + "Tip: Take and share your snapshots with the everyone using the Snap app.", + "Tip: Did you know you can show websites in-world by creating a web entity?", + "Tip: Find out more information about domains by visiting our website!", + "Tip: Did you know you can get cool new apps from the Marketplace?", + "Tip: Print your snapshots from the Snap app to share with others!", + "Tip: Log in to make friends, visit new domains, and save avatars!" + ]; + + var DEFAULT_DIMENSIONS = { x: 24, y: 24, z: 24 }; + + var loadingSphereID = Overlays.addOverlay("model", { + name: "Loading-Sphere", + position: Vec3.sum(Vec3.sum(MyAvatar.position, {x: 0.0, y: -1.0, z: 0.0}), Vec3.multiplyQbyV(MyAvatar.orientation, {x: 0, y: 0.95, z: 0})), + orientation: Quat.multiply(Quat.fromVec3Degrees({x: 0, y: 180, z: 0}), MyAvatar.orientation), + url: "http://hifi-content.s3.amazonaws.com/alexia/LoadingScreens/black-sphere.fbx", + dimensions: DEFAULT_DIMENSIONS, + alpha: 1, + visible: isVisible, + ignoreRayIntersection: true, + drawInFront: true, + grabbable: false, + parentID: MyAvatar.SELF_ID + }); + + var anchorOverlay = Overlays.addOverlay("cube", { + dimensions: {x: 0.2, y: 0.2, z: 0.2}, + visible: false, + grabbable: false, + ignoreRayIntersection: true, + localPosition: {x: 0.0, y: getAnchorLocalYOffset(), z: DEFAULT_Z_OFFSET }, + orientation: Quat.multiply(Quat.fromVec3Degrees({x: 0, y: 180, z: 0}), MyAvatar.orientation), + solid: true, + drawInFront: true, + parentID: loadingSphereID + }); + + + var domainName = ""; + var domainNameTextID = Overlays.addOverlay("text3d", { + name: "Loading-Destination-Card-Text", + localPosition: { x: 0.0, y: 0.8, z: -0.001 }, + text: domainName, + textAlpha: 1, + backgroundAlpha: 1, + lineHeight: 0.42, + visible: isVisible, + ignoreRayIntersection: true, + drawInFront: true, + grabbable: false, + localOrientation: Quat.fromVec3Degrees({ x: 0, y: 180, z: 0 }), + parentID: anchorOverlay + }); + + var domainText = ""; + var domainDescription = Overlays.addOverlay("text3d", { + name: "Loading-Hostname", + localPosition: { x: 0.0, y: 0.32, z: 0.0 }, + text: domainText, + textAlpha: 1, + backgroundAlpha: 1, + lineHeight: 0.13, + visible: isVisible, + backgroundAlpha: 0, + ignoreRayIntersection: true, + drawInFront: true, + grabbable: false, + localOrientation: Quat.fromVec3Degrees({ x: 0, y: 180, z: 0 }), + parentID: anchorOverlay + }); + + var toolTip = ""; + + var domainToolTip = Overlays.addOverlay("text3d", { + name: "Loading-Tooltip", + localPosition: { x: 0.0 , y: -1.6, z: 0.0 }, + text: toolTip, + textAlpha: 1, + backgroundAlpha: 1, + lineHeight: 0.13, + visible: isVisible, + ignoreRayIntersection: true, + drawInFront: true, + grabbable: false, + localOrientation: Quat.fromVec3Degrees({ x: 0, y: 180, z: 0 }), + parentID: anchorOverlay + }); + + var loadingToTheSpotID = Overlays.addOverlay("image3d", { + name: "Loading-Destination-Card-Text", + localPosition: { x: 0.0 , y: -1.5, z: -0.3 }, + url: Script.resourcesPath() + "images/interstitialPage/goTo_button.png", + alpha: 1, + visible: isVisible, + emissive: true, + ignoreRayIntersection: false, + drawInFront: true, + grabbable: false, + localOrientation: Quat.fromVec3Degrees({ x: 0.0, y: 180.0, z: 0.0 }), + parentID: anchorOverlay + }); + + var loadingBarPlacard = Overlays.addOverlay("image3d", { + name: "Loading-Bar-Placard", + localPosition: { x: 0.0, y: -0.99, z: 0.3 }, + url: Script.resourcesPath() + "images/loadingBar_placard.png", + alpha: 1, + dimensions: { x: 4, y: 2.8}, + visible: isVisible, + emissive: true, + ignoreRayIntersection: false, + drawInFront: true, + grabbable: false, + localOrientation: Quat.fromVec3Degrees({ x: 0.0, y: 180.0, z: 0.0 }), + parentID: anchorOverlay + }); + + var loadingBarProgress = Overlays.addOverlay("image3d", { + name: "Loading-Bar-Progress", + localPosition: { x: 0.0, y: -0.90, z: 0.0 }, + url: Script.resourcesPath() + "images/loadingBar_progress.png", + alpha: 1, + dimensions: {x: 3.8, y: 2.8}, + visible: isVisible, + emissive: true, + ignoreRayIntersection: false, + drawInFront: true, + grabbable: false, + localOrientation: Quat.fromVec3Degrees({ x: 0.0, y: 180.0, z: 0.0 }), + parentID: anchorOverlay + }); + + var TARGET_UPDATE_HZ = 60; // 50hz good enough, but we're using update + var BASIC_TIMER_INTERVAL_MS = 1000 / TARGET_UPDATE_HZ; + var lastInterval = Date.now(); + var currentDomain = "no domain"; + var timer = null; + var target = 0; + + var connectionToDomainFailed = false; + + + function getAnchorLocalYOffset() { + var loadingSpherePosition = Overlays.getProperty(loadingSphereID, "position"); + var loadingSphereOrientation = Overlays.getProperty(loadingSphereID, "rotation"); + var overlayXform = new Xform(loadingSphereOrientation, loadingSpherePosition); + var worldToOverlayXform = overlayXform.inv(); + var headPosition = MyAvatar.getHeadPosition(); + var headPositionInOverlaySpace = worldToOverlayXform.xformPoint(headPosition); + return headPositionInOverlaySpace.y; + } + + function getLeftMargin(overlayID, text) { + var textSize = Overlays.textSize(overlayID, text); + var sizeDifference = ((INNER_CIRCLE_WIDTH - textSize.width) / 2); + var leftMargin = -(MAX_LEFT_MARGIN - sizeDifference); + return leftMargin; + } + + function lerp(a, b, t) { + return ((1 - t) * a + t * b); + } + + function resetValues() { + var properties = { + localPosition: { x: 1.85, y: -0.935, z: 0.0 }, + dimensions: { + x: 0.1, + y: 2.8 + } + }; + + Overlays.editOverlay(loadingBarProgress, properties); + } + + function startInterstitialPage() { + if (timer === null) { + updateOverlays(false); + startAudio(); + target = 0; + currentProgress = 0.1; + connectionToDomainFailed = false; + previousCameraMode = Camera.mode; + Camera.mode = "first person"; + timer = Script.setTimeout(update, BASIC_TIMER_INTERVAL_MS); + } + } + + function startAudio() { + sample = Audio.playSound(tune, { + localOnly: true, + position: MyAvatar.getHeadPosition(), + volume: VOLUME + }); + } + + function endAudio() { + sample.stop(); + sample = null; + } + + function domainChanged(domain) { + if (domain !== currentDomain) { + MyAvatar.restoreAnimation(); + var name = location.placename; + domainName = name.charAt(0).toUpperCase() + name.slice(1); + var doRequest = true; + if (name.length === 0 && location.href === "file:///~/serverless/tutorial.json") { + domainName = "Serveless Domain (Tutorial)"; + doRequest = false; + } + var domainNameLeftMargin = getLeftMargin(domainNameTextID, domainName); + var textProperties = { + text: domainName, + leftMargin: domainNameLeftMargin + }; + + if (doRequest) { + var url = Account.metaverseServerURL + '/api/v1/places/' + domain; + request({ + uri: url + }, function(error, data) { + if (data.status === "success") { + var domainInfo = data.data; + var domainDescriptionText = domainInfo.place.description; + var leftMargin = getLeftMargin(domainDescription, domainDescriptionText); + var domainDescriptionProperties = { + text: domainDescriptionText, + leftMargin: leftMargin + }; + Overlays.editOverlay(domainDescription, domainDescriptionProperties); + } + }); + } else { + var domainDescriptionProperties = { + text: "" + }; + Overlays.editOverlay(domainDescription, domainDescriptionProperties); + } + + var randomIndex = Math.floor(Math.random() * userTips.length); + var tip = userTips[randomIndex]; + var tipLeftMargin = getLeftMargin(domainToolTip, tip); + var toolTipProperties = { + text: tip, + leftMargin: tipLeftMargin + }; + + Overlays.editOverlay(domainNameTextID, textProperties); + Overlays.editOverlay(domainToolTip, toolTipProperties); + + + startInterstitialPage(); + currentDomain = domain; + } + } + + var THE_PLACE = (HifiAbout.buildVersion === "dev") ? "hifi://TheSpot-dev": "hifi://TheSpot"; + function clickedOnOverlay(overlayID, event) { + if (loadingToTheSpotID === overlayID) { + location.handleLookupString(THE_PLACE); + } + } + + var currentProgress = 0.1; + + function updateOverlays(physicsEnabled) { + var properties = { + visible: !physicsEnabled + }; + + var mainSphereProperties = { + visible: !physicsEnabled + }; + + var domainTextProperties = { + text: domainText, + visible: !physicsEnabled + }; + + var loadingBarProperties = { + dimensions: { x: 0.0, y: 2.8 }, + visible: !physicsEnabled + }; + + if (!HMD.active) { + MyAvatar.headOrientation = Quat.multiply(Quat.cancelOutRollAndPitch(MyAvatar.headOrientation), Quat.fromPitchYawRollDegrees(-3.0, 0, 0)); + } + + renderViewTask.getConfig("LightingModel")["enableAmbientLight"] = physicsEnabled; + renderViewTask.getConfig("LightingModel")["enableDirectionalLight"] = physicsEnabled; + renderViewTask.getConfig("LightingModel")["enablePointLight"] = physicsEnabled; + Overlays.editOverlay(loadingSphereID, mainSphereProperties); + Overlays.editOverlay(loadingToTheSpotID, properties); + Overlays.editOverlay(domainNameTextID, properties); + Overlays.editOverlay(domainDescription, domainTextProperties); + Overlays.editOverlay(domainToolTip, properties); + Overlays.editOverlay(loadingBarPlacard, properties); + Overlays.editOverlay(loadingBarProgress, loadingBarProperties); + + + Menu.setIsOptionChecked("Show Overlays", physicsEnabled); + if (!HMD.active) { + toolbar.writeProperty("visible", physicsEnabled); + } + + resetValues(); + + if (physicsEnabled) { + Camera.mode = previousCameraMode; + } + } + + + function scaleInterstitialPage(sensorToWorldScale) { + var yOffset = getAnchorLocalYOffset(); + var localPosition = { + x: 0.0, + y: yOffset, + z: 5.45 + }; + + Overlays.editOverlay(anchorOverlay, { localPosition: localPosition }); + } + + function update() { + var physicsEnabled = Window.isPhysicsEnabled(); + var thisInterval = Date.now(); + var deltaTime = (thisInterval - lastInterval); + lastInterval = thisInterval; + + var domainLoadingProgressPercentage = Window.domainLoadingProgress(); + + var progress = MIN_LOADING_PROGRESS * domainLoadingProgressPercentage; + if (progress >= target) { + target = progress; + } + + if ((physicsEnabled && (currentProgress < TOTAL_LOADING_PROGRESS))) { + target = TOTAL_LOADING_PROGRESS; + } + + currentProgress = lerp(currentProgress, target, 0.2); + var properties = { + localPosition: { x: (1.85 - (currentProgress / 2) - (-0.029 * (currentProgress / TOTAL_LOADING_PROGRESS))), y: -0.935, z: 0.0 }, + dimensions: { + x: currentProgress, + y: 2.8 + } + }; + + Overlays.editOverlay(loadingBarProgress, properties); + if ((physicsEnabled && (currentProgress >= (TOTAL_LOADING_PROGRESS - EPSILON)))) { + updateOverlays((physicsEnabled || connectionToDomainFailed)); + endAudio(); + currentDomain = "no domain"; + timer = null; + return; + } + + timer = Script.setTimeout(update, BASIC_TIMER_INTERVAL_MS); + } + var whiteColor = {red: 255, green: 255, blue: 255}; + var greyColor = {red: 125, green: 125, blue: 125}; + Overlays.mouseReleaseOnOverlay.connect(clickedOnOverlay); + Overlays.hoverEnterOverlay.connect(function(overlayID, event) { + if (overlayID === loadingToTheSpotID) { + Overlays.editOverlay(loadingToTheSpotID, { color: greyColor }); + } + }); + + Overlays.hoverLeaveOverlay.connect(function(overlayID, event) { + if (overlayID === loadingToTheSpotID) { + Overlays.editOverlay(loadingToTheSpotID, { color: whiteColor }); + } + }); + + location.hostChanged.connect(domainChanged); + location.lookupResultsFinished.connect(function() { + Script.setTimeout(function() { + connectionToDomainFailed = !location.isConnected; + }, 1200); + }); + + MyAvatar.sensorToWorldScaleChanged.connect(scaleInterstitialPage); + MyAvatar.sessionUUIDChanged.connect(function() { + var avatarSessionUUID = MyAvatar.sessionUUID; + Overlays.editOverlay(loadingSphereID, { parentID: avatarSessionUUID }); + }); + + var toggle = true; + if (DEBUG) { + tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + button = tablet.addButton(BUTTON_PROPERTIES); + + button.clicked.connect(function() { + toggle = !toggle; + updateOverlays(toggle); + }); + } + + function cleanup() { + Overlays.deleteOverlay(loadingSphereID); + Overlays.deleteOverlay(loadingToTheSpotID); + Overlays.deleteOverlay(domainNameTextID); + Overlays.deleteOverlay(domainDescription); + Overlays.deleteOverlay(domainToolTip); + Overlays.deleteOverlay(loadingBarPlacard); + Overlays.deleteOverlay(loadingBarProgress); + Overlays.deleteOverlay(anchorOverlay); + + if (DEBUG) { + tablet.removeButton(button); + } + + renderViewTask.getConfig("LightingModel")["enableAmbientLight"] = true; + renderViewTask.getConfig("LightingModel")["enableDirectionalLight"] = true; + renderViewTask.getConfig("LightingModel")["enablePointLight"] = true; + Menu.setIsOptionChecked("Show Overlays", true); + if (!HMD.active) { + toolbar.writeProperty("visible", true); + } + } + + Script.scriptEnding.connect(cleanup); +}()); diff --git a/scripts/system/libraries/EditEntityList.qml b/scripts/system/libraries/EditEntityList.qml new file mode 100644 index 0000000000..d8099cb670 --- /dev/null +++ b/scripts/system/libraries/EditEntityList.qml @@ -0,0 +1,11 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.2 +import QtWebChannel 1.0 +import QtGraphicalEffects 1.0 +import "qrc:///qml/controls" as HifiControls + +HifiControls.WebView { + id: entityListToolWebView + url: Qt.resolvedUrl("../html/entityList.html") + enabled: true +} diff --git a/scripts/system/libraries/entityList.js b/scripts/system/libraries/entityList.js index 678b2eeb0b..27e079589b 100644 --- a/scripts/system/libraries/entityList.js +++ b/scripts/system/libraries/entityList.js @@ -11,6 +11,22 @@ /* global EntityListTool, Tablet, selectionManager, Entities, Camera, MyAvatar, Vec3, Menu, Messages, cameraManager, MENU_EASE_ON_FOCUS, deleteSelectedEntities, toggleSelectedEntitiesLocked, toggleSelectedEntitiesVisible */ +var PROFILING_ENABLED = false; +var profileIndent = ''; +const PROFILE_NOOP = function(_name, fn, args) { + fn.apply(this, args); +} ; +PROFILE = !PROFILING_ENABLED ? PROFILE_NOOP : function(name, fn, args) { + console.log("PROFILE-Script " + profileIndent + "(" + name + ") Begin"); + var previousIndent = profileIndent; + profileIndent += ' '; + var before = Date.now(); + fn.apply(this, args); + var delta = Date.now() - before; + profileIndent = previousIndent; + console.log("PROFILE-Script " + profileIndent + "(" + name + ") End " + delta + "ms"); +}; + EntityListTool = function(shouldUseEditTabletApp) { var that = {}; @@ -20,7 +36,7 @@ EntityListTool = function(shouldUseEditTabletApp) { var ENTITY_LIST_WIDTH = 495; var MAX_DEFAULT_CREATE_TOOLS_HEIGHT = 778; var entityListWindow = new CreateWindow( - Script.resourcesPath() + "qml/hifi/tablet/EditEntityList.qml", + Script.resolvePath("EditEntityList.qml"), 'Entity List', 'com.highfidelity.create.entityListWindow', function () { @@ -66,13 +82,18 @@ EntityListTool = function(shouldUseEditTabletApp) { that.setVisible(false); function emitJSONScriptEvent(data) { - var dataString = JSON.stringify(data); - webView.emitScriptEvent(dataString); - if (entityListWindow.window) { - entityListWindow.window.emitScriptEvent(dataString); - } + var dataString; + PROFILE("Script-JSON.stringify", function() { + dataString = JSON.stringify(data); + }); + PROFILE("Script-emitScriptEvent", function() { + webView.emitScriptEvent(dataString); + if (entityListWindow.window) { + entityListWindow.window.emitScriptEvent(dataString); + } + }); } - + that.toggleVisible = function() { that.setVisible(!visible); }; @@ -103,7 +124,7 @@ EntityListTool = function(shouldUseEditTabletApp) { selectedIDs: selectedIDs }); }; - + that.deleteEntities = function (deletedIDs) { emitJSONScriptEvent({ type: "deleted", @@ -116,59 +137,66 @@ EntityListTool = function(shouldUseEditTabletApp) { } that.sendUpdate = function() { - var entities = []; + PROFILE('Script-sendUpdate', function() { + var entities = []; - var ids; - if (filterInView) { - ids = Entities.findEntitiesInFrustum(Camera.frustum); - } else { - ids = Entities.findEntities(MyAvatar.position, searchRadius); - } - - var cameraPosition = Camera.position; - for (var i = 0; i < ids.length; i++) { - var id = ids[i]; - var properties = Entities.getEntityProperties(id); - - if (!filterInView || Vec3.distance(properties.position, cameraPosition) <= searchRadius) { - var url = ""; - if (properties.type === "Model") { - url = properties.modelURL; - } else if (properties.type === "Material") { - url = properties.materialURL; + var ids; + PROFILE("findEntities", function() { + if (filterInView) { + ids = Entities.findEntitiesInFrustum(Camera.frustum); + } else { + ids = Entities.findEntities(MyAvatar.position, searchRadius); } - entities.push({ - id: id, - name: properties.name, - type: properties.type, - url: url, - locked: properties.locked, - visible: properties.visible, - verticesCount: (properties.renderInfo !== undefined ? - valueIfDefined(properties.renderInfo.verticesCount) : ""), - texturesCount: (properties.renderInfo !== undefined ? - valueIfDefined(properties.renderInfo.texturesCount) : ""), - texturesSize: (properties.renderInfo !== undefined ? - valueIfDefined(properties.renderInfo.texturesSize) : ""), - hasTransparent: (properties.renderInfo !== undefined ? - valueIfDefined(properties.renderInfo.hasTransparent) : ""), - isBaked: properties.type === "Model" ? url.toLowerCase().endsWith(".baked.fbx") : false, - drawCalls: (properties.renderInfo !== undefined ? - valueIfDefined(properties.renderInfo.drawCalls) : ""), - hasScript: properties.script !== "" - }); + }); + + var cameraPosition = Camera.position; + PROFILE("getProperties", function() { + for (var i = 0; i < ids.length; i++) { + var id = ids[i]; + var properties = Entities.getEntityProperties(id, ['name', 'type', 'locked', + 'visible', 'renderInfo', 'type', 'modelURL', 'materialURL', 'script']); + + if (!filterInView || Vec3.distance(properties.position, cameraPosition) <= searchRadius) { + var url = ""; + if (properties.type === "Model") { + url = properties.modelURL; + } else if (properties.type === "Material") { + url = properties.materialURL; + } + entities.push({ + id: id, + name: properties.name, + type: properties.type, + url: url, + locked: properties.locked, + visible: properties.visible, + verticesCount: (properties.renderInfo !== undefined ? + valueIfDefined(properties.renderInfo.verticesCount) : ""), + texturesCount: (properties.renderInfo !== undefined ? + valueIfDefined(properties.renderInfo.texturesCount) : ""), + texturesSize: (properties.renderInfo !== undefined ? + valueIfDefined(properties.renderInfo.texturesSize) : ""), + hasTransparent: (properties.renderInfo !== undefined ? + valueIfDefined(properties.renderInfo.hasTransparent) : ""), + isBaked: properties.type === "Model" ? url.toLowerCase().endsWith(".baked.fbx") : false, + drawCalls: (properties.renderInfo !== undefined ? + valueIfDefined(properties.renderInfo.drawCalls) : ""), + hasScript: properties.script !== "" + }); + } + } + }); + + var selectedIDs = []; + for (var j = 0; j < selectionManager.selections.length; j++) { + selectedIDs.push(selectionManager.selections[j]); } - } - var selectedIDs = []; - for (var j = 0; j < selectionManager.selections.length; j++) { - selectedIDs.push(selectionManager.selections[j]); - } - - emitJSONScriptEvent({ - type: "update", - entities: entities, - selectedIDs: selectedIDs, + emitJSONScriptEvent({ + type: "update", + entities: entities, + selectedIDs: selectedIDs, + }); }); }; diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 1c7d5244a1..5bca58b1ac 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -14,7 +14,7 @@ // /* global SelectionManager, SelectionDisplay, grid, rayPlaneIntersection, rayPlaneIntersection2, pushCommandForSelections, - getMainTabletIDs, getControllerWorldLocation */ + getMainTabletIDs, getControllerWorldLocation, TRIGGER_ON_VALUE */ var SPACE_LOCAL = "local"; var SPACE_WORLD = "world"; @@ -22,6 +22,7 @@ var HIGHLIGHT_LIST_NAME = "editHandleHighlightList"; Script.include([ "./controllers.js", + "./controllerDispatcherUtils.js", "./utils.js" ]); @@ -410,7 +411,7 @@ SelectionDisplay = (function() { var COLOR_RED = { red: 226, green: 51, blue: 77 }; var COLOR_HOVER = { red: 227, green: 227, blue: 227 }; var COLOR_ROTATE_CURRENT_RING = { red: 255, green: 99, blue: 9 }; - var COLOR_SCALE_EDGE = { red: 87, green: 87, blue: 87 }; + var COLOR_BOUNDING_EDGE = { red: 87, green: 87, blue: 87 }; var COLOR_SCALE_CUBE = { red: 106, green: 106, blue: 106 }; var COLOR_SCALE_CUBE_SELECTED = { red: 18, green: 18, blue: 18 }; @@ -433,21 +434,23 @@ SelectionDisplay = (function() { var ROTATE_DISPLAY_SIZE_Y_MULTIPLIER = 0.09; var ROTATE_DISPLAY_LINE_HEIGHT_MULTIPLIER = 0.07; - var STRETCH_SPHERE_OFFSET = 0.06; - var STRETCH_SPHERE_CAMERA_DISTANCE_MULTIPLE = 0.01; + var STRETCH_CUBE_OFFSET = 0.06; + var STRETCH_CUBE_CAMERA_DISTANCE_MULTIPLE = 0.02; var STRETCH_MINIMUM_DIMENSION = 0.001; var STRETCH_ALL_MINIMUM_DIMENSION = 0.01; - var STRETCH_DIRECTION_ALL_CAMERA_DISTANCE_MULTIPLE = 6; + var STRETCH_DIRECTION_ALL_CAMERA_DISTANCE_MULTIPLE = 2; var STRETCH_PANEL_WIDTH = 0.01; - var SCALE_CUBE_OFFSET = 0.5; - var SCALE_CUBE_CAMERA_DISTANCE_MULTIPLE = 0.0125; + var BOUNDING_EDGE_OFFSET = 0.5; + var SCALE_CUBE_CAMERA_DISTANCE_MULTIPLE = 0.02; var CLONER_OFFSET = { x: 0.9, y: -0.9, z: 0.9 }; var CTRL_KEY_CODE = 16777249; var RAIL_AXIS_LENGTH = 10000; + + var NO_HAND = -1; var TRANSLATE_DIRECTION = { X: 0, @@ -478,8 +481,6 @@ SelectionDisplay = (function() { YAW: 1, ROLL: 2 }; - - var NO_TRIGGER_HAND = -1; var spaceMode = SPACE_LOCAL; var overlayNames = []; @@ -495,6 +496,8 @@ SelectionDisplay = (function() { var worldRotationX; var worldRotationY; var worldRotationZ; + + var activeStretchCubePanelOffset = null; var previousHandle = null; var previousHandleHelper = null; @@ -589,20 +592,18 @@ SelectionDisplay = (function() { leftMargin: 0 }); - var handlePropertiesStretchSpheres = { - alpha: 1, - shape: "Sphere", + var handlePropertiesStretchCubes = { solid: true, visible: false, ignoreRayIntersection: false, drawInFront: true }; - var handleStretchXSphere = Overlays.addOverlay("shape", handlePropertiesStretchSpheres); - Overlays.editOverlay(handleStretchXSphere, { color: COLOR_RED }); - var handleStretchYSphere = Overlays.addOverlay("shape", handlePropertiesStretchSpheres); - Overlays.editOverlay(handleStretchYSphere, { color: COLOR_GREEN }); - var handleStretchZSphere = Overlays.addOverlay("shape", handlePropertiesStretchSpheres); - Overlays.editOverlay(handleStretchZSphere, { color: COLOR_BLUE }); + var handleStretchXCube = Overlays.addOverlay("cube", handlePropertiesStretchCubes); + Overlays.editOverlay(handleStretchXCube, { color: COLOR_RED }); + var handleStretchYCube = Overlays.addOverlay("cube", handlePropertiesStretchCubes); + Overlays.editOverlay(handleStretchYCube, { color: COLOR_GREEN }); + var handleStretchZCube = Overlays.addOverlay("cube", handlePropertiesStretchCubes); + Overlays.editOverlay(handleStretchZCube, { color: COLOR_BLUE }); var handlePropertiesStretchPanel = { shape: "Quad", @@ -619,8 +620,7 @@ SelectionDisplay = (function() { var handleStretchZPanel = Overlays.addOverlay("shape", handlePropertiesStretchPanel); Overlays.editOverlay(handleStretchZPanel, { color: COLOR_BLUE }); - var handlePropertiesScaleCubes = { - alpha: 1, + var handleScaleCube = Overlays.addOverlay("cube", { size: 0.025, color: COLOR_SCALE_CUBE, solid: true, @@ -628,36 +628,28 @@ SelectionDisplay = (function() { ignoreRayIntersection: false, drawInFront: true, borderSize: 1.4 - }; - var handleScaleLBNCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // (-x, -y, -z) - var handleScaleRBNCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // ( x, -y, -z) - var handleScaleLBFCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // (-x, -y, z) - var handleScaleRBFCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // ( x, -y, z) - var handleScaleLTNCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // (-x, y, -z) - var handleScaleRTNCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // ( x, y, -z) - var handleScaleLTFCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // (-x, y, z) - var handleScaleRTFCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // ( x, y, z) + }); - var handlePropertiesScaleEdge = { + var handlePropertiesBoundingEdge = { alpha: 1, - color: COLOR_SCALE_EDGE, + color: COLOR_BOUNDING_EDGE, visible: false, ignoreRayIntersection: true, drawInFront: true, lineWidth: 0.2 }; - var handleScaleTREdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge); - var handleScaleTLEdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge); - var handleScaleTFEdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge); - var handleScaleTNEdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge); - var handleScaleBREdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge); - var handleScaleBLEdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge); - var handleScaleBFEdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge); - var handleScaleBNEdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge); - var handleScaleNREdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge); - var handleScaleNLEdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge); - var handleScaleFREdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge); - var handleScaleFLEdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge); + var handleBoundingTREdge = Overlays.addOverlay("line3d", handlePropertiesBoundingEdge); + var handleBoundingTLEdge = Overlays.addOverlay("line3d", handlePropertiesBoundingEdge); + var handleBoundingTFEdge = Overlays.addOverlay("line3d", handlePropertiesBoundingEdge); + var handleBoundingTNEdge = Overlays.addOverlay("line3d", handlePropertiesBoundingEdge); + var handleBoundingBREdge = Overlays.addOverlay("line3d", handlePropertiesBoundingEdge); + var handleBoundingBLEdge = Overlays.addOverlay("line3d", handlePropertiesBoundingEdge); + var handleBoundingBFEdge = Overlays.addOverlay("line3d", handlePropertiesBoundingEdge); + var handleBoundingBNEdge = Overlays.addOverlay("line3d", handlePropertiesBoundingEdge); + var handleBoundingNREdge = Overlays.addOverlay("line3d", handlePropertiesBoundingEdge); + var handleBoundingNLEdge = Overlays.addOverlay("line3d", handlePropertiesBoundingEdge); + var handleBoundingFREdge = Overlays.addOverlay("line3d", handlePropertiesBoundingEdge); + var handleBoundingFLEdge = Overlays.addOverlay("line3d", handlePropertiesBoundingEdge); var handleCloner = Overlays.addOverlay("cube", { alpha: 1, @@ -738,32 +730,25 @@ SelectionDisplay = (function() { handleRotateRollRing, handleRotateCurrentRing, rotationDegreesDisplay, - handleStretchXSphere, - handleStretchYSphere, - handleStretchZSphere, + handleStretchXCube, + handleStretchYCube, + handleStretchZCube, handleStretchXPanel, handleStretchYPanel, handleStretchZPanel, - handleScaleLBNCube, - handleScaleRBNCube, - handleScaleLBFCube, - handleScaleRBFCube, - handleScaleLTNCube, - handleScaleRTNCube, - handleScaleLTFCube, - handleScaleRTFCube, - handleScaleTREdge, - handleScaleTLEdge, - handleScaleTFEdge, - handleScaleTNEdge, - handleScaleBREdge, - handleScaleBLEdge, - handleScaleBFEdge, - handleScaleBNEdge, - handleScaleNREdge, - handleScaleNLEdge, - handleScaleFREdge, - handleScaleFLEdge, + handleScaleCube, + handleBoundingTREdge, + handleBoundingTLEdge, + handleBoundingTFEdge, + handleBoundingTNEdge, + handleBoundingBREdge, + handleBoundingBLEdge, + handleBoundingBFEdge, + handleBoundingBNEdge, + handleBoundingNREdge, + handleBoundingNLEdge, + handleBoundingFREdge, + handleBoundingFLEdge, handleCloner, selectionBox, iconSelectionBox, @@ -787,34 +772,27 @@ SelectionDisplay = (function() { overlayNames[handleRotateCurrentRing] = "handleRotateCurrentRing"; overlayNames[rotationDegreesDisplay] = "rotationDegreesDisplay"; - overlayNames[handleStretchXSphere] = "handleStretchXSphere"; - overlayNames[handleStretchYSphere] = "handleStretchYSphere"; - overlayNames[handleStretchZSphere] = "handleStretchZSphere"; + overlayNames[handleStretchXCube] = "handleStretchXCube"; + overlayNames[handleStretchYCube] = "handleStretchYCube"; + overlayNames[handleStretchZCube] = "handleStretchZCube"; overlayNames[handleStretchXPanel] = "handleStretchXPanel"; overlayNames[handleStretchYPanel] = "handleStretchYPanel"; overlayNames[handleStretchZPanel] = "handleStretchZPanel"; - overlayNames[handleScaleLBNCube] = "handleScaleLBNCube"; - overlayNames[handleScaleRBNCube] = "handleScaleRBNCube"; - overlayNames[handleScaleLBFCube] = "handleScaleLBFCube"; - overlayNames[handleScaleRBFCube] = "handleScaleRBFCube"; - overlayNames[handleScaleLTNCube] = "handleScaleLTNCube"; - overlayNames[handleScaleRTNCube] = "handleScaleRTNCube"; - overlayNames[handleScaleLTFCube] = "handleScaleLTFCube"; - overlayNames[handleScaleRTFCube] = "handleScaleRTFCube"; + overlayNames[handleScaleCube] = "handleScaleCube"; - overlayNames[handleScaleTREdge] = "handleScaleTREdge"; - overlayNames[handleScaleTLEdge] = "handleScaleTLEdge"; - overlayNames[handleScaleTFEdge] = "handleScaleTFEdge"; - overlayNames[handleScaleTNEdge] = "handleScaleTNEdge"; - overlayNames[handleScaleBREdge] = "handleScaleBREdge"; - overlayNames[handleScaleBLEdge] = "handleScaleBLEdge"; - overlayNames[handleScaleBFEdge] = "handleScaleBFEdge"; - overlayNames[handleScaleBNEdge] = "handleScaleBNEdge"; - overlayNames[handleScaleNREdge] = "handleScaleNREdge"; - overlayNames[handleScaleNLEdge] = "handleScaleNLEdge"; - overlayNames[handleScaleFREdge] = "handleScaleFREdge"; - overlayNames[handleScaleFLEdge] = "handleScaleFLEdge"; + overlayNames[handleBoundingTREdge] = "handleBoundingTREdge"; + overlayNames[handleBoundingTLEdge] = "handleBoundingTLEdge"; + overlayNames[handleBoundingTFEdge] = "handleBoundingTFEdge"; + overlayNames[handleBoundingTNEdge] = "handleBoundingTNEdge"; + overlayNames[handleBoundingBREdge] = "handleBoundingBREdge"; + overlayNames[handleBoundingBLEdge] = "handleBoundingBLEdge"; + overlayNames[handleBoundingBFEdge] = "handleBoundingBFEdge"; + overlayNames[handleBoundingBNEdge] = "handleBoundingBNEdge"; + overlayNames[handleBoundingNREdge] = "handleBoundingNREdge"; + overlayNames[handleBoundingNLEdge] = "handleBoundingNLEdge"; + overlayNames[handleBoundingFREdge] = "handleBoundingFREdge"; + overlayNames[handleBoundingFLEdge] = "handleBoundingFLEdge"; overlayNames[handleCloner] = "handleCloner"; overlayNames[selectionBox] = "selectionBox"; @@ -825,11 +803,21 @@ SelectionDisplay = (function() { // We get mouseMoveEvents from the handControllers, via handControllerPointer. // But we dont' get mousePressEvents. - that.triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click'); - Script.scriptEnding.connect(that.triggerMapping.disable); - that.triggeredHand = NO_TRIGGER_HAND; + that.triggerClickMapping = Controller.newMapping(Script.resolvePath('') + '-click'); + that.triggerPressMapping = Controller.newMapping(Script.resolvePath('') + '-press'); + that.triggeredHand = NO_HAND; + that.pressedHand = NO_HAND; that.triggered = function() { - return that.triggeredHand !== NO_TRIGGER_HAND; + return that.triggeredHand !== NO_HAND; + } + function pointingAtDesktopWindowOrTablet(hand) { + var pointingAtDesktopWindow = (hand === Controller.Standard.RightHand && + SelectionManager.pointingAtDesktopWindowRight) || + (hand === Controller.Standard.LeftHand && + SelectionManager.pointingAtDesktopWindowLeft); + var pointingAtTablet = (hand === Controller.Standard.RightHand && SelectionManager.pointingAtTabletRight) || + (hand === Controller.Standard.LeftHand && SelectionManager.pointingAtTabletLeft); + return pointingAtDesktopWindow || pointingAtTablet; } function makeClickHandler(hand) { return function (clicked) { @@ -837,26 +825,39 @@ SelectionDisplay = (function() { if (that.triggered() && hand !== that.triggeredHand) { return; } - if (!that.triggered() && clicked) { - var pointingAtDesktopWindow = (hand === Controller.Standard.RightHand && - SelectionManager.pointingAtDesktopWindowRight) || - (hand === Controller.Standard.LeftHand && - SelectionManager.pointingAtDesktopWindowLeft); - var pointingAtTablet = (hand === Controller.Standard.RightHand && SelectionManager.pointingAtTabletRight) || - (hand === Controller.Standard.LeftHand && SelectionManager.pointingAtTabletLeft); - if (pointingAtDesktopWindow || pointingAtTablet) { - return; - } + if (!that.triggered() && clicked && !pointingAtDesktopWindowOrTablet(hand)) { that.triggeredHand = hand; that.mousePressEvent({}); } else if (that.triggered() && !clicked) { - that.triggeredHand = NO_TRIGGER_HAND; + that.triggeredHand = NO_HAND; that.mouseReleaseEvent({}); } }; } - that.triggerMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand)); - that.triggerMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand)); + function makePressHandler(hand) { + return function (value) { + if (value >= TRIGGER_ON_VALUE && !that.triggered() && !pointingAtDesktopWindowOrTablet(hand)) { + that.pressedHand = hand; + that.updateHighlight({}); + } else { + that.pressedHand = NO_HAND; + that.resetPreviousHandleColor(); + } + } + } + that.triggerClickMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand)); + that.triggerClickMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand)); + that.triggerPressMapping.from(Controller.Standard.RT).peek().to(makePressHandler(Controller.Standard.RightHand)); + that.triggerPressMapping.from(Controller.Standard.LT).peek().to(makePressHandler(Controller.Standard.LeftHand)); + that.enableTriggerMapping = function() { + that.triggerClickMapping.enable(); + that.triggerPressMapping.enable(); + }; + that.disableTriggerMapping = function() { + that.triggerClickMapping.disable(); + that.triggerPressMapping.disable(); + } + Script.scriptEnding.connect(that.disableTriggerMapping); // FUNCTION DEF(s): Intersection Check Helpers function testRayIntersect(queryRay, overlayIncludes, overlayExcludes) { @@ -983,6 +984,61 @@ SelectionDisplay = (function() { } return Uuid.NULL; }; + + that.updateHighlight = function(event) { + // if no tool is active, then just look for handles to highlight... + var pickRay = generalComputePickRay(event.x, event.y); + var result = Overlays.findRayIntersection(pickRay); + var pickedColor; + var highlightNeeded = false; + + if (result.intersects) { + switch (result.overlayID) { + case handleTranslateXCone: + case handleTranslateXCylinder: + case handleRotatePitchRing: + case handleStretchXCube: + pickedColor = COLOR_RED; + highlightNeeded = true; + break; + case handleTranslateYCone: + case handleTranslateYCylinder: + case handleRotateYawRing: + case handleStretchYCube: + pickedColor = COLOR_GREEN; + highlightNeeded = true; + break; + case handleTranslateZCone: + case handleTranslateZCylinder: + case handleRotateRollRing: + case handleStretchZCube: + pickedColor = COLOR_BLUE; + highlightNeeded = true; + break; + case handleScaleCube: + pickedColor = COLOR_SCALE_CUBE; + highlightNeeded = true; + break; + default: + that.resetPreviousHandleColor(); + break; + } + + if (highlightNeeded) { + that.resetPreviousHandleColor(); + Overlays.editOverlay(result.overlayID, { color: COLOR_HOVER }); + previousHandle = result.overlayID; + previousHandleHelper = that.getHandleHelper(result.overlayID); + if (previousHandleHelper !== null) { + Overlays.editOverlay(previousHandleHelper, { color: COLOR_HOVER }); + } + previousHandleColor = pickedColor; + } + + } else { + that.resetPreviousHandleColor(); + } + }; // FUNCTION: MOUSE MOVE EVENT var lastMouseEvent = null; @@ -1010,66 +1066,8 @@ SelectionDisplay = (function() { return true; } - // if no tool is active, then just look for handles to highlight... - var pickRay = generalComputePickRay(event.x, event.y); - var result = Overlays.findRayIntersection(pickRay); - var pickedColor; - var highlightNeeded = false; - - if (result.intersects) { - switch (result.overlayID) { - case handleTranslateXCone: - case handleTranslateXCylinder: - case handleRotatePitchRing: - case handleStretchXSphere: - pickedColor = COLOR_RED; - highlightNeeded = true; - break; - case handleTranslateYCone: - case handleTranslateYCylinder: - case handleRotateYawRing: - case handleStretchYSphere: - pickedColor = COLOR_GREEN; - highlightNeeded = true; - break; - case handleTranslateZCone: - case handleTranslateZCylinder: - case handleRotateRollRing: - case handleStretchZSphere: - pickedColor = COLOR_BLUE; - highlightNeeded = true; - break; - case handleScaleLBNCube: - case handleScaleRBNCube: - case handleScaleLBFCube: - case handleScaleRBFCube: - case handleScaleLTNCube: - case handleScaleRTNCube: - case handleScaleLTFCube: - case handleScaleRTFCube: - pickedColor = COLOR_SCALE_CUBE; - highlightNeeded = true; - break; - default: - that.resetPreviousHandleColor(); - break; - } - - if (highlightNeeded) { - that.resetPreviousHandleColor(); - Overlays.editOverlay(result.overlayID, { color: COLOR_HOVER }); - previousHandle = result.overlayID; - previousHandleHelper = that.getHandleHelper(result.overlayID); - if (previousHandleHelper !== null) { - Overlays.editOverlay(previousHandleHelper, { color: COLOR_HOVER }); - } - previousHandleColor = pickedColor; - } - - } else { - that.resetPreviousHandleColor(); - } - + that.updateHighlight(event); + if (wantDebug) { print("=============== eST::MouseMoveEvent END ======================="); } @@ -1165,9 +1163,10 @@ SelectionDisplay = (function() { } }; - function controllerComputePickRay() { - var controllerPose = getControllerWorldLocation(that.triggeredHand, true); - if (controllerPose.valid && that.triggered()) { + function controllerComputePickRay(hand) { + var hand = that.triggered() ? that.triggeredHand : that.pressedHand; + var controllerPose = getControllerWorldLocation(hand, true); + if (controllerPose.valid) { var controllerPosition = controllerPose.translation; // This gets point direction right, but if you want general quaternion it would be more complicated: var controllerDirection = Quat.getUp(controllerPose.rotation); @@ -1178,6 +1177,12 @@ SelectionDisplay = (function() { function generalComputePickRay(x, y) { return controllerComputePickRay() || Camera.computePickRay(x, y); } + + function getControllerAvatarFramePositionFromPickRay(pickRay) { + var controllerPosition = Vec3.subtract(pickRay.origin, MyAvatar.position); + controllerPosition = Vec3.multiplyQbyV(Quat.inverse(MyAvatar.orientation), controllerPosition); + return controllerPosition; + } function getDistanceToCamera(position) { var cameraPosition = Camera.getPosition(); @@ -1424,127 +1429,56 @@ SelectionDisplay = (function() { dimensions: arrowConeDimensions }); - // UPDATE SCALE CUBES - var scaleCubeOffsetX = SCALE_CUBE_OFFSET * dimensions.x; - var scaleCubeOffsetY = SCALE_CUBE_OFFSET * dimensions.y; - var scaleCubeOffsetZ = SCALE_CUBE_OFFSET * dimensions.z; - var scaleCubeRotation = spaceMode === SPACE_LOCAL ? rotation : Quat.IDENTITY; - var scaleLBNCubePosition = { x: -scaleCubeOffsetX, y: -scaleCubeOffsetY, z: -scaleCubeOffsetZ }; - scaleLBNCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleLBNCubePosition)); - var scaleLBNCubeToCamera = getDistanceToCamera(scaleLBNCubePosition); - var scaleRBNCubePosition = { x: scaleCubeOffsetX, y: -scaleCubeOffsetY, z: -scaleCubeOffsetZ }; - scaleRBNCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleRBNCubePosition)); - var scaleRBNCubeToCamera = getDistanceToCamera(scaleRBNCubePosition); - var scaleLBFCubePosition = { x: -scaleCubeOffsetX, y: -scaleCubeOffsetY, z: scaleCubeOffsetZ }; - scaleLBFCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleLBFCubePosition)); - var scaleLBFCubeToCamera = getDistanceToCamera(scaleLBFCubePosition); - var scaleRBFCubePosition = { x: scaleCubeOffsetX, y: -scaleCubeOffsetY, z: scaleCubeOffsetZ }; - scaleRBFCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleRBFCubePosition)); - var scaleRBFCubeToCamera = getDistanceToCamera(scaleRBFCubePosition); - var scaleLTNCubePosition = { x: -scaleCubeOffsetX, y: scaleCubeOffsetY, z: -scaleCubeOffsetZ }; - scaleLTNCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleLTNCubePosition)); - var scaleLTNCubeToCamera = getDistanceToCamera(scaleLTNCubePosition); - var scaleRTNCubePosition = { x: scaleCubeOffsetX, y: scaleCubeOffsetY, z: -scaleCubeOffsetZ }; - scaleRTNCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleRTNCubePosition)); - var scaleRTNCubeToCamera = getDistanceToCamera(scaleRTNCubePosition); - var scaleLTFCubePosition = { x: -scaleCubeOffsetX, y: scaleCubeOffsetY, z: scaleCubeOffsetZ }; - scaleLTFCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleLTFCubePosition)); - var scaleLTFCubeToCamera = getDistanceToCamera(scaleLTFCubePosition); - var scaleRTFCubePosition = { x: scaleCubeOffsetX, y: scaleCubeOffsetY, z: scaleCubeOffsetZ }; - scaleRTFCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleRTFCubePosition)); - var scaleRTFCubeToCamera = getDistanceToCamera(scaleRTFCubePosition); - - var scaleCubeToCamera = Math.min(scaleLBNCubeToCamera, scaleRBNCubeToCamera, scaleLBFCubeToCamera, - scaleRBFCubeToCamera, scaleLTNCubeToCamera, scaleRTNCubeToCamera, - scaleLTFCubeToCamera, scaleRTFCubeToCamera); - var scaleCubeDimension = scaleCubeToCamera * SCALE_CUBE_CAMERA_DISTANCE_MULTIPLE; - var scaleCubeDimensions = { x: scaleCubeDimension, y: scaleCubeDimension, z: scaleCubeDimension }; - - Overlays.editOverlay(handleScaleLBNCube, { - position: scaleLBNCubePosition, - rotation: scaleCubeRotation, - dimensions: scaleCubeDimensions - }); - Overlays.editOverlay(handleScaleRBNCube, { - position: scaleRBNCubePosition, - rotation: scaleCubeRotation, - dimensions: scaleCubeDimensions - }); - Overlays.editOverlay(handleScaleLBFCube, { - position: scaleLBFCubePosition, - rotation: scaleCubeRotation, - dimensions: scaleCubeDimensions - }); - Overlays.editOverlay(handleScaleRBFCube, { - position: scaleRBFCubePosition, - rotation: scaleCubeRotation, - dimensions: scaleCubeDimensions - }); - Overlays.editOverlay(handleScaleLTNCube, { - position: scaleLTNCubePosition, - rotation: scaleCubeRotation, - dimensions: scaleCubeDimensions - }); - Overlays.editOverlay(handleScaleRTNCube, { - position: scaleRTNCubePosition, - rotation: scaleCubeRotation, - dimensions: scaleCubeDimensions - }); - Overlays.editOverlay(handleScaleLTFCube, { - position: scaleLTFCubePosition, - rotation: scaleCubeRotation, - dimensions: scaleCubeDimensions - }); - Overlays.editOverlay(handleScaleRTFCube, { - position: scaleRTFCubePosition, - rotation: scaleCubeRotation, - dimensions: scaleCubeDimensions - }); - - // UPDATE SCALE EDGES - Overlays.editOverlay(handleScaleTREdge, { start: scaleRTNCubePosition, end: scaleRTFCubePosition }); - Overlays.editOverlay(handleScaleTLEdge, { start: scaleLTNCubePosition, end: scaleLTFCubePosition }); - Overlays.editOverlay(handleScaleTFEdge, { start: scaleLTFCubePosition, end: scaleRTFCubePosition }); - Overlays.editOverlay(handleScaleTNEdge, { start: scaleLTNCubePosition, end: scaleRTNCubePosition }); - Overlays.editOverlay(handleScaleBREdge, { start: scaleRBNCubePosition, end: scaleRBFCubePosition }); - Overlays.editOverlay(handleScaleBLEdge, { start: scaleLBNCubePosition, end: scaleLBFCubePosition }); - Overlays.editOverlay(handleScaleBFEdge, { start: scaleLBFCubePosition, end: scaleRBFCubePosition }); - Overlays.editOverlay(handleScaleBNEdge, { start: scaleLBNCubePosition, end: scaleRBNCubePosition }); - Overlays.editOverlay(handleScaleNREdge, { start: scaleRTNCubePosition, end: scaleRBNCubePosition }); - Overlays.editOverlay(handleScaleNLEdge, { start: scaleLTNCubePosition, end: scaleLBNCubePosition }); - Overlays.editOverlay(handleScaleFREdge, { start: scaleRTFCubePosition, end: scaleRBFCubePosition }); - Overlays.editOverlay(handleScaleFLEdge, { start: scaleLTFCubePosition, end: scaleLBFCubePosition }); - - // UPDATE STRETCH SPHERES - var stretchSphereDimension = rotateDimension * STRETCH_SPHERE_CAMERA_DISTANCE_MULTIPLE / + // UPDATE SCALE CUBE + var scaleCubeRotation = spaceMode === SPACE_LOCAL ? rotation : Quat.IDENTITY; + var scaleCubeDimension = rotateDimension * SCALE_CUBE_CAMERA_DISTANCE_MULTIPLE / ROTATE_RING_CAMERA_DISTANCE_MULTIPLE; - var stretchSphereDimensions = { x: stretchSphereDimension, y: stretchSphereDimension, z: stretchSphereDimension }; - var stretchSphereOffset = rotateDimension * STRETCH_SPHERE_OFFSET / ROTATE_RING_CAMERA_DISTANCE_MULTIPLE; - var stretchXPosition = { x: stretchSphereOffset, y: 0, z: 0 }; - stretchXPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, stretchXPosition)); - Overlays.editOverlay(handleStretchXSphere, { - position: stretchXPosition, - dimensions: stretchSphereDimensions - }); - var stretchYPosition = { x: 0, y: stretchSphereOffset, z: 0 }; - stretchYPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, stretchYPosition)); - Overlays.editOverlay(handleStretchYSphere, { - position: stretchYPosition, - dimensions: stretchSphereDimensions - }); - var stretchZPosition = { x: 0, y: 0, z: stretchSphereOffset }; - stretchZPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, stretchZPosition)); - Overlays.editOverlay(handleStretchZSphere, { - position: stretchZPosition, - dimensions: stretchSphereDimensions + var scaleCubeDimensions = { x: scaleCubeDimension, y: scaleCubeDimension, z: scaleCubeDimension }; + Overlays.editOverlay(handleScaleCube, { + position: position, + rotation: scaleCubeRotation, + dimensions: scaleCubeDimensions }); + // UPDATE BOUNDING BOX EDGES + var edgeOffsetX = BOUNDING_EDGE_OFFSET * dimensions.x; + var edgeOffsetY = BOUNDING_EDGE_OFFSET * dimensions.y; + var edgeOffsetZ = BOUNDING_EDGE_OFFSET * dimensions.z; + var LBNPosition = { x: -edgeOffsetX, y: -edgeOffsetY, z: -edgeOffsetZ }; + LBNPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, LBNPosition)); + var RBNPosition = { x: edgeOffsetX, y: -edgeOffsetY, z: -edgeOffsetZ }; + RBNPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, RBNPosition)); + var LBFPosition = { x: -edgeOffsetX, y: -edgeOffsetY, z: edgeOffsetZ }; + LBFPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, LBFPosition)); + var RBFPosition = { x: edgeOffsetX, y: -edgeOffsetY, z: edgeOffsetZ }; + RBFPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, RBFPosition)); + var LTNPosition = { x: -edgeOffsetX, y: edgeOffsetY, z: -edgeOffsetZ }; + LTNPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, LTNPosition)); + var RTNPosition = { x: edgeOffsetX, y: edgeOffsetY, z: -edgeOffsetZ }; + RTNPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, RTNPosition)); + var LTFPosition = { x: -edgeOffsetX, y: edgeOffsetY, z: edgeOffsetZ }; + LTFPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, LTFPosition)); + var RTFPosition = { x: edgeOffsetX, y: edgeOffsetY, z: edgeOffsetZ }; + RTFPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, RTFPosition)); + Overlays.editOverlay(handleBoundingTREdge, { start: RTNPosition, end: RTFPosition }); + Overlays.editOverlay(handleBoundingTLEdge, { start: LTNPosition, end: LTFPosition }); + Overlays.editOverlay(handleBoundingTFEdge, { start: LTFPosition, end: RTFPosition }); + Overlays.editOverlay(handleBoundingTNEdge, { start: LTNPosition, end: RTNPosition }); + Overlays.editOverlay(handleBoundingBREdge, { start: RBNPosition, end: RBFPosition }); + Overlays.editOverlay(handleBoundingBLEdge, { start: LBNPosition, end: LBFPosition }); + Overlays.editOverlay(handleBoundingBFEdge, { start: LBFPosition, end: RBFPosition }); + Overlays.editOverlay(handleBoundingBNEdge, { start: LBNPosition, end: RBNPosition }); + Overlays.editOverlay(handleBoundingNREdge, { start: RTNPosition, end: RBNPosition }); + Overlays.editOverlay(handleBoundingNLEdge, { start: LTNPosition, end: LBNPosition }); + Overlays.editOverlay(handleBoundingFREdge, { start: RTFPosition, end: RBFPosition }); + Overlays.editOverlay(handleBoundingFLEdge, { start: LTFPosition, end: LBFPosition }); + // UPDATE STRETCH HIGHLIGHT PANELS - var scaleRBFCubePositionRotated = Vec3.multiplyQbyV(rotationInverse, scaleRBFCubePosition); - var scaleRTFCubePositionRotated = Vec3.multiplyQbyV(rotationInverse, scaleRTFCubePosition); - var scaleLTNCubePositionRotated = Vec3.multiplyQbyV(rotationInverse, scaleLTNCubePosition); - var scaleRTNCubePositionRotated = Vec3.multiplyQbyV(rotationInverse, scaleRTNCubePosition); - var stretchPanelXDimensions = Vec3.subtract(scaleRTNCubePositionRotated, scaleRBFCubePositionRotated); + var RBFPositionRotated = Vec3.multiplyQbyV(rotationInverse, RBFPosition); + var RTFPositionRotated = Vec3.multiplyQbyV(rotationInverse, RTFPosition); + var LTNPositionRotated = Vec3.multiplyQbyV(rotationInverse, LTNPosition); + var RTNPositionRotated = Vec3.multiplyQbyV(rotationInverse, RTNPosition); + var stretchPanelXDimensions = Vec3.subtract(RTNPositionRotated, RBFPositionRotated); var tempY = Math.abs(stretchPanelXDimensions.y); stretchPanelXDimensions.x = STRETCH_PANEL_WIDTH; stretchPanelXDimensions.y = Math.abs(stretchPanelXDimensions.z); @@ -1555,7 +1489,7 @@ SelectionDisplay = (function() { rotation: rotationZ, dimensions: stretchPanelXDimensions }); - var stretchPanelYDimensions = Vec3.subtract(scaleLTNCubePositionRotated, scaleRTFCubePositionRotated); + var stretchPanelYDimensions = Vec3.subtract(LTNPositionRotated, RTFPositionRotated); var tempX = Math.abs(stretchPanelYDimensions.x); stretchPanelYDimensions.x = Math.abs(stretchPanelYDimensions.z); stretchPanelYDimensions.y = STRETCH_PANEL_WIDTH; @@ -1566,7 +1500,7 @@ SelectionDisplay = (function() { rotation: rotationY, dimensions: stretchPanelYDimensions }); - var stretchPanelZDimensions = Vec3.subtract(scaleLTNCubePositionRotated, scaleRBFCubePositionRotated); + var stretchPanelZDimensions = Vec3.subtract(LTNPositionRotated, RBFPositionRotated); tempX = Math.abs(stretchPanelZDimensions.x); stretchPanelZDimensions.x = Math.abs(stretchPanelZDimensions.y); stretchPanelZDimensions.y = tempX; @@ -1578,6 +1512,46 @@ SelectionDisplay = (function() { dimensions: stretchPanelZDimensions }); + // UPDATE STRETCH CUBES + var stretchCubeDimension = rotateDimension * STRETCH_CUBE_CAMERA_DISTANCE_MULTIPLE / + ROTATE_RING_CAMERA_DISTANCE_MULTIPLE; + var stretchCubeDimensions = { x: stretchCubeDimension, y: stretchCubeDimension, z: stretchCubeDimension }; + var stretchCubeOffset = rotateDimension * STRETCH_CUBE_OFFSET / ROTATE_RING_CAMERA_DISTANCE_MULTIPLE; + var stretchXPosition, stretchYPosition, stretchZPosition; + if (isActiveTool(handleStretchXCube)) { + stretchXPosition = Vec3.subtract(stretchPanelXPosition, activeStretchCubePanelOffset); + } else { + stretchXPosition = { x: stretchCubeOffset, y: 0, z: 0 }; + stretchXPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, stretchXPosition)); + } + if (isActiveTool(handleStretchYCube)) { + stretchYPosition = Vec3.subtract(stretchPanelYPosition, activeStretchCubePanelOffset); + } else { + stretchYPosition = { x: 0, y: stretchCubeOffset, z: 0 }; + stretchYPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, stretchYPosition)); + } + if (isActiveTool(handleStretchZCube)) { + stretchZPosition = Vec3.subtract(stretchPanelZPosition, activeStretchCubePanelOffset); + } else { + stretchZPosition = { x: 0, y: 0, z: stretchCubeOffset }; + stretchZPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, stretchZPosition)); + } + Overlays.editOverlay(handleStretchXCube, { + position: stretchXPosition, + rotation: rotationX, + dimensions: stretchCubeDimensions + }); + Overlays.editOverlay(handleStretchYCube, { + position: stretchYPosition, + rotation: rotationY, + dimensions: stretchCubeDimensions + }); + Overlays.editOverlay(handleStretchZCube, { + position: stretchZPosition, + rotation: rotationZ, + dimensions: stretchCubeDimensions + }); + // UPDATE SELECTION BOX (CURRENTLY INVISIBLE WITH 0 ALPHA FOR TRANSLATE XZ TOOL) var inModeRotate = isActiveTool(handleRotatePitchRing) || isActiveTool(handleRotateYawRing) || @@ -1622,20 +1596,15 @@ SelectionDisplay = (function() { that.setHandleRotateRollVisible(!activeTool || isActiveTool(handleRotateRollRing)); var showScaleStretch = !activeTool && SelectionManager.selections.length === 1 && spaceMode === SPACE_LOCAL; - that.setHandleStretchXVisible(showScaleStretch || isActiveTool(handleStretchXSphere)); - that.setHandleStretchYVisible(showScaleStretch || isActiveTool(handleStretchYSphere)); - that.setHandleStretchZVisible(showScaleStretch || isActiveTool(handleStretchZSphere)); - that.setHandleScaleCubeVisible(showScaleStretch || isActiveTool(handleScaleLBNCube) || - isActiveTool(handleScaleRBNCube) || isActiveTool(handleScaleLBFCube) || - isActiveTool(handleScaleRBFCube) || isActiveTool(handleScaleLTNCube) || - isActiveTool(handleScaleRTNCube) || isActiveTool(handleScaleLTFCube) || - isActiveTool(handleScaleRTFCube) || isActiveTool(handleStretchXSphere) || - isActiveTool(handleStretchYSphere) || isActiveTool(handleStretchZSphere)); + that.setHandleStretchXVisible(showScaleStretch || isActiveTool(handleStretchXCube)); + that.setHandleStretchYVisible(showScaleStretch || isActiveTool(handleStretchYCube)); + that.setHandleStretchZVisible(showScaleStretch || isActiveTool(handleStretchZCube)); + that.setHandleScaleCubeVisible(showScaleStretch || isActiveTool(handleScaleCube)); var showOutlineForZone = (SelectionManager.selections.length === 1 && typeof SelectionManager.savedProperties[SelectionManager.selections[0]] !== "undefined" && SelectionManager.savedProperties[SelectionManager.selections[0]].type === "Zone"); - that.setHandleScaleEdgeVisible(showOutlineForZone || (!isActiveTool(handleRotatePitchRing) && + that.setHandleBoundingEdgeVisible(showOutlineForZone || (!isActiveTool(handleRotatePitchRing) && !isActiveTool(handleRotateYawRing) && !isActiveTool(handleRotateRollRing))); @@ -1721,47 +1690,40 @@ SelectionDisplay = (function() { }; that.setHandleStretchXVisible = function(isVisible) { - Overlays.editOverlay(handleStretchXSphere, { visible: isVisible }); + Overlays.editOverlay(handleStretchXCube, { visible: isVisible }); }; that.setHandleStretchYVisible = function(isVisible) { - Overlays.editOverlay(handleStretchYSphere, { visible: isVisible }); + Overlays.editOverlay(handleStretchYCube, { visible: isVisible }); }; that.setHandleStretchZVisible = function(isVisible) { - Overlays.editOverlay(handleStretchZSphere, { visible: isVisible }); + Overlays.editOverlay(handleStretchZCube, { visible: isVisible }); }; // FUNCTION: SET HANDLE SCALE VISIBLE that.setHandleScaleVisible = function(isVisible) { that.setHandleScaleCubeVisible(isVisible); - that.setHandleScaleEdgeVisible(isVisible); + that.setHandleBoundingEdgeVisible(isVisible); }; that.setHandleScaleCubeVisible = function(isVisible) { - Overlays.editOverlay(handleScaleLBNCube, { visible: isVisible }); - Overlays.editOverlay(handleScaleRBNCube, { visible: isVisible }); - Overlays.editOverlay(handleScaleLBFCube, { visible: isVisible }); - Overlays.editOverlay(handleScaleRBFCube, { visible: isVisible }); - Overlays.editOverlay(handleScaleLTNCube, { visible: isVisible }); - Overlays.editOverlay(handleScaleRTNCube, { visible: isVisible }); - Overlays.editOverlay(handleScaleLTFCube, { visible: isVisible }); - Overlays.editOverlay(handleScaleRTFCube, { visible: isVisible }); + Overlays.editOverlay(handleScaleCube, { visible: isVisible }); }; - that.setHandleScaleEdgeVisible = function(isVisible) { - Overlays.editOverlay(handleScaleTREdge, { visible: isVisible }); - Overlays.editOverlay(handleScaleTLEdge, { visible: isVisible }); - Overlays.editOverlay(handleScaleTFEdge, { visible: isVisible }); - Overlays.editOverlay(handleScaleTNEdge, { visible: isVisible }); - Overlays.editOverlay(handleScaleBREdge, { visible: isVisible }); - Overlays.editOverlay(handleScaleBLEdge, { visible: isVisible }); - Overlays.editOverlay(handleScaleBFEdge, { visible: isVisible }); - Overlays.editOverlay(handleScaleBNEdge, { visible: isVisible }); - Overlays.editOverlay(handleScaleNREdge, { visible: isVisible }); - Overlays.editOverlay(handleScaleNLEdge, { visible: isVisible }); - Overlays.editOverlay(handleScaleFREdge, { visible: isVisible }); - Overlays.editOverlay(handleScaleFLEdge, { visible: isVisible }); + that.setHandleBoundingEdgeVisible = function(isVisible) { + Overlays.editOverlay(handleBoundingTREdge, { visible: isVisible }); + Overlays.editOverlay(handleBoundingTLEdge, { visible: isVisible }); + Overlays.editOverlay(handleBoundingTFEdge, { visible: isVisible }); + Overlays.editOverlay(handleBoundingTNEdge, { visible: isVisible }); + Overlays.editOverlay(handleBoundingBREdge, { visible: isVisible }); + Overlays.editOverlay(handleBoundingBLEdge, { visible: isVisible }); + Overlays.editOverlay(handleBoundingBFEdge, { visible: isVisible }); + Overlays.editOverlay(handleBoundingBNEdge, { visible: isVisible }); + Overlays.editOverlay(handleBoundingNREdge, { visible: isVisible }); + Overlays.editOverlay(handleBoundingNLEdge, { visible: isVisible }); + Overlays.editOverlay(handleBoundingFREdge, { visible: isVisible }); + Overlays.editOverlay(handleBoundingFLEdge, { visible: isVisible }); }; // FUNCTION: SET HANDLE CLONER VISIBLE @@ -2123,7 +2085,7 @@ SelectionDisplay = (function() { }; // TOOL DEFINITION: HANDLE STRETCH TOOL - function makeStretchTool(stretchMode, directionEnum, directionVec, pivot, offset, stretchPanel, scaleHandle) { + function makeStretchTool(stretchMode, directionEnum, directionVec, pivot, offset, stretchPanel, cubeHandle) { var directionFor3DStretch = directionVec; var distanceFor3DStretch = 0; var DISTANCE_INFLUENCE_THRESHOLD = 1.2; @@ -2155,8 +2117,12 @@ SelectionDisplay = (function() { var pickRayPosition3D = null; var rotation = null; var previousPickRay = null; + var beginMouseEvent = null; + var beginControllerPosition = null; - var onBegin = function(event, pickRay, pickResult) { + var onBegin = function(event, pickRay, pickResult) { + var proportional = directionEnum === STRETCH_DIRECTION.ALL; + var properties = Entities.getEntityProperties(SelectionManager.selections[0]); initialProperties = properties; rotation = (spaceMode === SPACE_LOCAL) ? properties.rotation : Quat.IDENTITY; @@ -2244,9 +2210,7 @@ SelectionDisplay = (function() { } planeNormal = Vec3.multiplyQbyV(rotation, planeNormal); - lastPick = rayPlaneIntersection(pickRay, - pickRayPosition, - planeNormal); + lastPick = rayPlaneIntersection(pickRay, pickRayPosition, planeNormal); var planeNormal3D = { x: 0, @@ -2274,10 +2238,7 @@ SelectionDisplay = (function() { if (stretchPanel !== null) { Overlays.editOverlay(stretchPanel, { visible: true }); } - if (scaleHandle !== null) { - Overlays.editOverlay(scaleHandle, { color: COLOR_SCALE_CUBE_SELECTED }); - } - + var collisionToRemove = "myAvatar"; if (properties.collidesWith.indexOf(collisionToRemove) > -1) { var newCollidesWith = properties.collidesWith.replace(collisionToRemove, ""); @@ -2285,16 +2246,23 @@ SelectionDisplay = (function() { that.replaceCollisionsAfterStretch = true; } + if (!proportional) { + var stretchCubePosition = Overlays.getProperty(cubeHandle, "position"); + var stretchPanelPosition = Overlays.getProperty(stretchPanel, "position"); + activeStretchCubePanelOffset = Vec3.subtract(stretchPanelPosition, stretchCubePosition); + } + previousPickRay = pickRay; + beginMouseEvent = event; + if (that.triggered()) { + beginControllerPosition = getControllerAvatarFramePositionFromPickRay(pickRay); + } }; var onEnd = function(event, reason) { if (stretchPanel !== null) { Overlays.editOverlay(stretchPanel, { visible: false }); } - if (scaleHandle !== null) { - Overlays.editOverlay(scaleHandle, { color: COLOR_SCALE_CUBE }); - } if (that.replaceCollisionsAfterStretch) { var newCollidesWith = SelectionManager.savedProperties[SelectionManager.selections[0]].collidesWith; @@ -2302,6 +2270,8 @@ SelectionDisplay = (function() { that.replaceCollisionsAfterStretch = false; } + activeStretchCubePanelOffset = null; + pushCommandForSelections(); }; @@ -2325,13 +2295,15 @@ SelectionDisplay = (function() { if (usePreviousPickRay(pickRay.direction, previousPickRay.direction, planeNormal)) { pickRay = previousPickRay; } + + var controllerPose = getControllerWorldLocation(that.triggeredHand, true); + var controllerTrigger = HMD.isHMDAvailable() && HMD.isHandControllerAvailable() && + controllerPose.valid && that.triggered(); // Are we using handControllers or Mouse - only relevant for 3D tools - var controllerPose = getControllerWorldLocation(that.triggeredHand, true); var vector = null; var newPick = null; - if (HMD.isHMDAvailable() && HMD.isHandControllerAvailable() && - controllerPose.valid && that.triggered() && directionFor3DStretch) { + if (controllerTrigger && directionFor3DStretch) { localDeltaPivot = deltaPivot3D; newPick = pickRay.origin; vector = Vec3.subtract(newPick, lastPick3D); @@ -2351,34 +2323,38 @@ SelectionDisplay = (function() { vector = grid.snapToSpacing(vector); var changeInDimensions = Vec3.multiply(NEGATE_VECTOR, vec3Mult(localSigns, vector)); - if (directionEnum === STRETCH_DIRECTION.ALL) { - var toCameraDistance = getDistanceToCamera(position); - var dimensionsMultiple = toCameraDistance * STRETCH_DIRECTION_ALL_CAMERA_DISTANCE_MULTIPLE; - changeInDimensions = Vec3.multiply(changeInDimensions, dimensionsMultiple); - } - + var newDimensions; if (proportional) { - var absoluteX = Math.abs(changeInDimensions.x); - var absoluteY = Math.abs(changeInDimensions.y); - var absoluteZ = Math.abs(changeInDimensions.z); - var percentChange = 0; - if (absoluteX > absoluteY && absoluteX > absoluteZ) { - percentChange = changeInDimensions.x / initialProperties.dimensions.x; - percentChange = changeInDimensions.x / initialDimensions.x; - } else if (absoluteY > absoluteZ) { - percentChange = changeInDimensions.y / initialProperties.dimensions.y; - percentChange = changeInDimensions.y / initialDimensions.y; + var viewportDimensions = Controller.getViewportDimensions(); + var toCameraDistance = getDistanceToCamera(position); + var dimensionsMultiple = toCameraDistance * STRETCH_DIRECTION_ALL_CAMERA_DISTANCE_MULTIPLE; + + var dimensionChange; + if (controllerTrigger) { + var controllerPosition = getControllerAvatarFramePositionFromPickRay(pickRay); + var vecControllerDifference = Vec3.subtract(controllerPosition, beginControllerPosition); + var controllerDifference = vecControllerDifference.x + vecControllerDifference.y + + vecControllerDifference.z; + dimensionChange = controllerDifference * dimensionsMultiple; } else { - percentChange = changeInDimensions.z / initialProperties.dimensions.z; - percentChange = changeInDimensions.z / initialDimensions.z; + var mouseXDifference = (event.x - beginMouseEvent.x) / viewportDimensions.x; + var mouseYDifference = (beginMouseEvent.y - event.y) / viewportDimensions.y; + var mouseDifference = mouseXDifference + mouseYDifference; + dimensionChange = mouseDifference * dimensionsMultiple; } + + var averageInitialDimension = (initialDimensions.x + initialDimensions.y + initialDimensions.z) / 3; + percentChange = dimensionChange / averageInitialDimension; percentChange += 1.0; newDimensions = Vec3.multiply(percentChange, initialDimensions); + newDimensions.x = Math.abs(newDimensions.x); + newDimensions.y = Math.abs(newDimensions.y); + newDimensions.z = Math.abs(newDimensions.z); } else { newDimensions = Vec3.sum(initialDimensions, changeInDimensions); } - + var minimumDimension = directionEnum === STRETCH_DIRECTION.ALL ? STRETCH_ALL_MINIMUM_DIMENSION : STRETCH_MINIMUM_DIMENSION; if (newDimensions.x < minimumDimension) { @@ -2393,9 +2369,9 @@ SelectionDisplay = (function() { newDimensions.z = minimumDimension; changeInDimensions.z = minimumDimension - initialDimensions.z; } - + var changeInPosition = Vec3.multiplyQbyV(rotation, vec3Mult(localDeltaPivot, changeInDimensions)); - if (directionEnum === STRETCH_DIRECTION.ALL) { + if (proportional) { changeInPosition = { x: 0, y: 0, z: 0 }; } var newPosition = Vec3.sum(initialPosition, changeInPosition); @@ -2429,52 +2405,30 @@ SelectionDisplay = (function() { } function addHandleStretchTool(overlay, mode, directionEnum) { - var directionVector, offset, stretchPanel; + var directionVector, offset, stretchPanel, handleStretchCube; if (directionEnum === STRETCH_DIRECTION.X) { stretchPanel = handleStretchXPanel; + handleStretchCube = handleStretchXCube; directionVector = { x: -1, y: 0, z: 0 }; } else if (directionEnum === STRETCH_DIRECTION.Y) { stretchPanel = handleStretchYPanel; + handleStretchCube = handleStretchYCube; directionVector = { x: 0, y: -1, z: 0 }; } else if (directionEnum === STRETCH_DIRECTION.Z) { stretchPanel = handleStretchZPanel; + handleStretchCube = handleStretchZCube; directionVector = { x: 0, y: 0, z: -1 }; } offset = Vec3.multiply(directionVector, NEGATE_VECTOR); - var tool = makeStretchTool(mode, directionEnum, directionVector, directionVector, offset, stretchPanel, null); + var tool = makeStretchTool(mode, directionEnum, directionVector, directionVector, offset, stretchPanel, handleStretchCube); return addHandleTool(overlay, tool); } // TOOL DEFINITION: HANDLE SCALE TOOL - function addHandleScaleTool(overlay, mode, directionEnum) { - var directionVector, offset, selectedHandle; - if (directionEnum === SCALE_DIRECTION.LBN) { - directionVector = { x: 1, y: 1, z: 1 }; - selectedHandle = handleScaleLBNCube; - } else if (directionEnum === SCALE_DIRECTION.RBN) { - directionVector = { x: -1, y: 1, z: 1 }; - selectedHandle = handleScaleRBNCube; - } else if (directionEnum === SCALE_DIRECTION.LBF) { - directionVector = { x: 1, y: 1, z: -1 }; - selectedHandle = handleScaleLBFCube; - } else if (directionEnum === SCALE_DIRECTION.RBF) { - directionVector = { x: -1, y: 1, z: -1 }; - selectedHandle = handleScaleRBFCube; - } else if (directionEnum === SCALE_DIRECTION.LTN) { - directionVector = { x: 1, y: -1, z: 1 }; - selectedHandle = handleScaleLTNCube; - } else if (directionEnum === SCALE_DIRECTION.RTN) { - directionVector = { x: -1, y: -1, z: 1 }; - selectedHandle = handleScaleRTNCube; - } else if (directionEnum === SCALE_DIRECTION.LTF) { - directionVector = { x: 1, y: -1, z: -1 }; - selectedHandle = handleScaleLTFCube; - } else if (directionEnum === SCALE_DIRECTION.RTF) { - directionVector = { x: -1, y: -1, z: -1 }; - selectedHandle = handleScaleRTFCube; - } - offset = Vec3.multiply(directionVector, NEGATE_VECTOR); - var tool = makeStretchTool(mode, STRETCH_DIRECTION.ALL, directionVector, directionVector, offset, null, selectedHandle); + function addHandleScaleTool(overlay, mode) { + var directionVector = { x:0, y:0, z:0 }; + var offset = { x:0, y:0, z:0 }; + var tool = makeStretchTool(mode, STRETCH_DIRECTION.ALL, directionVector, directionVector, offset, null, handleScaleCube); return addHandleTool(overlay, tool); } @@ -2747,18 +2701,11 @@ SelectionDisplay = (function() { addHandleRotateTool(handleRotateYawRing, "ROTATE_YAW", ROTATE_DIRECTION.YAW); addHandleRotateTool(handleRotateRollRing, "ROTATE_ROLL", ROTATE_DIRECTION.ROLL); - addHandleStretchTool(handleStretchXSphere, "STRETCH_X", STRETCH_DIRECTION.X); - addHandleStretchTool(handleStretchYSphere, "STRETCH_Y", STRETCH_DIRECTION.Y); - addHandleStretchTool(handleStretchZSphere, "STRETCH_Z", STRETCH_DIRECTION.Z); - - addHandleScaleTool(handleScaleLBNCube, "SCALE_LBN", SCALE_DIRECTION.LBN); - addHandleScaleTool(handleScaleRBNCube, "SCALE_RBN", SCALE_DIRECTION.RBN); - addHandleScaleTool(handleScaleLBFCube, "SCALE_LBF", SCALE_DIRECTION.LBF); - addHandleScaleTool(handleScaleRBFCube, "SCALE_RBF", SCALE_DIRECTION.RBF); - addHandleScaleTool(handleScaleLTNCube, "SCALE_LTN", SCALE_DIRECTION.LTN); - addHandleScaleTool(handleScaleRTNCube, "SCALE_RTN", SCALE_DIRECTION.RTN); - addHandleScaleTool(handleScaleLTFCube, "SCALE_LTF", SCALE_DIRECTION.LTF); - addHandleScaleTool(handleScaleRTFCube, "SCALE_RTF", SCALE_DIRECTION.RTF); + addHandleStretchTool(handleStretchXCube, "STRETCH_X", STRETCH_DIRECTION.X); + addHandleStretchTool(handleStretchYCube, "STRETCH_Y", STRETCH_DIRECTION.Y); + addHandleStretchTool(handleStretchZCube, "STRETCH_Z", STRETCH_DIRECTION.Z); + addHandleScaleTool(handleScaleCube, "SCALE"); + return that; }()); diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 7b4f05193f..13ad1f6b69 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -16,1147 +16,1082 @@ var selectionDisplay = null; // for gridTool.js to ignore (function () { // BEGIN LOCAL_SCOPE +var AppUi = Script.require('appUi'); +Script.include("/~/system/libraries/gridTool.js"); +Script.include("/~/system/libraries/connectionUtils.js"); - Script.include("/~/system/libraries/WebTablet.js"); - Script.include("/~/system/libraries/gridTool.js"); - Script.include("/~/system/libraries/connectionUtils.js"); +var METAVERSE_SERVER_URL = Account.metaverseServerURL; +var MARKETPLACES_URL = Script.resolvePath("../html/marketplaces.html"); +var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js"); +var MARKETPLACE_CHECKOUT_QML_PATH = "hifi/commerce/checkout/Checkout.qml"; +var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml"; +var MARKETPLACE_WALLET_QML_PATH = "hifi/commerce/wallet/Wallet.qml"; +var MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH = "hifi/commerce/inspectionCertificate/InspectionCertificate.qml"; +var REZZING_SOUND = SoundCache.getSound(Script.resolvePath("../assets/sounds/rezzing.wav")); - var METAVERSE_SERVER_URL = Account.metaverseServerURL; - var MARKETPLACE_URL = METAVERSE_SERVER_URL + "/marketplace"; - var MARKETPLACE_URL_INITIAL = MARKETPLACE_URL + "?"; // Append "?" to signal injected script that it's the initial page. - var MARKETPLACES_URL = Script.resolvePath("../html/marketplaces.html"); - var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js"); - var MARKETPLACE_CHECKOUT_QML_PATH = "hifi/commerce/checkout/Checkout.qml"; - var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml"; - var MARKETPLACE_WALLET_QML_PATH = "hifi/commerce/wallet/Wallet.qml"; - var MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH = "commerce/inspectionCertificate/InspectionCertificate.qml"; - var REZZING_SOUND = SoundCache.getSound(Script.resolvePath("../assets/sounds/rezzing.wav")); +// Event bridge messages. +var CLARA_IO_DOWNLOAD = "CLARA.IO DOWNLOAD"; +var CLARA_IO_STATUS = "CLARA.IO STATUS"; +var CLARA_IO_CANCEL_DOWNLOAD = "CLARA.IO CANCEL DOWNLOAD"; +var CLARA_IO_CANCELLED_DOWNLOAD = "CLARA.IO CANCELLED DOWNLOAD"; +var GOTO_DIRECTORY = "GOTO_DIRECTORY"; +var QUERY_CAN_WRITE_ASSETS = "QUERY_CAN_WRITE_ASSETS"; +var CAN_WRITE_ASSETS = "CAN_WRITE_ASSETS"; +var WARN_USER_NO_PERMISSIONS = "WARN_USER_NO_PERMISSIONS"; - var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; - // var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; +var CLARA_DOWNLOAD_TITLE = "Preparing Download"; +var messageBox = null; +var isDownloadBeingCancelled = false; - // Event bridge messages. - var CLARA_IO_DOWNLOAD = "CLARA.IO DOWNLOAD"; - var CLARA_IO_STATUS = "CLARA.IO STATUS"; - var CLARA_IO_CANCEL_DOWNLOAD = "CLARA.IO CANCEL DOWNLOAD"; - var CLARA_IO_CANCELLED_DOWNLOAD = "CLARA.IO CANCELLED DOWNLOAD"; - var GOTO_DIRECTORY = "GOTO_DIRECTORY"; - var QUERY_CAN_WRITE_ASSETS = "QUERY_CAN_WRITE_ASSETS"; - var CAN_WRITE_ASSETS = "CAN_WRITE_ASSETS"; - var WARN_USER_NO_PERMISSIONS = "WARN_USER_NO_PERMISSIONS"; +var CANCEL_BUTTON = 4194304; // QMessageBox::Cancel +var NO_BUTTON = 0; // QMessageBox::NoButton - var CLARA_DOWNLOAD_TITLE = "Preparing Download"; - var messageBox = null; - var isDownloadBeingCancelled = false; +var NO_PERMISSIONS_ERROR_MESSAGE = "Cannot download model because you can't write to \nthe domain's Asset Server."; - var CANCEL_BUTTON = 4194304; // QMessageBox::Cancel - var NO_BUTTON = 0; // QMessageBox::NoButton - - var NO_PERMISSIONS_ERROR_MESSAGE = "Cannot download model because you can't write to \nthe domain's Asset Server."; - - function onMessageBoxClosed(id, button) { - if (id === messageBox && button === CANCEL_BUTTON) { - isDownloadBeingCancelled = true; - messageBox = null; - tablet.emitScriptEvent(CLARA_IO_CANCEL_DOWNLOAD); - } - } - - var onMarketplaceScreen = false; - var onCommerceScreen = false; - - var debugCheckout = false; - var debugError = false; - function showMarketplace() { - if (!debugCheckout) { - UserActivityLogger.openedMarketplace(); - tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); - } else { - tablet.pushOntoStack(MARKETPLACE_CHECKOUT_QML_PATH); - sendToQml({ - method: 'updateCheckoutQML', params: { - itemId: '424611a2-73d0-4c03-9087-26a6a279257b', - itemName: '2018-02-15 Finnegon', - itemPrice: (debugError ? 10 : 3), - itemHref: 'http://devmpassets.highfidelity.com/424611a2-73d0-4c03-9087-26a6a279257b-v1/finnigon.fst', - categories: ["Miscellaneous"] - } - }); - } - } - - function messagesWaiting(isWaiting) { - if (marketplaceButton) { - marketplaceButton.editProperties({ - icon: (isWaiting ? WAITING_ICON : NORMAL_ICON), - activeIcon: (isWaiting ? WAITING_ACTIVE : NORMAL_ACTIVE) - }); - } - } - - function onCanWriteAssetsChanged() { - var message = CAN_WRITE_ASSETS + " " + Entities.canWriteAssets(); - tablet.emitScriptEvent(message); - } - - - var tabletShouldBeVisibleInSecondaryCamera = false; - function setTabletVisibleInSecondaryCamera(visibleInSecondaryCam) { - if (visibleInSecondaryCam) { - // if we're potentially showing the tablet, only do so if it was visible before - if (!tabletShouldBeVisibleInSecondaryCamera) { - return; - } - } else { - // if we're hiding the tablet, check to see if it was visible in the first place - tabletShouldBeVisibleInSecondaryCamera = Overlays.getProperty(HMD.tabletID, "isVisibleInSecondaryCamera"); - } - - Overlays.editOverlay(HMD.tabletID, { isVisibleInSecondaryCamera : visibleInSecondaryCam }); - Overlays.editOverlay(HMD.homeButtonID, { isVisibleInSecondaryCamera : visibleInSecondaryCam }); - Overlays.editOverlay(HMD.homeButtonHighlightID, { isVisibleInSecondaryCamera : visibleInSecondaryCam }); - Overlays.editOverlay(HMD.tabletScreenID, { isVisibleInSecondaryCamera : visibleInSecondaryCam }); - } - - function openWallet() { - tablet.pushOntoStack(MARKETPLACE_WALLET_QML_PATH); - } - - function setCertificateInfo(currentEntityWithContextOverlay, itemCertificateId) { - wireEventBridge(true); - var certificateId = itemCertificateId || (Entities.getEntityProperties(currentEntityWithContextOverlay, ['certificateID']).certificateID); - sendToQml({ - method: 'inspectionCertificate_setCertificateId', - entityId: currentEntityWithContextOverlay, - certificateId: certificateId +function onMessageBoxClosed(id, button) { + if (id === messageBox && button === CANCEL_BUTTON) { + isDownloadBeingCancelled = true; + messageBox = null; + ui.sendToHtml({ + type: CLARA_IO_CANCEL_DOWNLOAD }); } +} - function onUsernameChanged() { - if (onMarketplaceScreen) { - tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); - } - } +function onCanWriteAssetsChanged() { + ui.sendToHtml({ + type: CAN_WRITE_ASSETS, + canWriteAssets: Entities.canWriteAssets() + }); +} - var userHasUpdates = false; - function sendCommerceSettings() { - tablet.emitScriptEvent(JSON.stringify({ - type: "marketplaces", - action: "commerceSetting", - data: { - commerceMode: Settings.getValue("commerce", true), - userIsLoggedIn: Account.loggedIn, - walletNeedsSetup: Wallet.walletStatus === 1, - metaverseServerURL: Account.metaverseServerURL, - messagesWaiting: userHasUpdates - } - })); - } - // BEGIN AVATAR SELECTOR LOGIC - var UNSELECTED_COLOR = { red: 0x1F, green: 0xC6, blue: 0xA6 }; - var SELECTED_COLOR = { red: 0xF3, green: 0x91, blue: 0x29 }; - var HOVER_COLOR = { red: 0xD0, green: 0xD0, blue: 0xD0 }; - - var overlays = {}; // Keeps track of all our extended overlay data objects, keyed by target identifier. - - function ExtendedOverlay(key, type, properties) { // A wrapper around overlays to store the key it is associated with. - overlays[key] = this; - this.key = key; - this.selected = false; - this.hovering = false; - this.activeOverlay = Overlays.addOverlay(type, properties); // We could use different overlays for (un)selected... - } - // Instance methods: - ExtendedOverlay.prototype.deleteOverlay = function () { // remove display and data of this overlay - Overlays.deleteOverlay(this.activeOverlay); - delete overlays[this.key]; - }; - - ExtendedOverlay.prototype.editOverlay = function (properties) { // change display of this overlay - Overlays.editOverlay(this.activeOverlay, properties); - }; - - function color(selected, hovering) { - var base = hovering ? HOVER_COLOR : selected ? SELECTED_COLOR : UNSELECTED_COLOR; - function scale(component) { - var delta = 0xFF - component; - return component; - } - return { red: scale(base.red), green: scale(base.green), blue: scale(base.blue) }; - } - // so we don't have to traverse the overlays to get the last one - var lastHoveringId = 0; - ExtendedOverlay.prototype.hover = function (hovering) { - this.hovering = hovering; - if (this.key === lastHoveringId) { - if (hovering) { - return; - } - lastHoveringId = 0; - } - this.editOverlay({ color: color(this.selected, hovering) }); - if (hovering) { - // un-hover the last hovering overlay - if (lastHoveringId && lastHoveringId !== this.key) { - ExtendedOverlay.get(lastHoveringId).hover(false); - } - lastHoveringId = this.key; - } - }; - ExtendedOverlay.prototype.select = function (selected) { - if (this.selected === selected) { +var tabletShouldBeVisibleInSecondaryCamera = false; +function setTabletVisibleInSecondaryCamera(visibleInSecondaryCam) { + if (visibleInSecondaryCam) { + // if we're potentially showing the tablet, only do so if it was visible before + if (!tabletShouldBeVisibleInSecondaryCamera) { return; } + } else { + // if we're hiding the tablet, check to see if it was visible in the first place + tabletShouldBeVisibleInSecondaryCamera = Overlays.getProperty(HMD.tabletID, "isVisibleInSecondaryCamera"); + } - this.editOverlay({ color: color(selected, this.hovering) }); - this.selected = selected; - }; - // Class methods: - var selectedId = false; - ExtendedOverlay.isSelected = function (id) { - return selectedId === id; - }; - ExtendedOverlay.get = function (key) { // answer the extended overlay data object associated with the given avatar identifier - return overlays[key]; - }; - ExtendedOverlay.some = function (iterator) { // Bails early as soon as iterator returns truthy. - var key; - for (key in overlays) { - if (iterator(ExtendedOverlay.get(key))) { - return; - } + Overlays.editOverlay(HMD.tabletID, { isVisibleInSecondaryCamera : visibleInSecondaryCam }); + Overlays.editOverlay(HMD.homeButtonID, { isVisibleInSecondaryCamera : visibleInSecondaryCam }); + Overlays.editOverlay(HMD.homeButtonHighlightID, { isVisibleInSecondaryCamera : visibleInSecondaryCam }); + Overlays.editOverlay(HMD.tabletScreenID, { isVisibleInSecondaryCamera : visibleInSecondaryCam }); +} + +function openWallet() { + ui.open(MARKETPLACE_WALLET_QML_PATH); +} + +// Function Name: wireQmlEventBridge() +// +// Description: +// -Used to connect/disconnect the script's response to the tablet's "fromQml" signal. Set the "on" argument to enable or +// disable to event bridge. +// +// Relevant Variables: +// -hasEventBridge: true/false depending on whether we've already connected the event bridge. +var hasEventBridge = false; +function wireQmlEventBridge(on) { + if (!ui.tablet) { + print("Warning in wireQmlEventBridge(): 'tablet' undefined!"); + return; + } + if (on) { + if (!hasEventBridge) { + ui.tablet.fromQml.connect(onQmlMessageReceived); + hasEventBridge = true; } - }; - ExtendedOverlay.unHover = function () { // calls hover(false) on lastHoveringId (if any) - if (lastHoveringId) { + } else { + if (hasEventBridge) { + ui.tablet.fromQml.disconnect(onQmlMessageReceived); + hasEventBridge = false; + } + } +} + +var contextOverlayEntity = ""; +function openInspectionCertificateQML(currentEntityWithContextOverlay) { + ui.open(MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH); + contextOverlayEntity = currentEntityWithContextOverlay; +} + +function setCertificateInfo(currentEntityWithContextOverlay, itemCertificateId) { + var certificateId = itemCertificateId || + (Entities.getEntityProperties(currentEntityWithContextOverlay, ['certificateID']).certificateID); + ui.tablet.sendToQml({ + method: 'inspectionCertificate_setCertificateId', + entityId: currentEntityWithContextOverlay, + certificateId: certificateId + }); +} + +function onUsernameChanged() { + if (onMarketplaceScreen) { + ui.open(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); + } +} + +var userHasUpdates = false; +function sendCommerceSettings() { + ui.sendToHtml({ + type: "marketplaces", + action: "commerceSetting", + data: { + commerceMode: Settings.getValue("commerce", true), + userIsLoggedIn: Account.loggedIn, + walletNeedsSetup: Wallet.walletStatus === 1, + metaverseServerURL: Account.metaverseServerURL, + messagesWaiting: userHasUpdates + } + }); +} + +// BEGIN AVATAR SELECTOR LOGIC +var UNSELECTED_COLOR = { red: 0x1F, green: 0xC6, blue: 0xA6 }; +var SELECTED_COLOR = { red: 0xF3, green: 0x91, blue: 0x29 }; +var HOVER_COLOR = { red: 0xD0, green: 0xD0, blue: 0xD0 }; + +var overlays = {}; // Keeps track of all our extended overlay data objects, keyed by target identifier. + +function ExtendedOverlay(key, type, properties) { // A wrapper around overlays to store the key it is associated with. + overlays[key] = this; + this.key = key; + this.selected = false; + this.hovering = false; + this.activeOverlay = Overlays.addOverlay(type, properties); // We could use different overlays for (un)selected... +} +// Instance methods: +ExtendedOverlay.prototype.deleteOverlay = function () { // remove display and data of this overlay + Overlays.deleteOverlay(this.activeOverlay); + delete overlays[this.key]; +}; + +ExtendedOverlay.prototype.editOverlay = function (properties) { // change display of this overlay + Overlays.editOverlay(this.activeOverlay, properties); +}; + +function color(selected, hovering) { + var base = hovering ? HOVER_COLOR : selected ? SELECTED_COLOR : UNSELECTED_COLOR; + function scale(component) { + return component; + } + return { red: scale(base.red), green: scale(base.green), blue: scale(base.blue) }; +} +// so we don't have to traverse the overlays to get the last one +var lastHoveringId = 0; +ExtendedOverlay.prototype.hover = function (hovering) { + this.hovering = hovering; + if (this.key === lastHoveringId) { + if (hovering) { + return; + } + lastHoveringId = 0; + } + this.editOverlay({ color: color(this.selected, hovering) }); + if (hovering) { + // un-hover the last hovering overlay + if (lastHoveringId && lastHoveringId !== this.key) { ExtendedOverlay.get(lastHoveringId).hover(false); } - }; + lastHoveringId = this.key; + } +}; +ExtendedOverlay.prototype.select = function (selected) { + if (this.selected === selected) { + return; + } - // hit(overlay) on the one overlay intersected by pickRay, if any. - // noHit() if no ExtendedOverlay was intersected (helps with hover) - ExtendedOverlay.applyPickRay = function (pickRay, hit, noHit) { - var pickedOverlay = Overlays.findRayIntersection(pickRay); // Depends on nearer coverOverlays to extend closer to us than farther ones. - if (!pickedOverlay.intersects) { - if (noHit) { - return noHit(); - } + this.editOverlay({ color: color(selected, this.hovering) }); + this.selected = selected; +}; +// Class methods: +var selectedId = false; +ExtendedOverlay.isSelected = function (id) { + return selectedId === id; +}; +ExtendedOverlay.get = function (key) { // answer the extended overlay data object associated with the given avatar identifier + return overlays[key]; +}; +ExtendedOverlay.some = function (iterator) { // Bails early as soon as iterator returns truthy. + var key; + for (key in overlays) { + if (iterator(ExtendedOverlay.get(key))) { return; } - ExtendedOverlay.some(function (overlay) { // See if pickedOverlay is one of ours. - if ((overlay.activeOverlay) === pickedOverlay.overlayID) { - hit(overlay); - return true; - } - }); - }; - - function addAvatarNode(id) { - return new ExtendedOverlay(id, "sphere", { - drawInFront: true, - solid: true, - alpha: 0.8, - color: color(false, false), - ignoreRayIntersection: false - }); } - - var pingPong = true; - function updateOverlays() { - var eye = Camera.position; - AvatarList.getAvatarIdentifiers().forEach(function (id) { - if (!id) { - return; // don't update ourself, or avatars we're not interested in - } - var avatar = AvatarList.getAvatar(id); - if (!avatar) { - return; // will be deleted below if there had been an overlay. - } - var overlay = ExtendedOverlay.get(id); - if (!overlay) { // For now, we're treating this as a temporary loss, as from the personal space bubble. Add it back. - overlay = addAvatarNode(id); - } - var target = avatar.position; - var distance = Vec3.distance(target, eye); - var offset = 0.2; - var diff = Vec3.subtract(target, eye); // get diff between target and eye (a vector pointing to the eye from avatar position) - var headIndex = avatar.getJointIndex("Head"); // base offset on 1/2 distance from hips to head if we can - if (headIndex > 0) { - offset = avatar.getAbsoluteJointTranslationInObjectFrame(headIndex).y / 2; - } - - // move a bit in front, towards the camera - target = Vec3.subtract(target, Vec3.multiply(Vec3.normalize(diff), offset)); - - // now bump it up a bit - target.y = target.y + offset; - - overlay.ping = pingPong; - overlay.editOverlay({ - color: color(ExtendedOverlay.isSelected(id), overlay.hovering), - position: target, - dimensions: 0.032 * distance - }); - }); - pingPong = !pingPong; - ExtendedOverlay.some(function (overlay) { // Remove any that weren't updated. (User is gone.) - if (overlay.ping === pingPong) { - overlay.deleteOverlay(); - } - }); - } - function removeOverlays() { - selectedId = false; - lastHoveringId = 0; - ExtendedOverlay.some(function (overlay) { - overlay.deleteOverlay(); - }); +}; +ExtendedOverlay.unHover = function () { // calls hover(false) on lastHoveringId (if any) + if (lastHoveringId) { + ExtendedOverlay.get(lastHoveringId).hover(false); } +}; - // - // Clicks. - // - function usernameFromIDReply(id, username, machineFingerprint, isAdmin) { - if (selectedId === id) { - var message = { - method: 'updateSelectedRecipientUsername', - userName: username === "" ? "unknown username" : username - }; - sendToQml(message); +// hit(overlay) on the one overlay intersected by pickRay, if any. +// noHit() if no ExtendedOverlay was intersected (helps with hover) +ExtendedOverlay.applyPickRay = function (pickRay, hit, noHit) { + var pickedOverlay = Overlays.findRayIntersection(pickRay); // Depends on nearer coverOverlays to extend closer to us than farther ones. + if (!pickedOverlay.intersects) { + if (noHit) { + return noHit(); } + return; } - function handleClick(pickRay) { - ExtendedOverlay.applyPickRay(pickRay, function (overlay) { - var nextSelectedStatus = !overlay.selected; - var avatarId = overlay.key; - selectedId = nextSelectedStatus ? avatarId : false; - if (nextSelectedStatus) { - Users.requestUsernameFromID(avatarId); - } - var message = { - method: 'selectRecipient', - id: avatarId, - isSelected: nextSelectedStatus, - displayName: '"' + AvatarList.getAvatar(avatarId).sessionDisplayName + '"', - userName: '' - }; - sendToQml(message); - - ExtendedOverlay.some(function (overlay) { - var id = overlay.key; - var selected = ExtendedOverlay.isSelected(id); - overlay.select(selected); - }); - + ExtendedOverlay.some(function (overlay) { // See if pickedOverlay is one of ours. + if ((overlay.activeOverlay) === pickedOverlay.overlayID) { + hit(overlay); return true; - }); - } - function handleMouseEvent(mousePressEvent) { // handleClick if we get one. - if (!mousePressEvent.isLeftButton) { - return; } - handleClick(Camera.computePickRay(mousePressEvent.x, mousePressEvent.y)); + }); +}; + +function addAvatarNode(id) { + return new ExtendedOverlay(id, "sphere", { + drawInFront: true, + solid: true, + alpha: 0.8, + color: color(false, false), + ignoreRayIntersection: false + }); +} + +var pingPong = true; +function updateOverlays() { + var eye = Camera.position; + AvatarList.getAvatarIdentifiers().forEach(function (id) { + if (!id) { + return; // don't update ourself, or avatars we're not interested in + } + var avatar = AvatarList.getAvatar(id); + if (!avatar) { + return; // will be deleted below if there had been an overlay. + } + var overlay = ExtendedOverlay.get(id); + if (!overlay) { // For now, we're treating this as a temporary loss, as from the personal space bubble. Add it back. + overlay = addAvatarNode(id); + } + var target = avatar.position; + var distance = Vec3.distance(target, eye); + var offset = 0.2; + var diff = Vec3.subtract(target, eye); // get diff between target and eye (a vector pointing to the eye from avatar position) + var headIndex = avatar.getJointIndex("Head"); // base offset on 1/2 distance from hips to head if we can + if (headIndex > 0) { + offset = avatar.getAbsoluteJointTranslationInObjectFrame(headIndex).y / 2; + } + + // move a bit in front, towards the camera + target = Vec3.subtract(target, Vec3.multiply(Vec3.normalize(diff), offset)); + + // now bump it up a bit + target.y = target.y + offset; + + overlay.ping = pingPong; + overlay.editOverlay({ + color: color(ExtendedOverlay.isSelected(id), overlay.hovering), + position: target, + dimensions: 0.032 * distance + }); + }); + pingPong = !pingPong; + ExtendedOverlay.some(function (overlay) { // Remove any that weren't updated. (User is gone.) + if (overlay.ping === pingPong) { + overlay.deleteOverlay(); + } + }); +} +function removeOverlays() { + selectedId = false; + lastHoveringId = 0; + ExtendedOverlay.some(function (overlay) { + overlay.deleteOverlay(); + }); +} + +// +// Clicks. +// +function usernameFromIDReply(id, username, machineFingerprint, isAdmin) { + if (selectedId === id) { + var message = { + method: 'updateSelectedRecipientUsername', + userName: username === "" ? "unknown username" : username + }; + ui.tablet.sendToQml(message); } - function handleMouseMove(pickRay) { // given the pickRay, just do the hover logic - ExtendedOverlay.applyPickRay(pickRay, function (overlay) { - overlay.hover(true); - }, function () { +} +function handleClick(pickRay) { + ExtendedOverlay.applyPickRay(pickRay, function (overlay) { + var nextSelectedStatus = !overlay.selected; + var avatarId = overlay.key; + selectedId = nextSelectedStatus ? avatarId : false; + if (nextSelectedStatus) { + Users.requestUsernameFromID(avatarId); + } + var message = { + method: 'selectRecipient', + id: avatarId, + isSelected: nextSelectedStatus, + displayName: '"' + AvatarList.getAvatar(avatarId).sessionDisplayName + '"', + userName: '' + }; + ui.tablet.sendToQml(message); + + ExtendedOverlay.some(function (overlay) { + var id = overlay.key; + var selected = ExtendedOverlay.isSelected(id); + overlay.select(selected); + }); + + return true; + }); +} +function handleMouseEvent(mousePressEvent) { // handleClick if we get one. + if (!mousePressEvent.isLeftButton) { + return; + } + handleClick(Camera.computePickRay(mousePressEvent.x, mousePressEvent.y)); +} +function handleMouseMove(pickRay) { // given the pickRay, just do the hover logic + ExtendedOverlay.applyPickRay(pickRay, function (overlay) { + overlay.hover(true); + }, function () { + ExtendedOverlay.unHover(); + }); +} + +// handy global to keep track of which hand is the mouse (if any) +var currentHandPressed = 0; +var TRIGGER_CLICK_THRESHOLD = 0.85; +var TRIGGER_PRESS_THRESHOLD = 0.05; + +function handleMouseMoveEvent(event) { // find out which overlay (if any) is over the mouse position + var pickRay; + if (HMD.active) { + if (currentHandPressed !== 0) { + pickRay = controllerComputePickRay(currentHandPressed); + } else { + // nothing should hover, so ExtendedOverlay.unHover(); - }); - } - - // handy global to keep track of which hand is the mouse (if any) - var currentHandPressed = 0; - var TRIGGER_CLICK_THRESHOLD = 0.85; - var TRIGGER_PRESS_THRESHOLD = 0.05; - - function handleMouseMoveEvent(event) { // find out which overlay (if any) is over the mouse position - var pickRay; - if (HMD.active) { - if (currentHandPressed !== 0) { - pickRay = controllerComputePickRay(currentHandPressed); - } else { - // nothing should hover, so - ExtendedOverlay.unHover(); - return; - } - } else { - pickRay = Camera.computePickRay(event.x, event.y); - } - handleMouseMove(pickRay); - } - function handleTriggerPressed(hand, value) { - // The idea is if you press one trigger, it is the one - // we will consider the mouse. Even if the other is pressed, - // we ignore it until this one is no longer pressed. - var isPressed = value > TRIGGER_PRESS_THRESHOLD; - if (currentHandPressed === 0) { - currentHandPressed = isPressed ? hand : 0; return; } - if (currentHandPressed === hand) { - currentHandPressed = isPressed ? hand : 0; - return; - } - // otherwise, the other hand is still triggered - // so do nothing. + } else { + pickRay = Camera.computePickRay(event.x, event.y); } - - // We get mouseMoveEvents from the handControllers, via handControllerPointer. - // But we don't get mousePressEvents. - var triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click'); - var triggerPressMapping = Controller.newMapping(Script.resolvePath('') + '-press'); - function controllerComputePickRay(hand) { - var controllerPose = getControllerWorldLocation(hand, true); - if (controllerPose.valid) { - return { origin: controllerPose.position, direction: Quat.getUp(controllerPose.orientation) }; - } + handleMouseMove(pickRay); +} +function handleTriggerPressed(hand, value) { + // The idea is if you press one trigger, it is the one + // we will consider the mouse. Even if the other is pressed, + // we ignore it until this one is no longer pressed. + var isPressed = value > TRIGGER_PRESS_THRESHOLD; + if (currentHandPressed === 0) { + currentHandPressed = isPressed ? hand : 0; + return; } - function makeClickHandler(hand) { - return function (clicked) { - if (clicked > TRIGGER_CLICK_THRESHOLD) { - var pickRay = controllerComputePickRay(hand); - handleClick(pickRay); - } - }; + if (currentHandPressed === hand) { + currentHandPressed = isPressed ? hand : 0; + return; } - function makePressHandler(hand) { - return function (value) { - handleTriggerPressed(hand, value); - }; + // otherwise, the other hand is still triggered + // so do nothing. +} + +// We get mouseMoveEvents from the handControllers, via handControllerPointer. +// But we don't get mousePressEvents. +var triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click'); +var triggerPressMapping = Controller.newMapping(Script.resolvePath('') + '-press'); +function controllerComputePickRay(hand) { + var controllerPose = getControllerWorldLocation(hand, true); + if (controllerPose.valid) { + return { origin: controllerPose.position, direction: Quat.getUp(controllerPose.orientation) }; } - triggerMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand)); - triggerMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand)); - triggerPressMapping.from(Controller.Standard.RT).peek().to(makePressHandler(Controller.Standard.RightHand)); - triggerPressMapping.from(Controller.Standard.LT).peek().to(makePressHandler(Controller.Standard.LeftHand)); - // END AVATAR SELECTOR LOGIC - - var grid = new Grid(); - function adjustPositionPerBoundingBox(position, direction, registration, dimensions, orientation) { - // Adjust the position such that the bounding box (registration, dimenions, and orientation) lies behind the original - // position in the given direction. - var CORNERS = [ - { x: 0, y: 0, z: 0 }, - { x: 0, y: 0, z: 1 }, - { x: 0, y: 1, z: 0 }, - { x: 0, y: 1, z: 1 }, - { x: 1, y: 0, z: 0 }, - { x: 1, y: 0, z: 1 }, - { x: 1, y: 1, z: 0 }, - { x: 1, y: 1, z: 1 }, - ]; - - // Go through all corners and find least (most negative) distance in front of position. - var distance = 0; - for (var i = 0, length = CORNERS.length; i < length; i++) { - var cornerVector = - Vec3.multiplyQbyV(orientation, Vec3.multiplyVbyV(Vec3.subtract(CORNERS[i], registration), dimensions)); - var cornerDistance = Vec3.dot(cornerVector, direction); - distance = Math.min(cornerDistance, distance); +} +function makeClickHandler(hand) { + return function (clicked) { + if (clicked > TRIGGER_CLICK_THRESHOLD) { + var pickRay = controllerComputePickRay(hand); + handleClick(pickRay); } - position = Vec3.sum(Vec3.multiply(distance, direction), position); - return position; - } - - var HALF_TREE_SCALE = 16384; - function getPositionToCreateEntity(extra) { - var CREATE_DISTANCE = 2; - var position; - var delta = extra !== undefined ? extra : 0; - if (Camera.mode === "entity" || Camera.mode === "independent") { - position = Vec3.sum(Camera.position, Vec3.multiply(Quat.getForward(Camera.orientation), CREATE_DISTANCE + delta)); - } else { - position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getForward(MyAvatar.orientation), CREATE_DISTANCE + delta)); - position.y += 0.5; - } - - if (position.x > HALF_TREE_SCALE || position.y > HALF_TREE_SCALE || position.z > HALF_TREE_SCALE) { - return null; - } - return position; - } - - function rezEntity(itemHref, itemType) { - var isWearable = itemType === "wearable"; - var success = Clipboard.importEntities(itemHref); - var wearableLocalPosition = null; - var wearableLocalRotation = null; - var wearableLocalDimensions = null; - var wearableDimensions = null; - - if (itemType === "contentSet") { - console.log("Item is a content set; codepath shouldn't go here."); - return; - } - - if (isWearable) { - var wearableTransforms = Settings.getValue("io.highfidelity.avatarStore.checkOut.transforms"); - if (!wearableTransforms) { - // TODO delete this clause - wearableTransforms = Settings.getValue("io.highfidelity.avatarStore.checkOut.tranforms"); - } - var certPos = itemHref.search("certificate_id="); // TODO how do I parse a URL from here? - if (certPos >= 0) { - certPos += 15; // length of "certificate_id=" - var certURLEncoded = itemHref.substring(certPos); - var certB64Encoded = decodeURIComponent(certURLEncoded); - for (var key in wearableTransforms) { - if (wearableTransforms.hasOwnProperty(key)) { - var certificateTransforms = wearableTransforms[key].certificateTransforms; - if (certificateTransforms) { - for (var certID in certificateTransforms) { - if (certificateTransforms.hasOwnProperty(certID) && - certID == certB64Encoded) { - var certificateTransform = certificateTransforms[certID]; - wearableLocalPosition = certificateTransform.localPosition; - wearableLocalRotation = certificateTransform.localRotation; - wearableLocalDimensions = certificateTransform.localDimensions; - wearableDimensions = certificateTransform.dimensions; - } - } - } - } - } - } - } - - if (success) { - var VERY_LARGE = 10000; - var isLargeImport = Clipboard.getClipboardContentsLargestDimension() >= VERY_LARGE; - var position = Vec3.ZERO; - if (!isLargeImport) { - position = getPositionToCreateEntity(Clipboard.getClipboardContentsLargestDimension() / 2); - } - if (position !== null && position !== undefined) { - var pastedEntityIDs = Clipboard.pasteEntities(position); - if (!isLargeImport) { - // The first entity in Clipboard gets the specified position with the rest being relative to it. Therefore, move - // entities after they're imported so that they're all the correct distance in front of and with geometric mean - // centered on the avatar/camera direction. - var deltaPosition = Vec3.ZERO; - var entityPositions = []; - var entityParentIDs = []; - - var propType = Entities.getEntityProperties(pastedEntityIDs[0], ["type"]).type; - var NO_ADJUST_ENTITY_TYPES = ["Zone", "Light", "ParticleEffect"]; - if (NO_ADJUST_ENTITY_TYPES.indexOf(propType) === -1) { - var targetDirection; - if (Camera.mode === "entity" || Camera.mode === "independent") { - targetDirection = Camera.orientation; - } else { - targetDirection = MyAvatar.orientation; - } - targetDirection = Vec3.multiplyQbyV(targetDirection, Vec3.UNIT_Z); - - var targetPosition = getPositionToCreateEntity(); - var deltaParallel = HALF_TREE_SCALE; // Distance to move entities parallel to targetDirection. - var deltaPerpendicular = Vec3.ZERO; // Distance to move entities perpendicular to targetDirection. - for (var i = 0, length = pastedEntityIDs.length; i < length; i++) { - var curLoopEntityProps = Entities.getEntityProperties(pastedEntityIDs[i], ["position", "dimensions", - "registrationPoint", "rotation", "parentID"]); - var adjustedPosition = adjustPositionPerBoundingBox(targetPosition, targetDirection, - curLoopEntityProps.registrationPoint, curLoopEntityProps.dimensions, curLoopEntityProps.rotation); - var delta = Vec3.subtract(adjustedPosition, curLoopEntityProps.position); - var distance = Vec3.dot(delta, targetDirection); - deltaParallel = Math.min(distance, deltaParallel); - deltaPerpendicular = Vec3.sum(Vec3.subtract(delta, Vec3.multiply(distance, targetDirection)), - deltaPerpendicular); - entityPositions[i] = curLoopEntityProps.position; - entityParentIDs[i] = curLoopEntityProps.parentID; - } - deltaPerpendicular = Vec3.multiply(1 / pastedEntityIDs.length, deltaPerpendicular); - deltaPosition = Vec3.sum(Vec3.multiply(deltaParallel, targetDirection), deltaPerpendicular); - } - - if (grid.getSnapToGrid()) { - var firstEntityProps = Entities.getEntityProperties(pastedEntityIDs[0], ["position", "dimensions", - "registrationPoint"]); - var positionPreSnap = Vec3.sum(deltaPosition, firstEntityProps.position); - position = grid.snapToSurface(grid.snapToGrid(positionPreSnap, false, firstEntityProps.dimensions, - firstEntityProps.registrationPoint), firstEntityProps.dimensions, firstEntityProps.registrationPoint); - deltaPosition = Vec3.subtract(position, firstEntityProps.position); - } - - if (!Vec3.equal(deltaPosition, Vec3.ZERO)) { - for (var editEntityIndex = 0, numEntities = pastedEntityIDs.length; editEntityIndex < numEntities; editEntityIndex++) { - if (Uuid.isNull(entityParentIDs[editEntityIndex])) { - Entities.editEntity(pastedEntityIDs[editEntityIndex], { - position: Vec3.sum(deltaPosition, entityPositions[editEntityIndex]) - }); - } - } - } - } - - if (isWearable) { - // apply the relative offsets saved during checkout - var offsets = {}; - if (wearableLocalPosition) { - offsets.localPosition = wearableLocalPosition; - } - if (wearableLocalRotation) { - offsets.localRotation = wearableLocalRotation; - } - if (wearableLocalDimensions) { - offsets.localDimensions = wearableLocalDimensions; - } else if (wearableDimensions) { - offsets.dimensions = wearableDimensions; - } - // we currently assume a wearable is a single entity - Entities.editEntity(pastedEntityIDs[0], offsets); - } - - var rezPosition = Entities.getEntityProperties(pastedEntityIDs[0], "position").position; - - Audio.playSound(REZZING_SOUND, { - volume: 1.0, - position: rezPosition, - localOnly: true - }); - - } else { - Window.notifyEditError("Can't import entities: entities would be out of bounds."); - } - } else { - Window.notifyEditError("There was an error importing the entity file."); - } - } - - var referrerURL; // Used for updating Purchases QML - var filterText; // Used for updating Purchases QML - function onMessage(message) { - if (message === GOTO_DIRECTORY) { - tablet.gotoWebScreen(MARKETPLACES_URL, MARKETPLACES_INJECT_SCRIPT_URL); - } else if (message === QUERY_CAN_WRITE_ASSETS) { - tablet.emitScriptEvent(CAN_WRITE_ASSETS + " " + Entities.canWriteAssets()); - } else if (message === WARN_USER_NO_PERMISSIONS) { - Window.alert(NO_PERMISSIONS_ERROR_MESSAGE); - } else if (message.slice(0, CLARA_IO_STATUS.length) === CLARA_IO_STATUS) { - if (isDownloadBeingCancelled) { - return; - } - - var text = message.slice(CLARA_IO_STATUS.length); - if (messageBox === null) { - messageBox = Window.openMessageBox(CLARA_DOWNLOAD_TITLE, text, CANCEL_BUTTON, NO_BUTTON); - } else { - Window.updateMessageBox(messageBox, CLARA_DOWNLOAD_TITLE, text, CANCEL_BUTTON, NO_BUTTON); - } - return; - } else if (message.slice(0, CLARA_IO_DOWNLOAD.length) === CLARA_IO_DOWNLOAD) { - if (messageBox !== null) { - Window.closeMessageBox(messageBox); - messageBox = null; - } - return; - } else if (message === CLARA_IO_CANCELLED_DOWNLOAD) { - isDownloadBeingCancelled = false; - } else { - var parsedJsonMessage = JSON.parse(message); - if (parsedJsonMessage.type === "CHECKOUT") { - wireEventBridge(true); - tablet.pushOntoStack(MARKETPLACE_CHECKOUT_QML_PATH); - sendToQml({ - method: 'updateCheckoutQML', - params: parsedJsonMessage - }); - } else if (parsedJsonMessage.type === "REQUEST_SETTING") { - sendCommerceSettings(); - } else if (parsedJsonMessage.type === "PURCHASES") { - referrerURL = parsedJsonMessage.referrerURL; - filterText = ""; - tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH); - } else if (parsedJsonMessage.type === "LOGIN") { - openLoginWindow(); - } else if (parsedJsonMessage.type === "WALLET_SETUP") { - wireEventBridge(true); - sendToQml({ - method: 'updateWalletReferrer', - referrer: "marketplace cta" - }); - openWallet(); - } else if (parsedJsonMessage.type === "MY_ITEMS") { - referrerURL = MARKETPLACE_URL_INITIAL; - filterText = ""; - tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH); - wireEventBridge(true); - sendToQml({ - method: 'purchases_showMyItems' - }); - } - } - } - - function onButtonClicked() { - if (!tablet) { - print("Warning in buttonClicked(): 'tablet' undefined!"); - return; - } - if (onMarketplaceScreen || onCommerceScreen) { - // for toolbar-mode: go back to home screen, this will close the window. - tablet.gotoHomeScreen(); - } else { - if (HMD.tabletID) { - Entities.editEntity(HMD.tabletID, { textures: JSON.stringify({ "tex.close": HOME_BUTTON_TEXTURE }) }); - } - showMarketplace(); - } - } - - // Function Name: sendToQml() - // - // Description: - // -Use this function to send a message to the QML (i.e. to change appearances). The "message" argument is what is sent to - // the QML in the format "{method, params}", like json-rpc. See also fromQml(). - function sendToQml(message) { - tablet.sendToQml(message); - } - - var sendAssetRecipient; - var sendAssetParticleEffectUpdateTimer; - var particleEffectTimestamp; - var sendAssetParticleEffect; - var SEND_ASSET_PARTICLE_TIMER_UPDATE = 250; - var SEND_ASSET_PARTICLE_EMITTING_DURATION = 3000; - var SEND_ASSET_PARTICLE_LIFETIME_SECONDS = 8; - var SEND_ASSET_PARTICLE_PROPERTIES = { - accelerationSpread: { x: 0, y: 0, z: 0 }, - alpha: 1, - alphaFinish: 1, - alphaSpread: 0, - alphaStart: 1, - azimuthFinish: 0, - azimuthStart: -6, - color: { red: 255, green: 222, blue: 255 }, - colorFinish: { red: 255, green: 229, blue: 225 }, - colorSpread: { red: 0, green: 0, blue: 0 }, - colorStart: { red: 243, green: 255, blue: 255 }, - emitAcceleration: { x: 0, y: 0, z: 0 }, // Immediately gets updated to be accurate - emitDimensions: { x: 0, y: 0, z: 0 }, - emitOrientation: { x: 0, y: 0, z: 0 }, - emitRate: 4, - emitSpeed: 2.1, - emitterShouldTrail: true, - isEmitting: 1, - lifespan: SEND_ASSET_PARTICLE_LIFETIME_SECONDS + 1, // Immediately gets updated to be accurate - lifetime: SEND_ASSET_PARTICLE_LIFETIME_SECONDS + 1, - maxParticles: 20, - name: 'asset-particles', - particleRadius: 0.2, - polarFinish: 0, - polarStart: 0, - radiusFinish: 0.05, - radiusSpread: 0, - radiusStart: 0.2, - speedSpread: 0, - textures: "http://hifi-content.s3.amazonaws.com/alan/dev/Particles/Bokeh-Particle-HFC.png", - type: 'ParticleEffect' }; +} +function makePressHandler(hand) { + return function (value) { + handleTriggerPressed(hand, value); + }; +} +triggerMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand)); +triggerMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand)); +triggerPressMapping.from(Controller.Standard.RT).peek().to(makePressHandler(Controller.Standard.RightHand)); +triggerPressMapping.from(Controller.Standard.LT).peek().to(makePressHandler(Controller.Standard.LeftHand)); +// END AVATAR SELECTOR LOGIC - function updateSendAssetParticleEffect() { - var timestampNow = Date.now(); - if ((timestampNow - particleEffectTimestamp) > (SEND_ASSET_PARTICLE_LIFETIME_SECONDS * 1000)) { - deleteSendAssetParticleEffect(); - return; - } else if ((timestampNow - particleEffectTimestamp) > SEND_ASSET_PARTICLE_EMITTING_DURATION) { - Entities.editEntity(sendAssetParticleEffect, { - isEmitting: 0 - }); - } else if (sendAssetParticleEffect) { - var recipientPosition = AvatarList.getAvatar(sendAssetRecipient).position; - var distance = Vec3.distance(recipientPosition, MyAvatar.position); - var accel = Vec3.subtract(recipientPosition, MyAvatar.position); - accel.y -= 3.0; - var life = Math.sqrt(2 * distance / Vec3.length(accel)); - Entities.editEntity(sendAssetParticleEffect, { - emitAcceleration: accel, - lifespan: life +var grid = new Grid(); +function adjustPositionPerBoundingBox(position, direction, registration, dimensions, orientation) { + // Adjust the position such that the bounding box (registration, dimenions, and orientation) lies behind the original + // position in the given direction. + var CORNERS = [ + { x: 0, y: 0, z: 0 }, + { x: 0, y: 0, z: 1 }, + { x: 0, y: 1, z: 0 }, + { x: 0, y: 1, z: 1 }, + { x: 1, y: 0, z: 0 }, + { x: 1, y: 0, z: 1 }, + { x: 1, y: 1, z: 0 }, + { x: 1, y: 1, z: 1 } + ]; + + // Go through all corners and find least (most negative) distance in front of position. + var distance = 0; + for (var i = 0, length = CORNERS.length; i < length; i++) { + var cornerVector = + Vec3.multiplyQbyV(orientation, Vec3.multiplyVbyV(Vec3.subtract(CORNERS[i], registration), dimensions)); + var cornerDistance = Vec3.dot(cornerVector, direction); + distance = Math.min(cornerDistance, distance); + } + position = Vec3.sum(Vec3.multiply(distance, direction), position); + return position; +} + +var HALF_TREE_SCALE = 16384; +function getPositionToCreateEntity(extra) { + var CREATE_DISTANCE = 2; + var position; + var delta = extra !== undefined ? extra : 0; + if (Camera.mode === "entity" || Camera.mode === "independent") { + position = Vec3.sum(Camera.position, Vec3.multiply(Quat.getForward(Camera.orientation), CREATE_DISTANCE + delta)); + } else { + position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getForward(MyAvatar.orientation), CREATE_DISTANCE + delta)); + position.y += 0.5; + } + + if (position.x > HALF_TREE_SCALE || position.y > HALF_TREE_SCALE || position.z > HALF_TREE_SCALE) { + return null; + } + return position; +} + +function rezEntity(itemHref, itemType) { + var isWearable = itemType === "wearable"; + var success = Clipboard.importEntities(itemHref); + var wearableLocalPosition = null; + var wearableLocalRotation = null; + var wearableLocalDimensions = null; + var wearableDimensions = null; + + if (itemType === "contentSet") { + console.log("Item is a content set; codepath shouldn't go here."); + return; + } + + if (isWearable) { + var wearableTransforms = Settings.getValue("io.highfidelity.avatarStore.checkOut.transforms"); + if (!wearableTransforms) { + // TODO delete this clause + wearableTransforms = Settings.getValue("io.highfidelity.avatarStore.checkOut.tranforms"); + } + var certPos = itemHref.search("certificate_id="); // TODO how do I parse a URL from here? + if (certPos >= 0) { + certPos += 15; // length of "certificate_id=" + var certURLEncoded = itemHref.substring(certPos); + var certB64Encoded = decodeURIComponent(certURLEncoded); + for (var key in wearableTransforms) { + if (wearableTransforms.hasOwnProperty(key)) { + var certificateTransforms = wearableTransforms[key].certificateTransforms; + if (certificateTransforms) { + for (var certID in certificateTransforms) { + if (certificateTransforms.hasOwnProperty(certID) && + certID == certB64Encoded) { + var certificateTransform = certificateTransforms[certID]; + wearableLocalPosition = certificateTransform.localPosition; + wearableLocalRotation = certificateTransform.localRotation; + wearableLocalDimensions = certificateTransform.localDimensions; + wearableDimensions = certificateTransform.dimensions; + } + } + } + } + } + } + } + + if (success) { + var VERY_LARGE = 10000; + var isLargeImport = Clipboard.getClipboardContentsLargestDimension() >= VERY_LARGE; + var position = Vec3.ZERO; + if (!isLargeImport) { + position = getPositionToCreateEntity(Clipboard.getClipboardContentsLargestDimension() / 2); + } + if (position !== null && position !== undefined) { + var pastedEntityIDs = Clipboard.pasteEntities(position); + if (!isLargeImport) { + // The first entity in Clipboard gets the specified position with the rest being relative to it. Therefore, move + // entities after they're imported so that they're all the correct distance in front of and with geometric mean + // centered on the avatar/camera direction. + var deltaPosition = Vec3.ZERO; + var entityPositions = []; + var entityParentIDs = []; + + var propType = Entities.getEntityProperties(pastedEntityIDs[0], ["type"]).type; + var NO_ADJUST_ENTITY_TYPES = ["Zone", "Light", "ParticleEffect"]; + if (NO_ADJUST_ENTITY_TYPES.indexOf(propType) === -1) { + var targetDirection; + if (Camera.mode === "entity" || Camera.mode === "independent") { + targetDirection = Camera.orientation; + } else { + targetDirection = MyAvatar.orientation; + } + targetDirection = Vec3.multiplyQbyV(targetDirection, Vec3.UNIT_Z); + + var targetPosition = getPositionToCreateEntity(); + var deltaParallel = HALF_TREE_SCALE; // Distance to move entities parallel to targetDirection. + var deltaPerpendicular = Vec3.ZERO; // Distance to move entities perpendicular to targetDirection. + for (var i = 0, length = pastedEntityIDs.length; i < length; i++) { + var curLoopEntityProps = Entities.getEntityProperties(pastedEntityIDs[i], ["position", "dimensions", + "registrationPoint", "rotation", "parentID"]); + var adjustedPosition = adjustPositionPerBoundingBox(targetPosition, targetDirection, + curLoopEntityProps.registrationPoint, curLoopEntityProps.dimensions, curLoopEntityProps.rotation); + var delta = Vec3.subtract(adjustedPosition, curLoopEntityProps.position); + var distance = Vec3.dot(delta, targetDirection); + deltaParallel = Math.min(distance, deltaParallel); + deltaPerpendicular = Vec3.sum(Vec3.subtract(delta, Vec3.multiply(distance, targetDirection)), + deltaPerpendicular); + entityPositions[i] = curLoopEntityProps.position; + entityParentIDs[i] = curLoopEntityProps.parentID; + } + deltaPerpendicular = Vec3.multiply(1 / pastedEntityIDs.length, deltaPerpendicular); + deltaPosition = Vec3.sum(Vec3.multiply(deltaParallel, targetDirection), deltaPerpendicular); + } + + if (grid.getSnapToGrid()) { + var firstEntityProps = Entities.getEntityProperties(pastedEntityIDs[0], ["position", "dimensions", + "registrationPoint"]); + var positionPreSnap = Vec3.sum(deltaPosition, firstEntityProps.position); + position = grid.snapToSurface(grid.snapToGrid(positionPreSnap, false, firstEntityProps.dimensions, + firstEntityProps.registrationPoint), firstEntityProps.dimensions, firstEntityProps.registrationPoint); + deltaPosition = Vec3.subtract(position, firstEntityProps.position); + } + + if (!Vec3.equal(deltaPosition, Vec3.ZERO)) { + for (var editEntityIndex = 0, numEntities = pastedEntityIDs.length; editEntityIndex < numEntities; editEntityIndex++) { + if (Uuid.isNull(entityParentIDs[editEntityIndex])) { + Entities.editEntity(pastedEntityIDs[editEntityIndex], { + position: Vec3.sum(deltaPosition, entityPositions[editEntityIndex]) + }); + } + } + } + } + + if (isWearable) { + // apply the relative offsets saved during checkout + var offsets = {}; + if (wearableLocalPosition) { + offsets.localPosition = wearableLocalPosition; + } + if (wearableLocalRotation) { + offsets.localRotation = wearableLocalRotation; + } + if (wearableLocalDimensions) { + offsets.localDimensions = wearableLocalDimensions; + } else if (wearableDimensions) { + offsets.dimensions = wearableDimensions; + } + // we currently assume a wearable is a single entity + Entities.editEntity(pastedEntityIDs[0], offsets); + } + + var rezPosition = Entities.getEntityProperties(pastedEntityIDs[0], "position").position; + + Audio.playSound(REZZING_SOUND, { + volume: 1.0, + position: rezPosition, + localOnly: true }); + + } else { + Window.notifyEditError("Can't import entities: entities would be out of bounds."); } + } else { + Window.notifyEditError("There was an error importing the entity file."); } +} - function deleteSendAssetParticleEffect() { - if (sendAssetParticleEffectUpdateTimer) { - Script.clearInterval(sendAssetParticleEffectUpdateTimer); - sendAssetParticleEffectUpdateTimer = null; - } - if (sendAssetParticleEffect) { - sendAssetParticleEffect = Entities.deleteEntity(sendAssetParticleEffect); - } - sendAssetRecipient = null; - } - - var savedDisablePreviewOptionLocked = false; - var savedDisablePreviewOption = Menu.isOptionChecked("Disable Preview");; - function maybeEnableHMDPreview() { - // Set a small timeout to prevent sensitive data from being shown during - // UI fade - Script.setTimeout(function () { - setTabletVisibleInSecondaryCamera(true); - DesktopPreviewProvider.setPreviewDisabledReason("USER"); - Menu.setIsOptionChecked("Disable Preview", savedDisablePreviewOption); - savedDisablePreviewOptionLocked = false; - }, 150); - } - - // Function Name: fromQml() - // - // Description: - // -Called when a message is received from Checkout.qml. The "message" argument is what is sent from the Checkout QML - // in the format "{method, params}", like json-rpc. - function fromQml(message) { - switch (message.method) { - case 'purchases_openWallet': - case 'checkout_openWallet': - case 'checkout_setUpClicked': - openWallet(); - break; - case 'purchases_walletNotSetUp': - wireEventBridge(true); - sendToQml({ - method: 'updateWalletReferrer', - referrer: "purchases" - }); - openWallet(); - break; - case 'checkout_walletNotSetUp': - wireEventBridge(true); - sendToQml({ - method: 'updateWalletReferrer', - referrer: message.referrer === "itemPage" ? message.itemId : message.referrer - }); - openWallet(); - break; - case 'checkout_cancelClicked': - tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.params, MARKETPLACES_INJECT_SCRIPT_URL); - // TODO: Make Marketplace a QML app that's a WebView wrapper so we can use the app stack. - // I don't think this is trivial to do since we also want to inject some JS into the DOM. - //tablet.popFromStack(); - break; - case 'header_goToPurchases': - case 'checkout_goToPurchases': - referrerURL = MARKETPLACE_URL_INITIAL; - filterText = message.filterText; - tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH); - break; - case 'checkout_itemLinkClicked': - tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.itemId, MARKETPLACES_INJECT_SCRIPT_URL); - break; - case 'checkout_continueShopping': - tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); - //tablet.popFromStack(); - break; - case 'purchases_itemInfoClicked': - var itemId = message.itemId; - if (itemId && itemId !== "") { - tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + itemId, MARKETPLACES_INJECT_SCRIPT_URL); - } - break; - case 'checkout_rezClicked': - case 'purchases_rezClicked': - rezEntity(message.itemHref, message.itemType); - break; - case 'header_marketplaceImageClicked': - case 'purchases_backClicked': - tablet.gotoWebScreen(message.referrerURL, MARKETPLACES_INJECT_SCRIPT_URL); - break; - case 'purchases_goToMarketplaceClicked': - tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); - break; - case 'updateItemClicked': - tablet.gotoWebScreen(message.upgradeUrl + "?edition=" + message.itemEdition, - MARKETPLACES_INJECT_SCRIPT_URL); - break; - case 'giftAsset': - - break; - case 'passphrasePopup_cancelClicked': - case 'needsLogIn_cancelClicked': - tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); - break; - case 'needsLogIn_loginClicked': - openLoginWindow(); - break; - case 'disableHmdPreview': - if (!savedDisablePreviewOption) { - savedDisablePreviewOption = Menu.isOptionChecked("Disable Preview"); - savedDisablePreviewOptionLocked = true; - } - - if (!savedDisablePreviewOption) { - DesktopPreviewProvider.setPreviewDisabledReason("SECURE_SCREEN"); - Menu.setIsOptionChecked("Disable Preview", true); - setTabletVisibleInSecondaryCamera(false); - } - break; - case 'maybeEnableHmdPreview': - maybeEnableHMDPreview(); - break; - case 'purchases_openGoTo': - tablet.loadQMLSource("hifi/tablet/TabletAddressDialog.qml"); - break; - case 'purchases_itemCertificateClicked': - setCertificateInfo("", message.itemCertificateId); - break; - case 'inspectionCertificate_closeClicked': - tablet.gotoHomeScreen(); - break; - case 'inspectionCertificate_requestOwnershipVerification': - ContextOverlay.requestOwnershipVerification(message.entity); - break; - case 'inspectionCertificate_showInMarketplaceClicked': - tablet.gotoWebScreen(message.marketplaceUrl, MARKETPLACES_INJECT_SCRIPT_URL); - break; - case 'header_myItemsClicked': - referrerURL = MARKETPLACE_URL_INITIAL; - filterText = ""; - tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH); - wireEventBridge(true); - sendToQml({ - method: 'purchases_showMyItems' - }); - break; - case 'refreshConnections': - // Guard to prevent this code from being executed while sending money -- - // we only want to execute this while sending non-HFC gifts - if (!onWalletScreen) { - print('Refreshing Connections...'); - getConnectionData(false); - } - break; - case 'enable_ChooseRecipientNearbyMode': - // Guard to prevent this code from being executed while sending money -- - // we only want to execute this while sending non-HFC gifts - if (!onWalletScreen) { - if (!isUpdateOverlaysWired) { - Script.update.connect(updateOverlays); - isUpdateOverlaysWired = true; - } - } - break; - case 'disable_ChooseRecipientNearbyMode': - // Guard to prevent this code from being executed while sending money -- - // we only want to execute this while sending non-HFC gifts - if (!onWalletScreen) { - if (isUpdateOverlaysWired) { - Script.update.disconnect(updateOverlays); - isUpdateOverlaysWired = false; - } - removeOverlays(); - } - break; - case 'wallet_availableUpdatesReceived': - case 'purchases_availableUpdatesReceived': - userHasUpdates = message.numUpdates > 0; - messagesWaiting(userHasUpdates); - break; - case 'purchases_updateWearables': - var currentlyWornWearables = []; - var ATTACHMENT_SEARCH_RADIUS = 100; // meters (just in case) - - var nearbyEntities = Entities.findEntitiesByType('Model', MyAvatar.position, ATTACHMENT_SEARCH_RADIUS); - - for (var i = 0; i < nearbyEntities.length; i++) { - var currentProperties = Entities.getEntityProperties(nearbyEntities[i], ['certificateID', 'editionNumber', 'parentID']); - if (currentProperties.parentID === MyAvatar.sessionUUID) { - currentlyWornWearables.push({ - entityID: nearbyEntities[i], - entityCertID: currentProperties.certificateID, - entityEdition: currentProperties.editionNumber - }); - } - } - - sendToQml({ method: 'updateWearables', wornWearables: currentlyWornWearables }); - break; - case 'sendAsset_sendPublicly': - if (message.assetName !== "") { - deleteSendAssetParticleEffect(); - sendAssetRecipient = message.recipient; - var amount = message.amount; - var props = SEND_ASSET_PARTICLE_PROPERTIES; - props.parentID = MyAvatar.sessionUUID; - props.position = MyAvatar.position; - props.position.y += 0.2; - if (message.effectImage) { - props.textures = message.effectImage; - } - sendAssetParticleEffect = Entities.addEntity(props, true); - particleEffectTimestamp = Date.now(); - updateSendAssetParticleEffect(); - sendAssetParticleEffectUpdateTimer = Script.setInterval(updateSendAssetParticleEffect, SEND_ASSET_PARTICLE_TIMER_UPDATE); - } - break; - case 'http.request': - // Handled elsewhere, don't log. - break; - case 'goToPurchases_fromWalletHome': // HRS FIXME What's this about? - break; - default: - print('Unrecognized message from Checkout.qml or Purchases.qml: ' + JSON.stringify(message)); - } - } - - // Function Name: wireEventBridge() - // - // Description: - // -Used to connect/disconnect the script's response to the tablet's "fromQml" signal. Set the "on" argument to enable or - // disable to event bridge. - // - // Relevant Variables: - // -hasEventBridge: true/false depending on whether we've already connected the event bridge. - var hasEventBridge = false; - function wireEventBridge(on) { - if (!tablet) { - print("Warning in wireEventBridge(): 'tablet' undefined!"); +var referrerURL; // Used for updating Purchases QML +var filterText; // Used for updating Purchases QML +function onWebEventReceived(message) { + message = JSON.parse(message); + if (message.type === GOTO_DIRECTORY) { + ui.open(MARKETPLACES_URL, MARKETPLACES_INJECT_SCRIPT_URL); + } else if (message.type === QUERY_CAN_WRITE_ASSETS) { + ui.sendToHtml(CAN_WRITE_ASSETS + " " + Entities.canWriteAssets()); + } else if (message.type === WARN_USER_NO_PERMISSIONS) { + Window.alert(NO_PERMISSIONS_ERROR_MESSAGE); + } else if (message.type === CLARA_IO_STATUS) { + if (isDownloadBeingCancelled) { return; } - if (on) { - if (!hasEventBridge) { - tablet.fromQml.connect(fromQml); - hasEventBridge = true; - } + + var text = message.status; + if (messageBox === null) { + messageBox = Window.openMessageBox(CLARA_DOWNLOAD_TITLE, text, CANCEL_BUTTON, NO_BUTTON); } else { - if (hasEventBridge) { - tablet.fromQml.disconnect(fromQml); - hasEventBridge = false; - } + Window.updateMessageBox(messageBox, CLARA_DOWNLOAD_TITLE, text, CANCEL_BUTTON, NO_BUTTON); } - } - - // Function Name: onTabletScreenChanged() - // - // Description: - // -Called when the TabletScriptingInterface::screenChanged() signal is emitted. The "type" argument can be either the string - // value of "Home", "Web", "Menu", "QML", or "Closed". The "url" argument is only valid for Web and QML. - var onWalletScreen = false; - var onCommerceScreen = false; - function onTabletScreenChanged(type, url) { - onMarketplaceScreen = type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1; - var onWalletScreenNow = url.indexOf(MARKETPLACE_WALLET_QML_PATH) !== -1; - var onCommerceScreenNow = type === "QML" && (url.indexOf(MARKETPLACE_CHECKOUT_QML_PATH) !== -1 || url === MARKETPLACE_PURCHASES_QML_PATH - || url.indexOf(MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH) !== -1); - - if ((!onWalletScreenNow && onWalletScreen) || (!onCommerceScreenNow && onCommerceScreen)) { // exiting wallet or commerce screen - maybeEnableHMDPreview(); + return; + } else if (message.type === CLARA_IO_DOWNLOAD) { + if (messageBox !== null) { + Window.closeMessageBox(messageBox); + messageBox = null; } - - onCommerceScreen = onCommerceScreenNow; - onWalletScreen = onWalletScreenNow; - wireEventBridge(onMarketplaceScreen || onCommerceScreen || onWalletScreen); - - if (url === MARKETPLACE_PURCHASES_QML_PATH) { - sendToQml({ - method: 'updatePurchases', - referrerURL: referrerURL, - filterText: filterText - }); - } - - // for toolbar mode: change button to active when window is first openend, false otherwise. - if (marketplaceButton) { - marketplaceButton.editProperties({ isActive: (onMarketplaceScreen || onCommerceScreen) && !onWalletScreen }); - } - if (type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1) { - ContextOverlay.isInMarketplaceInspectionMode = true; - } else { - ContextOverlay.isInMarketplaceInspectionMode = false; - } - - if (onCommerceScreen) { - if (!isWired) { - Users.usernameFromIDReply.connect(usernameFromIDReply); - Controller.mousePressEvent.connect(handleMouseEvent); - Controller.mouseMoveEvent.connect(handleMouseMoveEvent); - triggerMapping.enable(); - triggerPressMapping.enable(); - } - isWired = true; - Wallet.refreshWalletStatus(); - } else { - off(); - sendToQml({ - method: 'inspectionCertificate_resetCert' - }); - } - } - - // - // Manage the connection between the button and the window. - // - var marketplaceButton; - var buttonName = "MARKET"; - var tablet = null; - var NORMAL_ICON = "icons/tablet-icons/market-i.svg"; - var NORMAL_ACTIVE = "icons/tablet-icons/market-a.svg"; - var WAITING_ICON = "icons/tablet-icons/market-i-msg.svg"; - var WAITING_ACTIVE = "icons/tablet-icons/market-a-msg.svg"; - function startup() { - tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - marketplaceButton = tablet.addButton({ - icon: NORMAL_ICON, - activeIcon: NORMAL_ACTIVE, - text: buttonName, - sortOrder: 9 + return; + } else if (message.type === CLARA_IO_CANCELLED_DOWNLOAD) { + isDownloadBeingCancelled = false; + } else if (message.type === "CHECKOUT") { + wireQmlEventBridge(true); + ui.open(MARKETPLACE_CHECKOUT_QML_PATH); + ui.tablet.sendToQml({ + method: 'updateCheckoutQML', + params: message + }); + } else if (message.type === "REQUEST_SETTING") { + sendCommerceSettings(); + } else if (message.type === "PURCHASES") { + referrerURL = message.referrerURL; + filterText = ""; + ui.open(MARKETPLACE_PURCHASES_QML_PATH); + } else if (message.type === "LOGIN") { + openLoginWindow(); + } else if (message.type === "WALLET_SETUP") { + wireQmlEventBridge(true); + ui.tablet.sendToQml({ + method: 'updateWalletReferrer', + referrer: "marketplace cta" + }); + openWallet(); + } else if (message.type === "MY_ITEMS") { + referrerURL = MARKETPLACE_URL_INITIAL; + filterText = ""; + ui.open(MARKETPLACE_PURCHASES_QML_PATH); + wireQmlEventBridge(true); + ui.tablet.sendToQml({ + method: 'purchases_showMyItems' }); - - ContextOverlay.contextOverlayClicked.connect(setCertificateInfo); - Entities.canWriteAssetsChanged.connect(onCanWriteAssetsChanged); - GlobalServices.myUsernameChanged.connect(onUsernameChanged); - marketplaceButton.clicked.connect(onButtonClicked); - tablet.screenChanged.connect(onTabletScreenChanged); - tablet.webEventReceived.connect(onMessage); - Wallet.walletStatusChanged.connect(sendCommerceSettings); - Window.messageBoxClosed.connect(onMessageBoxClosed); - - Wallet.refreshWalletStatus(); } - var isWired = false; - var isUpdateOverlaysWired = false; - function off() { - if (isWired) { - Users.usernameFromIDReply.disconnect(usernameFromIDReply); - Controller.mousePressEvent.disconnect(handleMouseEvent); - Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent); - triggerMapping.disable(); - triggerPressMapping.disable(); +} +var sendAssetRecipient; +var sendAssetParticleEffectUpdateTimer; +var particleEffectTimestamp; +var sendAssetParticleEffect; +var SEND_ASSET_PARTICLE_TIMER_UPDATE = 250; +var SEND_ASSET_PARTICLE_EMITTING_DURATION = 3000; +var SEND_ASSET_PARTICLE_LIFETIME_SECONDS = 8; +var SEND_ASSET_PARTICLE_PROPERTIES = { + accelerationSpread: { x: 0, y: 0, z: 0 }, + alpha: 1, + alphaFinish: 1, + alphaSpread: 0, + alphaStart: 1, + azimuthFinish: 0, + azimuthStart: -6, + color: { red: 255, green: 222, blue: 255 }, + colorFinish: { red: 255, green: 229, blue: 225 }, + colorSpread: { red: 0, green: 0, blue: 0 }, + colorStart: { red: 243, green: 255, blue: 255 }, + emitAcceleration: { x: 0, y: 0, z: 0 }, // Immediately gets updated to be accurate + emitDimensions: { x: 0, y: 0, z: 0 }, + emitOrientation: { x: 0, y: 0, z: 0 }, + emitRate: 4, + emitSpeed: 2.1, + emitterShouldTrail: true, + isEmitting: 1, + lifespan: SEND_ASSET_PARTICLE_LIFETIME_SECONDS + 1, // Immediately gets updated to be accurate + lifetime: SEND_ASSET_PARTICLE_LIFETIME_SECONDS + 1, + maxParticles: 20, + name: 'asset-particles', + particleRadius: 0.2, + polarFinish: 0, + polarStart: 0, + radiusFinish: 0.05, + radiusSpread: 0, + radiusStart: 0.2, + speedSpread: 0, + textures: "http://hifi-content.s3.amazonaws.com/alan/dev/Particles/Bokeh-Particle-HFC.png", + type: 'ParticleEffect' +}; - isWired = false; - } - if (isUpdateOverlaysWired) { - Script.update.disconnect(updateOverlays); - isUpdateOverlaysWired = false; - } - removeOverlays(); - } - function shutdown() { - maybeEnableHMDPreview(); +function updateSendAssetParticleEffect() { + var timestampNow = Date.now(); + if ((timestampNow - particleEffectTimestamp) > (SEND_ASSET_PARTICLE_LIFETIME_SECONDS * 1000)) { deleteSendAssetParticleEffect(); + return; + } else if ((timestampNow - particleEffectTimestamp) > SEND_ASSET_PARTICLE_EMITTING_DURATION) { + Entities.editEntity(sendAssetParticleEffect, { + isEmitting: 0 + }); + } else if (sendAssetParticleEffect) { + var recipientPosition = AvatarList.getAvatar(sendAssetRecipient).position; + var distance = Vec3.distance(recipientPosition, MyAvatar.position); + var accel = Vec3.subtract(recipientPosition, MyAvatar.position); + accel.y -= 3.0; + var life = Math.sqrt(2 * distance / Vec3.length(accel)); + Entities.editEntity(sendAssetParticleEffect, { + emitAcceleration: accel, + lifespan: life + }); + } +} - ContextOverlay.contextOverlayClicked.disconnect(setCertificateInfo); - Entities.canWriteAssetsChanged.disconnect(onCanWriteAssetsChanged); - GlobalServices.myUsernameChanged.disconnect(onUsernameChanged); - marketplaceButton.clicked.disconnect(onButtonClicked); - tablet.removeButton(marketplaceButton); - tablet.webEventReceived.disconnect(onMessage); - Wallet.walletStatusChanged.disconnect(sendCommerceSettings); - Window.messageBoxClosed.disconnect(onMessageBoxClosed); +function deleteSendAssetParticleEffect() { + if (sendAssetParticleEffectUpdateTimer) { + Script.clearInterval(sendAssetParticleEffectUpdateTimer); + sendAssetParticleEffectUpdateTimer = null; + } + if (sendAssetParticleEffect) { + sendAssetParticleEffect = Entities.deleteEntity(sendAssetParticleEffect); + } + sendAssetRecipient = null; +} + +var savedDisablePreviewOption = Menu.isOptionChecked("Disable Preview"); +var UI_FADE_TIMEOUT_MS = 150; +function maybeEnableHMDPreview() { + // Set a small timeout to prevent sensitive data from being shown during UI fade + Script.setTimeout(function () { + setTabletVisibleInSecondaryCamera(true); + DesktopPreviewProvider.setPreviewDisabledReason("USER"); + Menu.setIsOptionChecked("Disable Preview", savedDisablePreviewOption); + }, UI_FADE_TIMEOUT_MS); +} - if (tablet) { - tablet.screenChanged.disconnect(onTabletScreenChanged); - if (onMarketplaceScreen) { - tablet.gotoHomeScreen(); +var onQmlMessageReceived = function onQmlMessageReceived(message) { + if (message.messageSrc === "HTML") { + return; + } + switch (message.method) { + case 'purchases_openWallet': + case 'checkout_openWallet': + case 'checkout_setUpClicked': + openWallet(); + break; + case 'purchases_walletNotSetUp': + wireQmlEventBridge(true); + ui.tablet.sendToQml({ + method: 'updateWalletReferrer', + referrer: "purchases" + }); + openWallet(); + break; + case 'checkout_walletNotSetUp': + wireQmlEventBridge(true); + ui.tablet.sendToQml({ + method: 'updateWalletReferrer', + referrer: message.referrer === "itemPage" ? message.itemId : message.referrer + }); + openWallet(); + break; + case 'checkout_cancelClicked': + ui.open(MARKETPLACE_URL + '/items/' + message.params, MARKETPLACES_INJECT_SCRIPT_URL); + break; + case 'header_goToPurchases': + case 'checkout_goToPurchases': + referrerURL = MARKETPLACE_URL_INITIAL; + filterText = message.filterText; + ui.open(MARKETPLACE_PURCHASES_QML_PATH); + break; + case 'checkout_itemLinkClicked': + ui.open(MARKETPLACE_URL + '/items/' + message.itemId, MARKETPLACES_INJECT_SCRIPT_URL); + break; + case 'checkout_continueShopping': + ui.open(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); + break; + case 'purchases_itemInfoClicked': + var itemId = message.itemId; + if (itemId && itemId !== "") { + ui.open(MARKETPLACE_URL + '/items/' + itemId, MARKETPLACES_INJECT_SCRIPT_URL); + } + break; + case 'checkout_rezClicked': + case 'purchases_rezClicked': + rezEntity(message.itemHref, message.itemType); + break; + case 'header_marketplaceImageClicked': + case 'purchases_backClicked': + ui.open(message.referrerURL, MARKETPLACES_INJECT_SCRIPT_URL); + break; + case 'purchases_goToMarketplaceClicked': + ui.open(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); + break; + case 'updateItemClicked': + ui.open(message.upgradeUrl + "?edition=" + message.itemEdition, + MARKETPLACES_INJECT_SCRIPT_URL); + break; + case 'giftAsset': + + break; + case 'passphrasePopup_cancelClicked': + case 'needsLogIn_cancelClicked': + ui.open(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); + break; + case 'needsLogIn_loginClicked': + openLoginWindow(); + break; + case 'disableHmdPreview': + if (!savedDisablePreviewOption) { + savedDisablePreviewOption = Menu.isOptionChecked("Disable Preview"); + } + + if (!savedDisablePreviewOption) { + DesktopPreviewProvider.setPreviewDisabledReason("SECURE_SCREEN"); + Menu.setIsOptionChecked("Disable Preview", true); + setTabletVisibleInSecondaryCamera(false); + } + break; + case 'maybeEnableHmdPreview': + maybeEnableHMDPreview(); + break; + case 'purchases_openGoTo': + ui.open("hifi/tablet/TabletAddressDialog.qml"); + break; + case 'purchases_itemCertificateClicked': + contextOverlayEntity = ""; + setCertificateInfo(contextOverlayEntity, message.itemCertificateId); + break; + case 'inspectionCertificate_closeClicked': + ui.close(); + break; + case 'inspectionCertificate_requestOwnershipVerification': + ContextOverlay.requestOwnershipVerification(message.entity); + break; + case 'inspectionCertificate_showInMarketplaceClicked': + ui.open(message.marketplaceUrl, MARKETPLACES_INJECT_SCRIPT_URL); + break; + case 'header_myItemsClicked': + referrerURL = MARKETPLACE_URL_INITIAL; + filterText = ""; + ui.open(MARKETPLACE_PURCHASES_QML_PATH); + wireQmlEventBridge(true); + ui.tablet.sendToQml({ + method: 'purchases_showMyItems' + }); + break; + case 'refreshConnections': + // Guard to prevent this code from being executed while sending money -- + // we only want to execute this while sending non-HFC gifts + if (!onWalletScreen) { + print('Refreshing Connections...'); + getConnectionData(false); + } + break; + case 'enable_ChooseRecipientNearbyMode': + // Guard to prevent this code from being executed while sending money -- + // we only want to execute this while sending non-HFC gifts + if (!onWalletScreen) { + if (!isUpdateOverlaysWired) { + Script.update.connect(updateOverlays); + isUpdateOverlaysWired = true; + } + } + break; + case 'disable_ChooseRecipientNearbyMode': + // Guard to prevent this code from being executed while sending money -- + // we only want to execute this while sending non-HFC gifts + if (!onWalletScreen) { + if (isUpdateOverlaysWired) { + Script.update.disconnect(updateOverlays); + isUpdateOverlaysWired = false; + } + removeOverlays(); + } + break; + case 'wallet_availableUpdatesReceived': + case 'purchases_availableUpdatesReceived': + userHasUpdates = message.numUpdates > 0; + ui.messagesWaiting(userHasUpdates); + break; + case 'purchases_updateWearables': + var currentlyWornWearables = []; + var ATTACHMENT_SEARCH_RADIUS = 100; // meters (just in case) + + var nearbyEntities = Entities.findEntitiesByType('Model', MyAvatar.position, ATTACHMENT_SEARCH_RADIUS); + + for (var i = 0; i < nearbyEntities.length; i++) { + var currentProperties = Entities.getEntityProperties( + nearbyEntities[i], ['certificateID', 'editionNumber', 'parentID'] + ); + if (currentProperties.parentID === MyAvatar.sessionUUID) { + currentlyWornWearables.push({ + entityID: nearbyEntities[i], + entityCertID: currentProperties.certificateID, + entityEdition: currentProperties.editionNumber + }); } } + ui.tablet.sendToQml({ method: 'updateWearables', wornWearables: currentlyWornWearables }); + break; + case 'sendAsset_sendPublicly': + if (message.assetName !== "") { + deleteSendAssetParticleEffect(); + sendAssetRecipient = message.recipient; + var props = SEND_ASSET_PARTICLE_PROPERTIES; + props.parentID = MyAvatar.sessionUUID; + props.position = MyAvatar.position; + props.position.y += 0.2; + if (message.effectImage) { + props.textures = message.effectImage; + } + sendAssetParticleEffect = Entities.addEntity(props, true); + particleEffectTimestamp = Date.now(); + updateSendAssetParticleEffect(); + sendAssetParticleEffectUpdateTimer = Script.setInterval(updateSendAssetParticleEffect, + SEND_ASSET_PARTICLE_TIMER_UPDATE); + } + break; + case 'http.request': + // Handled elsewhere, don't log. + break; + case 'goToPurchases_fromWalletHome': // HRS FIXME What's this about? + break; + default: + print('Unrecognized message from Checkout.qml or Purchases.qml: ' + JSON.stringify(message)); + } +}; + +// Function Name: onTabletScreenChanged() +// +// Description: +// -Called when the TabletScriptingInterface::screenChanged() signal is emitted. The "type" argument can be either the string +// value of "Home", "Web", "Menu", "QML", or "Closed". The "url" argument is only valid for Web and QML. +var onMarketplaceScreen = false; +var onWalletScreen = false; +var onCommerceScreen = false; +var onInspectionCertificateScreen = false; +var onTabletScreenChanged = function onTabletScreenChanged(type, url) { + ui.setCurrentVisibleScreenMetadata(type, url); + onMarketplaceScreen = type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1; + onInspectionCertificateScreen = type === "QML" && url.indexOf(MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH) !== -1; + var onWalletScreenNow = url.indexOf(MARKETPLACE_WALLET_QML_PATH) !== -1; + var onCommerceScreenNow = type === "QML" && + (url.indexOf(MARKETPLACE_CHECKOUT_QML_PATH) !== -1 || url === MARKETPLACE_PURCHASES_QML_PATH || + onInspectionCertificateScreen); + + // exiting wallet or commerce screen + if ((!onWalletScreenNow && onWalletScreen) || (!onCommerceScreenNow && onCommerceScreen)) { + maybeEnableHMDPreview(); + } + + onCommerceScreen = onCommerceScreenNow; + onWalletScreen = onWalletScreenNow; + wireQmlEventBridge(onMarketplaceScreen || onCommerceScreen || onWalletScreen); + + if (url === MARKETPLACE_PURCHASES_QML_PATH) { + ui.tablet.sendToQml({ + method: 'updatePurchases', + referrerURL: referrerURL, + filterText: filterText + }); + } + + ui.isOpen = (onMarketplaceScreen || onCommerceScreen) && !onWalletScreen; + ui.buttonActive(ui.isOpen); + + if (type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1) { + ContextOverlay.isInMarketplaceInspectionMode = true; + } else { + ContextOverlay.isInMarketplaceInspectionMode = false; + } + + if (onInspectionCertificateScreen) { + setCertificateInfo(contextOverlayEntity); + } + + if (onCommerceScreen) { + if (!isWired) { + Users.usernameFromIDReply.connect(usernameFromIDReply); + Controller.mousePressEvent.connect(handleMouseEvent); + Controller.mouseMoveEvent.connect(handleMouseMoveEvent); + triggerMapping.enable(); + triggerPressMapping.enable(); + } + isWired = true; + Wallet.refreshWalletStatus(); + } else { + ui.tablet.sendToQml({ + method: 'inspectionCertificate_resetCert' + }); off(); } + console.debug(ui.buttonName + " app reports: Tablet screen changed.\nNew screen type: " + type + + "\nNew screen URL: " + url + "\nCurrent app open status: " + ui.isOpen + "\n"); +}; - // - // Run the functions. - // - startup(); - Script.scriptEnding.connect(shutdown); +// +// Manage the connection between the button and the window. +// +var BUTTON_NAME = "MARKET"; +var MARKETPLACE_URL = METAVERSE_SERVER_URL + "/marketplace"; +var MARKETPLACE_URL_INITIAL = MARKETPLACE_URL + "?"; // Append "?" to signal injected script that it's the initial page. +var ui; +function startup() { + ui = new AppUi({ + buttonName: BUTTON_NAME, + sortOrder: 9, + inject: MARKETPLACES_INJECT_SCRIPT_URL, + home: MARKETPLACE_URL_INITIAL, + onScreenChanged: onTabletScreenChanged, + onMessage: onQmlMessageReceived + }); + ContextOverlay.contextOverlayClicked.connect(openInspectionCertificateQML); + Entities.canWriteAssetsChanged.connect(onCanWriteAssetsChanged); + GlobalServices.myUsernameChanged.connect(onUsernameChanged); + ui.tablet.webEventReceived.connect(onWebEventReceived); + Wallet.walletStatusChanged.connect(sendCommerceSettings); + Window.messageBoxClosed.connect(onMessageBoxClosed); + Wallet.refreshWalletStatus(); +} + +var isWired = false; +var isUpdateOverlaysWired = false; +function off() { + if (isWired) { + Users.usernameFromIDReply.disconnect(usernameFromIDReply); + Controller.mousePressEvent.disconnect(handleMouseEvent); + Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent); + triggerMapping.disable(); + triggerPressMapping.disable(); + + isWired = false; + } + + if (isUpdateOverlaysWired) { + Script.update.disconnect(updateOverlays); + isUpdateOverlaysWired = false; + } + removeOverlays(); +} +function shutdown() { + maybeEnableHMDPreview(); + deleteSendAssetParticleEffect(); + + Window.messageBoxClosed.disconnect(onMessageBoxClosed); + Wallet.walletStatusChanged.disconnect(sendCommerceSettings); + ui.tablet.webEventReceived.disconnect(onWebEventReceived); + GlobalServices.myUsernameChanged.disconnect(onUsernameChanged); + Entities.canWriteAssetsChanged.disconnect(onCanWriteAssetsChanged); + ContextOverlay.contextOverlayClicked.disconnect(openInspectionCertificateQML); + + off(); +} + +// +// Run the functions. +// +startup(); +Script.scriptEnding.connect(shutdown); }()); // END LOCAL_SCOPE diff --git a/scripts/system/modules/createWindow.js b/scripts/system/modules/createWindow.js index 7369cf91f8..0c4412abfb 100644 --- a/scripts/system/modules/createWindow.js +++ b/scripts/system/modules/createWindow.js @@ -125,6 +125,9 @@ module.exports = (function() { Script.scriptEnding.connect(this, function() { this.window.close(); + // FIXME: temp solution for reload crash (MS18269), + // we should decide on proper object ownership strategy for InteractiveWindow API + this.window = null; }); }, setVisible: function(visible) { diff --git a/scripts/system/notifications.js b/scripts/system/notifications.js index 0778e2a44b..36fe264274 100644 --- a/scripts/system/notifications.js +++ b/scripts/system/notifications.js @@ -564,7 +564,7 @@ } function walletNotSetup() { - createNotification("Your wallet isn't set up. Open the WALLET app.", NotificationType.WALLET); + createNotification("Your wallet isn't activated yet. Open the WALLET app.", NotificationType.WALLET); } function connectionAdded(connectionName) { diff --git a/scripts/system/pal.js b/scripts/system/pal.js index ebb45130e5..85898c28fb 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -1,6 +1,9 @@ "use strict"; -/*jslint vars:true, plusplus:true, forin:true*/ -/*global Tablet, Settings, Script, AvatarList, Users, Entities, MyAvatar, Camera, Overlays, Vec3, Quat, HMD, Controller, Account, UserActivityLogger, Messages, Window, XMLHttpRequest, print, location, getControllerWorldLocation*/ +/* jslint vars:true, plusplus:true, forin:true */ +/* global Tablet, Settings, Script, AvatarList, Users, Entities, + MyAvatar, Camera, Overlays, Vec3, Quat, HMD, Controller, Account, + UserActivityLogger, Messages, Window, XMLHttpRequest, print, location, getControllerWorldLocation +*/ /* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ // // pal.js @@ -20,7 +23,7 @@ var AppUi = Script.require('appUi'); var populateNearbyUserList, color, textures, removeOverlays, controllerComputePickRay, off, receiveMessage, avatarDisconnected, clearLocalQMLDataAndClosePAL, - CHANNEL, getConnectionData, findableByChanged, + CHANNEL, getConnectionData, avatarAdded, avatarRemoved, avatarSessionChanged; // forward references; // hardcoding these as it appears we cannot traverse the originalTextures in overlays??? Maybe I've missed @@ -318,6 +321,10 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See break; case 'http.request': break; // Handled by request-service. + case 'hideNotificationDot': + shouldShowDot = false; + ui.messagesWaiting(shouldShowDot); + break; default: print('Unrecognized message from Pal.qml:', JSON.stringify(message)); } @@ -361,8 +368,8 @@ function getProfilePicture(username, callback) { // callback(url) if successfull }); } var SAFETY_LIMIT = 400; -function getAvailableConnections(domain, callback) { // callback([{usename, location}...]) if successfull. (Logs otherwise) - var url = METAVERSE_BASE + '/api/v1/users?per_page=' + SAFETY_LIMIT + '&'; +function getAvailableConnections(domain, callback, numResultsPerPage) { // callback([{usename, location}...]) if successfull. (Logs otherwise) + var url = METAVERSE_BASE + '/api/v1/users?per_page=' + (numResultsPerPage || SAFETY_LIMIT) + '&'; if (domain) { url += 'status=' + domain.slice(1, -1); // without curly braces } else { @@ -713,7 +720,7 @@ function tabletVisibilityChanged() { if (!ui.tablet.tabletShown && ui.isOpen) { ui.close(); } - } +} var UPDATE_INTERVAL_MS = 100; var updateInterval; @@ -725,10 +732,14 @@ function createUpdateInterval() { var previousContextOverlay = ContextOverlay.enabled; var previousRequestsDomainListData = Users.requestsDomainListData; -function on() { +function palOpened() { + ui.sendMessage({ + method: 'changeConnectionsDotStatus', + shouldShowDot: shouldShowDot + }); previousContextOverlay = ContextOverlay.enabled; - previousRequestsDomainListData = Users.requestsDomainListData + previousRequestsDomainListData = Users.requestsDomainListData; ContextOverlay.enabled = false; Users.requestsDomainListData = true; @@ -807,14 +818,98 @@ function avatarSessionChanged(avatarID) { sendToQml({ method: 'palIsStale', params: [avatarID, 'avatarSessionChanged'] }); } +function notificationDataProcessPage(data) { + return data.data.users; +} + +var shouldShowDot = false; +var storedOnlineUsersArray = []; +function notificationPollCallback(connectionsArray) { + // + // START logic for handling online/offline user changes + // + var i, j; + var newlyOnlineConnectionsArray = []; + for (i = 0; i < connectionsArray.length; i++) { + var currentUser = connectionsArray[i]; + + if (connectionsArray[i].online) { + var indexOfStoredOnlineUser = -1; + for (j = 0; j < storedOnlineUsersArray.length; j++) { + if (currentUser.username === storedOnlineUsersArray[j].username) { + indexOfStoredOnlineUser = j; + break; + } + } + // If the user record isn't already presesnt inside `storedOnlineUsersArray`... + if (indexOfStoredOnlineUser < 0) { + storedOnlineUsersArray.push(currentUser); + newlyOnlineConnectionsArray.push(currentUser); + } + } else { + var indexOfOfflineUser = -1; + for (j = 0; j < storedOnlineUsersArray.length; j++) { + if (currentUser.username === storedOnlineUsersArray[j].username) { + indexOfOfflineUser = j; + break; + } + } + if (indexOfOfflineUser >= 0) { + storedOnlineUsersArray.splice(indexOfOfflineUser); + } + } + } + // If there's new data, the light should turn on. + // If the light is already on and you have connections online, the light should stay on. + // In all other cases, the light should turn off or stay off. + shouldShowDot = newlyOnlineConnectionsArray.length > 0 || (storedOnlineUsersArray.length > 0 && shouldShowDot); + // + // END logic for handling online/offline user changes + // + + if (!ui.isOpen) { + ui.messagesWaiting(shouldShowDot); + ui.sendMessage({ + method: 'changeConnectionsDotStatus', + shouldShowDot: shouldShowDot + }); + + if (newlyOnlineConnectionsArray.length > 0) { + var message; + if (!ui.notificationInitialCallbackMade) { + message = newlyOnlineConnectionsArray.length + " of your connections " + + (newlyOnlineConnectionsArray.length === 1 ? "is" : "are") + " online. Open PEOPLE to join them!"; + ui.notificationDisplayBanner(message); + } else { + for (i = 0; i < newlyOnlineConnectionsArray.length; i++) { + message = newlyOnlineConnectionsArray[i].username + " is available in " + + newlyOnlineConnectionsArray[i].location.root.name + ". Open PEOPLE to join them!"; + ui.notificationDisplayBanner(message); + } + } + } + } +} + +function isReturnedDataEmpty(data) { + var usersArray = data.data.users; + return usersArray.length === 0; +} + function startup() { ui = new AppUi({ buttonName: "PEOPLE", sortOrder: 7, home: "hifi/Pal.qml", - onOpened: on, + onOpened: palOpened, onClosed: off, - onMessage: fromQml + onMessage: fromQml, + notificationPollEndpoint: "/api/v1/users?filter=connections&per_page=10", + notificationPollTimeoutMs: 60000, + notificationDataProcessPage: notificationDataProcessPage, + notificationPollCallback: notificationPollCallback, + notificationPollStopPaginatingConditionMet: isReturnedDataEmpty, + notificationPollCaresAboutSince: false }); Window.domainChanged.connect(clearLocalQMLDataAndClosePAL); Window.domainConnectionRefused.connect(clearLocalQMLDataAndClosePAL); diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 37270f896e..3d744b3bd2 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -7,28 +7,19 @@ // Distributed under the Apache License, Version 2.0 // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* globals Tablet, Script, HMD, Settings, DialogsManager, Menu, Reticle, OverlayWebWindow, Desktop, Account, MyAvatar, Snapshot */ +/* globals Tablet, Script, HMD, Settings, DialogsManager, Menu, Reticle, + OverlayWebWindow, Desktop, Account, MyAvatar, Snapshot */ /* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ (function () { // BEGIN LOCAL_SCOPE Script.include("/~/system/libraries/accountUtils.js"); +var AppUi = Script.require('appUi'); var SNAPSHOT_DELAY = 500; // 500ms var FINISH_SOUND_DELAY = 350; var resetOverlays; var reticleVisible; -var buttonName = "SNAP"; -var buttonConnected = false; - -var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); -var button = tablet.addButton({ - icon: "icons/tablet-icons/snap-i.svg", - activeIcon: "icons/tablet-icons/snap-a.svg", - text: buttonName, - sortOrder: 5 -}); - var snapshotOptions = {}; var imageData = []; var storyIDsToMaybeDelete = []; @@ -52,8 +43,6 @@ try { print('Failed to resolve request api, error: ' + err); } - - function removeFromStoryIDsToMaybeDelete(story_id) { storyIDsToMaybeDelete.splice(storyIDsToMaybeDelete.indexOf(story_id), 1); print('storyIDsToMaybeDelete[] now:', JSON.stringify(storyIDsToMaybeDelete)); @@ -73,33 +62,32 @@ function onMessage(message) { // 2. Although we currently use a single image, we would like to take snapshot, a selfie, a 360 etc. all at the // same time, show the user all of them, and have the user deselect any that they do not want to share. // So we'll ultimately be receiving a set of objects, perhaps with different post processing for each. - message = JSON.parse(message); if (message.type !== "snapshot") { return; } switch (message.action) { case 'ready': // DOM is ready and page has loaded - tablet.emitScriptEvent(JSON.stringify({ + ui.sendMessage({ type: "snapshot", action: "captureSettings", setting: Settings.getValue("alsoTakeAnimatedSnapshot", true) - })); + }); if (Snapshot.getSnapshotsLocation() !== "") { isDomainOpen(Settings.getValue("previousSnapshotDomainID"), function (canShare) { - tablet.emitScriptEvent(JSON.stringify({ + ui.sendMessage({ type: "snapshot", action: "showPreviousImages", options: snapshotOptions, image_data: imageData, canShare: canShare - })); + }); }); } else { - tablet.emitScriptEvent(JSON.stringify({ + ui.sendMessage({ type: "snapshot", action: "showSetupInstructions" - })); + }); Settings.setValue("previousStillSnapPath", ""); Settings.setValue("previousStillSnapStoryID", ""); Settings.setValue("previousStillSnapBlastingDisabled", false); @@ -124,7 +112,7 @@ function onMessage(message) { || (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar", true))) { Desktop.show("hifi/dialogs/GeneralPreferencesDialog.qml", "GeneralPreferencesDialog"); } else { - tablet.loadQMLOnTop("hifi/tablet/TabletGeneralPreferences.qml"); + ui.openNewAppOnTop("hifi/tablet/TabletGeneralPreferences.qml"); } break; case 'captureStillAndGif': @@ -284,7 +272,6 @@ var POLAROID_RATE_LIMIT_MS = 1000; var polaroidPrintingIsRateLimited = false; function printToPolaroid(image_url) { - // Rate-limit printing if (polaroidPrintingIsRateLimited) { return; @@ -376,19 +363,6 @@ function fillImageDataFromPrevious() { } } -var SNAPSHOT_REVIEW_URL = Script.resolvePath("html/SnapshotReview.html"); -var isInSnapshotReview = false; -function onButtonClicked() { - if (isInSnapshotReview){ - // for toolbar-mode: go back to home screen, this will close the window. - tablet.gotoHomeScreen(); - } else { - fillImageDataFromPrevious(); - tablet.gotoWebScreen(SNAPSHOT_REVIEW_URL); - HMD.openTablet(); - } -} - function snapshotUploaded(isError, reply) { if (!isError) { var replyJson = JSON.parse(reply), @@ -409,12 +383,12 @@ function snapshotUploaded(isError, reply) { } if ((isGif && !ignoreGifSnapshotData) || (!isGif && !ignoreStillSnapshotData)) { print('SUCCESS: Snapshot uploaded! Story with audience:for_url created! ID:', storyID); - tablet.emitScriptEvent(JSON.stringify({ + ui.sendMessage({ type: "snapshot", action: "snapshotUploadComplete", story_id: storyID, image_url: imageURL, - })); + }); if (isGif) { Settings.setValue("previousAnimatedSnapStoryID", storyID); } else { @@ -429,10 +403,10 @@ function snapshotUploaded(isError, reply) { } var href, snapshotDomainID; function takeSnapshot() { - tablet.emitScriptEvent(JSON.stringify({ + ui.sendMessage({ type: "snapshot", action: "clearPreviousImages" - })); + }); Settings.setValue("previousStillSnapPath", ""); Settings.setValue("previousStillSnapStoryID", ""); Settings.setValue("previousStillSnapBlastingDisabled", false); @@ -471,10 +445,6 @@ function takeSnapshot() { } else { Window.stillSnapshotTaken.connect(stillSnapshotTaken); } - if (buttonConnected) { - button.clicked.disconnect(onButtonClicked); - buttonConnected = false; - } // hide overlays if they are on if (resetOverlays) { @@ -538,10 +508,6 @@ function stillSnapshotTaken(pathStillSnapshot, notify) { Menu.setIsOptionChecked("Show Overlays", true); } Window.stillSnapshotTaken.disconnect(stillSnapshotTaken); - if (!buttonConnected) { - button.clicked.connect(onButtonClicked); - buttonConnected = true; - } // A Snapshot Review dialog might be left open indefinitely after taking the picture, // during which time the user may have moved. So stash that info in the dialog so that @@ -559,12 +525,12 @@ function stillSnapshotTaken(pathStillSnapshot, notify) { isLoggedIn: isLoggedIn }; imageData = [{ localPath: pathStillSnapshot, href: href }]; - tablet.emitScriptEvent(JSON.stringify({ + ui.sendMessage({ type: "snapshot", action: "addImages", options: snapshotOptions, image_data: imageData - })); + }); }); } @@ -572,10 +538,10 @@ function snapshotDirChanged(snapshotPath) { Window.browseDirChanged.disconnect(snapshotDirChanged); if (snapshotPath !== "") { // not cancelled Snapshot.setSnapshotsLocation(snapshotPath); - tablet.emitScriptEvent(JSON.stringify({ + ui.sendMessage({ type: "snapshot", action: "snapshotLocationChosen" - })); + }); } } @@ -603,22 +569,18 @@ function processingGifStarted(pathStillSnapshot) { isLoggedIn: isLoggedIn }; imageData = [{ localPath: pathStillSnapshot, href: href }]; - tablet.emitScriptEvent(JSON.stringify({ + ui.sendMessage({ type: "snapshot", action: "addImages", options: snapshotOptions, image_data: imageData - })); + }); }); } function processingGifCompleted(pathAnimatedSnapshot) { isLoggedIn = Account.isLoggedIn(); Window.processingGifCompleted.disconnect(processingGifCompleted); - if (!buttonConnected) { - button.clicked.connect(onButtonClicked); - buttonConnected = true; - } Settings.setValue("previousAnimatedSnapPath", pathAnimatedSnapshot); @@ -631,12 +593,12 @@ function processingGifCompleted(pathAnimatedSnapshot) { canBlast: location.domainID === Settings.getValue("previousSnapshotDomainID"), }; imageData = [{ localPath: pathAnimatedSnapshot, href: href }]; - tablet.emitScriptEvent(JSON.stringify({ + ui.sendMessage({ type: "snapshot", action: "addImages", options: snapshotOptions, image_data: imageData - })); + }); }); } function maybeDeleteSnapshotStories() { @@ -655,28 +617,16 @@ function maybeDeleteSnapshotStories() { }); storyIDsToMaybeDelete = []; } -function onTabletScreenChanged(type, url) { - var wasInSnapshotReview = isInSnapshotReview; - isInSnapshotReview = (type === "Web" && url === SNAPSHOT_REVIEW_URL); - button.editProperties({ isActive: isInSnapshotReview }); - if (isInSnapshotReview !== wasInSnapshotReview) { - if (isInSnapshotReview) { - tablet.webEventReceived.connect(onMessage); - } else { - tablet.webEventReceived.disconnect(onMessage); - } - } -} function onUsernameChanged() { fillImageDataFromPrevious(); isDomainOpen(Settings.getValue("previousSnapshotDomainID"), function (canShare) { - tablet.emitScriptEvent(JSON.stringify({ + ui.sendMessage({ type: "snapshot", action: "showPreviousImages", options: snapshotOptions, image_data: imageData, canShare: canShare - })); + }); }); if (isLoggedIn) { if (shareAfterLogin) { @@ -705,10 +655,10 @@ function onUsernameChanged() { function snapshotLocationSet(location) { if (location !== "") { - tablet.emitScriptEvent(JSON.stringify({ + ui.sendMessage({ type: "snapshot", action: "snapshotLocationChosen" - })); + }); } } @@ -733,36 +683,36 @@ function processRezPermissionChange(canRez) { action = 'setPrintButtonDisabled'; } - tablet.emitScriptEvent(JSON.stringify({ + ui.sendMessage({ type: "snapshot", action : action - })); + }); } -button.clicked.connect(onButtonClicked); -buttonConnected = true; +function startup() { + ui = new AppUi({ + buttonName: "SNAP", + sortOrder: 5, + home: Script.resolvePath("html/SnapshotReview.html"), + onOpened: fillImageDataFromPrevious, + onMessage: onMessage + }); -Window.snapshotShared.connect(snapshotUploaded); -tablet.screenChanged.connect(onTabletScreenChanged); -GlobalServices.myUsernameChanged.connect(onUsernameChanged); -Snapshot.snapshotLocationSet.connect(snapshotLocationSet); + Entities.canRezChanged.connect(updatePrintPermissions); + Entities.canRezTmpChanged.connect(updatePrintPermissions); + GlobalServices.myUsernameChanged.connect(onUsernameChanged); + Snapshot.snapshotLocationSet.connect(snapshotLocationSet); + Window.snapshotShared.connect(snapshotUploaded); +} +startup(); -Entities.canRezChanged.connect(updatePrintPermissions); -Entities.canRezTmpChanged.connect(updatePrintPermissions); - -Script.scriptEnding.connect(function () { - if (buttonConnected) { - button.clicked.disconnect(onButtonClicked); - buttonConnected = false; - } - if (tablet) { - tablet.removeButton(button); - tablet.screenChanged.disconnect(onTabletScreenChanged); - } +function shutdown() { Window.snapshotShared.disconnect(snapshotUploaded); Snapshot.snapshotLocationSet.disconnect(snapshotLocationSet); + GlobalServices.myUsernameChanged.disconnect(onUsernameChanged); Entities.canRezChanged.disconnect(updatePrintPermissions); Entities.canRezTmpChanged.disconnect(updatePrintPermissions); -}); +} +Script.scriptEnding.connect(shutdown); }()); // END LOCAL_SCOPE diff --git a/scripts/system/tablet-goto.js b/scripts/system/tablet-goto.js index 46ddeb2bab..804f838d04 100644 --- a/scripts/system/tablet-goto.js +++ b/scripts/system/tablet-goto.js @@ -1,9 +1,10 @@ "use strict"; -/*jslint vars:true, plusplus:true, forin:true*/ -/*global Window, Script, Tablet, HMD, Controller, Account, XMLHttpRequest, location, print*/ +/* jslint vars:true, plusplus:true, forin:true */ +/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ +/* global Window, Script, Tablet, HMD, Controller, Account, XMLHttpRequest, location, print */ // -// goto.js +// tablet-goto.js // scripts/system/ // // Created by Dante Ruiz on 8 February 2017 @@ -14,148 +15,118 @@ // (function () { // BEGIN LOCAL_SCOPE +var request = Script.require('request').request; +var AppUi = Script.require('appUi'); +var DEBUG = false; +function debug() { + if (!DEBUG) { + return; + } + print('tablet-goto.js:', [].map.call(arguments, JSON.stringify)); +} - var request = Script.require('request').request; - var DEBUG = false; - function debug() { - if (!DEBUG) { +var stories = {}, pingPong = false; +function expire(id) { + var options = { + uri: Account.metaverseServerURL + '/api/v1/user_stories/' + id, + method: 'PUT', + json: true, + body: {expire: "true"} + }; + request(options, function (error, response) { + debug('expired story', options, 'error:', error, 'response:', response); + if (error || (response.status !== 'success')) { + print("ERROR expiring story: ", error || response.status); + } + }); +} +var PER_PAGE_DEBUG = 10; +var PER_PAGE_NORMAL = 100; +function pollForAnnouncements() { + // We could bail now if !Account.isLoggedIn(), but what if we someday have system-wide announcments? + var actions = 'announcement'; + var count = DEBUG ? PER_PAGE_DEBUG : PER_PAGE_NORMAL; + var options = [ + 'now=' + new Date().toISOString(), + 'include_actions=' + actions, + 'restriction=' + (Account.isLoggedIn() ? 'open,hifi' : 'open'), + 'require_online=true', + 'protocol=' + encodeURIComponent(Window.protocolSignature()), + 'per_page=' + count + ]; + var url = Account.metaverseServerURL + '/api/v1/user_stories?' + options.join('&'); + request({ + uri: url + }, function (error, data) { + debug(url, error, data); + if (error || (data.status !== 'success')) { + print("Error: unable to get", url, error || data.status); return; } - print('tablet-goto.js:', [].map.call(arguments, JSON.stringify)); - } - - var gotoQmlSource = "hifi/tablet/TabletAddressDialog.qml"; - var buttonName = "GOTO"; - var onGotoScreen = false; - var shouldActivateButton = false; - function ignore() { } - - var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - var NORMAL_ICON = "icons/tablet-icons/goto-i.svg"; - var NORMAL_ACTIVE = "icons/tablet-icons/goto-a.svg"; - var WAITING_ICON = "icons/tablet-icons/goto-msg.svg"; - var button = tablet.addButton({ - icon: NORMAL_ICON, - activeIcon: NORMAL_ACTIVE, - text: buttonName, - sortOrder: 8 - }); - - function messagesWaiting(isWaiting) { - button.editProperties({ - icon: isWaiting ? WAITING_ICON : NORMAL_ICON - // No need for a different activeIcon, because we issue messagesWaiting(false) when the button goes active anyway. - }); - } - - function onClicked() { - if (onGotoScreen) { - // for toolbar-mode: go back to home screen, this will close the window. - tablet.gotoHomeScreen(); - } else { - shouldActivateButton = true; - tablet.loadQMLSource(gotoQmlSource); - onGotoScreen = true; - } - } - - function onScreenChanged(type, url) { - ignore(type); - if (url === gotoQmlSource) { - onGotoScreen = true; - shouldActivateButton = true; - button.editProperties({isActive: shouldActivateButton}); - messagesWaiting(false); - } else { - shouldActivateButton = false; - onGotoScreen = false; - button.editProperties({isActive: shouldActivateButton}); - } - } - button.clicked.connect(onClicked); - tablet.screenChanged.connect(onScreenChanged); - - var stories = {}, pingPong = false; - function expire(id) { - var options = { - uri: Account.metaverseServerURL + '/api/v1/user_stories/' + id, - method: 'PUT', - json: true, - body: {expire: "true"} - }; - request(options, function (error, response) { - debug('expired story', options, 'error:', error, 'response:', response); - if (error || (response.status !== 'success')) { - print("ERROR expiring story: ", error || response.status); + var didNotify = false, key; + pingPong = !pingPong; + data.user_stories.forEach(function (story) { + var stored = stories[story.id], storedOrNew = stored || story; + debug('story exists:', !!stored, storedOrNew); + if ((storedOrNew.username === Account.username) && (storedOrNew.place_name !== location.placename)) { + if (storedOrNew.audience === 'for_connections') { // Only expire if we haven't already done so. + expire(story.id); + } + return; // before marking } - }); - } - function pollForAnnouncements() { - // We could bail now if !Account.isLoggedIn(), but what if we someday have system-wide announcments? - var actions = 'announcement'; - var count = DEBUG ? 10 : 100; - var options = [ - 'now=' + new Date().toISOString(), - 'include_actions=' + actions, - 'restriction=' + (Account.isLoggedIn() ? 'open,hifi' : 'open'), - 'require_online=true', - 'protocol=' + encodeURIComponent(Window.protocolSignature()), - 'per_page=' + count - ]; - var url = Account.metaverseServerURL + '/api/v1/user_stories?' + options.join('&'); - request({ - uri: url - }, function (error, data) { - debug(url, error, data); - if (error || (data.status !== 'success')) { - print("Error: unable to get", url, error || data.status); + storedOrNew.pingPong = pingPong; + if (stored) { // already seen return; } - var didNotify = false, key; - pingPong = !pingPong; - data.user_stories.forEach(function (story) { - var stored = stories[story.id], storedOrNew = stored || story; - debug('story exists:', !!stored, storedOrNew); - if ((storedOrNew.username === Account.username) && (storedOrNew.place_name !== location.placename)) { - if (storedOrNew.audience == 'for_connections') { // Only expire if we haven't already done so. - expire(story.id); - } - return; // before marking - } - storedOrNew.pingPong = pingPong; - if (stored) { // already seen - return; - } - stories[story.id] = story; - var message = story.username + " says something is happening in " + story.place_name + ". Open GOTO to join them."; - Window.displayAnnouncement(message); - didNotify = true; - }); - for (key in stories) { // Any story we were tracking that was not marked, has expired. - if (stories[key].pingPong !== pingPong) { - debug('removing story', key); - delete stories[key]; - } - } - if (didNotify) { - messagesWaiting(true); - if (HMD.isHandControllerAvailable()) { - var STRENGTH = 1.0, DURATION_MS = 60, HAND = 2; // both hands - Controller.triggerHapticPulse(STRENGTH, DURATION_MS, HAND); - } - } else if (!Object.keys(stories).length) { // If there's nothing being tracked, then any messageWaiting has expired. - messagesWaiting(false); - } + stories[story.id] = story; + var message = story.username + " " + story.action_string + " in " + + story.place_name + ". Open GOTO to join them."; + Window.displayAnnouncement(message); + didNotify = true; }); - } - var ANNOUNCEMENTS_POLL_TIME_MS = (DEBUG ? 10 : 60) * 1000; - var pollTimer = Script.setInterval(pollForAnnouncements, ANNOUNCEMENTS_POLL_TIME_MS); - - Script.scriptEnding.connect(function () { - Script.clearInterval(pollTimer); - button.clicked.disconnect(onClicked); - tablet.removeButton(button); - tablet.screenChanged.disconnect(onScreenChanged); + for (key in stories) { // Any story we were tracking that was not marked, has expired. + if (stories[key].pingPong !== pingPong) { + debug('removing story', key); + delete stories[key]; + } + } + if (didNotify) { + ui.messagesWaiting(true); + if (HMD.isHandControllerAvailable()) { + var STRENGTH = 1.0, DURATION_MS = 60, HAND = 2; // both hands + Controller.triggerHapticPulse(STRENGTH, DURATION_MS, HAND); + } + } else if (!Object.keys(stories).length) { // If there's nothing being tracked, then any messageWaiting has expired. + ui.messagesWaiting(false); + } }); +} +var MS_PER_SEC = 1000; +var DEBUG_POLL_TIME_SEC = 10; +var NORMAL_POLL_TIME_SEC = 60; +var ANNOUNCEMENTS_POLL_TIME_MS = (DEBUG ? DEBUG_POLL_TIME_SEC : NORMAL_POLL_TIME_SEC) * MS_PER_SEC; +var pollTimer = Script.setInterval(pollForAnnouncements, ANNOUNCEMENTS_POLL_TIME_MS); +function gotoOpened() { + ui.messagesWaiting(false); +} + +var ui; +var GOTO_QML_SOURCE = "hifi/tablet/TabletAddressDialog.qml"; +var BUTTON_NAME = "GOTO"; +function startup() { + ui = new AppUi({ + buttonName: BUTTON_NAME, + sortOrder: 8, + onOpened: gotoOpened, + home: GOTO_QML_SOURCE + }); +} + +function shutdown() { + Script.clearInterval(pollTimer); +} + +startup(); +Script.scriptEnding.connect(shutdown); }()); // END LOCAL_SCOPE diff --git a/tests/shaders/src/ShaderTests.cpp b/tests/shaders/src/ShaderTests.cpp index 4dd15710f9..03dc034cd0 100644 --- a/tests/shaders/src/ShaderTests.cpp +++ b/tests/shaders/src/ShaderTests.cpp @@ -230,8 +230,8 @@ void ShaderTests::testShaderLoad() { auto glBackend = std::static_pointer_cast(_gpuContext->getBackend()); auto glshader = gpu::gl::GLShader::sync(*glBackend, *program); if (!glshader) { - qDebug() << "Failed to compile or link vertex " << vertexId << " fragment " << fragmentId; - continue; + qWarning() << "Failed to compile or link vertex " << vertexId << " fragment " << fragmentId; + QFAIL("Program link error"); } QVERIFY(glshader != nullptr); @@ -301,7 +301,7 @@ void ShaderTests::testShaderLoad() { QFAIL(error.what()); } - for (uint32_t i = 0; i <= maxShader; ++i) { + for (uint32_t i = 1; i <= maxShader; ++i) { auto used = usedShaders.count(i); if (0 != usedShaders.count(i)) { continue;