mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-07 18:02:31 +02:00
Merge pull request #15259 from SimonWalton-HiFi/avatar-hero-zone-improvements
Avatar hero zone improvements
This commit is contained in:
commit
c4925ddfa0
15 changed files with 138 additions and 41 deletions
|
@ -253,10 +253,29 @@ void AvatarMixer::start() {
|
|||
|
||||
int lockWait, nodeTransform, functor;
|
||||
|
||||
// Set our query each frame
|
||||
{
|
||||
_entityViewer.queryOctree();
|
||||
}
|
||||
|
||||
// Dirty the hero status if there's been an entity change.
|
||||
{
|
||||
if (_dirtyHeroStatus) {
|
||||
_dirtyHeroStatus = false;
|
||||
nodeList->nestedEach([](NodeList::const_iterator cbegin, NodeList::const_iterator cend) {
|
||||
std::for_each(cbegin, cend, [](const SharedNodePointer& node) {
|
||||
if (node->getType() == NodeType::Agent) {
|
||||
NodeData* nodeData = node->getLinkedData();
|
||||
if (nodeData) {
|
||||
auto& avatar = static_cast<AvatarMixerClientData*>(nodeData)->getAvatar();
|
||||
avatar.setNeedsHeroCheck();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Allow nodes to process any pending/queued packets across our worker threads
|
||||
{
|
||||
auto start = usecTimestampNow();
|
||||
|
@ -827,7 +846,7 @@ void AvatarMixer::sendStatsPacket() {
|
|||
|
||||
QJsonObject avatarsObject;
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
// add stats for each listerner
|
||||
// add stats for each listener
|
||||
nodeList->eachNode([&](const SharedNodePointer& node) {
|
||||
QJsonObject avatarStats;
|
||||
|
||||
|
@ -851,6 +870,12 @@ void AvatarMixer::sendStatsPacket() {
|
|||
avatarStats["delta_full_vs_avatar_data_kbps"] =
|
||||
(double)outboundAvatarDataKbps - avatarStats[OUTBOUND_AVATAR_DATA_STATS_KEY].toDouble();
|
||||
}
|
||||
|
||||
if (node->getType() != NodeType::Agent) { // Nodes that aren't avatars
|
||||
const QString displayName
|
||||
{ node->getType() == NodeType::EntityScriptServer ? "ENTITY SCRIPT SERVER" : "ENTITY SERVER" };
|
||||
avatarStats["display_name"] = displayName;
|
||||
}
|
||||
}
|
||||
|
||||
avatarsObject[uuidStringWithoutCurlyBraces(node->getUUID())] = avatarStats;
|
||||
|
@ -973,19 +998,30 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) {
|
|||
{
|
||||
const QString CONNECTION_RATE = "connection_rate";
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
auto defaultConnectionRate = nodeList->getMaxConnectionRate();
|
||||
int connectionRate = avatarMixerGroupObject[CONNECTION_RATE].toInt((int)defaultConnectionRate);
|
||||
nodeList->setMaxConnectionRate(connectionRate);
|
||||
bool success;
|
||||
int connectionRate = avatarMixerGroupObject[CONNECTION_RATE].toString().toInt(&success);
|
||||
if (success) {
|
||||
nodeList->setMaxConnectionRate(connectionRate);
|
||||
}
|
||||
}
|
||||
|
||||
{ // Fraction of downstream bandwidth reserved for 'hero' avatars:
|
||||
static const QString PRIORITY_FRACTION_KEY = "priority_fraction";
|
||||
if (avatarMixerGroupObject.contains(PRIORITY_FRACTION_KEY)) {
|
||||
float priorityFraction = float(avatarMixerGroupObject[PRIORITY_FRACTION_KEY].toDouble());
|
||||
_slavePool.setPriorityReservedFraction(std::min(std::max(0.0f, priorityFraction), 1.0f));
|
||||
qCDebug(avatars) << "Avatar mixer reserving" << priorityFraction << "of bandwidth for priority avatars";
|
||||
}
|
||||
}
|
||||
|
||||
const QString AVATARS_SETTINGS_KEY = "avatars";
|
||||
|
||||
static const QString MIN_HEIGHT_OPTION = "min_avatar_height";
|
||||
float settingMinHeight = domainSettings[AVATARS_SETTINGS_KEY].toObject()[MIN_HEIGHT_OPTION].toDouble(MIN_AVATAR_HEIGHT);
|
||||
float settingMinHeight = avatarMixerGroupObject[MIN_HEIGHT_OPTION].toDouble(MIN_AVATAR_HEIGHT);
|
||||
_domainMinimumHeight = glm::clamp(settingMinHeight, MIN_AVATAR_HEIGHT, MAX_AVATAR_HEIGHT);
|
||||
|
||||
static const QString MAX_HEIGHT_OPTION = "max_avatar_height";
|
||||
float settingMaxHeight = domainSettings[AVATARS_SETTINGS_KEY].toObject()[MAX_HEIGHT_OPTION].toDouble(MAX_AVATAR_HEIGHT);
|
||||
float settingMaxHeight = avatarMixerGroupObject[MAX_HEIGHT_OPTION].toDouble(MAX_AVATAR_HEIGHT);
|
||||
_domainMaximumHeight = glm::clamp(settingMaxHeight, MIN_AVATAR_HEIGHT, MAX_AVATAR_HEIGHT);
|
||||
|
||||
// make sure that the domain owner didn't flip min and max
|
||||
|
@ -997,11 +1033,11 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) {
|
|||
<< "and a maximum avatar height of" << _domainMaximumHeight;
|
||||
|
||||
static const QString AVATAR_WHITELIST_OPTION = "avatar_whitelist";
|
||||
_slaveSharedData.skeletonURLWhitelist = domainSettings[AVATARS_SETTINGS_KEY].toObject()[AVATAR_WHITELIST_OPTION]
|
||||
_slaveSharedData.skeletonURLWhitelist = avatarMixerGroupObject[AVATAR_WHITELIST_OPTION]
|
||||
.toString().split(',', QString::KeepEmptyParts);
|
||||
|
||||
static const QString REPLACEMENT_AVATAR_OPTION = "replacement_avatar";
|
||||
_slaveSharedData.skeletonReplacementURL = domainSettings[AVATARS_SETTINGS_KEY].toObject()[REPLACEMENT_AVATAR_OPTION]
|
||||
_slaveSharedData.skeletonReplacementURL = avatarMixerGroupObject[REPLACEMENT_AVATAR_OPTION]
|
||||
.toString();
|
||||
|
||||
if (_slaveSharedData.skeletonURLWhitelist.count() == 1 && _slaveSharedData.skeletonURLWhitelist[0].isEmpty()) {
|
||||
|
@ -1018,9 +1054,12 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) {
|
|||
|
||||
void AvatarMixer::setupEntityQuery() {
|
||||
_entityViewer.init();
|
||||
EntityTreePointer entityTree = _entityViewer.getTree();
|
||||
DependencyManager::registerInheritance<SpatialParentFinder, AssignmentParentFinder>();
|
||||
DependencyManager::set<AssignmentParentFinder>(_entityViewer.getTree());
|
||||
_slaveSharedData.entityTree = _entityViewer.getTree();
|
||||
DependencyManager::set<AssignmentParentFinder>(entityTree);
|
||||
|
||||
connect(entityTree.get(), &EntityTree::addingEntityPointer, this, &AvatarMixer::entityAdded);
|
||||
connect(entityTree.get(), &EntityTree::deletingEntityPointer, this, &AvatarMixer::entityChange);
|
||||
|
||||
// ES query: {"avatarPriority": true, "type": "Zone"}
|
||||
QJsonObject priorityZoneQuery;
|
||||
|
@ -1028,6 +1067,7 @@ void AvatarMixer::setupEntityQuery() {
|
|||
priorityZoneQuery["type"] = "Zone";
|
||||
|
||||
_entityViewer.getOctreeQuery().setJSONParameters(priorityZoneQuery);
|
||||
_slaveSharedData.entityTree = entityTree;
|
||||
}
|
||||
|
||||
void AvatarMixer::handleOctreePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
|
@ -1064,6 +1104,25 @@ void AvatarMixer::handleOctreePacket(QSharedPointer<ReceivedMessage> message, Sh
|
|||
}
|
||||
}
|
||||
|
||||
void AvatarMixer::entityAdded(EntityItem* entity) {
|
||||
if (entity->getType() == EntityTypes::Zone) {
|
||||
_dirtyHeroStatus = true;
|
||||
entity->registerChangeHandler([this](const EntityItemID& entityItemID) {
|
||||
entityChange();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void AvatarMixer::entityRemoved(EntityItem * entity) {
|
||||
if (entity->getType() == EntityTypes::Zone) {
|
||||
_dirtyHeroStatus = true;
|
||||
}
|
||||
}
|
||||
|
||||
void AvatarMixer::entityChange() {
|
||||
_dirtyHeroStatus = true;
|
||||
}
|
||||
|
||||
void AvatarMixer::aboutToFinish() {
|
||||
DependencyManager::destroy<ResourceManager>();
|
||||
DependencyManager::destroy<ResourceCacheSharedItems>();
|
||||
|
|
|
@ -34,8 +34,8 @@ public:
|
|||
|
||||
static bool shouldReplicateTo(const Node& from, const Node& to) {
|
||||
return to.getType() == NodeType::DownstreamAvatarMixer &&
|
||||
to.getPublicSocket() != from.getPublicSocket() &&
|
||||
to.getLocalSocket() != from.getLocalSocket();
|
||||
to.getPublicSocket() != from.getPublicSocket() &&
|
||||
to.getLocalSocket() != from.getLocalSocket();
|
||||
}
|
||||
|
||||
public slots:
|
||||
|
@ -46,6 +46,11 @@ public slots:
|
|||
|
||||
void sendStatsPacket() override;
|
||||
|
||||
// Avatar zone possibly changed
|
||||
void entityAdded(EntityItem* entity);
|
||||
void entityRemoved(EntityItem* entity);
|
||||
void entityChange();
|
||||
|
||||
private slots:
|
||||
void queueIncomingPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer node);
|
||||
void handleAdjustAvatarSorting(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
|
@ -80,6 +85,7 @@ private:
|
|||
|
||||
// Attach to entity tree for avatar-priority zone info.
|
||||
EntityTreeHeadlessViewer _entityViewer;
|
||||
bool _dirtyHeroStatus { true }; // Dirty the needs-hero-update
|
||||
|
||||
// FIXME - new throttling - use these values somehow
|
||||
float _trailingMixRatio { 0.0f };
|
||||
|
|
|
@ -129,7 +129,7 @@ int AvatarMixerClientData::parseData(ReceivedMessage& message, const SlaveShared
|
|||
incrementNumOutOfOrderSends();
|
||||
}
|
||||
_lastReceivedSequenceNumber = sequenceNumber;
|
||||
glm::vec3 oldPosition = getPosition();
|
||||
glm::vec3 oldPosition = _avatar->getClientGlobalPosition();
|
||||
bool oldHasPriority = _avatar->getHasPriority();
|
||||
|
||||
// compute the offset to the data payload
|
||||
|
@ -140,23 +140,13 @@ int AvatarMixerClientData::parseData(ReceivedMessage& message, const SlaveShared
|
|||
// Regardless of what the client says, restore the priority as we know it without triggering any update.
|
||||
_avatar->setHasPriorityWithoutTimestampReset(oldHasPriority);
|
||||
|
||||
auto newPosition = getPosition();
|
||||
if (newPosition != oldPosition) {
|
||||
//#define AVATAR_HERO_TEST_HACK
|
||||
#ifdef AVATAR_HERO_TEST_HACK
|
||||
{
|
||||
const static QString heroKey { "HERO" };
|
||||
_avatar->setPriorityAvatar(_avatar->getDisplayName().contains(heroKey));
|
||||
}
|
||||
#else
|
||||
auto newPosition = _avatar->getClientGlobalPosition();
|
||||
if (newPosition != oldPosition || _avatar->getNeedsHeroCheck()) {
|
||||
EntityTree& entityTree = *slaveSharedData.entityTree;
|
||||
FindPriorityZone findPriorityZone { newPosition, false } ;
|
||||
FindPriorityZone findPriorityZone { newPosition } ;
|
||||
entityTree.recurseTreeWithOperation(&FindPriorityZone::operation, &findPriorityZone);
|
||||
_avatar->setHasPriority(findPriorityZone.isInPriorityZone);
|
||||
//if (findPriorityZone.isInPriorityZone) {
|
||||
// qCWarning(avatars) << "Avatar" << _avatar->getSessionDisplayName() << "in hero zone";
|
||||
//}
|
||||
#endif
|
||||
_avatar->setNeedsHeroCheck(false);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
|
@ -43,12 +43,14 @@ void AvatarMixerSlave::configure(ConstIter begin, ConstIter end) {
|
|||
|
||||
void AvatarMixerSlave::configureBroadcast(ConstIter begin, ConstIter end,
|
||||
p_high_resolution_clock::time_point lastFrameTimestamp,
|
||||
float maxKbpsPerNode, float throttlingRatio) {
|
||||
float maxKbpsPerNode, float throttlingRatio,
|
||||
float priorityReservedFraction) {
|
||||
_begin = begin;
|
||||
_end = end;
|
||||
_lastFrameTimestamp = lastFrameTimestamp;
|
||||
_maxKbpsPerNode = maxKbpsPerNode;
|
||||
_throttlingRatio = throttlingRatio;
|
||||
_avatarHeroFraction = priorityReservedFraction;
|
||||
}
|
||||
|
||||
void AvatarMixerSlave::harvestStats(AvatarMixerSlaveStats& stats) {
|
||||
|
@ -310,7 +312,6 @@ namespace {
|
|||
} // Close anonymous namespace.
|
||||
|
||||
void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) {
|
||||
const float AVATAR_HERO_FRACTION { 0.4f };
|
||||
const Node* destinationNode = node.data();
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
@ -345,7 +346,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
|
||||
// max number of avatarBytes per frame (13 900, typical)
|
||||
const int maxAvatarBytesPerFrame = int(_maxKbpsPerNode * BYTES_PER_KILOBIT / AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND);
|
||||
const int maxHeroBytesPerFrame = int(maxAvatarBytesPerFrame * AVATAR_HERO_FRACTION); // 5555, typical
|
||||
const int maxHeroBytesPerFrame = int(maxAvatarBytesPerFrame * _avatarHeroFraction); // 5555, typical
|
||||
|
||||
// keep track of the number of other avatars held back in this frame
|
||||
int numAvatarsHeldBack = 0;
|
||||
|
@ -471,8 +472,8 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
SortableAvatar(avatarNodeData, sourceAvatarNode, lastEncodeTime));
|
||||
}
|
||||
|
||||
// If Avatar A's PAL WAS open but is no longer open, AND
|
||||
// Avatar A is ignoring Avatar B OR Avatar B is ignoring Avatar A...
|
||||
// If Node A's PAL WAS open but is no longer open, AND
|
||||
// Node A is ignoring Avatar B OR Node B is ignoring Avatar A...
|
||||
//
|
||||
// This is a bit heavy-handed still - there are cases where a kill packet
|
||||
// will be sent when it doesn't need to be (but where it _should_ be OK to send).
|
||||
|
@ -541,7 +542,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
const MixerAvatar* sourceAvatar = sourceNodeData->getConstAvatarData();
|
||||
|
||||
// Typically all out-of-view avatars but such avatars' priorities will rise with time:
|
||||
bool isLowerPriority = currentVariant != kHero && sortedAvatar.getPriority() <= OUT_OF_VIEW_THRESHOLD; // XXX: hero handling?
|
||||
bool isLowerPriority = sortedAvatar.getPriority() <= OUT_OF_VIEW_THRESHOLD;
|
||||
|
||||
if (isLowerPriority) {
|
||||
detail = PALIsOpen ? AvatarData::PALMinimum : AvatarData::MinimumData;
|
||||
|
@ -550,8 +551,8 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
detail = distribution(generator) < AVATAR_SEND_FULL_UPDATE_RATIO ? AvatarData::SendAllData : AvatarData::CullSmallData;
|
||||
destinationNodeData->incrementAvatarInView();
|
||||
|
||||
// If the time that the mixer sent AVATAR DATA about Avatar B to Avatar A is BEFORE OR EQUAL TO
|
||||
// the time that Avatar B flagged an IDENTITY DATA change, send IDENTITY DATA about Avatar B to Avatar A.
|
||||
// If the time that the mixer sent AVATAR DATA about Avatar B to Node A is BEFORE OR EQUAL TO
|
||||
// the time that Avatar B flagged an IDENTITY DATA change, send IDENTITY DATA about Avatar B to Node A.
|
||||
if (sourceAvatar->hasProcessedFirstIdentity()
|
||||
&& destinationNodeData->getLastBroadcastTime(sourceNode->getLocalID()) <= sourceNodeData->getIdentityChangeTimestamp()) {
|
||||
identityBytesSent += sendIdentityPacket(*identityPacketList, sourceNodeData, *destinationNode);
|
||||
|
|
|
@ -110,7 +110,8 @@ public:
|
|||
void configure(ConstIter begin, ConstIter end);
|
||||
void configureBroadcast(ConstIter begin, ConstIter end,
|
||||
p_high_resolution_clock::time_point lastFrameTimestamp,
|
||||
float maxKbpsPerNode, float throttlingRatio);
|
||||
float maxKbpsPerNode, float throttlingRatio,
|
||||
float priorityReservedFraction);
|
||||
|
||||
void processIncomingPackets(const SharedNodePointer& node);
|
||||
void broadcastAvatarData(const SharedNodePointer& node);
|
||||
|
@ -140,6 +141,7 @@ private:
|
|||
p_high_resolution_clock::time_point _lastFrameTimestamp;
|
||||
float _maxKbpsPerNode { 0.0f };
|
||||
float _throttlingRatio { 0.0f };
|
||||
float _avatarHeroFraction { 0.4f };
|
||||
|
||||
AvatarMixerSlaveStats _stats;
|
||||
SlaveSharedData* _sharedData;
|
||||
|
|
|
@ -76,7 +76,8 @@ void AvatarMixerSlavePool::broadcastAvatarData(ConstIter begin, ConstIter end,
|
|||
float maxKbpsPerNode, float throttlingRatio) {
|
||||
_function = &AvatarMixerSlave::broadcastAvatarData;
|
||||
_configure = [=](AvatarMixerSlave& slave) {
|
||||
slave.configureBroadcast(begin, end, lastFrameTimestamp, maxKbpsPerNode, throttlingRatio);
|
||||
slave.configureBroadcast(begin, end, lastFrameTimestamp, maxKbpsPerNode, throttlingRatio,
|
||||
_priorityReservedFraction);
|
||||
};
|
||||
run(begin, end);
|
||||
}
|
||||
|
|
|
@ -73,7 +73,10 @@ public:
|
|||
void each(std::function<void(AvatarMixerSlave& slave)> functor);
|
||||
|
||||
void setNumThreads(int numThreads);
|
||||
int numThreads() { return _numThreads; }
|
||||
int numThreads() const { return _numThreads; }
|
||||
|
||||
void setPriorityReservedFraction(float fraction) { _priorityReservedFraction = fraction; }
|
||||
float getPriorityReservedFraction() const { return _priorityReservedFraction; }
|
||||
|
||||
private:
|
||||
void run(ConstIter begin, ConstIter end);
|
||||
|
@ -91,7 +94,11 @@ private:
|
|||
ConditionVariable _poolCondition;
|
||||
void (AvatarMixerSlave::*_function)(const SharedNodePointer& node);
|
||||
std::function<void(AvatarMixerSlave&)> _configure;
|
||||
|
||||
// Set from Domain Settings:
|
||||
float _priorityReservedFraction { 0.4f };
|
||||
int _numThreads { 0 };
|
||||
|
||||
int _numStarted { 0 }; // guarded by _mutex
|
||||
int _numFinished { 0 }; // guarded by _mutex
|
||||
int _numStopped { 0 }; // guarded by _mutex
|
||||
|
|
|
@ -19,8 +19,12 @@
|
|||
|
||||
class MixerAvatar : public AvatarData {
|
||||
public:
|
||||
bool getNeedsHeroCheck() const { return _needsHeroCheck; }
|
||||
void setNeedsHeroCheck(bool needsHeroCheck = true)
|
||||
{ _needsHeroCheck = needsHeroCheck; }
|
||||
|
||||
private:
|
||||
bool _needsHeroCheck { false };
|
||||
};
|
||||
|
||||
using MixerAvatarSharedPointer = std::shared_ptr<MixerAvatar>;
|
||||
|
|
|
@ -74,6 +74,7 @@
|
|||
* avatar. <em>Read-only.</em>
|
||||
* @property {number} sensorToWorldScale - The scale that transforms dimensions in the user's real world to the avatar's
|
||||
* size in the virtual world. <em>Read-only.</em>
|
||||
* @property {boolean} hasPriority - is the avatar in a Hero zone? <em>Read-only.</em>
|
||||
*
|
||||
* @example <caption>Create a scriptable avatar.</caption>
|
||||
* (function () {
|
||||
|
|
|
@ -1310,6 +1310,15 @@
|
|||
"placeholder": "50",
|
||||
"default": "50",
|
||||
"advanced": true
|
||||
},
|
||||
{
|
||||
"name": "priority_fraction",
|
||||
"type": "double",
|
||||
"label": "Hero Bandwidth",
|
||||
"help": "Fraction of downstream bandwidth reserved for avatars in 'Hero' zones",
|
||||
"placeholder": "0.40",
|
||||
"default": "0.40",
|
||||
"advanced": true
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -295,6 +295,7 @@ class MyAvatar : public Avatar {
|
|||
* @comment Avatar.setAbsoluteJointTranslationInObjectFrame as setAbsoluteJointTranslationInObjectFrame - Don't borrow because implementation is different.
|
||||
* @borrows Avatar.getTargetScale as getTargetScale
|
||||
* @borrows Avatar.resetLastSent as resetLastSent
|
||||
* @borrows Avatar.hasPriority as hasPriority
|
||||
*/
|
||||
// FIXME: `glm::vec3 position` is not accessible from QML, so this exposes position in a QML-native type
|
||||
Q_PROPERTY(QVector3D qmlPosition READ getQmlPosition)
|
||||
|
|
|
@ -1143,10 +1143,11 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
|
|||
// we store the hand state as well as other items in a shared bitset. The hand state is an octal, but is split
|
||||
// into two sections to maintain backward compatibility. The bits are ordered as such (0-7 left to right).
|
||||
// AA 6/1/18 added three more flags bits 8,9, and 10 for procedural audio, blink, and eye saccade enabled
|
||||
// +---+-----+-----+--+--+--+--+-----+
|
||||
// |x,x|H0,H1|x,x,x|H2|Au|Bl|Ey|xxxxx|
|
||||
// +---+-----+-----+--+--+--+--+-----+
|
||||
// +---+-----+-----+--+--+--+--+--+----+
|
||||
// |x,x|H0,H1|x,x,x|H2|Au|Bl|Ey|He|xxxx|
|
||||
// +---+-----+-----+--+--+--+--+--+----+
|
||||
// Hand state - H0,H1,H2 is found in the 3rd, 4th, and 8th bits
|
||||
// Hero-avatar status (He) - 12th bit
|
||||
auto newHandState = getSemiNibbleAt(bitItems, HAND_STATE_START_BIT)
|
||||
+ (oneAtBit16(bitItems, HAND_STATE_FINGER_POINTING_BIT) ? IS_FINGER_POINTING_FLAG : 0);
|
||||
|
||||
|
|
|
@ -479,6 +479,7 @@ class AvatarData : public QObject, public SpatiallyNestable {
|
|||
* avatar. <em>Read-only.</em>
|
||||
* @property {number} sensorToWorldScale - The scale that transforms dimensions in the user's real world to the avatar's
|
||||
* size in the virtual world. <em>Read-only.</em>
|
||||
* @property {boolean} hasPriority - is the avatar in a Hero zone? <em>Read-only.</em>
|
||||
*/
|
||||
Q_PROPERTY(glm::vec3 position READ getWorldPosition WRITE setPositionViaScript)
|
||||
Q_PROPERTY(float scale READ getDomainLimitedScale WRITE setTargetScale)
|
||||
|
@ -518,6 +519,8 @@ class AvatarData : public QObject, public SpatiallyNestable {
|
|||
|
||||
Q_PROPERTY(float sensorToWorldScale READ getSensorToWorldScale)
|
||||
|
||||
Q_PROPERTY(bool hasPriority READ getHasPriority)
|
||||
|
||||
public:
|
||||
virtual QString getName() const override { return QString("Avatar:") + _displayName; }
|
||||
|
||||
|
|
|
@ -343,6 +343,14 @@ glm::mat4 ScriptAvatarData::getControllerRightHandMatrix() const {
|
|||
// END
|
||||
//
|
||||
|
||||
bool ScriptAvatarData::getHasPriority() const {
|
||||
if (AvatarSharedPointer sharedAvatarData = _avatarData.lock()) {
|
||||
return sharedAvatarData->getHasPriority();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
glm::quat ScriptAvatarData::getAbsoluteJointRotationInObjectFrame(int index) const {
|
||||
if (AvatarSharedPointer sharedAvatarData = _avatarData.lock()) {
|
||||
return sharedAvatarData->getAbsoluteJointRotationInObjectFrame(index);
|
||||
|
|
|
@ -68,6 +68,8 @@ class ScriptAvatarData : public QObject {
|
|||
Q_PROPERTY(glm::mat4 controllerLeftHandMatrix READ getControllerLeftHandMatrix)
|
||||
Q_PROPERTY(glm::mat4 controllerRightHandMatrix READ getControllerRightHandMatrix)
|
||||
|
||||
Q_PROPERTY(bool hasPriority READ getHasPriority)
|
||||
|
||||
public:
|
||||
ScriptAvatarData(AvatarSharedPointer avatarData);
|
||||
|
||||
|
@ -133,6 +135,8 @@ public:
|
|||
glm::mat4 getControllerLeftHandMatrix() const;
|
||||
glm::mat4 getControllerRightHandMatrix() const;
|
||||
|
||||
bool getHasPriority() const;
|
||||
|
||||
signals:
|
||||
void displayNameChanged();
|
||||
void sessionDisplayNameChanged();
|
||||
|
|
Loading…
Reference in a new issue