mirror of
https://github.com/AleziaKurdis/overte.git
synced 2025-04-08 06:32:35 +02:00
Merge branch 'master' into newAudioMuteStates
This commit is contained in:
commit
abe5748e60
63 changed files with 821 additions and 392 deletions
|
@ -51,7 +51,7 @@ void sendEnvironmentPacket(const SharedNodePointer& node, AudioMixerClientData&
|
|||
// mix helpers
|
||||
inline float approximateGain(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd);
|
||||
inline float computeGain(float masterAvatarGain, float masterInjectorGain, const AvatarAudioStream& listeningNodeStream,
|
||||
const PositionalAudioStream& streamToAdd, const glm::vec3& relativePosition, float distance, bool isEcho);
|
||||
const PositionalAudioStream& streamToAdd, const glm::vec3& relativePosition, float distance);
|
||||
inline float computeAzimuth(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd,
|
||||
const glm::vec3& relativePosition);
|
||||
|
||||
|
@ -504,14 +504,12 @@ void AudioMixerSlave::addStream(AudioMixerClientData::MixableStream& mixableStre
|
|||
glm::vec3 relativePosition = streamToAdd->getPosition() - listeningNodeStream.getPosition();
|
||||
|
||||
float distance = glm::max(glm::length(relativePosition), EPSILON);
|
||||
float gain = isEcho ? 1.0f
|
||||
: (isSoloing ? masterAvatarGain
|
||||
: computeGain(masterAvatarGain, masterInjectorGain, listeningNodeStream, *streamToAdd,
|
||||
relativePosition, distance));
|
||||
float azimuth = isEcho ? 0.0f : computeAzimuth(listeningNodeStream, listeningNodeStream, relativePosition);
|
||||
|
||||
float gain = masterAvatarGain;
|
||||
if (!isSoloing) {
|
||||
gain = computeGain(masterAvatarGain, masterInjectorGain, listeningNodeStream, *streamToAdd, relativePosition,
|
||||
distance, isEcho);
|
||||
}
|
||||
|
||||
const int HRTF_DATASET_INDEX = 1;
|
||||
|
||||
if (!streamToAdd->lastPopSucceeded()) {
|
||||
|
@ -599,8 +597,8 @@ void AudioMixerSlave::updateHRTFParameters(AudioMixerClientData::MixableStream&
|
|||
glm::vec3 relativePosition = streamToAdd->getPosition() - listeningNodeStream.getPosition();
|
||||
|
||||
float distance = glm::max(glm::length(relativePosition), EPSILON);
|
||||
float gain = computeGain(masterAvatarGain, masterInjectorGain, listeningNodeStream, *streamToAdd, relativePosition,
|
||||
distance, isEcho);
|
||||
float gain = isEcho ? 1.0f : computeGain(masterAvatarGain, masterInjectorGain, listeningNodeStream, *streamToAdd,
|
||||
relativePosition, distance);
|
||||
float azimuth = isEcho ? 0.0f : computeAzimuth(listeningNodeStream, listeningNodeStream, relativePosition);
|
||||
|
||||
mixableStream.hrtf->setParameterHistory(azimuth, distance, gain);
|
||||
|
@ -743,8 +741,7 @@ float computeGain(float masterAvatarGain,
|
|||
const AvatarAudioStream& listeningNodeStream,
|
||||
const PositionalAudioStream& streamToAdd,
|
||||
const glm::vec3& relativePosition,
|
||||
float distance,
|
||||
bool isEcho) {
|
||||
float distance) {
|
||||
float gain = 1.0f;
|
||||
|
||||
// injector: apply attenuation
|
||||
|
@ -754,7 +751,7 @@ float computeGain(float masterAvatarGain,
|
|||
gain *= masterInjectorGain;
|
||||
|
||||
// avatar: apply fixed off-axis attenuation to make them quieter as they turn away
|
||||
} else if (!isEcho && (streamToAdd.getType() == PositionalAudioStream::Microphone)) {
|
||||
} else if (streamToAdd.getType() == PositionalAudioStream::Microphone) {
|
||||
glm::vec3 rotatedListenerPosition = glm::inverse(streamToAdd.getOrientation()) * relativePosition;
|
||||
|
||||
// source directivity is based on angle of emission, in local coordinates
|
||||
|
|
|
@ -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;
|
||||
|
@ -341,7 +331,7 @@ void AvatarMixerClientData::checkSkeletonURLAgainstWhitelist(const SlaveSharedDa
|
|||
|
||||
// the returned set traits packet uses the trait version from the incoming packet
|
||||
// so the client knows they should not overwrite if they have since changed the trait
|
||||
_avatar->packTrait(AvatarTraits::SkeletonModelURL, *packet, traitVersion);
|
||||
AvatarTraits::packVersionedTrait(AvatarTraits::SkeletonModelURL, *packet, traitVersion, *_avatar);
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
nodeList->sendPacket(std::move(packet), sendingNode);
|
||||
|
|
|
@ -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) {
|
||||
|
@ -139,7 +141,8 @@ qint64 AvatarMixerSlave::addChangedTraitsToBulkPacket(AvatarMixerClientData* lis
|
|||
if (lastReceivedVersion > lastSentVersionRef) {
|
||||
bytesWritten += addTraitsNodeHeader(listeningNodeData, sendingNodeData, traitsPacketList, bytesWritten);
|
||||
// there is an update to this trait, add it to the traits packet
|
||||
bytesWritten += sendingAvatar->packTrait(traitType, traitsPacketList, lastReceivedVersion);
|
||||
bytesWritten += AvatarTraits::packVersionedTrait(traitType, traitsPacketList,
|
||||
lastReceivedVersion, *sendingAvatar);
|
||||
// update the last sent version
|
||||
lastSentVersionRef = lastReceivedVersion;
|
||||
// Remember which versions we sent in this particular packet
|
||||
|
@ -194,7 +197,8 @@ qint64 AvatarMixerSlave::addChangedTraitsToBulkPacket(AvatarMixerClientData* lis
|
|||
bytesWritten += addTraitsNodeHeader(listeningNodeData, sendingNodeData, traitsPacketList, bytesWritten);
|
||||
|
||||
// this instance version exists and has never been sent or is newer so we need to send it
|
||||
bytesWritten += sendingAvatar->packTraitInstance(traitType, instanceID, traitsPacketList, receivedVersion);
|
||||
bytesWritten += AvatarTraits::packVersionedTraitInstance(traitType, instanceID, traitsPacketList,
|
||||
receivedVersion, *sendingAvatar);
|
||||
|
||||
if (sentInstanceIt != sentIDValuePairs.end()) {
|
||||
sentInstanceIt->value = receivedVersion;
|
||||
|
@ -308,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>();
|
||||
|
@ -343,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;
|
||||
|
@ -469,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).
|
||||
|
@ -539,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;
|
||||
|
@ -548,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
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -1766,14 +1766,14 @@ void DomainServer::processOctreeDataRequestMessage(QSharedPointer<ReceivedMessag
|
|||
|
||||
bool remoteHasExistingData { false };
|
||||
QUuid id;
|
||||
int version;
|
||||
int dataVersion;
|
||||
message->readPrimitive(&remoteHasExistingData);
|
||||
if (remoteHasExistingData) {
|
||||
constexpr size_t UUID_SIZE_BYTES = 16;
|
||||
auto idData = message->read(UUID_SIZE_BYTES);
|
||||
id = QUuid::fromRfc4122(idData);
|
||||
message->readPrimitive(&version);
|
||||
qCDebug(domain_server) << "Entity server does have existing data: ID(" << id << ") DataVersion(" << version << ")";
|
||||
message->readPrimitive(&dataVersion);
|
||||
qCDebug(domain_server) << "Entity server does have existing data: ID(" << id << ") DataVersion(" << dataVersion << ")";
|
||||
} else {
|
||||
qCDebug(domain_server) << "Entity server does not have existing data";
|
||||
}
|
||||
|
@ -1782,11 +1782,11 @@ void DomainServer::processOctreeDataRequestMessage(QSharedPointer<ReceivedMessag
|
|||
auto reply = NLPacketList::create(PacketType::OctreeDataFileReply, QByteArray(), true, true);
|
||||
OctreeUtils::RawEntityData data;
|
||||
if (data.readOctreeDataInfoFromFile(entityFilePath)) {
|
||||
if (data.id == id && data.version <= version) {
|
||||
if (data.id == id && data.dataVersion <= dataVersion) {
|
||||
qCDebug(domain_server) << "ES has sufficient octree data, not sending data";
|
||||
reply->writePrimitive(false);
|
||||
} else {
|
||||
qCDebug(domain_server) << "Sending newer octree data to ES: ID(" << data.id << ") DataVersion(" << data.version << ")";
|
||||
qCDebug(domain_server) << "Sending newer octree data to ES: ID(" << data.id << ") DataVersion(" << data.dataVersion << ")";
|
||||
QFile file(entityFilePath);
|
||||
if (file.open(QIODevice::ReadOnly)) {
|
||||
reply->writePrimitive(true);
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
//
|
||||
|
||||
import QtQuick 2.7
|
||||
import Qt.labs.folderlistmodel 2.1
|
||||
import Qt.labs.folderlistmodel 2.2
|
||||
import Qt.labs.settings 1.0
|
||||
import QtQuick.Dialogs 1.2 as OriginalDialogs
|
||||
import QtQuick.Controls 1.4 as QQC1
|
||||
|
@ -320,6 +320,7 @@ ModalWindow {
|
|||
FolderListModel {
|
||||
id: folderListModel
|
||||
nameFilters: selectionType.currentFilter
|
||||
caseSensitive: false
|
||||
showDirsFirst: true
|
||||
showDotAndDotDot: false
|
||||
showFiles: !root.selectDirectory
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
//
|
||||
|
||||
import QtQuick 2.7
|
||||
import Qt.labs.folderlistmodel 2.1
|
||||
import Qt.labs.folderlistmodel 2.2
|
||||
import Qt.labs.settings 1.0
|
||||
import QtQuick.Dialogs 1.2 as OriginalDialogs
|
||||
import QtQuick.Controls 1.4 as QQC1
|
||||
|
@ -285,6 +285,7 @@ TabletModalWindow {
|
|||
FolderListModel {
|
||||
id: folderListModel
|
||||
nameFilters: selectionType.currentFilter
|
||||
caseSensitive: false
|
||||
showDirsFirst: true
|
||||
showDotAndDotDot: false
|
||||
showFiles: !root.selectDirectory
|
||||
|
|
|
@ -16,6 +16,8 @@ Rectangle {
|
|||
property bool keyboardRaised: false
|
||||
property bool punctuationMode: false
|
||||
|
||||
HifiConstants { id: hifi }
|
||||
|
||||
HifiControls.Keyboard {
|
||||
id: keyboard
|
||||
z: 1000
|
||||
|
@ -48,6 +50,7 @@ Rectangle {
|
|||
|
||||
property var jointNames: []
|
||||
property var currentAvatarSettings;
|
||||
property bool wearablesFrozen;
|
||||
|
||||
function fetchAvatarModelName(marketId, avatar) {
|
||||
var xmlhttp = new XMLHttpRequest();
|
||||
|
@ -187,6 +190,8 @@ Rectangle {
|
|||
updateCurrentAvatarInBookmarks(currentAvatar);
|
||||
} else if (message.method === 'selectAvatarEntity') {
|
||||
adjustWearables.selectWearableByID(message.entityID);
|
||||
} else if (message.method === 'wearablesFrozenChanged') {
|
||||
wearablesFrozen = message.wearablesFrozen;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -507,6 +512,7 @@ Rectangle {
|
|||
}
|
||||
|
||||
SquareLabel {
|
||||
id: adjustLabel
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: wearablesLabel.verticalCenter
|
||||
glyphText: "\ue02e"
|
||||
|
@ -515,6 +521,17 @@ Rectangle {
|
|||
adjustWearables.open(currentAvatar);
|
||||
}
|
||||
}
|
||||
|
||||
SquareLabel {
|
||||
anchors.right: adjustLabel.left
|
||||
anchors.verticalCenter: wearablesLabel.verticalCenter
|
||||
anchors.rightMargin: 15
|
||||
glyphText: wearablesFrozen ? hifi.glyphs.lock : hifi.glyphs.unlock;
|
||||
|
||||
onClicked: {
|
||||
emitSendToScript({'method' : 'toggleWearablesFrozen'});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
|
|
|
@ -40,6 +40,9 @@ Rectangle {
|
|||
AudioScriptingInterface.pushToTalkChanged.connect(function() {
|
||||
pushToTalk = AudioScriptingInterface.pushToTalk;
|
||||
});
|
||||
AudioScriptingInterface.pushingToTalkChanged.connect(function() {
|
||||
pushingToTalk = AudioScriptingInterface.pushingToTalk;
|
||||
});
|
||||
}
|
||||
|
||||
property bool standalone: false;
|
||||
|
@ -100,16 +103,16 @@ Rectangle {
|
|||
QtObject {
|
||||
id: colors;
|
||||
|
||||
readonly property string unmuted: "#FFF";
|
||||
readonly property string muted: "#E2334D";
|
||||
readonly property string unmutedColor: "#FFF";
|
||||
readonly property string mutedColor: "#E2334D";
|
||||
readonly property string gutter: "#575757";
|
||||
readonly property string greenStart: "#39A38F";
|
||||
readonly property string greenEnd: "#1FC6A6";
|
||||
readonly property string yellow: "#C0C000";
|
||||
readonly property string red: colors.muted;
|
||||
readonly property string red: colors.mutedColor;
|
||||
readonly property string fill: "#55000000";
|
||||
readonly property string border: standalone ? "#80FFFFFF" : "#55FFFFFF";
|
||||
readonly property string icon: micBar.muted ? muted : unmuted;
|
||||
readonly property string icon: muted ? colors.mutedColor : unmutedColor;
|
||||
}
|
||||
|
||||
Item {
|
||||
|
@ -157,7 +160,6 @@ Rectangle {
|
|||
Item {
|
||||
id: status;
|
||||
|
||||
|
||||
visible: (pushToTalk && !pushingToTalk) || muted;
|
||||
|
||||
anchors {
|
||||
|
|
|
@ -113,6 +113,7 @@ Rectangle {
|
|||
} else if (prop === 'dimensions') {
|
||||
scalespinner.set(wearable[prop].x / wearable.naturalDimensions.x);
|
||||
}
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -664,7 +664,7 @@ Rectangle {
|
|||
text: "LOG IN"
|
||||
|
||||
onClicked: {
|
||||
sendToScript({method: 'needsLogIn_loginClicked'});
|
||||
sendToScript({method: 'marketplace_loginClicked'});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
//
|
||||
|
||||
import QtQuick 2.7
|
||||
import Qt.labs.folderlistmodel 2.1
|
||||
import Qt.labs.folderlistmodel 2.2
|
||||
import Qt.labs.settings 1.0
|
||||
import QtQuick.Dialogs 1.2 as OriginalDialogs
|
||||
import QtQuick.Controls 1.4 as QQC1
|
||||
|
@ -279,6 +279,7 @@ Rectangle {
|
|||
FolderListModel {
|
||||
id: folderListModel
|
||||
nameFilters: selectionType.currentFilter
|
||||
caseSensitive: false
|
||||
showDirsFirst: true
|
||||
showDotAndDotDot: false
|
||||
showFiles: !root.selectDirectory
|
||||
|
|
|
@ -344,6 +344,7 @@ Item {
|
|||
readonly property string stop_square: "\ue01e"
|
||||
readonly property string avatarTPose: "\ue01f"
|
||||
readonly property string lock: "\ue006"
|
||||
readonly property string unlock: "\ue039"
|
||||
readonly property string checkmark: "\ue020"
|
||||
readonly property string leftRightArrows: "\ue021"
|
||||
readonly property string hfc: "\ue022"
|
||||
|
|
|
@ -330,6 +330,7 @@ QtObject {
|
|||
readonly property string stop_square: "\ue01e"
|
||||
readonly property string avatarTPose: "\ue01f"
|
||||
readonly property string lock: "\ue006"
|
||||
readonly property string unlock: "\ue039"
|
||||
readonly property string checkmark: "\ue020"
|
||||
readonly property string leftRightArrows: "\ue021"
|
||||
readonly property string hfc: "\ue022"
|
||||
|
|
|
@ -1210,10 +1210,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
connect(&domainHandler, SIGNAL(connectedToDomain(QUrl)), SLOT(updateWindowTitle()));
|
||||
connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(updateWindowTitle()));
|
||||
connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, [this]() {
|
||||
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
||||
if (tabletScriptingInterface) {
|
||||
tabletScriptingInterface->setQmlTabletRoot(SYSTEM_TABLET, nullptr);
|
||||
}
|
||||
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||
entityScriptingInterface->deleteEntity(getTabletScreenID());
|
||||
entityScriptingInterface->deleteEntity(getTabletHomeButtonID());
|
||||
|
|
|
@ -92,7 +92,7 @@ void AvatarDoctor::startDiagnosing() {
|
|||
_model = resource;
|
||||
const auto model = resource.data();
|
||||
const auto avatarModel = resource.data()->getHFMModel();
|
||||
if (!avatarModel.originalURL.endsWith(".fbx")) {
|
||||
if (!avatarModel.originalURL.toLower().endsWith(".fbx")) {
|
||||
addError("Unsupported avatar model format.", "unsupported-format");
|
||||
emit complete(getErrors());
|
||||
return;
|
||||
|
|
|
@ -5463,7 +5463,7 @@ void MyAvatar::useFlow(bool isActive, bool isCollidable, const QVariantMap& phys
|
|||
}
|
||||
auto collisionJoints = collisionsConfig.keys();
|
||||
if (collisionJoints.size() > 0) {
|
||||
collisionSystem.resetCollisions();
|
||||
collisionSystem.clearSelfCollisions();
|
||||
for (auto &jointName : collisionJoints) {
|
||||
int jointIndex = getJointIndex(jointName);
|
||||
FlowCollisionSettings collisionsSettings;
|
||||
|
@ -5478,6 +5478,7 @@ void MyAvatar::useFlow(bool isActive, bool isCollidable, const QVariantMap& phys
|
|||
collisionSystem.addCollisionSphere(jointIndex, collisionsSettings);
|
||||
}
|
||||
}
|
||||
flow.updateScale();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
@ -2170,7 +2171,7 @@ private:
|
|||
bool getEnableStepResetRotation() const { return _stepResetRotationEnabled; }
|
||||
void setEnableDrawAverageFacing(bool drawAverage) { _drawAverageFacingEnabled = drawAverage; }
|
||||
bool getEnableDrawAverageFacing() const { return _drawAverageFacingEnabled; }
|
||||
bool isMyAvatar() const override { return true; }
|
||||
virtual bool isMyAvatar() const override { return true; }
|
||||
virtual int parseDataFromBuffer(const QByteArray& buffer) override;
|
||||
virtual glm::vec3 getSkeletonPosition() const override;
|
||||
int _skeletonModelChangeCount { 0 };
|
||||
|
|
|
@ -365,7 +365,7 @@ void OtherAvatar::handleChangedAvatarEntityData() {
|
|||
// AVATAR ENTITY UPDATE FLOW
|
||||
// - if queueEditEntityMessage() sees "AvatarEntity" HostType it calls _myAvatar->storeAvatarEntityDataPayload()
|
||||
// - storeAvatarEntityDataPayload() saves the payload and flags the trait instance for the entity as updated,
|
||||
// - ClientTraitsHandler::sendChangedTraitsToMixea() sends the entity bytes to the mixer which relays them to other interfaces
|
||||
// - ClientTraitsHandler::sendChangedTraitsToMixer() sends the entity bytes to the mixer which relays them to other interfaces
|
||||
// - AvatarHashMap::processBulkAvatarTraits() on other interfaces calls avatar->processTraitInstance()
|
||||
// - AvatarData::processTraitInstance() calls storeAvatarEntityDataPayload(), which sets _avatarEntityDataChanged = true
|
||||
// - (My)Avatar::simulate() calls handleChangedAvatarEntityData() every frame which checks _avatarEntityDataChanged
|
||||
|
@ -495,6 +495,18 @@ void OtherAvatar::handleChangedAvatarEntityData() {
|
|||
const QUuid NULL_ID = QUuid("{00000000-0000-0000-0000-000000000005}");
|
||||
entity->setParentID(NULL_ID);
|
||||
entity->setParentID(oldParentID);
|
||||
|
||||
if (entity->stillHasMyGrabAction()) {
|
||||
// For this case: we want to ignore transform+velocities coming from authoritative OtherAvatar
|
||||
// because the MyAvatar is grabbing and we expect the local grab state
|
||||
// to have enough information to prevent simulation drift.
|
||||
//
|
||||
// Clever readers might realize this could cause problems. For example,
|
||||
// if an ignored OtherAvagtar were to simultanously grab the object then there would be
|
||||
// a noticeable discrepancy between participants in the distributed physics simulation,
|
||||
// however the difference would be stable and would not drift.
|
||||
properties.clearTransformOrVelocityChanges();
|
||||
}
|
||||
if (entityTree->updateEntity(entityID, properties)) {
|
||||
entity->updateLastEditedFromRemote();
|
||||
} else {
|
||||
|
|
|
@ -67,17 +67,23 @@ void FlowCollisionSystem::addCollisionSphere(int jointIndex, const FlowCollision
|
|||
auto collision = FlowCollisionSphere(jointIndex, settings, isTouch);
|
||||
collision.setPosition(position);
|
||||
if (isSelfCollision) {
|
||||
_selfCollisions.push_back(collision);
|
||||
if (!isTouch) {
|
||||
_selfCollisions.push_back(collision);
|
||||
} else {
|
||||
_selfTouchCollisions.push_back(collision);
|
||||
}
|
||||
} else {
|
||||
_othersCollisions.push_back(collision);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
void FlowCollisionSystem::resetCollisions() {
|
||||
_allCollisions.clear();
|
||||
_othersCollisions.clear();
|
||||
_selfTouchCollisions.clear();
|
||||
_selfCollisions.clear();
|
||||
}
|
||||
|
||||
FlowCollisionResult FlowCollisionSystem::computeCollision(const std::vector<FlowCollisionResult> collisions) {
|
||||
FlowCollisionResult result;
|
||||
if (collisions.size() > 1) {
|
||||
|
@ -106,6 +112,10 @@ void FlowCollisionSystem::setScale(float scale) {
|
|||
_selfCollisions[j]._radius = _selfCollisions[j]._initialRadius * scale;
|
||||
_selfCollisions[j]._offset = _selfCollisions[j]._initialOffset * scale;
|
||||
}
|
||||
for (size_t j = 0; j < _selfTouchCollisions.size(); j++) {
|
||||
_selfTouchCollisions[j]._radius = _selfTouchCollisions[j]._initialRadius * scale;
|
||||
_selfTouchCollisions[j]._offset = _selfTouchCollisions[j]._initialOffset * scale;
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<FlowCollisionResult> FlowCollisionSystem::checkFlowThreadCollisions(FlowThread* flowThread) {
|
||||
|
@ -178,9 +188,9 @@ void FlowCollisionSystem::setCollisionSettingsByJoint(int jointIndex, const Flow
|
|||
}
|
||||
void FlowCollisionSystem::prepareCollisions() {
|
||||
_allCollisions.clear();
|
||||
_allCollisions.resize(_selfCollisions.size() + _othersCollisions.size());
|
||||
std::copy(_selfCollisions.begin(), _selfCollisions.begin() + _selfCollisions.size(), _allCollisions.begin());
|
||||
std::copy(_othersCollisions.begin(), _othersCollisions.begin() + _othersCollisions.size(), _allCollisions.begin() + _selfCollisions.size());
|
||||
_allCollisions.insert(_allCollisions.end(), _selfCollisions.begin(), _selfCollisions.end());
|
||||
_allCollisions.insert(_allCollisions.end(), _othersCollisions.begin(), _othersCollisions.end());
|
||||
_allCollisions.insert(_allCollisions.end(), _selfTouchCollisions.begin(), _selfTouchCollisions.end());
|
||||
_othersCollisions.clear();
|
||||
}
|
||||
|
||||
|
@ -273,18 +283,20 @@ void FlowJoint::setRecoveryPosition(const glm::vec3& recoveryPosition) {
|
|||
}
|
||||
|
||||
void FlowJoint::update(float deltaTime) {
|
||||
glm::vec3 accelerationOffset = glm::vec3(0.0f);
|
||||
if (_settings._stiffness > 0.0f) {
|
||||
glm::vec3 recoveryVector = _recoveryPosition - _currentPosition;
|
||||
float recoveryFactor = powf(_settings._stiffness, 3.0f);
|
||||
accelerationOffset = recoveryVector * recoveryFactor;
|
||||
}
|
||||
FlowNode::update(deltaTime, accelerationOffset);
|
||||
if (_anchored) {
|
||||
if (!_isHelper) {
|
||||
_currentPosition = _updatedPosition;
|
||||
} else {
|
||||
_currentPosition = _parentPosition;
|
||||
if (_settings._active) {
|
||||
glm::vec3 accelerationOffset = glm::vec3(0.0f);
|
||||
if (_settings._stiffness > 0.0f) {
|
||||
glm::vec3 recoveryVector = _recoveryPosition - _currentPosition;
|
||||
float recoveryFactor = powf(_settings._stiffness, 3.0f);
|
||||
accelerationOffset = recoveryVector * recoveryFactor;
|
||||
}
|
||||
FlowNode::update(deltaTime, accelerationOffset);
|
||||
if (_anchored) {
|
||||
if (!_isHelper) {
|
||||
_currentPosition = _updatedPosition;
|
||||
} else {
|
||||
_currentPosition = _parentPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -674,6 +686,14 @@ bool Flow::updateRootFramePositions(const AnimPoseVec& absolutePoses, size_t thr
|
|||
return true;
|
||||
}
|
||||
|
||||
void Flow::updateCollisionJoint(FlowCollisionSphere& collision, AnimPoseVec& absolutePoses) {
|
||||
glm::quat jointRotation;
|
||||
getJointPositionInWorldFrame(absolutePoses, collision._jointIndex, collision._position, _entityPosition, _entityRotation);
|
||||
getJointRotationInWorldFrame(absolutePoses, collision._jointIndex, jointRotation, _entityRotation);
|
||||
glm::vec3 worldOffset = jointRotation * collision._offset;
|
||||
collision._position = collision._position + worldOffset;
|
||||
}
|
||||
|
||||
void Flow::updateJoints(AnimPoseVec& relativePoses, AnimPoseVec& absolutePoses) {
|
||||
updateAbsolutePoses(relativePoses, absolutePoses);
|
||||
for (auto &jointData : _flowJointData) {
|
||||
|
@ -695,11 +715,11 @@ void Flow::updateJoints(AnimPoseVec& relativePoses, AnimPoseVec& absolutePoses)
|
|||
}
|
||||
auto &selfCollisions = _collisionSystem.getSelfCollisions();
|
||||
for (auto &collision : selfCollisions) {
|
||||
glm::quat jointRotation;
|
||||
getJointPositionInWorldFrame(absolutePoses, collision._jointIndex, collision._position, _entityPosition, _entityRotation);
|
||||
getJointRotationInWorldFrame(absolutePoses, collision._jointIndex, jointRotation, _entityRotation);
|
||||
glm::vec3 worldOffset = jointRotation * collision._offset;
|
||||
collision._position = collision._position + worldOffset;
|
||||
updateCollisionJoint(collision, absolutePoses);
|
||||
}
|
||||
auto &selfTouchCollisions = _collisionSystem.getSelfTouchCollisions();
|
||||
for (auto &collision : selfTouchCollisions) {
|
||||
updateCollisionJoint(collision, absolutePoses);
|
||||
}
|
||||
_collisionSystem.prepareCollisions();
|
||||
}
|
||||
|
@ -710,7 +730,7 @@ void Flow::setJoints(AnimPoseVec& relativePoses, const std::vector<bool>& overri
|
|||
for (int jointIndex : joints) {
|
||||
auto &joint = _flowJointData[jointIndex];
|
||||
if (jointIndex >= 0 && jointIndex < (int)relativePoses.size() && !overrideFlags[jointIndex]) {
|
||||
relativePoses[jointIndex].rot() = joint.getCurrentRotation();
|
||||
relativePoses[jointIndex].rot() = joint.getSettings()._active ? joint.getCurrentRotation() : joint.getInitialRotation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -140,6 +140,7 @@ public:
|
|||
std::vector<FlowCollisionResult> checkFlowThreadCollisions(FlowThread* flowThread);
|
||||
|
||||
std::vector<FlowCollisionSphere>& getSelfCollisions() { return _selfCollisions; };
|
||||
std::vector<FlowCollisionSphere>& getSelfTouchCollisions() { return _selfTouchCollisions; };
|
||||
void setOthersCollisions(const std::vector<FlowCollisionSphere>& othersCollisions) { _othersCollisions = othersCollisions; }
|
||||
void prepareCollisions();
|
||||
void resetCollisions();
|
||||
|
@ -150,9 +151,11 @@ public:
|
|||
void setActive(bool active) { _active = active; }
|
||||
bool getActive() const { return _active; }
|
||||
const std::vector<FlowCollisionSphere>& getCollisions() const { return _selfCollisions; }
|
||||
void clearSelfCollisions() { _selfCollisions.clear(); }
|
||||
protected:
|
||||
std::vector<FlowCollisionSphere> _selfCollisions;
|
||||
std::vector<FlowCollisionSphere> _othersCollisions;
|
||||
std::vector<FlowCollisionSphere> _selfTouchCollisions;
|
||||
std::vector<FlowCollisionSphere> _allCollisions;
|
||||
float _scale { 1.0f };
|
||||
bool _active { false };
|
||||
|
@ -210,7 +213,7 @@ public:
|
|||
bool isHelper() const { return _isHelper; }
|
||||
|
||||
const FlowPhysicsSettings& getSettings() { return _settings; }
|
||||
void setSettings(const FlowPhysicsSettings& settings) { _settings = settings; }
|
||||
void setSettings(const FlowPhysicsSettings& settings) { _settings = settings; _initialRadius = _settings._radius; }
|
||||
|
||||
const glm::vec3& getCurrentPosition() const { return _currentPosition; }
|
||||
int getIndex() const { return _index; }
|
||||
|
@ -222,6 +225,7 @@ public:
|
|||
const glm::quat& getCurrentRotation() const { return _currentRotation; }
|
||||
const glm::vec3& getCurrentTranslation() const { return _initialTranslation; }
|
||||
const glm::vec3& getInitialPosition() const { return _initialPosition; }
|
||||
const glm::quat& getInitialRotation() const { return _initialRotation; }
|
||||
bool isColliding() const { return _colliding; }
|
||||
|
||||
protected:
|
||||
|
@ -297,6 +301,7 @@ public:
|
|||
void setPhysicsSettingsForGroup(const QString& group, const FlowPhysicsSettings& settings);
|
||||
const std::map<QString, FlowPhysicsSettings>& getGroupSettings() const { return _groupSettings; }
|
||||
void cleanUp();
|
||||
void updateScale() { setScale(_scale); }
|
||||
|
||||
signals:
|
||||
void onCleanup();
|
||||
|
@ -311,6 +316,7 @@ private:
|
|||
|
||||
void setJoints(AnimPoseVec& relativePoses, const std::vector<bool>& overrideFlags);
|
||||
void updateJoints(AnimPoseVec& relativePoses, AnimPoseVec& absolutePoses);
|
||||
void updateCollisionJoint(FlowCollisionSphere& collision, AnimPoseVec& absolutePoses);
|
||||
bool updateRootFramePositions(const AnimPoseVec& absolutePoses, size_t threadIndex);
|
||||
void updateGroupSettings(const QString& group, const FlowPhysicsSettings& settings);
|
||||
void setScale(float scale);
|
||||
|
|
|
@ -1422,7 +1422,8 @@ void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, cons
|
|||
_networkAnimState.blendTime += deltaTime;
|
||||
alpha = _computeNetworkAnimation ? (_networkAnimState.blendTime / TOTAL_BLEND_TIME) : (1.0f - (_networkAnimState.blendTime / TOTAL_BLEND_TIME));
|
||||
alpha = glm::clamp(alpha, 0.0f, 1.0f);
|
||||
for (size_t i = 0; i < _networkPoseSet._relativePoses.size(); i++) {
|
||||
size_t numJoints = std::min(_networkPoseSet._relativePoses.size(), _internalPoseSet._relativePoses.size());
|
||||
for (size_t i = 0; i < numJoints; i++) {
|
||||
_networkPoseSet._relativePoses[i].blend(_internalPoseSet._relativePoses[i], alpha);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -372,13 +372,6 @@ bool Avatar::applyGrabChanges() {
|
|||
target->removeGrab(grab);
|
||||
_avatarGrabs.erase(itr);
|
||||
grabAddedOrRemoved = true;
|
||||
if (isMyAvatar()) {
|
||||
const EntityItemPointer& entity = std::dynamic_pointer_cast<EntityItem>(target);
|
||||
if (entity && entity->getEntityHostType() == entity::HostType::AVATAR && entity->getSimulationOwner().getID() == getID()) {
|
||||
EntityItemProperties properties = entity->getProperties();
|
||||
sendPacket(entity->getID());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
undeleted.push_back(id);
|
||||
}
|
||||
|
|
|
@ -180,7 +180,6 @@ public:
|
|||
/// Returns the distance to use as a LOD parameter.
|
||||
float getLODDistance() const;
|
||||
|
||||
virtual bool isMyAvatar() const override { return false; }
|
||||
virtual void createOrb() { }
|
||||
|
||||
enum class LoadingStatus {
|
||||
|
|
|
@ -28,9 +28,10 @@
|
|||
namespace AvatarTraits {
|
||||
template<typename T, T defaultValue>
|
||||
class AssociatedTraitValues {
|
||||
using SimpleTypesArray = std::array<T, NUM_SIMPLE_TRAITS>;
|
||||
public:
|
||||
// constructor that pre-fills _simpleTypes with the default value specified by the template
|
||||
AssociatedTraitValues() : _simpleTypes(FirstInstancedTrait, defaultValue) {}
|
||||
AssociatedTraitValues() { std::fill(_simpleTypes.begin(), _simpleTypes.end(), defaultValue); }
|
||||
|
||||
/// inserts the given value for the given simple trait type
|
||||
void insert(TraitType type, T value) { _simpleTypes[type] = value; }
|
||||
|
@ -71,12 +72,12 @@ namespace AvatarTraits {
|
|||
}
|
||||
|
||||
/// const iterators for the vector of simple type values
|
||||
typename std::vector<T>::const_iterator simpleCBegin() const { return _simpleTypes.cbegin(); }
|
||||
typename std::vector<T>::const_iterator simpleCEnd() const { return _simpleTypes.cend(); }
|
||||
typename SimpleTypesArray::const_iterator simpleCBegin() const { return _simpleTypes.cbegin(); }
|
||||
typename SimpleTypesArray::const_iterator simpleCEnd() const { return _simpleTypes.cend(); }
|
||||
|
||||
/// non-const iterators for the vector of simple type values
|
||||
typename std::vector<T>::iterator simpleBegin() { return _simpleTypes.begin(); }
|
||||
typename std::vector<T>::iterator simpleEnd() { return _simpleTypes.end(); }
|
||||
typename SimpleTypesArray::iterator simpleBegin() { return _simpleTypes.begin(); }
|
||||
typename SimpleTypesArray::iterator simpleEnd() { return _simpleTypes.end(); }
|
||||
|
||||
struct TraitWithInstances {
|
||||
TraitType traitType;
|
||||
|
@ -96,7 +97,7 @@ namespace AvatarTraits {
|
|||
typename std::vector<TraitWithInstances>::iterator instancedEnd() { return _instancedTypes.end(); }
|
||||
|
||||
private:
|
||||
std::vector<T> _simpleTypes;
|
||||
SimpleTypesArray _simpleTypes;
|
||||
|
||||
/// return the iterator to the matching TraitWithInstances object for a given instanced trait type
|
||||
typename std::vector<TraitWithInstances>::iterator instancesForTrait(TraitType traitType) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
@ -1990,42 +1991,16 @@ QUrl AvatarData::getWireSafeSkeletonModelURL() const {
|
|||
}
|
||||
}
|
||||
|
||||
qint64 AvatarData::packTrait(AvatarTraits::TraitType traitType, ExtendedIODevice& destination,
|
||||
AvatarTraits::TraitVersion traitVersion) {
|
||||
|
||||
qint64 bytesWritten = 0;
|
||||
|
||||
if (traitType == AvatarTraits::SkeletonModelURL) {
|
||||
|
||||
QByteArray encodedSkeletonURL = getWireSafeSkeletonModelURL().toEncoded();
|
||||
|
||||
if (encodedSkeletonURL.size() > AvatarTraits::MAXIMUM_TRAIT_SIZE) {
|
||||
qWarning() << "Refusing to pack simple trait" << traitType << "of size" << encodedSkeletonURL.size()
|
||||
<< "bytes since it exceeds the maximum size" << AvatarTraits::MAXIMUM_TRAIT_SIZE << "bytes";
|
||||
return 0;
|
||||
}
|
||||
|
||||
bytesWritten += destination.writePrimitive(traitType);
|
||||
|
||||
if (traitVersion > AvatarTraits::DEFAULT_TRAIT_VERSION) {
|
||||
bytesWritten += destination.writePrimitive(traitVersion);
|
||||
}
|
||||
|
||||
AvatarTraits::TraitWireSize encodedURLSize = encodedSkeletonURL.size();
|
||||
bytesWritten += destination.writePrimitive(encodedURLSize);
|
||||
|
||||
bytesWritten += destination.write(encodedSkeletonURL);
|
||||
}
|
||||
|
||||
return bytesWritten;
|
||||
QByteArray AvatarData::packSkeletonModelURL() const {
|
||||
return getWireSafeSkeletonModelURL().toEncoded();
|
||||
}
|
||||
|
||||
void AvatarData::unpackSkeletonModelURL(const QByteArray& data) {
|
||||
auto skeletonModelURL = QUrl::fromEncoded(data);
|
||||
setSkeletonModelURL(skeletonModelURL);
|
||||
}
|
||||
|
||||
qint64 AvatarData::packAvatarEntityTraitInstance(AvatarTraits::TraitType traitType,
|
||||
AvatarTraits::TraitInstanceID traitInstanceID,
|
||||
ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion) {
|
||||
qint64 bytesWritten = 0;
|
||||
|
||||
QByteArray AvatarData::packAvatarEntityTraitInstance(AvatarTraits::TraitInstanceID traitInstanceID) {
|
||||
// grab a read lock on the avatar entities and check for entity data for the given ID
|
||||
QByteArray entityBinaryData;
|
||||
_avatarEntitiesLock.withReadLock([this, &entityBinaryData, &traitInstanceID] {
|
||||
|
@ -2034,104 +2009,48 @@ qint64 AvatarData::packAvatarEntityTraitInstance(AvatarTraits::TraitType traitTy
|
|||
}
|
||||
});
|
||||
|
||||
if (entityBinaryData.size() > AvatarTraits::MAXIMUM_TRAIT_SIZE) {
|
||||
qWarning() << "Refusing to pack instanced trait" << traitType << "of size" << entityBinaryData.size()
|
||||
<< "bytes since it exceeds the maximum size " << AvatarTraits::MAXIMUM_TRAIT_SIZE << "bytes";
|
||||
return 0;
|
||||
}
|
||||
|
||||
bytesWritten += destination.writePrimitive(traitType);
|
||||
|
||||
if (traitVersion > AvatarTraits::DEFAULT_TRAIT_VERSION) {
|
||||
bytesWritten += destination.writePrimitive(traitVersion);
|
||||
}
|
||||
|
||||
bytesWritten += destination.write(traitInstanceID.toRfc4122());
|
||||
|
||||
if (!entityBinaryData.isNull()) {
|
||||
AvatarTraits::TraitWireSize entityBinarySize = entityBinaryData.size();
|
||||
|
||||
bytesWritten += destination.writePrimitive(entityBinarySize);
|
||||
bytesWritten += destination.write(entityBinaryData);
|
||||
} else {
|
||||
bytesWritten += destination.writePrimitive(AvatarTraits::DELETED_TRAIT_SIZE);
|
||||
}
|
||||
|
||||
return bytesWritten;
|
||||
return entityBinaryData;
|
||||
}
|
||||
|
||||
|
||||
qint64 AvatarData::packGrabTraitInstance(AvatarTraits::TraitType traitType,
|
||||
AvatarTraits::TraitInstanceID traitInstanceID,
|
||||
ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion) {
|
||||
qint64 bytesWritten = 0;
|
||||
|
||||
QByteArray AvatarData::packGrabTraitInstance(AvatarTraits::TraitInstanceID traitInstanceID) {
|
||||
// grab a read lock on the avatar grabs and check for grab data for the given ID
|
||||
QByteArray grabBinaryData;
|
||||
|
||||
_avatarGrabsLock.withReadLock([this, &grabBinaryData, &traitInstanceID] {
|
||||
if (_avatarGrabData.contains(traitInstanceID)) {
|
||||
grabBinaryData = _avatarGrabData[traitInstanceID];
|
||||
}
|
||||
});
|
||||
|
||||
if (grabBinaryData.size() > AvatarTraits::MAXIMUM_TRAIT_SIZE) {
|
||||
qWarning() << "Refusing to pack instanced trait" << traitType << "of size" << grabBinaryData.size()
|
||||
<< "bytes since it exceeds the maximum size " << AvatarTraits::MAXIMUM_TRAIT_SIZE << "bytes";
|
||||
return 0;
|
||||
}
|
||||
|
||||
bytesWritten += destination.writePrimitive(traitType);
|
||||
|
||||
if (traitVersion > AvatarTraits::DEFAULT_TRAIT_VERSION) {
|
||||
bytesWritten += destination.writePrimitive(traitVersion);
|
||||
}
|
||||
|
||||
bytesWritten += destination.write(traitInstanceID.toRfc4122());
|
||||
|
||||
if (!grabBinaryData.isNull()) {
|
||||
AvatarTraits::TraitWireSize grabBinarySize = grabBinaryData.size();
|
||||
|
||||
bytesWritten += destination.writePrimitive(grabBinarySize);
|
||||
bytesWritten += destination.write(grabBinaryData);
|
||||
} else {
|
||||
bytesWritten += destination.writePrimitive(AvatarTraits::DELETED_TRAIT_SIZE);
|
||||
}
|
||||
|
||||
return bytesWritten;
|
||||
return grabBinaryData;
|
||||
}
|
||||
|
||||
qint64 AvatarData::packTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID traitInstanceID,
|
||||
ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion) {
|
||||
qint64 bytesWritten = 0;
|
||||
QByteArray AvatarData::packTrait(AvatarTraits::TraitType traitType) const {
|
||||
QByteArray traitBinaryData;
|
||||
|
||||
// Call packer function
|
||||
if (traitType == AvatarTraits::SkeletonModelURL) {
|
||||
traitBinaryData = packSkeletonModelURL();
|
||||
}
|
||||
|
||||
return traitBinaryData;
|
||||
}
|
||||
|
||||
QByteArray AvatarData::packTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID traitInstanceID) {
|
||||
QByteArray traitBinaryData;
|
||||
|
||||
// Call packer function
|
||||
if (traitType == AvatarTraits::AvatarEntity) {
|
||||
bytesWritten += packAvatarEntityTraitInstance(traitType, traitInstanceID, destination, traitVersion);
|
||||
traitBinaryData = packAvatarEntityTraitInstance(traitInstanceID);
|
||||
} else if (traitType == AvatarTraits::Grab) {
|
||||
bytesWritten += packGrabTraitInstance(traitType, traitInstanceID, destination, traitVersion);
|
||||
traitBinaryData = packGrabTraitInstance(traitInstanceID);
|
||||
}
|
||||
|
||||
return bytesWritten;
|
||||
}
|
||||
|
||||
void AvatarData::prepareResetTraitInstances() {
|
||||
if (_clientTraitsHandler) {
|
||||
_avatarEntitiesLock.withReadLock([this]{
|
||||
foreach (auto entityID, _packedAvatarEntityData.keys()) {
|
||||
_clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::AvatarEntity, entityID);
|
||||
}
|
||||
foreach (auto grabID, _avatarGrabData.keys()) {
|
||||
_clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::Grab, grabID);
|
||||
}
|
||||
});
|
||||
}
|
||||
return traitBinaryData;
|
||||
}
|
||||
|
||||
void AvatarData::processTrait(AvatarTraits::TraitType traitType, QByteArray traitBinaryData) {
|
||||
if (traitType == AvatarTraits::SkeletonModelURL) {
|
||||
// get the URL from the binary data
|
||||
auto skeletonModelURL = QUrl::fromEncoded(traitBinaryData);
|
||||
setSkeletonModelURL(skeletonModelURL);
|
||||
unpackSkeletonModelURL(traitBinaryData);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2152,6 +2071,19 @@ void AvatarData::processDeletedTraitInstance(AvatarTraits::TraitType traitType,
|
|||
}
|
||||
}
|
||||
|
||||
void AvatarData::prepareResetTraitInstances() {
|
||||
if (_clientTraitsHandler) {
|
||||
_avatarEntitiesLock.withReadLock([this]{
|
||||
foreach (auto entityID, _packedAvatarEntityData.keys()) {
|
||||
_clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::AvatarEntity, entityID);
|
||||
}
|
||||
foreach (auto grabID, _avatarGrabData.keys()) {
|
||||
_clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::Grab, grabID);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray AvatarData::identityByteArray(bool setIsReplicated) const {
|
||||
QByteArray identityData;
|
||||
QDataStream identityStream(&identityData, QIODevice::Append);
|
||||
|
|
|
@ -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; }
|
||||
|
||||
|
@ -1134,18 +1137,16 @@ public:
|
|||
// identityChanged returns true if identity has changed, false otherwise. Similarly for displayNameChanged and skeletonModelUrlChange.
|
||||
void processAvatarIdentity(QDataStream& packetStream, bool& identityChanged, bool& displayNameChanged);
|
||||
|
||||
qint64 packTrait(AvatarTraits::TraitType traitType, ExtendedIODevice& destination,
|
||||
AvatarTraits::TraitVersion traitVersion = AvatarTraits::NULL_TRAIT_VERSION);
|
||||
qint64 packTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID instanceID,
|
||||
ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion = AvatarTraits::NULL_TRAIT_VERSION);
|
||||
|
||||
void prepareResetTraitInstances();
|
||||
QByteArray packTrait(AvatarTraits::TraitType traitType) const;
|
||||
QByteArray packTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID instanceID);
|
||||
|
||||
void processTrait(AvatarTraits::TraitType traitType, QByteArray traitBinaryData);
|
||||
void processTraitInstance(AvatarTraits::TraitType traitType,
|
||||
AvatarTraits::TraitInstanceID instanceID, QByteArray traitBinaryData);
|
||||
void processDeletedTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID instanceID);
|
||||
|
||||
void prepareResetTraitInstances();
|
||||
|
||||
QByteArray identityByteArray(bool setIsReplicated = false) const;
|
||||
|
||||
QUrl getWireSafeSkeletonModelURL() const;
|
||||
|
@ -1596,13 +1597,13 @@ protected:
|
|||
bool hasParent() const { return !getParentID().isNull(); }
|
||||
bool hasFaceTracker() const { return _headData ? _headData->_isFaceTrackerConnected : false; }
|
||||
|
||||
qint64 packAvatarEntityTraitInstance(AvatarTraits::TraitType traitType,
|
||||
AvatarTraits::TraitInstanceID traitInstanceID,
|
||||
ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion);
|
||||
qint64 packGrabTraitInstance(AvatarTraits::TraitType traitType,
|
||||
AvatarTraits::TraitInstanceID traitInstanceID,
|
||||
ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion);
|
||||
QByteArray packSkeletonModelURL() const;
|
||||
QByteArray packAvatarEntityTraitInstance(AvatarTraits::TraitInstanceID traitInstanceID);
|
||||
QByteArray packGrabTraitInstance(AvatarTraits::TraitInstanceID traitInstanceID);
|
||||
|
||||
void unpackSkeletonModelURL(const QByteArray& data);
|
||||
|
||||
|
||||
// isReplicated will be true on downstream Avatar Mixers and their clients, but false on the upstream "master"
|
||||
// Audio Mixer that the replicated avatar is connected to.
|
||||
bool _isReplicated{ false };
|
||||
|
|
135
libraries/avatars/src/AvatarTraits.cpp
Normal file
135
libraries/avatars/src/AvatarTraits.cpp
Normal file
|
@ -0,0 +1,135 @@
|
|||
//
|
||||
// AvatarTraits.cpp
|
||||
// libraries/avatars/src
|
||||
//
|
||||
// Created by Clement Brisset on 3/19/19.
|
||||
// Copyright 2019 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "AvatarTraits.h"
|
||||
|
||||
#include <ExtendedIODevice.h>
|
||||
|
||||
#include "AvatarData.h"
|
||||
|
||||
namespace AvatarTraits {
|
||||
|
||||
qint64 packTrait(TraitType traitType, ExtendedIODevice& destination, const AvatarData& avatar) {
|
||||
// Call packer function
|
||||
auto traitBinaryData = avatar.packTrait(traitType);
|
||||
auto traitBinaryDataSize = traitBinaryData.size();
|
||||
|
||||
// Verify packed data
|
||||
if (traitBinaryDataSize > MAXIMUM_TRAIT_SIZE) {
|
||||
qWarning() << "Refusing to pack simple trait" << traitType << "of size" << traitBinaryDataSize
|
||||
<< "bytes since it exceeds the maximum size" << MAXIMUM_TRAIT_SIZE << "bytes";
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Write packed data to stream
|
||||
qint64 bytesWritten = 0;
|
||||
bytesWritten += destination.writePrimitive((TraitType)traitType);
|
||||
bytesWritten += destination.writePrimitive((TraitWireSize)traitBinaryDataSize);
|
||||
bytesWritten += destination.write(traitBinaryData);
|
||||
return bytesWritten;
|
||||
}
|
||||
|
||||
qint64 packVersionedTrait(TraitType traitType, ExtendedIODevice& destination,
|
||||
TraitVersion traitVersion, const AvatarData& avatar) {
|
||||
// Call packer function
|
||||
auto traitBinaryData = avatar.packTrait(traitType);
|
||||
auto traitBinaryDataSize = traitBinaryData.size();
|
||||
|
||||
// Verify packed data
|
||||
if (traitBinaryDataSize > MAXIMUM_TRAIT_SIZE) {
|
||||
qWarning() << "Refusing to pack simple trait" << traitType << "of size" << traitBinaryDataSize
|
||||
<< "bytes since it exceeds the maximum size" << MAXIMUM_TRAIT_SIZE << "bytes";
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Write packed data to stream
|
||||
qint64 bytesWritten = 0;
|
||||
bytesWritten += destination.writePrimitive((TraitType)traitType);
|
||||
bytesWritten += destination.writePrimitive((TraitVersion)traitVersion);
|
||||
bytesWritten += destination.writePrimitive((TraitWireSize)traitBinaryDataSize);
|
||||
bytesWritten += destination.write(traitBinaryData);
|
||||
return bytesWritten;
|
||||
}
|
||||
|
||||
|
||||
qint64 packTraitInstance(TraitType traitType, TraitInstanceID traitInstanceID,
|
||||
ExtendedIODevice& destination, AvatarData& avatar) {
|
||||
// Call packer function
|
||||
auto traitBinaryData = avatar.packTraitInstance(traitType, traitInstanceID);
|
||||
auto traitBinaryDataSize = traitBinaryData.size();
|
||||
|
||||
|
||||
// Verify packed data
|
||||
if (traitBinaryDataSize > AvatarTraits::MAXIMUM_TRAIT_SIZE) {
|
||||
qWarning() << "Refusing to pack instanced trait" << traitType << "of size" << traitBinaryDataSize
|
||||
<< "bytes since it exceeds the maximum size " << AvatarTraits::MAXIMUM_TRAIT_SIZE << "bytes";
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Write packed data to stream
|
||||
qint64 bytesWritten = 0;
|
||||
bytesWritten += destination.writePrimitive((TraitType)traitType);
|
||||
bytesWritten += destination.write(traitInstanceID.toRfc4122());
|
||||
|
||||
if (!traitBinaryData.isNull()) {
|
||||
bytesWritten += destination.writePrimitive((TraitWireSize)traitBinaryDataSize);
|
||||
bytesWritten += destination.write(traitBinaryData);
|
||||
} else {
|
||||
bytesWritten += destination.writePrimitive(AvatarTraits::DELETED_TRAIT_SIZE);
|
||||
}
|
||||
|
||||
return bytesWritten;
|
||||
}
|
||||
|
||||
qint64 packVersionedTraitInstance(TraitType traitType, TraitInstanceID traitInstanceID,
|
||||
ExtendedIODevice& destination, TraitVersion traitVersion,
|
||||
AvatarData& avatar) {
|
||||
// Call packer function
|
||||
auto traitBinaryData = avatar.packTraitInstance(traitType, traitInstanceID);
|
||||
auto traitBinaryDataSize = traitBinaryData.size();
|
||||
|
||||
|
||||
// Verify packed data
|
||||
if (traitBinaryDataSize > AvatarTraits::MAXIMUM_TRAIT_SIZE) {
|
||||
qWarning() << "Refusing to pack instanced trait" << traitType << "of size" << traitBinaryDataSize
|
||||
<< "bytes since it exceeds the maximum size " << AvatarTraits::MAXIMUM_TRAIT_SIZE << "bytes";
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Write packed data to stream
|
||||
qint64 bytesWritten = 0;
|
||||
bytesWritten += destination.writePrimitive((TraitType)traitType);
|
||||
bytesWritten += destination.writePrimitive((TraitVersion)traitVersion);
|
||||
bytesWritten += destination.write(traitInstanceID.toRfc4122());
|
||||
|
||||
if (!traitBinaryData.isNull()) {
|
||||
bytesWritten += destination.writePrimitive((TraitWireSize)traitBinaryDataSize);
|
||||
bytesWritten += destination.write(traitBinaryData);
|
||||
} else {
|
||||
bytesWritten += destination.writePrimitive(AvatarTraits::DELETED_TRAIT_SIZE);
|
||||
}
|
||||
|
||||
return bytesWritten;
|
||||
}
|
||||
|
||||
|
||||
qint64 packInstancedTraitDelete(TraitType traitType, TraitInstanceID instanceID, ExtendedIODevice& destination,
|
||||
TraitVersion traitVersion) {
|
||||
qint64 bytesWritten = 0;
|
||||
bytesWritten += destination.writePrimitive(traitType);
|
||||
if (traitVersion > DEFAULT_TRAIT_VERSION) {
|
||||
bytesWritten += destination.writePrimitive(traitVersion);
|
||||
}
|
||||
bytesWritten += destination.write(instanceID.toRfc4122());
|
||||
bytesWritten += destination.writePrimitive(DELETED_TRAIT_SIZE);
|
||||
return bytesWritten;
|
||||
}
|
||||
};
|
|
@ -14,20 +14,35 @@
|
|||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
#include <QtCore/QUuid>
|
||||
|
||||
class ExtendedIODevice;
|
||||
class AvatarData;
|
||||
|
||||
namespace AvatarTraits {
|
||||
enum TraitType : int8_t {
|
||||
// Null trait
|
||||
NullTrait = -1,
|
||||
SkeletonModelURL,
|
||||
|
||||
// Simple traits
|
||||
SkeletonModelURL = 0,
|
||||
|
||||
// Instanced traits
|
||||
FirstInstancedTrait,
|
||||
AvatarEntity = FirstInstancedTrait,
|
||||
Grab,
|
||||
|
||||
// Traits count
|
||||
TotalTraitTypes
|
||||
};
|
||||
|
||||
const int NUM_SIMPLE_TRAITS = (int)FirstInstancedTrait;
|
||||
const int NUM_INSTANCED_TRAITS = (int)TotalTraitTypes - (int)FirstInstancedTrait;
|
||||
const int NUM_TRAITS = (int)TotalTraitTypes;
|
||||
|
||||
using TraitInstanceID = QUuid;
|
||||
|
||||
inline bool isSimpleTrait(TraitType traitType) {
|
||||
|
@ -46,22 +61,19 @@ namespace AvatarTraits {
|
|||
const TraitMessageSequence FIRST_TRAIT_SEQUENCE = 0;
|
||||
const TraitMessageSequence MAX_TRAIT_SEQUENCE = INT64_MAX;
|
||||
|
||||
inline qint64 packInstancedTraitDelete(TraitType traitType, TraitInstanceID instanceID, ExtendedIODevice& destination,
|
||||
TraitVersion traitVersion = NULL_TRAIT_VERSION) {
|
||||
qint64 bytesWritten = 0;
|
||||
qint64 packTrait(TraitType traitType, ExtendedIODevice& destination, const AvatarData& avatar);
|
||||
qint64 packVersionedTrait(TraitType traitType, ExtendedIODevice& destination,
|
||||
TraitVersion traitVersion, const AvatarData& avatar);
|
||||
|
||||
bytesWritten += destination.writePrimitive(traitType);
|
||||
qint64 packTraitInstance(TraitType traitType, TraitInstanceID traitInstanceID,
|
||||
ExtendedIODevice& destination, AvatarData& avatar);
|
||||
qint64 packVersionedTraitInstance(TraitType traitType, TraitInstanceID traitInstanceID,
|
||||
ExtendedIODevice& destination, TraitVersion traitVersion,
|
||||
AvatarData& avatar);
|
||||
|
||||
if (traitVersion > DEFAULT_TRAIT_VERSION) {
|
||||
bytesWritten += destination.writePrimitive(traitVersion);
|
||||
}
|
||||
qint64 packInstancedTraitDelete(TraitType traitType, TraitInstanceID instanceID, ExtendedIODevice& destination,
|
||||
TraitVersion traitVersion = NULL_TRAIT_VERSION);
|
||||
|
||||
bytesWritten += destination.write(instanceID.toRfc4122());
|
||||
|
||||
bytesWritten += destination.writePrimitive(DELETED_TRAIT_SIZE);
|
||||
|
||||
return bytesWritten;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // hifi_AvatarTraits_h
|
||||
|
|
|
@ -106,9 +106,10 @@ int ClientTraitsHandler::sendChangedTraitsToMixer() {
|
|||
auto traitType = static_cast<AvatarTraits::TraitType>(std::distance(traitStatusesCopy.simpleCBegin(), simpleIt));
|
||||
|
||||
if (initialSend || *simpleIt == Updated) {
|
||||
if (traitType == AvatarTraits::SkeletonModelURL) {
|
||||
bytesWritten += _owningAvatar->packTrait(traitType, *traitsPacketList);
|
||||
bytesWritten += AvatarTraits::packTrait(traitType, *traitsPacketList, *_owningAvatar);
|
||||
|
||||
|
||||
if (traitType == AvatarTraits::SkeletonModelURL) {
|
||||
// keep track of our skeleton version in case we get an override back
|
||||
_currentSkeletonVersion = _currentTraitVersion;
|
||||
}
|
||||
|
@ -124,7 +125,9 @@ int ClientTraitsHandler::sendChangedTraitsToMixer() {
|
|||
|| instanceIDValuePair.value == Updated) {
|
||||
// this is a changed trait we need to send or we haven't send out trait information yet
|
||||
// ask the owning avatar to pack it
|
||||
bytesWritten += _owningAvatar->packTraitInstance(instancedIt->traitType, instanceIDValuePair.id, *traitsPacketList);
|
||||
bytesWritten += AvatarTraits::packTraitInstance(instancedIt->traitType, instanceIDValuePair.id,
|
||||
*traitsPacketList, *_owningAvatar);
|
||||
|
||||
} else if (!initialSend && instanceIDValuePair.value == Deleted) {
|
||||
// pack delete for this trait instance
|
||||
bytesWritten += AvatarTraits::packInstancedTraitDelete(instancedIt->traitType, instanceIDValuePair.id,
|
||||
|
@ -162,11 +165,11 @@ void ClientTraitsHandler::processTraitOverride(QSharedPointer<ReceivedMessage> m
|
|||
|
||||
// override the skeleton URL but do not mark the trait as having changed
|
||||
// so that we don't unecessarily send a new trait packet to the mixer with the overriden URL
|
||||
auto encodedSkeletonURL = QUrl::fromEncoded(message->readWithoutCopy(traitBinarySize));
|
||||
|
||||
auto hasChangesBefore = _hasChangedTraits;
|
||||
|
||||
_owningAvatar->setSkeletonModelURL(encodedSkeletonURL);
|
||||
auto traitBinaryData = message->readWithoutCopy(traitBinarySize);
|
||||
_owningAvatar->processTrait(traitType, traitBinaryData);
|
||||
|
||||
// setSkeletonModelURL will flag us for changes to the SkeletonModelURL so we reset some state here to
|
||||
// avoid unnecessarily sending the overriden skeleton model URL back to the mixer
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -265,7 +265,7 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) {
|
|||
if (_procedural.isReady()) {
|
||||
outColor = _procedural.getColor(outColor);
|
||||
outColor.a *= _procedural.isFading() ? Interpolate::calculateFadeRatio(_procedural.getFadeStartTime()) : 1.0f;
|
||||
_procedural.prepare(batch, _position, _dimensions, _orientation, ProceduralProgramKey(outColor.a < 1.0f));
|
||||
_procedural.prepare(batch, _position, _dimensions, _orientation, _created, ProceduralProgramKey(outColor.a < 1.0f));
|
||||
proceduralRender = true;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -33,7 +33,7 @@ using namespace render::entities;
|
|||
|
||||
ZoneEntityRenderer::ZoneEntityRenderer(const EntityItemPointer& entity)
|
||||
: Parent(entity) {
|
||||
_background->setSkybox(std::make_shared<ProceduralSkybox>());
|
||||
_background->setSkybox(std::make_shared<ProceduralSkybox>(entity->getCreated()));
|
||||
}
|
||||
|
||||
void ZoneEntityRenderer::onRemoveFromSceneTyped(const TypedEntityPointer& entity) {
|
||||
|
|
|
@ -789,8 +789,10 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
|
|||
|
||||
auto lastEdited = lastEditedFromBufferAdjusted;
|
||||
bool otherOverwrites = overwriteLocalData && !weOwnSimulation;
|
||||
auto shouldUpdate = [this, lastEdited, otherOverwrites, filterRejection](quint64 updatedTimestamp, bool valueChanged) {
|
||||
if (stillHasGrabActions()) {
|
||||
// calculate hasGrab once outside the lambda rather than calling it every time inside
|
||||
bool hasGrab = stillHasGrabAction();
|
||||
auto shouldUpdate = [this, lastEdited, otherOverwrites, filterRejection, hasGrab](quint64 updatedTimestamp, bool valueChanged) {
|
||||
if (hasGrab) {
|
||||
return false;
|
||||
}
|
||||
bool simulationChanged = lastEdited > updatedTimestamp;
|
||||
|
@ -957,12 +959,18 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
|
|||
// by doing this parsing here... but it's not likely going to fully recover the content.
|
||||
//
|
||||
|
||||
if (overwriteLocalData && (getDirtyFlags() & (Simulation::DIRTY_TRANSFORM | Simulation::DIRTY_VELOCITIES))) {
|
||||
if (overwriteLocalData &&
|
||||
!hasGrab &&
|
||||
(getDirtyFlags() & (Simulation::DIRTY_TRANSFORM | Simulation::DIRTY_VELOCITIES))) {
|
||||
// NOTE: This code is attempting to "repair" the old data we just got from the server to make it more
|
||||
// closely match where the entities should be if they'd stepped forward in time to "now". The server
|
||||
// is sending us data with a known "last simulated" time. That time is likely in the past, and therefore
|
||||
// this "new" data is actually slightly out of date. We calculate the time we need to skip forward and
|
||||
// use our simulation helper routine to get a best estimate of where the entity should be.
|
||||
//
|
||||
// NOTE: We don't want to do this in the hasGrab case because grabs "know best"
|
||||
// (e.g. grabs will prevent drift between distributed physics simulations).
|
||||
//
|
||||
float skipTimeForward = (float)(now - lastSimulatedFromBufferAdjusted) / (float)(USECS_PER_SECOND);
|
||||
|
||||
// we want to extrapolate the motion forward to compensate for packet travel time, but
|
||||
|
@ -1426,7 +1434,7 @@ void EntityItem::getTransformAndVelocityProperties(EntityItemProperties& propert
|
|||
|
||||
void EntityItem::upgradeScriptSimulationPriority(uint8_t priority) {
|
||||
uint8_t newPriority = glm::max(priority, _scriptSimulationPriority);
|
||||
if (newPriority < SCRIPT_GRAB_SIMULATION_PRIORITY && stillHasGrabActions()) {
|
||||
if (newPriority < SCRIPT_GRAB_SIMULATION_PRIORITY && stillHasMyGrabAction()) {
|
||||
newPriority = SCRIPT_GRAB_SIMULATION_PRIORITY;
|
||||
}
|
||||
if (newPriority != _scriptSimulationPriority) {
|
||||
|
@ -1439,7 +1447,7 @@ void EntityItem::upgradeScriptSimulationPriority(uint8_t priority) {
|
|||
void EntityItem::clearScriptSimulationPriority() {
|
||||
// DO NOT markDirtyFlags(Simulation::DIRTY_SIMULATION_OWNERSHIP_PRIORITY) here, because this
|
||||
// is only ever called from the code that actually handles the dirty flags, and it knows best.
|
||||
_scriptSimulationPriority = stillHasGrabActions() ? SCRIPT_GRAB_SIMULATION_PRIORITY : 0;
|
||||
_scriptSimulationPriority = stillHasMyGrabAction() ? SCRIPT_GRAB_SIMULATION_PRIORITY : 0;
|
||||
}
|
||||
|
||||
void EntityItem::setPendingOwnershipPriority(uint8_t priority) {
|
||||
|
@ -2186,7 +2194,7 @@ void EntityItem::enableNoBootstrap() {
|
|||
}
|
||||
|
||||
void EntityItem::disableNoBootstrap() {
|
||||
if (!stillHasGrabActions()) {
|
||||
if (!stillHasMyGrabAction()) {
|
||||
_flags &= ~Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING;
|
||||
_flags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar
|
||||
|
||||
|
@ -2272,7 +2280,13 @@ bool EntityItem::removeAction(EntitySimulationPointer simulation, const QUuid& a
|
|||
return success;
|
||||
}
|
||||
|
||||
bool EntityItem::stillHasGrabActions() const {
|
||||
bool EntityItem::stillHasGrabAction() const {
|
||||
return !_grabActions.empty();
|
||||
}
|
||||
|
||||
// retutrns 'true' if there exists an action that returns 'true' for EntityActionInterface::isMine()
|
||||
// (e.g. the action belongs to the MyAvatar instance)
|
||||
bool EntityItem::stillHasMyGrabAction() const {
|
||||
QList<EntityDynamicPointer> holdActions = getActionsOfType(DYNAMIC_TYPE_HOLD);
|
||||
QList<EntityDynamicPointer>::const_iterator i = holdActions.begin();
|
||||
while (i != holdActions.end()) {
|
||||
|
@ -2700,20 +2714,6 @@ void EntityItem::setLastEdited(quint64 lastEdited) {
|
|||
});
|
||||
}
|
||||
|
||||
quint64 EntityItem::getLastBroadcast() const {
|
||||
quint64 result;
|
||||
withReadLock([&] {
|
||||
result = _lastBroadcast;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
void EntityItem::setLastBroadcast(quint64 lastBroadcast) {
|
||||
withWriteLock([&] {
|
||||
_lastBroadcast = lastBroadcast;
|
||||
});
|
||||
}
|
||||
|
||||
void EntityItem::markAsChangedOnServer() {
|
||||
withWriteLock([&] {
|
||||
_changedOnServer = usecTimestampNow();
|
||||
|
@ -3479,6 +3479,9 @@ void EntityItem::addGrab(GrabPointer grab) {
|
|||
simulation->addDynamic(action);
|
||||
markDirtyFlags(Simulation::DIRTY_MOTION_TYPE);
|
||||
simulation->changeEntity(getThisPointer());
|
||||
|
||||
// don't forget to set isMine() for locally-created grabs
|
||||
action->setIsMine(grab->getOwnerID() == Physics::getSessionUUID());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -124,8 +124,8 @@ public:
|
|||
{ return (float)(usecTimestampNow() - getLastEdited()) / (float)USECS_PER_SECOND; }
|
||||
|
||||
/// Last time we sent out an edit packet for this entity
|
||||
quint64 getLastBroadcast() const;
|
||||
void setLastBroadcast(quint64 lastBroadcast);
|
||||
quint64 getLastBroadcast() const { return _lastBroadcast; }
|
||||
void setLastBroadcast(quint64 lastBroadcast) { _lastBroadcast = lastBroadcast; }
|
||||
|
||||
void markAsChangedOnServer();
|
||||
quint64 getLastChangedOnServer() const;
|
||||
|
@ -562,6 +562,8 @@ public:
|
|||
static void setPrimaryViewFrustumPositionOperator(std::function<glm::vec3()> getPrimaryViewFrustumPositionOperator) { _getPrimaryViewFrustumPositionOperator = getPrimaryViewFrustumPositionOperator; }
|
||||
static glm::vec3 getPrimaryViewFrustumPosition() { return _getPrimaryViewFrustumPositionOperator(); }
|
||||
|
||||
bool stillHasMyGrabAction() const;
|
||||
|
||||
signals:
|
||||
void requestRenderUpdate();
|
||||
void spaceUpdate(std::pair<int32_t, glm::vec4> data);
|
||||
|
@ -574,7 +576,7 @@ protected:
|
|||
void setSimulated(bool simulated) { _simulated = simulated; }
|
||||
|
||||
const QByteArray getDynamicDataInternal() const;
|
||||
bool stillHasGrabActions() const;
|
||||
bool stillHasGrabAction() const;
|
||||
void setDynamicDataInternal(QByteArray dynamicData);
|
||||
|
||||
virtual void dimensionsChanged() override;
|
||||
|
|
|
@ -1101,13 +1101,13 @@ void EntityScriptingInterface::handleEntityScriptCallMethodPacket(QSharedPointer
|
|||
|
||||
void EntityScriptingInterface::onAddingEntity(EntityItem* entity) {
|
||||
if (entity->isWearable()) {
|
||||
emit addingWearable(entity->getEntityItemID());
|
||||
QMetaObject::invokeMethod(this, "addingWearable", Q_ARG(QUuid, entity->getEntityItemID()));
|
||||
}
|
||||
}
|
||||
|
||||
void EntityScriptingInterface::onDeletingEntity(EntityItem* entity) {
|
||||
if (entity->isWearable()) {
|
||||
emit deletingWearable(entity->getEntityItemID());
|
||||
QMetaObject::invokeMethod(this, "deletingWearable", Q_ARG(QUuid, entity->getEntityItemID()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -443,6 +443,7 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const
|
|||
QString hifiGlobalNodeID;
|
||||
unsigned int meshIndex = 0;
|
||||
haveReportedUnhandledRotationOrder = false;
|
||||
int fbxVersionNumber = -1;
|
||||
foreach (const FBXNode& child, node.children) {
|
||||
|
||||
if (child.name == "FBXHeaderExtension") {
|
||||
|
@ -465,6 +466,8 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const
|
|||
}
|
||||
}
|
||||
}
|
||||
} else if (object.name == "FBXVersion") {
|
||||
fbxVersionNumber = object.properties.at(0).toInt();
|
||||
}
|
||||
}
|
||||
} else if (child.name == "GlobalSettings") {
|
||||
|
@ -1161,8 +1164,14 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const
|
|||
counter++;
|
||||
}
|
||||
}
|
||||
_connectionParentMap.insert(getID(connection.properties, 1), getID(connection.properties, 2));
|
||||
_connectionChildMap.insert(getID(connection.properties, 2), getID(connection.properties, 1));
|
||||
if (_connectionParentMap.value(getID(connection.properties, 1)) == "0") {
|
||||
// don't assign the new parent
|
||||
qCDebug(modelformat) << "root node " << getID(connection.properties, 1) << " has discarded parent " << getID(connection.properties, 2);
|
||||
_connectionChildMap.insert(getID(connection.properties, 2), getID(connection.properties, 1));
|
||||
} else {
|
||||
_connectionParentMap.insert(getID(connection.properties, 1), getID(connection.properties, 2));
|
||||
_connectionChildMap.insert(getID(connection.properties, 2), getID(connection.properties, 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1311,8 +1320,6 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const
|
|||
|
||||
joint.bindTransformFoundInCluster = false;
|
||||
|
||||
hfmModel.joints.append(joint);
|
||||
|
||||
QString rotationID = localRotations.value(modelID);
|
||||
AnimationCurve xRotCurve = animationCurves.value(xComponents.value(rotationID));
|
||||
AnimationCurve yRotCurve = animationCurves.value(yComponents.value(rotationID));
|
||||
|
@ -1335,7 +1342,13 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const
|
|||
xPosCurve.values.isEmpty() ? defaultPosValues.x : xPosCurve.values.at(i % xPosCurve.values.size()),
|
||||
yPosCurve.values.isEmpty() ? defaultPosValues.y : yPosCurve.values.at(i % yPosCurve.values.size()),
|
||||
zPosCurve.values.isEmpty() ? defaultPosValues.z : zPosCurve.values.at(i % zPosCurve.values.size()));
|
||||
if ((fbxVersionNumber < 7500) && (i == 0)) {
|
||||
joint.translation = hfmModel.animationFrames[i].translations[jointIndex];
|
||||
joint.rotation = hfmModel.animationFrames[i].rotations[jointIndex];
|
||||
}
|
||||
|
||||
}
|
||||
hfmModel.joints.append(joint);
|
||||
}
|
||||
|
||||
// NOTE: shapeVertices are in joint-frame
|
||||
|
|
|
@ -536,7 +536,7 @@ void AccountManager::requestAccessToken(const QString& login, const QString& pas
|
|||
|
||||
QByteArray postData;
|
||||
postData.append("grant_type=password&");
|
||||
postData.append("username=" + login + "&");
|
||||
postData.append("username=" + QUrl::toPercentEncoding(login) + "&");
|
||||
postData.append("password=" + QUrl::toPercentEncoding(password) + "&");
|
||||
postData.append("scope=" + ACCOUNT_MANAGER_REQUESTED_SCOPE);
|
||||
|
||||
|
|
|
@ -677,6 +677,9 @@ SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t
|
|||
// If there is a new node with the same socket, this is a reconnection, kill the old node
|
||||
removeOldNode(findNodeWithAddr(publicSocket));
|
||||
removeOldNode(findNodeWithAddr(localSocket));
|
||||
// If there is an old Connection to the new node's address kill it
|
||||
_nodeSocket.cleanupConnection(publicSocket);
|
||||
_nodeSocket.cleanupConnection(localSocket);
|
||||
|
||||
auto it = _connectionIDs.find(uuid);
|
||||
if (it == _connectionIDs.end()) {
|
||||
|
|
|
@ -82,11 +82,11 @@ void OctreePersistThread::start() {
|
|||
}
|
||||
|
||||
if (data.readOctreeDataInfoFromData(_cachedJSONData)) {
|
||||
qCDebug(octree) << "Current octree data: ID(" << data.id << ") DataVersion(" << data.version << ")";
|
||||
qCDebug(octree) << "Current octree data: ID(" << data.id << ") DataVersion(" << data.dataVersion << ")";
|
||||
packet->writePrimitive(true);
|
||||
auto id = data.id.toRfc4122();
|
||||
packet->write(id);
|
||||
packet->writePrimitive(data.version);
|
||||
packet->writePrimitive(data.dataVersion);
|
||||
} else {
|
||||
_cachedJSONData.clear();
|
||||
qCWarning(octree) << "No octree data found";
|
||||
|
@ -144,8 +144,8 @@ void OctreePersistThread::handleOctreeDataFileReply(QSharedPointer<ReceivedMessa
|
|||
quint64 loadStarted = usecTimestampNow();
|
||||
|
||||
if (hasValidOctreeData) {
|
||||
qDebug() << "Setting entity version info to: " << data.id << data.version;
|
||||
_tree->setOctreeVersionInfo(data.id, data.version);
|
||||
qDebug() << "Setting entity version info to: " << data.id << data.dataVersion;
|
||||
_tree->setOctreeVersionInfo(data.id, data.dataVersion);
|
||||
}
|
||||
|
||||
bool persistentFileRead;
|
||||
|
|
|
@ -225,13 +225,15 @@ void Procedural::prepare(gpu::Batch& batch,
|
|||
const glm::vec3& position,
|
||||
const glm::vec3& size,
|
||||
const glm::quat& orientation,
|
||||
const uint64_t& created,
|
||||
const ProceduralProgramKey key) {
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
_entityDimensions = size;
|
||||
_entityPosition = position;
|
||||
_entityOrientation = glm::mat3_cast(orientation);
|
||||
_entityCreated = created;
|
||||
if (!_shaderPath.isEmpty()) {
|
||||
auto lastModified = (quint64)QFileInfo(_shaderPath).lastModified().toMSecsSinceEpoch();
|
||||
auto lastModified = (uint64_t)QFileInfo(_shaderPath).lastModified().toMSecsSinceEpoch();
|
||||
if (lastModified > _shaderModified) {
|
||||
QFile file(_shaderPath);
|
||||
file.open(QIODevice::ReadOnly);
|
||||
|
@ -278,7 +280,10 @@ void Procedural::prepare(gpu::Batch& batch,
|
|||
|
||||
_proceduralPipelines[key] = gpu::Pipeline::create(program, key.isTransparent() ? _transparentState : _opaqueState);
|
||||
|
||||
_start = usecTimestampNow();
|
||||
_lastCompile = usecTimestampNow();
|
||||
if (_firstCompile == 0) {
|
||||
_firstCompile = _lastCompile;
|
||||
}
|
||||
_frameCount = 0;
|
||||
recompiledShader = true;
|
||||
}
|
||||
|
@ -371,7 +376,11 @@ void Procedural::setupUniforms() {
|
|||
_uniforms.push_back([=](gpu::Batch& batch) {
|
||||
_standardInputs.position = vec4(_entityPosition, 1.0f);
|
||||
// Minimize floating point error by doing an integer division to milliseconds, before the floating point division to seconds
|
||||
_standardInputs.time = (float)((usecTimestampNow() - _start) / USECS_PER_MSEC) / MSECS_PER_SECOND;
|
||||
auto now = usecTimestampNow();
|
||||
_standardInputs.timeSinceLastCompile = (float)((now - _lastCompile) / USECS_PER_MSEC) / MSECS_PER_SECOND;
|
||||
_standardInputs.timeSinceFirstCompile = (float)((now - _firstCompile) / USECS_PER_MSEC) / MSECS_PER_SECOND;
|
||||
_standardInputs.timeSinceEntityCreation = (float)((now - _entityCreated) / USECS_PER_MSEC) / MSECS_PER_SECOND;
|
||||
|
||||
|
||||
// Date
|
||||
{
|
||||
|
|
|
@ -82,10 +82,11 @@ public:
|
|||
|
||||
bool isReady() const;
|
||||
bool isEnabled() const { return _enabled; }
|
||||
void prepare(gpu::Batch& batch, const glm::vec3& position, const glm::vec3& size, const glm::quat& orientation, const ProceduralProgramKey key = ProceduralProgramKey());
|
||||
void prepare(gpu::Batch& batch, const glm::vec3& position, const glm::vec3& size, const glm::quat& orientation,
|
||||
const uint64_t& created, const ProceduralProgramKey key = ProceduralProgramKey());
|
||||
|
||||
glm::vec4 getColor(const glm::vec4& entityColor) const;
|
||||
quint64 getFadeStartTime() const { return _fadeStartTime; }
|
||||
uint64_t getFadeStartTime() const { return _fadeStartTime; }
|
||||
bool isFading() const { return _doesFade && _isFading; }
|
||||
void setIsFading(bool isFading) { _isFading = isFading; }
|
||||
void setDoesFade(bool doesFade) { _doesFade = doesFade; }
|
||||
|
@ -106,9 +107,10 @@ protected:
|
|||
vec4 date;
|
||||
vec4 position;
|
||||
vec4 scale;
|
||||
float time;
|
||||
float timeSinceLastCompile;
|
||||
float timeSinceFirstCompile;
|
||||
float timeSinceEntityCreation;
|
||||
int frameCount;
|
||||
vec2 _spare1;
|
||||
vec4 resolution[4];
|
||||
mat4 orientation;
|
||||
};
|
||||
|
@ -116,9 +118,10 @@ protected:
|
|||
static_assert(0 == offsetof(StandardInputs, date), "ProceduralOffsets");
|
||||
static_assert(16 == offsetof(StandardInputs, position), "ProceduralOffsets");
|
||||
static_assert(32 == offsetof(StandardInputs, scale), "ProceduralOffsets");
|
||||
static_assert(48 == offsetof(StandardInputs, time), "ProceduralOffsets");
|
||||
static_assert(52 == offsetof(StandardInputs, frameCount), "ProceduralOffsets");
|
||||
static_assert(56 == offsetof(StandardInputs, _spare1), "ProceduralOffsets");
|
||||
static_assert(48 == offsetof(StandardInputs, timeSinceLastCompile), "ProceduralOffsets");
|
||||
static_assert(52 == offsetof(StandardInputs, timeSinceFirstCompile), "ProceduralOffsets");
|
||||
static_assert(56 == offsetof(StandardInputs, timeSinceEntityCreation), "ProceduralOffsets");
|
||||
static_assert(60 == offsetof(StandardInputs, frameCount), "ProceduralOffsets");
|
||||
static_assert(64 == offsetof(StandardInputs, resolution), "ProceduralOffsets");
|
||||
static_assert(128 == offsetof(StandardInputs, orientation), "ProceduralOffsets");
|
||||
|
||||
|
@ -126,13 +129,14 @@ protected:
|
|||
ProceduralData _data;
|
||||
|
||||
bool _enabled { false };
|
||||
uint64_t _start { 0 };
|
||||
uint64_t _lastCompile { 0 };
|
||||
uint64_t _firstCompile { 0 };
|
||||
int32_t _frameCount { 0 };
|
||||
|
||||
// Rendering object descriptions, from userData
|
||||
QString _shaderSource;
|
||||
QString _shaderPath;
|
||||
quint64 _shaderModified { 0 };
|
||||
uint64_t _shaderModified { 0 };
|
||||
NetworkShaderPointer _networkShader;
|
||||
bool _shaderDirty { true };
|
||||
bool _uniformsDirty { true };
|
||||
|
@ -152,11 +156,12 @@ protected:
|
|||
glm::vec3 _entityDimensions;
|
||||
glm::vec3 _entityPosition;
|
||||
glm::mat3 _entityOrientation;
|
||||
uint64_t _entityCreated;
|
||||
|
||||
private:
|
||||
void setupUniforms();
|
||||
|
||||
mutable quint64 _fadeStartTime { 0 };
|
||||
mutable uint64_t _fadeStartTime { 0 };
|
||||
mutable bool _hasStartedFade { false };
|
||||
mutable bool _isFading { false };
|
||||
bool _doesFade { true };
|
||||
|
|
|
@ -36,9 +36,11 @@ LAYOUT_STD140(binding=0) uniform standardInputsBuffer {
|
|||
// Offset 48
|
||||
float globalTime;
|
||||
// Offset 52
|
||||
int frameCount;
|
||||
float localCreatedTime;
|
||||
// Offset 56
|
||||
vec2 _spare1;
|
||||
float entityTime;
|
||||
// Offset 60
|
||||
int frameCount;
|
||||
// Offset 64, acts as vec4[4] for alignment purposes
|
||||
vec3 channelResolution[4];
|
||||
// Offset 128, acts as vec4[3] for alignment purposes
|
||||
|
@ -52,6 +54,8 @@ LAYOUT_STD140(binding=0) uniform standardInputsBuffer {
|
|||
#define iWorldPosition standardInputs.worldPosition
|
||||
#define iWorldScale standardInputs.worldScale
|
||||
#define iGlobalTime standardInputs.globalTime
|
||||
#define iLocalCreatedTime standardInputs.localCreatedTime
|
||||
#define iEntityTime standardInputs.entityTime
|
||||
#define iFrameCount standardInputs.frameCount
|
||||
#define iChannelResolution standardInputs.channelResolution
|
||||
#define iWorldOrientation standardInputs.worldOrientation
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
#include <ViewFrustum.h>
|
||||
#include <shaders/Shaders.h>
|
||||
|
||||
ProceduralSkybox::ProceduralSkybox() : graphics::Skybox() {
|
||||
ProceduralSkybox::ProceduralSkybox(uint64_t created) : graphics::Skybox(), _created(created) {
|
||||
_procedural._vertexSource = gpu::Shader::createVertex(shader::graphics::vertex::skybox)->getSource();
|
||||
_procedural._opaqueFragmentSource = shader::Source::get(shader::procedural::fragment::proceduralSkybox);
|
||||
// Adjust the pipeline state for background using the stencil test
|
||||
|
@ -59,7 +59,7 @@ void ProceduralSkybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum,
|
|||
batch.setModelTransform(Transform()); // only for Mac
|
||||
|
||||
auto& procedural = skybox._procedural;
|
||||
procedural.prepare(batch, glm::vec3(0), glm::vec3(1), glm::quat());
|
||||
procedural.prepare(batch, glm::vec3(0), glm::vec3(1), glm::quat(), skybox.getCreated());
|
||||
skybox.prepare(batch);
|
||||
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
class ProceduralSkybox: public graphics::Skybox {
|
||||
public:
|
||||
ProceduralSkybox();
|
||||
ProceduralSkybox(uint64_t created = 0);
|
||||
|
||||
void parse(const QString& userData) { _procedural.setProceduralData(ProceduralData::parse(userData)); }
|
||||
|
||||
|
@ -29,8 +29,11 @@ public:
|
|||
void render(gpu::Batch& batch, const ViewFrustum& frustum) const override;
|
||||
static void render(gpu::Batch& batch, const ViewFrustum& frustum, const ProceduralSkybox& skybox);
|
||||
|
||||
uint64_t getCreated() const { return _created; }
|
||||
|
||||
protected:
|
||||
mutable Procedural _procedural;
|
||||
uint64_t _created;
|
||||
};
|
||||
typedef std::shared_ptr< ProceduralSkybox > ProceduralSkyboxPointer;
|
||||
|
||||
|
|
|
@ -353,10 +353,11 @@ function AppUi(properties) {
|
|||
// Close if necessary, clean up any remaining handlers, and remove the button.
|
||||
GlobalServices.myUsernameChanged.disconnect(restartNotificationPoll);
|
||||
GlobalServices.findableByChanged.disconnect(restartNotificationPoll);
|
||||
that.tablet.screenChanged.disconnect(that.onScreenChanged);
|
||||
if (that.isOpen) {
|
||||
that.close();
|
||||
that.onScreenChanged("", "");
|
||||
}
|
||||
that.tablet.screenChanged.disconnect(that.onScreenChanged);
|
||||
if (that.button) {
|
||||
if (that.onClicked) {
|
||||
that.button.clicked.disconnect(that.onClicked);
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
"use strict";
|
||||
/*jslint vars:true, plusplus:true, forin:true*/
|
||||
/*global Tablet, Settings, Script, AvatarList, Users, Entities, MyAvatar, Camera, Overlays, Vec3, Quat, HMD, Controller, Account, UserActivityLogger, Messages, Window, XMLHttpRequest, print, location, getControllerWorldLocation*/
|
||||
/*global Tablet, Script, Entities, MyAvatar, Camera, Quat, HMD, Account, UserActivityLogger, Messages, print,
|
||||
AvatarBookmarks, ContextOverlay, AddressManager
|
||||
*/
|
||||
/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
|
||||
//
|
||||
// avatarapp.js
|
||||
|
@ -14,7 +16,6 @@
|
|||
|
||||
(function() { // BEGIN LOCAL_SCOPE
|
||||
|
||||
var request = Script.require('request').request;
|
||||
var AVATARAPP_QML_SOURCE = "hifi/AvatarApp.qml";
|
||||
Script.include("/~/system/libraries/controllers.js");
|
||||
|
||||
|
@ -22,7 +23,6 @@ Script.include("/~/system/libraries/controllers.js");
|
|||
var ENTRY_AVATAR_URL = "avatarUrl";
|
||||
var ENTRY_AVATAR_ENTITIES = "avatarEntites";
|
||||
var ENTRY_AVATAR_SCALE = "avatarScale";
|
||||
var ENTRY_VERSION = "version";
|
||||
|
||||
function executeLater(callback) {
|
||||
Script.setTimeout(callback, 300);
|
||||
|
@ -44,7 +44,7 @@ function getMyAvatarWearables() {
|
|||
}
|
||||
|
||||
var localRotation = entity.properties.localRotation;
|
||||
entity.properties.localRotationAngles = Quat.safeEulerAngles(localRotation)
|
||||
entity.properties.localRotationAngles = Quat.safeEulerAngles(localRotation);
|
||||
wearablesArray.push(entity);
|
||||
}
|
||||
|
||||
|
@ -52,7 +52,7 @@ function getMyAvatarWearables() {
|
|||
}
|
||||
|
||||
function getMyAvatar() {
|
||||
var avatar = {}
|
||||
var avatar = {};
|
||||
avatar[ENTRY_AVATAR_URL] = MyAvatar.skeletonModelURL;
|
||||
avatar[ENTRY_AVATAR_SCALE] = MyAvatar.getAvatarScale();
|
||||
avatar[ENTRY_AVATAR_ENTITIES] = getMyAvatarWearables();
|
||||
|
@ -68,7 +68,7 @@ function getMyAvatarSettings() {
|
|||
collisionSoundUrl : MyAvatar.collisionSoundURL,
|
||||
animGraphUrl: MyAvatar.getAnimGraphUrl(),
|
||||
animGraphOverrideUrl : MyAvatar.getAnimGraphOverrideUrl(),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function updateAvatarWearables(avatar, callback, wearablesOverride) {
|
||||
|
@ -76,7 +76,8 @@ function updateAvatarWearables(avatar, callback, wearablesOverride) {
|
|||
var wearables = wearablesOverride ? wearablesOverride : getMyAvatarWearables();
|
||||
avatar[ENTRY_AVATAR_ENTITIES] = wearables;
|
||||
|
||||
sendToQml({'method' : 'wearablesUpdated', 'wearables' : wearables})
|
||||
sendToQml({'method' : 'wearablesUpdated', 'wearables' : wearables});
|
||||
sendToQml({ 'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()});
|
||||
|
||||
if(callback)
|
||||
callback();
|
||||
|
@ -101,7 +102,7 @@ var adjustWearables = {
|
|||
this.opened = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var currentAvatarWearablesBackup = null;
|
||||
var currentAvatar = null;
|
||||
|
@ -112,7 +113,7 @@ function onTargetScaleChanged() {
|
|||
if(currentAvatar.scale !== MyAvatar.getAvatarScale()) {
|
||||
currentAvatar.scale = MyAvatar.getAvatarScale();
|
||||
if(notifyScaleChanged) {
|
||||
sendToQml({'method' : 'scaleChanged', 'value' : currentAvatar.scale})
|
||||
sendToQml({'method' : 'scaleChanged', 'value' : currentAvatar.scale});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -126,7 +127,7 @@ function onSkeletonModelURLChanged() {
|
|||
function onDominantHandChanged(dominantHand) {
|
||||
if(currentAvatarSettings.dominantHand !== dominantHand) {
|
||||
currentAvatarSettings.dominantHand = dominantHand;
|
||||
sendToQml({'method' : 'settingChanged', 'name' : 'dominantHand', 'value' : dominantHand})
|
||||
sendToQml({'method' : 'settingChanged', 'name' : 'dominantHand', 'value' : dominantHand});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -140,37 +141,37 @@ function onHmdAvatarAlignmentTypeChanged(type) {
|
|||
function onCollisionsEnabledChanged(enabled) {
|
||||
if(currentAvatarSettings.collisionsEnabled !== enabled) {
|
||||
currentAvatarSettings.collisionsEnabled = enabled;
|
||||
sendToQml({'method' : 'settingChanged', 'name' : 'collisionsEnabled', 'value' : enabled})
|
||||
sendToQml({'method' : 'settingChanged', 'name' : 'collisionsEnabled', 'value' : enabled});
|
||||
}
|
||||
}
|
||||
|
||||
function onOtherAvatarsCollisionsEnabledChanged(enabled) {
|
||||
if (currentAvatarSettings.otherAvatarsCollisionsEnabled !== enabled) {
|
||||
currentAvatarSettings.otherAvatarsCollisionsEnabled = enabled;
|
||||
sendToQml({ 'method': 'settingChanged', 'name': 'otherAvatarsCollisionsEnabled', 'value': enabled })
|
||||
sendToQml({ 'method': 'settingChanged', 'name': 'otherAvatarsCollisionsEnabled', 'value': enabled });
|
||||
}
|
||||
}
|
||||
|
||||
function onNewCollisionSoundUrl(url) {
|
||||
if(currentAvatarSettings.collisionSoundUrl !== url) {
|
||||
currentAvatarSettings.collisionSoundUrl = url;
|
||||
sendToQml({'method' : 'settingChanged', 'name' : 'collisionSoundUrl', 'value' : url})
|
||||
sendToQml({'method' : 'settingChanged', 'name' : 'collisionSoundUrl', 'value' : url});
|
||||
}
|
||||
}
|
||||
|
||||
function onAnimGraphUrlChanged(url) {
|
||||
if (currentAvatarSettings.animGraphUrl !== url) {
|
||||
currentAvatarSettings.animGraphUrl = url;
|
||||
sendToQml({ 'method': 'settingChanged', 'name': 'animGraphUrl', 'value': currentAvatarSettings.animGraphUrl })
|
||||
sendToQml({ 'method': 'settingChanged', 'name': 'animGraphUrl', 'value': currentAvatarSettings.animGraphUrl });
|
||||
|
||||
if (currentAvatarSettings.animGraphOverrideUrl !== MyAvatar.getAnimGraphOverrideUrl()) {
|
||||
currentAvatarSettings.animGraphOverrideUrl = MyAvatar.getAnimGraphOverrideUrl();
|
||||
sendToQml({ 'method': 'settingChanged', 'name': 'animGraphOverrideUrl', 'value': currentAvatarSettings.animGraphOverrideUrl })
|
||||
sendToQml({ 'method': 'settingChanged', 'name': 'animGraphOverrideUrl',
|
||||
'value': currentAvatarSettings.animGraphOverrideUrl });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var selectedAvatarEntityGrabbable = false;
|
||||
var selectedAvatarEntityID = null;
|
||||
var grabbedAvatarEntityChangeNotifier = null;
|
||||
|
||||
|
@ -178,6 +179,33 @@ var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/wallet/Wallet.qml";
|
|||
var MARKETPLACE_URL = Account.metaverseServerURL + "/marketplace";
|
||||
var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("html/js/marketplacesInject.js");
|
||||
|
||||
function getWearablesFrozen() {
|
||||
var wearablesFrozen = true;
|
||||
var wearablesArray = getMyAvatarWearables();
|
||||
wearablesArray.forEach(function(wearable) {
|
||||
if (isGrabbable(wearable.id)) {
|
||||
wearablesFrozen = false;
|
||||
}
|
||||
});
|
||||
|
||||
return wearablesFrozen;
|
||||
}
|
||||
|
||||
function freezeWearables() {
|
||||
var wearablesArray = getMyAvatarWearables();
|
||||
wearablesArray.forEach(function(wearable) {
|
||||
setGrabbable(wearable.id, false);
|
||||
});
|
||||
}
|
||||
|
||||
function unfreezeWearables() {
|
||||
var wearablesArray = getMyAvatarWearables();
|
||||
wearablesArray.forEach(function(wearable) {
|
||||
setGrabbable(wearable.id, true);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml.
|
||||
switch (message.method) {
|
||||
case 'getAvatars':
|
||||
|
@ -201,7 +229,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
|
|||
}
|
||||
}
|
||||
|
||||
sendToQml(message)
|
||||
sendToQml(message);
|
||||
break;
|
||||
case 'selectAvatar':
|
||||
Entities.addingWearable.disconnect(onAddingWearable);
|
||||
|
@ -209,6 +237,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
|
|||
AvatarBookmarks.loadBookmark(message.name);
|
||||
Entities.addingWearable.connect(onAddingWearable);
|
||||
Entities.deletingWearable.connect(onDeletingWearable);
|
||||
sendToQml({ 'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()});
|
||||
break;
|
||||
case 'deleteAvatar':
|
||||
AvatarBookmarks.removeBookmark(message.name);
|
||||
|
@ -228,11 +257,12 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
|
|||
message.properties.localRotationAngles = Quat.safeEulerAngles(message.properties.localRotation);
|
||||
}
|
||||
|
||||
sendToQml({'method' : 'wearableUpdated', 'entityID' : message.entityID, wearableIndex : message.wearableIndex, properties : message.properties, updateUI : false})
|
||||
sendToQml({'method' : 'wearableUpdated', 'entityID' : message.entityID, wearableIndex : message.wearableIndex, properties : message.properties, updateUI : false});
|
||||
break;
|
||||
case 'adjustWearablesOpened':
|
||||
currentAvatarWearablesBackup = getMyAvatarWearables();
|
||||
adjustWearables.setOpened(true);
|
||||
unfreezeWearables();
|
||||
|
||||
Entities.mousePressOnEntity.connect(onSelectedEntity);
|
||||
Messages.subscribe('Hifi-Object-Manipulation');
|
||||
|
@ -305,11 +335,11 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
|
|||
var currentAvatarURL = MyAvatar.getFullAvatarURLFromPreferences();
|
||||
if(currentAvatarURL !== message.avatarURL) {
|
||||
MyAvatar.useFullAvatarURL(message.avatarURL);
|
||||
sendToQml({'method' : 'externalAvatarApplied', 'avatarURL' : message.avatarURL})
|
||||
sendToQml({'method' : 'externalAvatarApplied', 'avatarURL' : message.avatarURL});
|
||||
}
|
||||
break;
|
||||
case 'navigate':
|
||||
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system")
|
||||
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||
if(message.url.indexOf('app://') === 0) {
|
||||
if (message.url === 'app://marketplace') {
|
||||
tablet.gotoWebScreen(MARKETPLACE_URL, MARKETPLACES_INJECT_SCRIPT_URL);
|
||||
|
@ -345,7 +375,17 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
|
|||
MyAvatar.collisionSoundURL = message.settings.collisionSoundUrl;
|
||||
MyAvatar.setAnimGraphOverrideUrl(message.settings.animGraphOverrideUrl);
|
||||
|
||||
settings = getMyAvatarSettings();
|
||||
currentAvatarSettings = getMyAvatarSettings();
|
||||
break;
|
||||
case 'toggleWearablesFrozen':
|
||||
var wearablesFrozen = getWearablesFrozen();
|
||||
wearablesFrozen = !wearablesFrozen;
|
||||
if (wearablesFrozen) {
|
||||
freezeWearables();
|
||||
} else {
|
||||
unfreezeWearables();
|
||||
}
|
||||
sendToQml({'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : wearablesFrozen});
|
||||
break;
|
||||
default:
|
||||
print('Unrecognized message from AvatarApp.qml');
|
||||
|
@ -366,9 +406,11 @@ function isGrabbable(entityID) {
|
|||
}
|
||||
|
||||
function setGrabbable(entityID, grabbable) {
|
||||
var properties = Entities.getEntityProperties(entityID, ['avatarEntity']);
|
||||
if (properties.avatarEntity) {
|
||||
Entities.editEntity(entityID, { grab: { grabbable: grabbable }});
|
||||
var properties = Entities.getEntityProperties(entityID, ['avatarEntity', 'grab.grabbable']);
|
||||
if (properties.avatarEntity && properties.grab.grabbable != grabbable) {
|
||||
var editProps = { grab: { grabbable: grabbable }};
|
||||
Entities.editEntity(entityID, editProps);
|
||||
sendToQml({ 'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -378,18 +420,7 @@ function ensureWearableSelected(entityID) {
|
|||
Script.clearInterval(grabbedAvatarEntityChangeNotifier);
|
||||
grabbedAvatarEntityChangeNotifier = null;
|
||||
}
|
||||
|
||||
if(selectedAvatarEntityID !== null) {
|
||||
setGrabbable(selectedAvatarEntityID, selectedAvatarEntityGrabbable);
|
||||
}
|
||||
|
||||
selectedAvatarEntityID = entityID;
|
||||
selectedAvatarEntityGrabbable = isGrabbable(entityID);
|
||||
|
||||
if(selectedAvatarEntityID !== null) {
|
||||
setGrabbable(selectedAvatarEntityID, true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -398,7 +429,7 @@ function ensureWearableSelected(entityID) {
|
|||
|
||||
function isEntityBeingWorn(entityID) {
|
||||
return Entities.getEntityProperties(entityID, 'parentID').parentID === MyAvatar.sessionUUID;
|
||||
};
|
||||
}
|
||||
|
||||
function onSelectedEntity(entityID, pointerEvent) {
|
||||
if(selectedAvatarEntityID !== entityID && isEntityBeingWorn(entityID))
|
||||
|
@ -413,12 +444,14 @@ function onAddingWearable(entityID) {
|
|||
updateAvatarWearables(currentAvatar, function() {
|
||||
sendToQml({'method' : 'updateAvatarInBookmarks'});
|
||||
});
|
||||
sendToQml({ 'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()});
|
||||
}
|
||||
|
||||
function onDeletingWearable(entityID) {
|
||||
updateAvatarWearables(currentAvatar, function() {
|
||||
sendToQml({'method' : 'updateAvatarInBookmarks'});
|
||||
});
|
||||
sendToQml({ 'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()});
|
||||
}
|
||||
|
||||
function handleWearableMessages(channel, message, sender) {
|
||||
|
@ -435,30 +468,35 @@ function handleWearableMessages(channel, message, sender) {
|
|||
}
|
||||
|
||||
var entityID = parsedMessage.grabbedEntity;
|
||||
|
||||
var updateWearable = function() {
|
||||
// for some reasons Entities.getEntityProperties returns more than was asked..
|
||||
var propertyNames = ['localPosition', 'localRotation', 'dimensions', 'naturalDimensions'];
|
||||
var entityProperties = Entities.getEntityProperties(selectedAvatarEntityID, propertyNames);
|
||||
var properties = {};
|
||||
|
||||
propertyNames.forEach(function(propertyName) {
|
||||
properties[propertyName] = entityProperties[propertyName];
|
||||
});
|
||||
|
||||
properties.localRotationAngles = Quat.safeEulerAngles(properties.localRotation);
|
||||
sendToQml({'method' : 'wearableUpdated', 'entityID' : selectedAvatarEntityID,
|
||||
'wearableIndex' : -1, 'properties' : properties, updateUI : true});
|
||||
|
||||
};
|
||||
|
||||
if(parsedMessage.action === 'grab') {
|
||||
if(selectedAvatarEntityID !== entityID) {
|
||||
ensureWearableSelected(entityID);
|
||||
sendToQml({'method' : 'selectAvatarEntity', 'entityID' : selectedAvatarEntityID});
|
||||
}
|
||||
|
||||
grabbedAvatarEntityChangeNotifier = Script.setInterval(function() {
|
||||
// for some reasons Entities.getEntityProperties returns more than was asked..
|
||||
var propertyNames = ['localPosition', 'localRotation', 'dimensions', 'naturalDimensions'];
|
||||
var entityProperties = Entities.getEntityProperties(selectedAvatarEntityID, propertyNames);
|
||||
var properties = {}
|
||||
|
||||
propertyNames.forEach(function(propertyName) {
|
||||
properties[propertyName] = entityProperties[propertyName];
|
||||
})
|
||||
|
||||
properties.localRotationAngles = Quat.safeEulerAngles(properties.localRotation);
|
||||
sendToQml({'method' : 'wearableUpdated', 'entityID' : selectedAvatarEntityID, 'wearableIndex' : -1, 'properties' : properties, updateUI : true})
|
||||
|
||||
}, 1000);
|
||||
grabbedAvatarEntityChangeNotifier = Script.setInterval(updateWearable, 1000);
|
||||
} else if(parsedMessage.action === 'release') {
|
||||
if(grabbedAvatarEntityChangeNotifier !== null) {
|
||||
Script.clearInterval(grabbedAvatarEntityChangeNotifier);
|
||||
grabbedAvatarEntityChangeNotifier = null;
|
||||
updateWearable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -481,8 +519,8 @@ function onBookmarkDeleted(bookmarkName) {
|
|||
function onBookmarkAdded(bookmarkName) {
|
||||
var bookmark = AvatarBookmarks.getBookmark(bookmarkName);
|
||||
bookmark.avatarEntites.forEach(function(avatarEntity) {
|
||||
avatarEntity.properties.localRotationAngles = Quat.safeEulerAngles(avatarEntity.properties.localRotation)
|
||||
})
|
||||
avatarEntity.properties.localRotationAngles = Quat.safeEulerAngles(avatarEntity.properties.localRotation);
|
||||
});
|
||||
|
||||
sendToQml({ 'method': 'bookmarkAdded', 'bookmarkName': bookmarkName, 'bookmark': bookmark });
|
||||
}
|
||||
|
@ -601,14 +639,8 @@ function onTabletScreenChanged(type, url) {
|
|||
onAvatarAppScreen = onAvatarAppScreenNow;
|
||||
|
||||
if(onAvatarAppScreenNow) {
|
||||
var message = {
|
||||
'method' : 'initialize',
|
||||
'data' : {
|
||||
'jointNames' : MyAvatar.getJointNames()
|
||||
}
|
||||
};
|
||||
|
||||
sendToQml(message)
|
||||
sendToQml({ 'method' : 'initialize', 'data' : { jointNames : MyAvatar.getJointNames() }});
|
||||
sendToQml({ 'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -377,6 +377,9 @@ function deleteSendMoneyParticleEffect() {
|
|||
}
|
||||
|
||||
function onUsernameChanged() {
|
||||
if (ui.checkIsOpen()) {
|
||||
ui.open(WALLET_QML_SOURCE);
|
||||
}
|
||||
}
|
||||
|
||||
var MARKETPLACE_QML_PATH = "hifi/commerce/marketplace/Marketplace.qml";
|
||||
|
|
|
@ -341,8 +341,6 @@ entityIsGrabbable = function (eigProps) {
|
|||
var grabbable = getGrabbableData(eigProps).grabbable;
|
||||
if (!grabbable ||
|
||||
eigProps.locked ||
|
||||
isAnothersAvatarEntity(eigProps) ||
|
||||
isAnothersChildEntity(eigProps) ||
|
||||
FORBIDDEN_GRAB_TYPES.indexOf(eigProps.type) >= 0) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -615,11 +615,10 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) {
|
|||
openMarketplace(message.itemId, message.itemEdition);
|
||||
break;
|
||||
case 'passphrasePopup_cancelClicked':
|
||||
case 'needsLogIn_cancelClicked':
|
||||
// Should/must NOT check for wallet setup.
|
||||
openMarketplace();
|
||||
break;
|
||||
case 'needsLogIn_loginClicked':
|
||||
case 'marketplace_loginClicked':
|
||||
openLoginWindow();
|
||||
break;
|
||||
case 'disableHmdPreview':
|
||||
|
|
0
tools/nitpick/compiledResources/.placeholder
Normal file
0
tools/nitpick/compiledResources/.placeholder
Normal file
Binary file not shown.
|
@ -9,6 +9,7 @@
|
|||
//
|
||||
#include "TestRunnerMobile.h"
|
||||
|
||||
#include <QNetworkInterface>
|
||||
#include <QThread>
|
||||
#include <QtWidgets/QMessageBox>
|
||||
#include <QtWidgets/QFileDialog>
|
||||
|
@ -69,13 +70,25 @@ void TestRunnerMobile::connectDevice() {
|
|||
_adbInterface = new AdbInterface();
|
||||
}
|
||||
|
||||
// Get list of devices
|
||||
QString devicesFullFilename{ _workingFolder + "/devices.txt" };
|
||||
QString command = _adbInterface->getAdbCommand() + " devices -l > " + devicesFullFilename;
|
||||
appendLog(command);
|
||||
system(command.toStdString().c_str());
|
||||
|
||||
if (!QFile::exists(devicesFullFilename)) {
|
||||
QMessageBox::critical(0, "Internal error", "devicesFullFilename not found");
|
||||
QMessageBox::critical(0, "Internal error", "devices.txt not found");
|
||||
exit (-1);
|
||||
}
|
||||
|
||||
// Get device IP address
|
||||
QString ifconfigFullFilename{ _workingFolder + "/ifconfig.txt" };
|
||||
command = _adbInterface->getAdbCommand() + " shell ifconfig > " + ifconfigFullFilename;
|
||||
appendLog(command);
|
||||
system(command.toStdString().c_str());
|
||||
|
||||
if (!QFile::exists(ifconfigFullFilename)) {
|
||||
QMessageBox::critical(0, "Internal error", "ifconfig.txt not found");
|
||||
exit (-1);
|
||||
}
|
||||
|
||||
|
@ -86,6 +99,8 @@ void TestRunnerMobile::connectDevice() {
|
|||
QString line2 = devicesFile.readLine();
|
||||
|
||||
const QString DEVICE{ "device" };
|
||||
const QString MODEL{ "model" };
|
||||
|
||||
if (line2.contains("unauthorized")) {
|
||||
QMessageBox::critical(0, "Unauthorized device detected", "Please allow USB debugging on device");
|
||||
} else if (line2.contains(DEVICE)) {
|
||||
|
@ -98,13 +113,20 @@ void TestRunnerMobile::connectDevice() {
|
|||
QStringList tokens = line2.split(QRegExp("[\r\n\t ]+"));
|
||||
QString deviceID = tokens[0];
|
||||
|
||||
QString modelID = tokens[3].split(':')[1];
|
||||
QString modelName = "UNKNOWN";
|
||||
if (modelNames.count(modelID) == 1) {
|
||||
modelName = modelNames[modelID];
|
||||
// Find the model entry
|
||||
_modelName = "UNKNOWN";
|
||||
for (int i = 0; i < tokens.size(); ++i) {
|
||||
if (tokens[i].contains(MODEL)) {
|
||||
QString modelID = tokens[i].split(':')[1];
|
||||
|
||||
if (modelNames.count(modelID) == 1) {
|
||||
_modelName = modelNames[modelID];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_detectedDeviceLabel->setText(modelName + " [" + deviceID + "]");
|
||||
_detectedDeviceLabel->setText(_modelName + " [" + deviceID + "]");
|
||||
_pullFolderButton->setEnabled(true);
|
||||
_folderLineEdit->setEnabled(true);
|
||||
_downloadAPKPushbutton->setEnabled(true);
|
||||
|
@ -191,14 +213,28 @@ void TestRunnerMobile::runInterface() {
|
|||
? QString("https://raw.githubusercontent.com/") + nitpick->getSelectedUser() + "/hifi_tests/" + nitpick->getSelectedBranch() + "/tests/testRecursive.js"
|
||||
: _scriptURL->text();
|
||||
|
||||
// Quest and Android have different commands to run interface
|
||||
QString startCommand;
|
||||
if (_modelName == "Quest") {
|
||||
startCommand = "io.highfidelity.questInterface/.PermissionsChecker";
|
||||
} else {
|
||||
startCommand = "io.highfidelity.hifiinterface/.PermissionChecker";
|
||||
}
|
||||
|
||||
QString serverIP { getServerIP() };
|
||||
if (serverIP == NETWORK_NOT_FOUND) {
|
||||
_runInterfacePushbutton->setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
QString command = _adbInterface->getAdbCommand() +
|
||||
" shell am start -n io.highfidelity.hifiinterface/.PermissionChecker" +
|
||||
" --es args \\\"" +
|
||||
" --url file:///~/serverless/tutorial.json" +
|
||||
" --no-updater" +
|
||||
" --no-login-suggestion" +
|
||||
" --testScript " + testScript + " quitWhenFinished" +
|
||||
" --testResultsLocation /sdcard/snapshots" +
|
||||
" shell am start -n " + startCommand +
|
||||
" --es args \\\"" +
|
||||
" --url hifi://" + serverIP + "/0,0,0"
|
||||
" --no-updater" +
|
||||
" --no-login-suggestion" +
|
||||
" --testScript " + testScript + " quitWhenFinished" +
|
||||
" --testResultsLocation /sdcard/snapshots" +
|
||||
"\\\"";
|
||||
|
||||
appendLog(command);
|
||||
|
@ -220,3 +256,85 @@ void TestRunnerMobile::pullFolder() {
|
|||
_statusLabel->setText("Pull complete");
|
||||
#endif
|
||||
}
|
||||
|
||||
QString TestRunnerMobile::getServerIP() {
|
||||
// Get device IP (ifconfig.txt was created when connecting)
|
||||
QFile ifconfigFile{ _workingFolder + "/ifconfig.txt" };
|
||||
if (!ifconfigFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),
|
||||
"Could not open 'ifconfig.txt'");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
QTextStream stream(&ifconfigFile);
|
||||
QString line = ifconfigFile.readLine();
|
||||
while (!line.isNull()) {
|
||||
// The device IP is in the line following the "wlan0" line
|
||||
if (line.left(6) == "wlan0 ") {
|
||||
break;
|
||||
}
|
||||
line = ifconfigFile.readLine();
|
||||
}
|
||||
|
||||
// The following line looks like this "inet addr:192.168.0.15 Bcast:192.168.0.255 Mask:255.255.255.0"
|
||||
// Extract the address and mask
|
||||
line = ifconfigFile.readLine();
|
||||
QStringList lineParts = line.split(':');
|
||||
if (lineParts.size() < 4) {
|
||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),
|
||||
"IP address line not in expected format: " + line + "(check that device WIFI is on)");
|
||||
|
||||
return NETWORK_NOT_FOUND;
|
||||
}
|
||||
|
||||
qint64 deviceIP = convertToBinary(lineParts[1].split(' ')[0]);
|
||||
qint64 deviceMask = convertToBinary(lineParts[3].split(' ')[0]);
|
||||
qint64 deviceSubnet = deviceMask & deviceIP;
|
||||
|
||||
// The device needs to be on the same subnet as the server
|
||||
// To find which of our IPs is the server - choose the 1st that is on the same subnet
|
||||
// If more than one found then report an error
|
||||
|
||||
QString serverIP;
|
||||
|
||||
QList<QNetworkInterface> interfaces = QNetworkInterface::allInterfaces();
|
||||
for (int i = 0; i < interfaces.count(); i++) {
|
||||
QList<QNetworkAddressEntry> entries = interfaces.at(i).addressEntries();
|
||||
for (int j = 0; j < entries.count(); j++) {
|
||||
if (entries.at(j).ip().protocol() == QAbstractSocket::IPv4Protocol) {
|
||||
qint64 hostIP = convertToBinary(entries.at(j).ip().toString());
|
||||
qint64 hostMask = convertToBinary(entries.at(j).netmask().toString());
|
||||
qint64 hostSubnet = hostMask & hostIP;
|
||||
|
||||
if (hostSubnet == deviceSubnet) {
|
||||
if (!serverIP.isNull()) {
|
||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),
|
||||
"Cannot identify server IP (multiple interfaces on device submask)");
|
||||
return QString("CANNOT IDENTIFY SERVER IP");
|
||||
} else {
|
||||
union {
|
||||
uint32_t ip;
|
||||
uint8_t bytes[4];
|
||||
} u;
|
||||
u.ip = hostIP;
|
||||
|
||||
serverIP = QString::number(u.bytes[3]) + '.' + QString::number(u.bytes[2]) + '.' + QString::number(u.bytes[1]) + '.' + QString::number(u.bytes[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ifconfigFile.close();
|
||||
|
||||
return serverIP;
|
||||
}
|
||||
|
||||
qint64 TestRunnerMobile::convertToBinary(const QString& str) {
|
||||
QString binary;
|
||||
foreach (const QString& s, str.split(".")) {
|
||||
binary += QString::number(s.toInt(), 2).rightJustified(8, '0');
|
||||
}
|
||||
|
||||
return binary.toLongLong(NULL, 2);
|
||||
}
|
||||
|
|
|
@ -52,6 +52,9 @@ public:
|
|||
|
||||
void pullFolder();
|
||||
|
||||
QString getServerIP();
|
||||
qint64 convertToBinary (const QString& str);
|
||||
|
||||
private:
|
||||
QPushButton* _connectDeviceButton;
|
||||
QPushButton* _pullFolderButton;
|
||||
|
@ -75,5 +78,9 @@ private:
|
|||
std::map<QString, QString> modelNames;
|
||||
|
||||
AdbInterface* _adbInterface;
|
||||
|
||||
QString _modelName;
|
||||
|
||||
QString NETWORK_NOT_FOUND{ "NETWORK NOT FOUND"};
|
||||
};
|
||||
#endif
|
||||
|
|
|
@ -60,5 +60,5 @@ const double R_Y = 0.212655f;
|
|||
const double G_Y = 0.715158f;
|
||||
const double B_Y = 0.072187f;
|
||||
|
||||
const QString nitpickVersion { "v3.1.4" };
|
||||
const QString nitpickVersion{ "v3.1.5" };
|
||||
#endif // hifi_common_h
|
Loading…
Reference in a new issue