3
0
Fork 0
mirror of https://github.com/lubosz/overte.git synced 2025-04-26 13:55:31 +02:00
This commit is contained in:
Wayne Chen 2018-09-18 09:17:28 -07:00
commit f82063ed46
42 changed files with 927 additions and 625 deletions

View file

@ -11,6 +11,7 @@
#include "AvatarMixerClientData.h"
#include <algorithm>
#include <udt/PacketHeaders.h>
#include <DependencyManager.h>
@ -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);
}
}

View file

@ -15,7 +15,7 @@
#include <algorithm>
#include <cfloat>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include <queue>
#include <QtCore/QJsonObject>
@ -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<QUuid> _radiusIgnoredOthers;
std::vector<QUuid> _radiusIgnoredOthers;
ConicalViewFrustums _currentViewFrustums;
int _recentOtherAvatarsInView { 0 };

View file

@ -13,6 +13,7 @@
#include <algorithm>
#include <random>
#include <chrono>
#include <glm/glm.hpp>
#include <glm/gtx/norm.hpp>
@ -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<NodeList>();
@ -220,7 +234,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
_stats.nodesBroadcastedTo++;
AvatarMixerClientData* nodeData = reinterpret_cast<AvatarMixerClientData*>(node->getLinkedData());
AvatarMixerClientData* nodeData = reinterpret_cast<AvatarMixerClientData*>(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<AvatarSharedPointer> avatarsToSort;
std::unordered_map<AvatarSharedPointer, SharedNodePointer> avatarDataToNodes;
std::unordered_map<QUuid, uint64_t> 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<const AvatarMixerClientData*>(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<const AvatarMixerClientData*>(avatarNode->getLinkedData());
assert(avatarNodeData); // we can't have gotten here without avatarNode having valid data
const AvatarMixerClientData* avatarClientNodeData = reinterpret_cast<const AvatarMixerClientData*>(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<QUuid, uint64_t>::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<JointData>& 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<chrono::microseconds>(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<chrono::microseconds>(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

Binary file not shown.

After

(image error) Size: 9 KiB

Binary file not shown.

After

(image error) Size: 4.4 KiB

View file

@ -346,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 {
@ -354,23 +362,20 @@ Item {
bodyLoader.item.width = root.pane.width
bodyLoader.item.height = root.pane.height
}
if (Settings.getValue("loginDialogPoppedUp", false)) {
var data = {
"action": "user logged in"
};
UserActivityLogger.logAction("encourageLoginDialog", data);
}
}
onHandleLoginFailed: {
console.log("Login Failed")
mainTextContainer.visible = true
toggleLoading(false)
if (Settings.getValue("loginDialogPoppedUp", false)) {
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)
}
onHandleLinkCompleted: {
console.log("Link Succeeded")

View file

@ -42,7 +42,11 @@ Item {
}
if (HMD.active) {
tablet.popFromStack();
if (gotoPreviousApp) {
tablet.returnToPreviousApp();
} else {
tablet.popFromStack();
}
} else {
closeDialog();
}
@ -55,7 +59,11 @@ Item {
}
if (HMD.active) {
tablet.popFromStack();
if (gotoPreviousApp) {
tablet.returnToPreviousApp();
} else {
tablet.popFromStack();
}
} else {
closeDialog();
}

View file

@ -2302,8 +2302,7 @@ 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();
#endif
#else
static int CHECK_LOGIN_TIMER = 3000;
QTimer* checkLoginTimer = new QTimer(this);
checkLoginTimer->setInterval(CHECK_LOGIN_TIMER);
@ -2321,6 +2320,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
});
Setting::Handle<bool>{"loginDialogPoppedUp", false}.set(false);
checkLoginTimer->start();
#endif
}
void Application::updateVerboseLogging() {
@ -3498,7 +3498,9 @@ bool Application::isServerlessMode() const {
}
void Application::setIsInterstitialMode(bool interstitialMode) {
if (_interstitialMode != interstitialMode) {
Settings settings;
bool enableInterstitial = settings.value("enableIntersitialMode", false).toBool();
if (_interstitialMode != interstitialMode && enableInterstitial) {
_interstitialMode = interstitialMode;
DependencyManager::get<AudioClient>()->setAudioPaused(_interstitialMode);
@ -6346,6 +6348,7 @@ void Application::updateWindowTitle() const {
auto nodeList = DependencyManager::get<NodeList>();
auto accountManager = DependencyManager::get<AccountManager>();
auto isInErrorState = nodeList->getDomainHandler().isInErrorState();
QString buildVersion = " - "
+ (BuildInfo::BUILD_TYPE == BuildInfo::BuildType::Stable ? QString("Version") : QString("Build"))
@ -6353,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<AddressManager>()->getDomainURL().toString();
if (isInErrorState) {
currentPlaceName = "serverless: " + nodeList->getDomainHandler().getErrorDomainURL().toString();
} else {
currentPlaceName = "serverless: " + DependencyManager::get<AddressManager>()->getDomainURL().toString();
}
} else {
currentPlaceName = DependencyManager::get<AddressManager>()->getDomainURL().host();
if (currentPlaceName.isEmpty()) {

View file

@ -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;

View file

@ -176,29 +176,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(avatar) {}
glm::vec3 getPosition() const override { return _avatar->getWorldPosition(); }
float getRadius() const override { return std::static_pointer_cast<Avatar>(_avatar)->getBoundingRadius(); }
uint64_t getTimestamp() const override { return std::static_pointer_cast<Avatar>(_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<Avatar> getAvatar() const { return _avatar; }
private:
AvatarSharedPointer _avatar;
std::shared_ptr<Avatar> _avatar;
};
auto avatarMap = getHashCopy();
AvatarHash::iterator itr = avatarMap.begin();
const auto& views = qApp->getConicalViews();
PrioritySortUtil::PriorityQueue<SortableAvatar> sortedAvatars(views,
@ -207,22 +207,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<NodeList>();
AvatarHash::iterator itr = avatarMap.begin();
while (itr != avatarMap.end()) {
const auto& avatar = std::static_pointer_cast<Avatar>(*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;
@ -241,18 +243,12 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
avatar->updateOrbPosition();
}
bool ignoring = DependencyManager::get<NodeList>()->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
@ -273,7 +269,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<Avatar>(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) {

View file

@ -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();
@ -3011,7 +3016,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
@ -3031,6 +3036,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 "
@ -3303,6 +3309,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);

View file

@ -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
@ -1180,11 +1186,12 @@ public slots:
* @param {boolean} [hasOrientation=false] - Set to <code>true</code> to set the orientation of the avatar.
* @param {Quat} [orientation=Quat.IDENTITY] - The new orientation for the avatar.
* @param {boolean} [shouldFaceLocation=false] - Set to <code>true</code> to position the avatar a short distance away from
* @param {boolean} [withSafeLanding=true] - Set to <code>false</code> 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
@ -1498,6 +1505,7 @@ signals:
private slots:
void leaveDomain();
void updateCollisionCapsuleCache();
protected:
virtual void beParentOfChild(SpatiallyNestablePointer newChild) const override;
@ -1722,6 +1730,7 @@ private:
bool _goToPending { false };
bool _physicsSafetyPending { false };
bool _goToSafe { true };
glm::vec3 _goToPosition;
glm::quat _goToOrientation;

View file

@ -66,13 +66,18 @@ void SafeLanding::addTrackedEntity(const EntityItemID& entityID) {
Locker lock(_lock);
EntityItemPointer entity = _entityTree->findEntityByID(entityID);
_trackedEntities.emplace(entityID, entity);
int trackedEntityCount = (int)_trackedEntities.size();
if (entity) {
if (trackedEntityCount > _maxTrackedEntityCount) {
_maxTrackedEntityCount = trackedEntityCount;
_trackedEntities.emplace(entityID, entity);
int trackedEntityCount = (int)_trackedEntities.size();
if (trackedEntityCount > _maxTrackedEntityCount) {
_maxTrackedEntityCount = trackedEntityCount;
}
qCDebug(interfaceapp) << "Safe Landing: Tracking entity " << entity->getItemName();
}
qCDebug(interfaceapp) << "Safe Landing: Tracking entity " << entity->getItemName();
} else {
qCDebug(interfaceapp) << "Safe Landing: Null Entity: " << entityID;
}
}
@ -146,7 +151,7 @@ bool isEntityPhysicsReady(const EntityItemPointer& entity) {
bool hasAABox;
entity->getAABox(hasAABox);
if (hasAABox && downloadedCollisionTypes.count(modelEntity->getShapeType()) != 0) {
return entity->isReadyToComputeShape();
return (!entity->shouldBePhysical() || entity->isReadyToComputeShape());
}
}
}
@ -156,12 +161,23 @@ bool isEntityPhysicsReady(const EntityItemPointer& entity) {
bool SafeLanding::isEntityLoadingComplete() {
Locker lock(_lock);
auto entityTree = qApp->getEntities();
auto entityMapIter = _trackedEntities.begin();
while (entityMapIter != _trackedEntities.end()) {
auto entity = entityMapIter->second;
bool isVisuallyReady = (entity->isVisuallyReady() || !entityTree->renderableForEntityId(entityMapIter->first));
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 {

View file

@ -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<CollisionPickResult>(pick, entityIntersections, std::vector<ContactTestResult>());
}
@ -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<CollisionPickResult>(pick, std::vector<ContactTestResult>(), avatarIntersections);
}
PickResultPointer CollisionPick::getHUDIntersection(const CollisionRegion& pick) {
return std::make_shared<CollisionPickResult>(pick.toVariantMap(), std::vector<ContactTestResult>(), std::vector<ContactTestResult>());
return std::make_shared<CollisionPickResult>(pick, std::vector<ContactTestResult>(), std::vector<ContactTestResult>());
}
Transform CollisionPick::getResultTransform() const {

View file

@ -70,6 +70,9 @@ protected:
CollisionRegion _mathPick;
PhysicsEnginePointer _physicsEngine;
QSharedPointer<GeometryResource> _cachedResource;
// Options for what information to get from collision results
bool _includeNormals;
};
#endif // hifi_CollisionPick_h

View file

@ -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.

View file

@ -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

View file

@ -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<AvatarDataPacket::AvatarGlobalPosition*>(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<AvatarDataPacket::AvatarBoundingBox*>(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<AvatarDataPacket::LookAtPosition*>(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<AvatarDataPacket::AvatarLocalPosition*>(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> 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<JointData> 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) {
@ -2873,10 +2829,8 @@ 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 };
float AvatarData::_avatarSortCoefficientSize { 8.0f };
float AvatarData::_avatarSortCoefficientCenter { 4.0f };
float AvatarData::_avatarSortCoefficientAge { 1.0f };
QScriptValue AvatarEntityMapToScriptValue(QScriptEngine* engine, const AvatarEntityMap& value) {

View file

@ -1097,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
@ -1169,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;
@ -1440,6 +1438,8 @@ protected:
ThreadSafeValueCache<glm::mat4> _farGrabLeftMatrixCache { glm::mat4() };
ThreadSafeValueCache<glm::mat4> _farGrabMouseMatrixCache { glm::mat4() };
ThreadSafeValueCache<QVariantMap> _collisionCapsuleCache{ QVariantMap() };
int getFauxJointIndex(const QString& name) const;
float _audioLoudness { 0.0f };

View file

@ -816,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<NodeList>()->getDomainHandler().isInErrorState();
if (domainURL != _domainURL) {
if (domainURL != _domainURL || isInErrorState) {
addCurrentAddressToHistory(trigger);
emitHostChanged = true;
}

View file

@ -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() {
@ -107,13 +110,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();

View file

@ -172,6 +172,11 @@ public slots:
void processICEResponsePacket(QSharedPointer<ReceivedMessage> icePacket);
void processDomainServerConnectionDeniedPacket(QSharedPointer<ReceivedMessage> 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();

View file

@ -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<char[]>(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<uint32_t*>(buffer.get()) & CONTROL_BIT_MASK;
bool isControlPacket = *reinterpret_cast<uint32_t*>(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();
}
}

View file

@ -17,6 +17,7 @@
#include <functional>
#include <unordered_map>
#include <mutex>
#include <list>
#include <QtCore/QObject>
#include <QtCore/QTimer>
@ -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<char[]> _datagram;
p_high_resolution_clock::time_point _receiveTime;
};
std::list<Datagram> _incomingDatagrams;
int _maxDatagramsRead { 0 };
friend UDTTest;
};

View file

@ -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 {

View file

@ -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

View file

@ -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;
}

View file

@ -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<ContactKey, ContactInfo>;

View file

@ -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<QVariant> 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<EntityItemID> 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;

View file

@ -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<QVariant> 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<QTimer*, CallbackData> _timerFunctionMap;
QSet<QUrl> _includedURLs;
mutable QReadWriteLock _entityScriptsLock { QReadWriteLock::Recursive };
QHash<EntityItemID, EntityScriptDetails> _entityScripts;
QHash<QString, EntityItemID> _occupiedScriptURLs;
QList<DeferredLoadEntity> _deferredEntityLoads;

View file

@ -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<Thing> and add them to the queue:
PrioritySortUtil::PriorityQueue<SortableWrapper> sortedThings(viewFrustum);
std::priority_queue< PrioritySortUtil::Sortable<Thing> > 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<T>.
Under the hood std::priority_queue<T> 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<T> 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

View file

@ -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<ShapeInfo>()),
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> shapeInfo = std::make_shared<ShapeInfo>();
Transform transform;
float threshold;
float threshold { 0.0f };
uint16_t collisionGroup { USER_COLLISION_GROUP_MY_AVATAR };
};
namespace std {

View file

@ -35,10 +35,13 @@ var DEFAULT_SCRIPTS_COMBINED = [
];
var DEFAULT_SCRIPTS_SEPARATE = [
"system/controllers/controllerScripts.js",
"system/interstitialPage.js"
//"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";

View file

@ -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');

View file

@ -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);

View file

@ -282,14 +282,15 @@ function loaded() {
function refreshEntityList() {
PROFILE("refresh-entity-list", function() {
PROFILE("filter", function() {
let searchTerm = elFilter.value;
let searchTerm = elFilter.value.toLowerCase();
if (searchTerm === '') {
visibleEntities = entities.slice(0);
} else {
visibleEntities = entities.filter(function(e) {
return e.name.indexOf(searchTerm) > -1
|| e.type.indexOf(searchTerm) > -1
|| e.fullUrl.indexOf(searchTerm) > -1;
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;
});
}
});

View file

@ -113,7 +113,7 @@
var span = document.createElement('span');
span.style = "margin:10px 5px;color:#1b6420;font-size:15px;";
span.innerHTML = "<a href='#' onclick='emitWalletSetupEvent(); return false;'>Setup your Wallet</a> to get money and shop in Marketplace.";
span.innerHTML = "<a href='#' onclick='emitWalletSetupEvent(); return false;'>Activate your Wallet</a> to get money and shop in Marketplace.";
var xButton = document.createElement('a');
xButton.id = "xButton";

View file

@ -137,10 +137,10 @@
var loadingToTheSpotID = Overlays.addOverlay("image3d", {
name: "Loading-Destination-Card-Text",
localPosition: { x: 0.0 , y: -1.8, z: 0.0 },
url: "http://hifi-content.s3.amazonaws.com/alexia/LoadingScreens/goTo_button.png",
localPosition: { x: 0.0 , y: -1.5, z: -0.3 },
url: Script.resourcesPath() + "images/interstitialPage/goTo_button.png",
alpha: 1,
dimensions: { x: 1.2, y: 0.6},
dimensions: { x: 1.5, y: 1.0 },
visible: isVisible,
emissive: true,
ignoreRayIntersection: false,
@ -415,13 +415,13 @@
Overlays.mouseReleaseOnOverlay.connect(clickedOnOverlay);
Overlays.hoverEnterOverlay.connect(function(overlayID, event) {
if (overlayID === loadingToTheSpotID) {
Overlays.editOverlay(loadingToTheSpotID, { color: greyColor});
Overlays.editOverlay(loadingToTheSpotID, { color: greyColor });
}
});
Overlays.hoverLeaveOverlay.connect(function(overlayID, event) {
if (overlayID === loadingToTheSpotID) {
Overlays.editOverlay(loadingToTheSpotID, { color: whiteColor});
Overlays.editOverlay(loadingToTheSpotID, { color: whiteColor });
}
});

View file

@ -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"
]);
@ -448,6 +449,8 @@ SelectionDisplay = (function() {
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 = [];
@ -802,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) {
@ -814,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) {
@ -960,35 +984,10 @@ SelectionDisplay = (function() {
}
return Uuid.NULL;
};
// FUNCTION: MOUSE MOVE EVENT
var lastMouseEvent = null;
that.mouseMoveEvent = function(event) {
var wantDebug = false;
if (wantDebug) {
print("=============== eST::MouseMoveEvent BEG =======================");
}
lastMouseEvent = event;
if (activeTool) {
if (wantDebug) {
print(" Trigger ActiveTool(" + activeTool.mode + ")'s onMove");
}
activeTool.onMove(event);
if (wantDebug) {
print(" Trigger SelectionManager::update");
}
SelectionManager._update();
if (wantDebug) {
print("=============== eST::MouseMoveEvent END =======================");
}
// EARLY EXIT--(Move handled via active tool)
return true;
}
that.updateHighlight = function(event) {
// if no tool is active, then just look for handles to highlight...
var pickRay = generalComputePickRay(event.x, event.y);
var pickRay = generalComputePickRay(event.x, event.y);
var result = Overlays.findRayIntersection(pickRay);
var pickedColor;
var highlightNeeded = false;
@ -1039,7 +1038,36 @@ SelectionDisplay = (function() {
} else {
that.resetPreviousHandleColor();
}
};
// FUNCTION: MOUSE MOVE EVENT
var lastMouseEvent = null;
that.mouseMoveEvent = function(event) {
var wantDebug = false;
if (wantDebug) {
print("=============== eST::MouseMoveEvent BEG =======================");
}
lastMouseEvent = event;
if (activeTool) {
if (wantDebug) {
print(" Trigger ActiveTool(" + activeTool.mode + ")'s onMove");
}
activeTool.onMove(event);
if (wantDebug) {
print(" Trigger SelectionManager::update");
}
SelectionManager._update();
if (wantDebug) {
print("=============== eST::MouseMoveEvent END =======================");
}
// EARLY EXIT--(Move handled via active tool)
return true;
}
that.updateHighlight(event);
if (wantDebug) {
print("=============== eST::MouseMoveEvent END =======================");
}
@ -1135,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);
@ -1148,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();
@ -2083,6 +2118,7 @@ SelectionDisplay = (function() {
var rotation = null;
var previousPickRay = null;
var beginMouseEvent = null;
var beginControllerPosition = null;
var onBegin = function(event, pickRay, pickResult) {
var proportional = directionEnum === STRETCH_DIRECTION.ALL;
@ -2218,6 +2254,9 @@ SelectionDisplay = (function() {
previousPickRay = pickRay;
beginMouseEvent = event;
if (that.triggered()) {
beginControllerPosition = getControllerAvatarFramePositionFromPickRay(pickRay);
}
};
var onEnd = function(event, reason) {
@ -2256,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);
@ -2286,12 +2327,23 @@ SelectionDisplay = (function() {
var newDimensions;
if (proportional) {
var viewportDimensions = Controller.getViewportDimensions();
var mouseXDifference = (event.x - beginMouseEvent.x) / viewportDimensions.x;
var mouseYDifference = (beginMouseEvent.y - event.y) / viewportDimensions.y;
var mouseDifference = mouseXDifference + mouseYDifference;
var toCameraDistance = getDistanceToCamera(position);
var dimensionsMultiple = toCameraDistance * STRETCH_DIRECTION_ALL_CAMERA_DISTANCE_MULTIPLE;
var dimensionChange = mouseDifference * dimensionsMultiple;
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 {
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;

View file

@ -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) {