Merge branch 'master' into newAudioMuteStates

This commit is contained in:
Wayne Chen 2019-04-01 12:24:46 -07:00 committed by GitHub
commit abe5748e60
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
63 changed files with 821 additions and 392 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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
}
]
},

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -113,6 +113,7 @@ Rectangle {
} else if (prop === 'dimensions') {
scalespinner.set(wearable[prop].x / wearable.naturalDimensions.x);
}
modified = true;
}
}

View file

@ -664,7 +664,7 @@ Rectangle {
text: "LOG IN"
onClicked: {
sendToScript({method: 'needsLogIn_loginClicked'});
sendToScript({method: 'marketplace_loginClicked'});
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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