Merge branch 'master' into feature/hmd-avatar-alignment-type

This commit is contained in:
Anthony Thibault 2019-01-17 09:36:26 -08:00
commit 39cf50def0
139 changed files with 2345 additions and 935 deletions

View file

@ -504,7 +504,7 @@ void AudioMixerSlave::addStream(AudioMixerClientData::MixableStream& mixableStre
float distance = glm::max(glm::length(relativePosition), EPSILON);
float azimuth = isEcho ? 0.0f : computeAzimuth(listeningNodeStream, listeningNodeStream, relativePosition);
float gain = 1.0f;
float gain = masterListenerGain;
if (!isSoloing) {
gain = computeGain(masterListenerGain, listeningNodeStream, *streamToAdd, relativePosition, distance, isEcho);
}

View file

@ -56,6 +56,7 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) :
packetReceiver.registerListener(PacketType::RequestsDomainListData, this, "handleRequestsDomainListDataPacket");
packetReceiver.registerListener(PacketType::AvatarIdentityRequest, this, "handleAvatarIdentityRequestPacket");
packetReceiver.registerListener(PacketType::SetAvatarTraits, this, "queueIncomingPacket");
packetReceiver.registerListener(PacketType::BulkAvatarTraitsAck, this, "queueIncomingPacket");
packetReceiver.registerListenerForTypes({
PacketType::ReplicatedAvatarIdentity,
@ -767,6 +768,9 @@ void AvatarMixer::sendStatsPacket() {
float averageOverBudgetAvatars = averageNodes ? aggregateStats.overBudgetAvatars / averageNodes : 0.0f;
slavesAggregatObject["sent_3_averageOverBudgetAvatars"] = TIGHT_LOOP_STAT(averageOverBudgetAvatars);
slavesAggregatObject["sent_4_averageDataBytes"] = TIGHT_LOOP_STAT(aggregateStats.numDataBytesSent);
slavesAggregatObject["sent_5_averageTraitsBytes"] = TIGHT_LOOP_STAT(aggregateStats.numTraitsBytesSent);
slavesAggregatObject["sent_6_averageIdentityBytes"] = TIGHT_LOOP_STAT(aggregateStats.numIdentityBytesSent);
slavesAggregatObject["timing_1_processIncomingPackets"] = TIGHT_LOOP_STAT_UINT64(aggregateStats.processIncomingPacketsElapsedTime);
slavesAggregatObject["timing_2_ignoreCalculation"] = TIGHT_LOOP_STAT_UINT64(aggregateStats.ignoreCalculationElapsedTime);
@ -775,7 +779,7 @@ void AvatarMixer::sendStatsPacket() {
slavesAggregatObject["timing_5_packetSending"] = TIGHT_LOOP_STAT_UINT64(aggregateStats.packetSendingElapsedTime);
slavesAggregatObject["timing_6_jobElapsedTime"] = TIGHT_LOOP_STAT_UINT64(aggregateStats.jobElapsedTime);
statsObject["slaves_aggregate"] = slavesAggregatObject;
statsObject["slaves_aggregate (per frame)"] = slavesAggregatObject;
_handleViewFrustumPacketElapsedTime = 0;
_handleAvatarIdentityPacketElapsedTime = 0;
@ -800,7 +804,8 @@ void AvatarMixer::sendStatsPacket() {
// add the key to ask the domain-server for a username replacement, if it has it
avatarStats[USERNAME_UUID_REPLACEMENT_STATS_KEY] = uuidStringWithoutCurlyBraces(node->getUUID());
avatarStats[NODE_OUTBOUND_KBPS_STAT_KEY] = node->getOutboundKbps();
float outboundAvatarDataKbps = node->getOutboundKbps();
avatarStats[NODE_OUTBOUND_KBPS_STAT_KEY] = outboundAvatarDataKbps;
avatarStats[NODE_INBOUND_KBPS_STAT_KEY] = node->getInboundKbps();
AvatarMixerClientData* clientData = static_cast<AvatarMixerClientData*>(node->getLinkedData());
@ -811,7 +816,7 @@ void AvatarMixer::sendStatsPacket() {
// add the diff between the full outbound bandwidth and the measured bandwidth for AvatarData send only
avatarStats["delta_full_vs_avatar_data_kbps"] =
avatarStats[NODE_OUTBOUND_KBPS_STAT_KEY].toDouble() - avatarStats[OUTBOUND_AVATAR_DATA_STATS_KEY].toDouble();
(double)outboundAvatarDataKbps - avatarStats[OUTBOUND_AVATAR_DATA_STATS_KEY].toDouble();
}
}

View file

@ -19,9 +19,8 @@
#include "AvatarMixerSlave.h"
AvatarMixerClientData::AvatarMixerClientData(const QUuid& nodeID, Node::LocalID nodeLocalID) :
NodeData(nodeID, nodeLocalID)
{
AvatarMixerClientData::AvatarMixerClientData(const QUuid& nodeID, Node::LocalID nodeLocalID) :
NodeData(nodeID, nodeLocalID) {
// in case somebody calls getSessionUUID on the AvatarData instance, make sure it has the right ID
_avatar->setID(nodeID);
}
@ -68,6 +67,9 @@ int AvatarMixerClientData::processPackets(const SlaveSharedData& slaveSharedData
case PacketType::SetAvatarTraits:
processSetTraitsMessage(*packet, slaveSharedData, *node);
break;
case PacketType::BulkAvatarTraitsAck:
processBulkAvatarTraitsAckMessage(*packet);
break;
default:
Q_UNREACHABLE();
}
@ -79,12 +81,11 @@ int AvatarMixerClientData::processPackets(const SlaveSharedData& slaveSharedData
}
int AvatarMixerClientData::parseData(ReceivedMessage& message) {
// pull the sequence number from the data first
uint16_t sequenceNumber;
message.readPrimitive(&sequenceNumber);
if (sequenceNumber < _lastReceivedSequenceNumber && _lastReceivedSequenceNumber != UINT16_MAX) {
incrementNumOutOfOrderSends();
}
@ -95,7 +96,8 @@ int AvatarMixerClientData::parseData(ReceivedMessage& message) {
}
void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message,
const SlaveSharedData& slaveSharedData, Node& sendingNode) {
const SlaveSharedData& slaveSharedData,
Node& sendingNode) {
// pull the trait version from the message
AvatarTraits::TraitVersion packetTraitVersion;
message.readPrimitive(&packetTraitVersion);
@ -134,7 +136,7 @@ void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message,
AvatarTraits::TraitInstanceID instanceID = QUuid::fromRfc4122(message.readWithoutCopy(NUM_BYTES_RFC4122_UUID));
if (message.getBytesLeftToRead() == 0) {
qWarning () << "Received an instanced trait with no size from" << message.getSenderSockAddr();
qWarning() << "Received an instanced trait with no size from" << message.getSenderSockAddr();
break;
}
@ -142,7 +144,8 @@ void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message,
message.readPrimitive(&traitSize);
if (traitSize < -1 || traitSize > message.getBytesLeftToRead()) {
qWarning() << "Refusing to process instanced trait of size" << traitSize << "from" << message.getSenderSockAddr();
qWarning() << "Refusing to process instanced trait of size" << traitSize << "from"
<< message.getSenderSockAddr();
break;
}
@ -154,7 +157,7 @@ void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message,
if (traitSize == AvatarTraits::DELETED_TRAIT_SIZE) {
_avatar->processDeletedTraitInstance(traitType, instanceID);
// Mixer doesn't need deleted IDs.
_avatar->getAndClearRecentlyDetachedIDs();
_avatar->getAndClearRecentlyRemovedIDs();
// to track a deleted instance but keep version information
// the avatar mixer uses the negative value of the sent version
@ -169,7 +172,8 @@ void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message,
message.seek(message.getPosition() + traitSize);
}
} else {
qWarning() << "Refusing to process traits packet with instanced trait of unprocessable type from" << message.getSenderSockAddr();
qWarning() << "Refusing to process traits packet with instanced trait of unprocessable type from"
<< message.getSenderSockAddr();
break;
}
}
@ -180,7 +184,63 @@ void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message,
}
}
void AvatarMixerClientData::checkSkeletonURLAgainstWhitelist(const SlaveSharedData &slaveSharedData, Node& sendingNode,
void AvatarMixerClientData::processBulkAvatarTraitsAckMessage(ReceivedMessage& message) {
// Avatar Traits flow control marks each outgoing avatar traits packet with a
// sequence number. The mixer caches the traits sent in the traits packet.
// Until an ack with the sequence number comes back, all updates to _traits
// in that packet_ are ignored. Updates to traits not in that packet will
// be sent.
// Look up the avatar/trait data associated with this ack and update the 'last ack' list
// with it.
AvatarTraits::TraitMessageSequence seq;
message.readPrimitive(&seq);
auto sentAvatarTraitVersions = _perNodePendingTraitVersions.find(seq);
if (sentAvatarTraitVersions != _perNodePendingTraitVersions.end()) {
for (auto& perNodeTraitVersions : sentAvatarTraitVersions->second) {
auto& nodeId = perNodeTraitVersions.first;
auto& traitVersions = perNodeTraitVersions.second;
// For each trait that was sent in the traits packet,
// update the 'acked' trait version. Traits not
// sent in the traits packet keep their version.
// process simple traits
auto simpleReceivedIt = traitVersions.simpleCBegin();
while (simpleReceivedIt != traitVersions.simpleCEnd()) {
if (*simpleReceivedIt != AvatarTraits::DEFAULT_TRAIT_VERSION) {
auto traitType = static_cast<AvatarTraits::TraitType>(std::distance(traitVersions.simpleCBegin(), simpleReceivedIt));
_perNodeAckedTraitVersions[nodeId][traitType] = *simpleReceivedIt;
}
simpleReceivedIt++;
}
// process instanced traits
auto instancedSentIt = traitVersions.instancedCBegin();
while (instancedSentIt != traitVersions.instancedCEnd()) {
auto traitType = instancedSentIt->traitType;
for (auto& sentInstance : instancedSentIt->instances) {
auto instanceID = sentInstance.id;
const auto sentVersion = sentInstance.value;
_perNodeAckedTraitVersions[nodeId].instanceInsert(traitType, instanceID, sentVersion);
}
instancedSentIt++;
}
}
_perNodePendingTraitVersions.erase(sentAvatarTraitVersions);
} else {
// This can happen either the BulkAvatarTraits was sent with no simple traits,
// or if the avatar mixer restarts while there are pending
// BulkAvatarTraits messages in-flight.
if (seq > getTraitsMessageSequence()) {
qWarning() << "Received BulkAvatarTraitsAck with future seq (potential avatar mixer restart) " << seq << " from "
<< message.getSenderSockAddr();
}
}
}
void AvatarMixerClientData::checkSkeletonURLAgainstWhitelist(const SlaveSharedData& slaveSharedData,
Node& sendingNode,
AvatarTraits::TraitVersion traitVersion) {
const auto& whitelist = slaveSharedData.skeletonURLWhitelist;
@ -282,14 +342,18 @@ void AvatarMixerClientData::removeFromRadiusIgnoringSet(const QUuid& other) {
void AvatarMixerClientData::resetSentTraitData(Node::LocalID nodeLocalID) {
_lastSentTraitsTimestamps[nodeLocalID] = TraitsCheckTimestamp();
_sentTraitVersions[nodeLocalID].reset();
_perNodeSentTraitVersions[nodeLocalID].reset();
_perNodeAckedTraitVersions[nodeLocalID].reset();
for (auto && pendingTraitVersions : _perNodePendingTraitVersions) {
pendingTraitVersions.second[nodeLocalID].reset();
}
}
void AvatarMixerClientData::readViewFrustumPacket(const QByteArray& message) {
_currentViewFrustums.clear();
auto sourceBuffer = reinterpret_cast<const unsigned char*>(message.constData());
uint8_t numFrustums = 0;
memcpy(&numFrustums, sourceBuffer, sizeof(numFrustums));
sourceBuffer += sizeof(numFrustums);
@ -317,7 +381,8 @@ void AvatarMixerClientData::loadJSONStats(QJsonObject& jsonObject) const {
jsonObject["total_num_out_of_order_sends"] = _numOutOfOrderSends;
jsonObject[OUTBOUND_AVATAR_DATA_STATS_KEY] = getOutboundAvatarDataKbps();
jsonObject[INBOUND_AVATAR_DATA_STATS_KEY] = _avatar->getAverageBytesReceivedPerSecond() / (float) BYTES_PER_KILOBIT;
jsonObject[OUTBOUND_AVATAR_TRAITS_STATS_KEY] = getOutboundAvatarTraitsKbps();
jsonObject[INBOUND_AVATAR_DATA_STATS_KEY] = _avatar->getAverageBytesReceivedPerSecond() / (float)BYTES_PER_KILOBIT;
jsonObject["av_data_receive_rate"] = _avatar->getReceiveRate();
jsonObject["recent_other_av_in_view"] = _recentOtherAvatarsInView;
@ -338,5 +403,5 @@ void AvatarMixerClientData::cleanupKilledNode(const QUuid&, Node::LocalID nodeLo
removeLastBroadcastSequenceNumber(nodeLocalID);
removeLastBroadcastTime(nodeLocalID);
_lastSentTraitsTimestamps.erase(nodeLocalID);
_sentTraitVersions.erase(nodeLocalID);
_perNodeSentTraitVersions.erase(nodeLocalID);
}

View file

@ -32,6 +32,7 @@
#include <shared/ConicalViewFrustum.h>
const QString OUTBOUND_AVATAR_DATA_STATS_KEY = "outbound_av_data_kbps";
const QString OUTBOUND_AVATAR_TRAITS_STATS_KEY = "outbound_av_traits_kbps";
const QString INBOUND_AVATAR_DATA_STATS_KEY = "inbound_av_data_kbps";
struct SlaveSharedData;
@ -42,6 +43,7 @@ public:
AvatarMixerClientData(const QUuid& nodeID, Node::LocalID nodeLocalID);
virtual ~AvatarMixerClientData() {}
using HRCTime = p_high_resolution_clock::time_point;
using PerNodeTraitVersions = std::unordered_map<Node::LocalID, AvatarTraits::TraitVersions>;
int parseData(ReceivedMessage& message) override;
AvatarData& getAvatar() { return *_avatar; }
@ -85,10 +87,15 @@ public:
void incrementNumFramesSinceFRDAdjustment() { ++_numFramesSinceAdjustment; }
void resetNumFramesSinceFRDAdjustment() { _numFramesSinceAdjustment = 0; }
void recordSentAvatarData(int numBytes) { _avgOtherAvatarDataRate.updateAverage((float) numBytes); }
void recordSentAvatarData(int numDataBytes, int numTraitsBytes = 0) {
_avgOtherAvatarDataRate.updateAverage(numDataBytes);
_avgOtherAvatarTraitsRate.updateAverage(numTraitsBytes);
}
float getOutboundAvatarDataKbps() const
{ return _avgOtherAvatarDataRate.getAverageSampleValuePerSecond() / (float) BYTES_PER_KILOBIT; }
float getOutboundAvatarTraitsKbps() const
{ return _avgOtherAvatarTraitsRate.getAverageSampleValuePerSecond() / BYTES_PER_KILOBIT; }
void loadJSONStats(QJsonObject& jsonObject) const;
@ -124,6 +131,7 @@ public:
int processPackets(const SlaveSharedData& slaveSharedData); // returns number of packets processed
void processSetTraitsMessage(ReceivedMessage& message, const SlaveSharedData& slaveSharedData, Node& sendingNode);
void processBulkAvatarTraitsAckMessage(ReceivedMessage& message);
void checkSkeletonURLAgainstWhitelist(const SlaveSharedData& slaveSharedData, Node& sendingNode,
AvatarTraits::TraitVersion traitVersion);
@ -138,7 +146,14 @@ public:
void setLastOtherAvatarTraitsSendPoint(Node::LocalID otherAvatar, TraitsCheckTimestamp sendPoint)
{ _lastSentTraitsTimestamps[otherAvatar] = sendPoint; }
AvatarTraits::TraitVersions& getLastSentTraitVersions(Node::LocalID otherAvatar) { return _sentTraitVersions[otherAvatar]; }
AvatarTraits::TraitMessageSequence getTraitsMessageSequence() const { return _currentTraitsMessageSequence; }
AvatarTraits::TraitMessageSequence nextTraitsMessageSequence() { return ++_currentTraitsMessageSequence; }
AvatarTraits::TraitVersions& getPendingTraitVersions(AvatarTraits::TraitMessageSequence seq, Node::LocalID otherId) {
return _perNodePendingTraitVersions[seq][otherId];
}
AvatarTraits::TraitVersions& getLastSentTraitVersions(Node::LocalID otherAvatar) { return _perNodeSentTraitVersions[otherAvatar]; }
AvatarTraits::TraitVersions& getLastAckedTraitVersions(Node::LocalID otherAvatar) { return _perNodeAckedTraitVersions[otherAvatar]; }
void resetSentTraitData(Node::LocalID nodeID);
@ -171,6 +186,7 @@ private:
int _numOutOfOrderSends = 0;
SimpleMovingAverage _avgOtherAvatarDataRate;
SimpleMovingAverage _avgOtherAvatarTraitsRate;
std::vector<QUuid> _radiusIgnoredOthers;
ConicalViewFrustums _currentViewFrustums;
@ -183,8 +199,27 @@ private:
AvatarTraits::TraitVersions _lastReceivedTraitVersions;
TraitsCheckTimestamp _lastReceivedTraitsChange;
AvatarTraits::TraitMessageSequence _currentTraitsMessageSequence{ 0 };
// Cache of trait versions sent in a given packet (indexed by sequence number)
// When an ack is received, the sequence number in the ack is used to look up
// the sent trait versions and they are copied to _perNodeAckedTraitVersions.
// We remember the data in _perNodePendingTraitVersions instead of requiring
// the client to return all of the versions for each trait it received in a given packet,
// reducing the size of the ack packet.
std::unordered_map<AvatarTraits::TraitMessageSequence, PerNodeTraitVersions> _perNodePendingTraitVersions;
// Versions of traits that have been acked, which will be compared to incoming
// trait updates. Incoming updates going to a given node will be ignored if
// the ack for the previous packet (containing those versions) has not been
// received.
PerNodeTraitVersions _perNodeAckedTraitVersions;
std::unordered_map<Node::LocalID, TraitsCheckTimestamp> _lastSentTraitsTimestamps;
std::unordered_map<Node::LocalID, AvatarTraits::TraitVersions> _sentTraitVersions;
// cache of traits sent to a node which are compared to incoming traits to
// prevent sending traits that have already been sent.
PerNodeTraitVersions _perNodeSentTraitVersions;
std::atomic_bool _isIgnoreRadiusEnabled { false };
};

View file

@ -73,52 +73,82 @@ int AvatarMixerSlave::sendIdentityPacket(NLPacketList& packetList, const AvatarM
QByteArray individualData = nodeData->getConstAvatarData()->identityByteArray();
individualData.replace(0, NUM_BYTES_RFC4122_UUID, nodeData->getNodeID().toRfc4122()); // FIXME, this looks suspicious
packetList.write(individualData);
_stats.numIdentityPackets++;
_stats.numIdentityPacketsSent++;
_stats.numIdentityBytesSent += individualData.size();
return individualData.size();
} else {
return 0;
}
}
qint64 AvatarMixerSlave::addTraitsNodeHeader(AvatarMixerClientData* listeningNodeData,
const AvatarMixerClientData* sendingNodeData,
NLPacketList& traitsPacketList,
qint64 bytesWritten) {
if (bytesWritten == 0) {
if (traitsPacketList.getNumPackets() == 0) {
// This is the beginning of the traits packet, write out the sequence number.
bytesWritten += traitsPacketList.writePrimitive(listeningNodeData->nextTraitsMessageSequence());
}
// This is the beginning of the traits for a node, write out the node id
bytesWritten += traitsPacketList.write(sendingNodeData->getNodeID().toRfc4122());
}
return bytesWritten;
}
qint64 AvatarMixerSlave::addChangedTraitsToBulkPacket(AvatarMixerClientData* listeningNodeData,
const AvatarMixerClientData* sendingNodeData,
NLPacketList& traitsPacketList) {
auto otherNodeLocalID = sendingNodeData->getNodeLocalID();
// Avatar Traits flow control marks each outgoing avatar traits packet with a
// sequence number. The mixer caches the traits sent in the traits packet.
// Until an ack with the sequence number comes back, all updates to _traits
// in that packet_ are ignored. Updates to traits not in that packet will
// be sent.
auto sendingNodeLocalID = sendingNodeData->getNodeLocalID();
// Perform a simple check with two server clock time points
// to see if there is any new traits data for this avatar that we need to send
auto timeOfLastTraitsSent = listeningNodeData->getLastOtherAvatarTraitsSendPoint(otherNodeLocalID);
auto timeOfLastTraitsSent = listeningNodeData->getLastOtherAvatarTraitsSendPoint(sendingNodeLocalID);
auto timeOfLastTraitsChange = sendingNodeData->getLastReceivedTraitsChange();
bool allTraitsUpdated = true;
qint64 bytesWritten = 0;
if (timeOfLastTraitsChange > timeOfLastTraitsSent) {
// there is definitely new traits data to send
// add the avatar ID to mark the beginning of traits for this avatar
bytesWritten += traitsPacketList.write(sendingNodeData->getNodeID().toRfc4122());
auto sendingAvatar = sendingNodeData->getAvatarSharedPointer();
// compare trait versions so we can see what exactly needs to go out
auto& lastSentVersions = listeningNodeData->getLastSentTraitVersions(otherNodeLocalID);
auto& lastSentVersions = listeningNodeData->getLastSentTraitVersions(sendingNodeLocalID);
auto& lastAckedVersions = listeningNodeData->getLastAckedTraitVersions(sendingNodeLocalID);
const auto& lastReceivedVersions = sendingNodeData->getLastReceivedTraitVersions();
auto simpleReceivedIt = lastReceivedVersions.simpleCBegin();
while (simpleReceivedIt != lastReceivedVersions.simpleCEnd()) {
auto traitType = static_cast<AvatarTraits::TraitType>(std::distance(lastReceivedVersions.simpleCBegin(),
simpleReceivedIt));
auto lastReceivedVersion = *simpleReceivedIt;
auto& lastSentVersionRef = lastSentVersions[traitType];
auto& lastAckedVersionRef = lastAckedVersions[traitType];
if (lastReceivedVersions[traitType] > lastSentVersionRef) {
// there is an update to this trait, add it to the traits packet
bytesWritten += sendingAvatar->packTrait(traitType, traitsPacketList, lastReceivedVersion);
// update the last sent version
lastSentVersionRef = lastReceivedVersion;
// hold sending more traits until we've been acked that the last one we sent was received
if (lastSentVersionRef == lastAckedVersionRef) {
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);
// update the last sent version
lastSentVersionRef = lastReceivedVersion;
// Remember which versions we sent in this particular packet
// so we can verify when it's acked.
auto& pendingTraitVersions = listeningNodeData->getPendingTraitVersions(listeningNodeData->getTraitsMessageSequence(), sendingNodeLocalID);
pendingTraitVersions[traitType] = lastReceivedVersion;
}
} else {
allTraitsUpdated = false;
}
++simpleReceivedIt;
@ -131,6 +161,7 @@ qint64 AvatarMixerSlave::addChangedTraitsToBulkPacket(AvatarMixerClientData* lis
// get or create the sent trait versions for this trait type
auto& sentIDValuePairs = lastSentVersions.getInstanceIDValuePairs(traitType);
auto& ackIDValuePairs = lastAckedVersions.getInstanceIDValuePairs(traitType);
// enumerate each received instance
for (auto& receivedInstance : instancedReceivedIt->instances) {
@ -148,8 +179,19 @@ qint64 AvatarMixerSlave::addChangedTraitsToBulkPacket(AvatarMixerClientData* lis
{
return sentInstance.id == instanceID;
});
// look for existing acked version for this instance
auto ackedInstanceIt = std::find_if(ackIDValuePairs.begin(), ackIDValuePairs.end(),
[instanceID](auto& ackInstance) { return ackInstance.id == instanceID; });
// if we have a sent version, then we must have an acked instance of the same trait with the same
// version to go on, otherwise we drop the received trait
if (sentInstanceIt != sentIDValuePairs.end() &&
(ackedInstanceIt == ackIDValuePairs.end() || sentInstanceIt->value != ackedInstanceIt->value)) {
allTraitsUpdated = false;
continue;
}
if (!isDeleted && (sentInstanceIt == sentIDValuePairs.end() || receivedVersion > sentInstanceIt->value)) {
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);
@ -159,25 +201,40 @@ qint64 AvatarMixerSlave::addChangedTraitsToBulkPacket(AvatarMixerClientData* lis
} else {
sentIDValuePairs.emplace_back(instanceID, receivedVersion);
}
auto& pendingTraitVersions =
listeningNodeData->getPendingTraitVersions(listeningNodeData->getTraitsMessageSequence(),
sendingNodeLocalID);
pendingTraitVersions.instanceInsert(traitType, instanceID, receivedVersion);
} else if (isDeleted && sentInstanceIt != sentIDValuePairs.end() && absoluteReceivedVersion > sentInstanceIt->value) {
bytesWritten += addTraitsNodeHeader(listeningNodeData, sendingNodeData, traitsPacketList, bytesWritten);
// this instance version was deleted and we haven't sent the delete to this client yet
bytesWritten += AvatarTraits::packInstancedTraitDelete(traitType, instanceID, traitsPacketList, absoluteReceivedVersion);
// update the last sent version for this trait instance to the absolute value of the deleted version
sentInstanceIt->value = absoluteReceivedVersion;
auto& pendingTraitVersions =
listeningNodeData->getPendingTraitVersions(listeningNodeData->getTraitsMessageSequence(),
sendingNodeLocalID);
pendingTraitVersions.instanceInsert(traitType, instanceID, absoluteReceivedVersion);
}
}
++instancedReceivedIt;
}
// write a null trait type to mark the end of trait data for this avatar
bytesWritten += traitsPacketList.writePrimitive(AvatarTraits::NullTrait);
// since we send all traits for this other avatar, update the time of last traits sent
// to match the time of last traits change
listeningNodeData->setLastOtherAvatarTraitsSendPoint(otherNodeLocalID, timeOfLastTraitsChange);
if (bytesWritten) {
// write a null trait type to mark the end of trait data for this avatar
bytesWritten += traitsPacketList.writePrimitive(AvatarTraits::NullTrait);
// since we send all traits for this other avatar, update the time of last traits sent
// to match the time of last traits change
if (allTraitsUpdated) {
listeningNodeData->setLastOtherAvatarTraitsSendPoint(sendingNodeLocalID, timeOfLastTraitsChange);
}
}
}
@ -191,7 +248,8 @@ int AvatarMixerSlave::sendReplicatedIdentityPacket(const Node& agentNode, const
auto identityPacket = NLPacketList::create(PacketType::ReplicatedAvatarIdentity, QByteArray(), true, true);
identityPacket->write(individualData);
DependencyManager::get<NodeList>()->sendPacketList(std::move(identityPacket), destinationNode);
_stats.numIdentityPackets++;
_stats.numIdentityPacketsSent++;
_stats.numIdentityBytesSent += individualData.size();
return individualData.size();
} else {
return 0;
@ -419,6 +477,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
int remainingAvatars = (int)sortedAvatars.size();
auto traitsPacketList = NLPacketList::create(PacketType::BulkAvatarTraits, QByteArray(), true, true);
auto avatarPacket = NLPacket::create(PacketType::BulkAvatarData);
const int avatarPacketCapacity = avatarPacket->getPayloadCapacity();
int avatarSpaceAvailable = avatarPacketCapacity;
@ -539,17 +598,16 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
++numPacketsSent;
}
_stats.numPacketsSent += numPacketsSent;
_stats.numBytesSent += numAvatarDataBytes;
// record the bytes sent for other avatar data in the AvatarMixerClientData
nodeData->recordSentAvatarData(numAvatarDataBytes);
_stats.numDataPacketsSent += numPacketsSent;
_stats.numDataBytesSent += numAvatarDataBytes;
// close the current traits packet list
traitsPacketList->closeCurrentPacket();
if (traitsPacketList->getNumPackets() >= 1) {
// send the traits packet list
_stats.numTraitsBytesSent += traitBytesSent;
_stats.numTraitsPacketsSent += (int) traitsPacketList->getNumPackets();
nodeList->sendPacketList(std::move(traitsPacketList), *destinationNode);
}
@ -559,6 +617,10 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
nodeList->sendPacketList(std::move(identityPacketList), *destinationNode);
}
// record the bytes sent for other avatar data in the AvatarMixerClientData
nodeData->recordSentAvatarData(numAvatarDataBytes, traitBytesSent);
// record the number of avatars held back this frame
nodeData->recordNumOtherAvatarStarves(numAvatarsHeldBack);
nodeData->recordNumOtherAvatarSkips(numAvatarsWithSkippedFrames);
@ -685,8 +747,8 @@ void AvatarMixerSlave::broadcastAvatarDataToDownstreamMixer(const SharedNodePoin
// close the current packet so that we're always sending something
avatarPacketList->closeCurrentPacket(true);
_stats.numPacketsSent += (int)avatarPacketList->getNumPackets();
_stats.numBytesSent += numAvatarDataBytes;
_stats.numDataPacketsSent += (int)avatarPacketList->getNumPackets();
_stats.numDataBytesSent += numAvatarDataBytes;
// send the replicated bulk avatar data
auto nodeList = DependencyManager::get<NodeList>();

View file

@ -24,9 +24,12 @@ public:
int nodesBroadcastedTo { 0 };
int downstreamMixersBroadcastedTo { 0 };
int numPacketsSent { 0 };
int numBytesSent { 0 };
int numIdentityPackets { 0 };
int numDataBytesSent { 0 };
int numTraitsBytesSent { 0 };
int numIdentityBytesSent { 0 };
int numDataPacketsSent { 0 };
int numTraitsPacketsSent { 0 };
int numIdentityPacketsSent { 0 };
int numOthersIncluded { 0 };
int overBudgetAvatars { 0 };
@ -45,9 +48,13 @@ public:
// sending job stats
nodesBroadcastedTo = 0;
downstreamMixersBroadcastedTo = 0;
numPacketsSent = 0;
numBytesSent = 0;
numIdentityPackets = 0;
numDataBytesSent = 0;
numTraitsBytesSent = 0;
numIdentityBytesSent = 0;
numDataPacketsSent = 0;
numTraitsPacketsSent = 0;
numIdentityPacketsSent = 0;
numOthersIncluded = 0;
overBudgetAvatars = 0;
@ -65,9 +72,12 @@ public:
nodesBroadcastedTo += rhs.nodesBroadcastedTo;
downstreamMixersBroadcastedTo += rhs.downstreamMixersBroadcastedTo;
numPacketsSent += rhs.numPacketsSent;
numBytesSent += rhs.numBytesSent;
numIdentityPackets += rhs.numIdentityPackets;
numDataBytesSent += rhs.numDataBytesSent;
numTraitsBytesSent += rhs.numTraitsBytesSent;
numIdentityBytesSent += rhs.numIdentityBytesSent;
numDataPacketsSent += rhs.numDataPacketsSent;
numTraitsPacketsSent += rhs.numTraitsPacketsSent;
numIdentityPacketsSent += rhs.numIdentityPacketsSent;
numOthersIncluded += rhs.numOthersIncluded;
overBudgetAvatars += rhs.overBudgetAvatars;
@ -104,6 +114,11 @@ private:
int sendIdentityPacket(NLPacketList& packet, const AvatarMixerClientData* nodeData, const Node& destinationNode);
int sendReplicatedIdentityPacket(const Node& agentNode, const AvatarMixerClientData* nodeData, const Node& destinationNode);
qint64 addTraitsNodeHeader(AvatarMixerClientData* listeningNodeData,
const AvatarMixerClientData* sendingNodeData,
NLPacketList& traitsPacketList,
qint64 bytesWritten);
qint64 addChangedTraitsToBulkPacket(AvatarMixerClientData* listeningNodeData,
const AvatarMixerClientData* sendingNodeData,
NLPacketList& traitsPacketList);

View file

@ -21,6 +21,8 @@
#include <GLMHelpers.h>
#include <ResourceRequestObserver.h>
#include <AvatarLogging.h>
#include <EntityItem.h>
#include <EntityItemProperties.h>
ScriptableAvatar::ScriptableAvatar() {
@ -249,3 +251,157 @@ void ScriptableAvatar::setHasProceduralEyeFaceMovement(bool hasProceduralEyeFace
void ScriptableAvatar::setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement) {
_headData->setHasAudioEnabledFaceMovement(hasAudioEnabledFaceMovement);
}
AvatarEntityMap ScriptableAvatar::getAvatarEntityData() const {
// DANGER: Now that we store the AvatarEntityData in packed format this call is potentially Very Expensive!
// Avoid calling this method if possible.
AvatarEntityMap data;
QUuid sessionID = getID();
_avatarEntitiesLock.withReadLock([&] {
for (const auto& itr : _entities) {
QUuid id = itr.first;
EntityItemPointer entity = itr.second;
EntityItemProperties properties = entity->getProperties();
QByteArray blob;
EntityItemProperties::propertiesToBlob(_scriptEngine, sessionID, properties, blob);
data[id] = blob;
}
});
return data;
}
void ScriptableAvatar::setAvatarEntityData(const AvatarEntityMap& avatarEntityData) {
// Note: this is an invokable Script call
// avatarEntityData is expected to be a map of QByteArrays that represent EntityItemProperties objects from JavaScript
//
if (avatarEntityData.size() > MAX_NUM_AVATAR_ENTITIES) {
// the data is suspect
qCDebug(avatars) << "discard suspect avatarEntityData with size =" << avatarEntityData.size();
return;
}
// convert binary data to EntityItemProperties
// NOTE: this operation is NOT efficient
std::map<QUuid, EntityItemProperties> newProperties;
AvatarEntityMap::const_iterator dataItr = avatarEntityData.begin();
while (dataItr != avatarEntityData.end()) {
EntityItemProperties properties;
const QByteArray& blob = dataItr.value();
if (!blob.isNull() && EntityItemProperties::blobToProperties(_scriptEngine, blob, properties)) {
newProperties[dataItr.key()] = properties;
}
++dataItr;
}
// delete existing entities not found in avatarEntityData
std::vector<QUuid> idsToClear;
_avatarEntitiesLock.withWriteLock([&] {
std::map<QUuid, EntityItemPointer>::iterator entityItr = _entities.begin();
while (entityItr != _entities.end()) {
QUuid id = entityItr->first;
std::map<QUuid, EntityItemProperties>::const_iterator propertiesItr = newProperties.find(id);
if (propertiesItr == newProperties.end()) {
idsToClear.push_back(id);
entityItr = _entities.erase(entityItr);
} else {
++entityItr;
}
}
});
// add or update entities
_avatarEntitiesLock.withWriteLock([&] {
std::map<QUuid, EntityItemProperties>::const_iterator propertiesItr = newProperties.begin();
while (propertiesItr != newProperties.end()) {
QUuid id = propertiesItr->first;
const EntityItemProperties& properties = propertiesItr->second;
std::map<QUuid, EntityItemPointer>::iterator entityItr = _entities.find(id);
EntityItemPointer entity;
if (entityItr != _entities.end()) {
entity = entityItr->second;
entity->setProperties(properties);
} else {
entity = EntityTypes::constructEntityItem(id, properties);
}
if (entity) {
// build outgoing payload
OctreePacketData packetData(false, AvatarTraits::MAXIMUM_TRAIT_SIZE);
EncodeBitstreamParams params;
EntityTreeElementExtraEncodeDataPointer extra { nullptr };
OctreeElement::AppendState appendState = entity->appendEntityData(&packetData, params, extra);
if (appendState == OctreeElement::COMPLETED) {
_entities[id] = entity;
QByteArray tempArray((const char*)packetData.getUncompressedData(), packetData.getUncompressedSize());
storeAvatarEntityDataPayload(id, tempArray);
} else {
// payload doesn't fit
entityItr = _entities.find(id);
if (entityItr != _entities.end()) {
_entities.erase(entityItr);
idsToClear.push_back(id);
}
}
}
++propertiesItr;
}
});
// clear deleted traits
for (const auto& id : idsToClear) {
clearAvatarEntity(id);
}
}
void ScriptableAvatar::updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData) {
if (entityData.isNull()) {
// interpret this as a DELETE
std::map<QUuid, EntityItemPointer>::iterator itr = _entities.find(entityID);
if (itr != _entities.end()) {
_entities.erase(itr);
clearAvatarEntity(entityID);
}
return;
}
EntityItemPointer entity;
EntityItemProperties properties;
if (!EntityItemProperties::blobToProperties(_scriptEngine, entityData, properties)) {
// entityData is corrupt
return;
}
std::map<QUuid, EntityItemPointer>::iterator itr = _entities.find(entityID);
if (itr == _entities.end()) {
// this is an ADD
entity = EntityTypes::constructEntityItem(entityID, properties);
if (entity) {
OctreePacketData packetData(false, AvatarTraits::MAXIMUM_TRAIT_SIZE);
EncodeBitstreamParams params;
EntityTreeElementExtraEncodeDataPointer extra { nullptr };
OctreeElement::AppendState appendState = entity->appendEntityData(&packetData, params, extra);
if (appendState == OctreeElement::COMPLETED) {
_entities[entityID] = entity;
QByteArray tempArray((const char*)packetData.getUncompressedData(), packetData.getUncompressedSize());
storeAvatarEntityDataPayload(entityID, tempArray);
}
}
} else {
// this is an UPDATE
entity = itr->second;
bool somethingChanged = entity->setProperties(properties);
if (somethingChanged) {
OctreePacketData packetData(false, AvatarTraits::MAXIMUM_TRAIT_SIZE);
EncodeBitstreamParams params;
EntityTreeElementExtraEncodeDataPointer extra { nullptr };
OctreeElement::AppendState appendState = entity->appendEntityData(&packetData, params, extra);
if (appendState == OctreeElement::COMPLETED) {
QByteArray tempArray((const char*)packetData.getUncompressedData(), packetData.getUncompressedSize());
storeAvatarEntityDataPayload(entityID, tempArray);
}
}
}
}

View file

@ -16,6 +16,7 @@
#include <AnimSkeleton.h>
#include <AvatarData.h>
#include <ScriptEngine.h>
#include <EntityItem.h>
/**jsdoc
* The <code>Avatar</code> API is used to manipulate scriptable avatars on the domain. This API is a subset of the
@ -185,6 +186,26 @@ public:
void setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement);
bool getHasAudioEnabledFaceMovement() const override { return _headData->getHasAudioEnabledFaceMovement(); }
/**jsdoc
* Potentially Very Expensive. Do not use.
* @function Avatar.getAvatarEntityData
* @returns {object}
*/
Q_INVOKABLE AvatarEntityMap getAvatarEntityData() const override;
/**jsdoc
* @function MyAvatar.setAvatarEntityData
* @param {object} avatarEntityData
*/
Q_INVOKABLE void setAvatarEntityData(const AvatarEntityMap& avatarEntityData) override;
/**jsdoc
* @function MyAvatar.updateAvatarEntity
* @param {Uuid} entityID
* @param {string} entityData
*/
Q_INVOKABLE void updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData) override;
public slots:
void update(float deltatime);
@ -202,6 +223,8 @@ private:
QHash<QString, int> _fstJointIndices; ///< 1-based, since zero is returned for missing keys
QStringList _fstJointNames; ///< in order of depth-first traversal
QUrl _skeletonFBXURL;
mutable QScriptEngine _scriptEngine;
std::map<QUuid, EntityItemPointer> _entities;
/// Loads the joint indices, names from the FST file (if any)
void updateJointMappings();

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View file

@ -123,7 +123,16 @@
{ "from": { "makeAxis" : ["Keyboard.MouseMoveLeft", "Keyboard.MouseMoveRight"] },
"when": "Keyboard.RightMouseButton",
"to": "Actions.Yaw",
"to": "Actions.DeltaYaw",
"filters":
[
{ "type": "scale", "scale": 0.6 }
]
},
{ "from": { "makeAxis" : ["Keyboard.MouseMoveUp", "Keyboard.MouseMoveDown"] },
"when": "Keyboard.RightMouseButton",
"to": "Actions.DeltaPitch",
"filters":
[
{ "type": "scale", "scale": 0.6 }
@ -144,20 +153,6 @@
{ "from": "Keyboard.PgDown", "to": "Actions.VERTICAL_DOWN" },
{ "from": "Keyboard.PgUp", "to": "Actions.VERTICAL_UP" },
{ "from": "Keyboard.MouseMoveUp", "when": "Keyboard.RightMouseButton", "to": "Actions.PITCH_UP",
"filters":
[
{ "type": "scale", "scale": 0.6 }
]
},
{ "from": "Keyboard.MouseMoveDown", "when": "Keyboard.RightMouseButton", "to": "Actions.PITCH_DOWN",
"filters":
[
{ "type": "scale", "scale": 0.6 }
]
},
{ "from": "Keyboard.TouchpadDown", "to": "Actions.PITCH_DOWN" },
{ "from": "Keyboard.TouchpadUp", "to": "Actions.PITCH_UP" },

View file

@ -10,7 +10,7 @@ import "avatarapp"
Rectangle {
id: root
width: 480
height: 706
height: 706
property bool keyboardEnabled: true
property bool keyboardRaised: false
@ -417,7 +417,7 @@ Rectangle {
width: 21.2
height: 19.3
source: isAvatarInFavorites ? '../../images/FavoriteIconActive.svg' : '../../images/FavoriteIconInActive.svg'
anchors.verticalCenter: parent.verticalCenter
Layout.alignment: Qt.AlignVCenter
}
// TextStyle5
@ -426,7 +426,7 @@ Rectangle {
Layout.fillWidth: true
text: isAvatarInFavorites ? avatarName : "Add to Favorites"
elide: Qt.ElideRight
anchors.verticalCenter: parent.verticalCenter
Layout.alignment: Qt.AlignVCenter
}
}

View file

@ -157,7 +157,7 @@ Rectangle {
visible = false;
adjustWearablesClosed(status, avatarName);
}
HifiConstants { id: hifi }
@ -230,7 +230,7 @@ Rectangle {
lineHeightMode: Text.FixedHeight
lineHeight: 18;
text: "Wearable"
anchors.verticalCenter: parent.verticalCenter
Layout.alignment: Qt.AlignVCenter
}
spacing: 10
@ -241,7 +241,7 @@ Rectangle {
lineHeight: 18;
text: "<a href='#'>Get more</a>"
linkColor: hifi.colors.blueHighlight
anchors.verticalCenter: parent.verticalCenter
Layout.alignment: Qt.AlignVCenter
onLinkActivated: {
popup.showGetWearables(function() {
emitSendToScript({'method' : 'navigate', 'url' : 'hifi://AvatarIsland/11.5848,-8.10862,-2.80195'})

View file

@ -110,11 +110,11 @@ Rectangle {
size: 17;
text: "Avatar Scale"
verticalAlignment: Text.AlignVCenter
anchors.verticalCenter: parent.verticalCenter
Layout.alignment: Qt.AlignVCenter
}
RowLayout {
anchors.verticalCenter: parent.verticalCenter
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
spacing: 0
@ -124,7 +124,7 @@ Rectangle {
text: 'T'
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
anchors.verticalCenter: parent.verticalCenter
Layout.alignment: Qt.AlignVCenter
}
HifiControlsUit.Slider {
@ -142,7 +142,7 @@ Rectangle {
}
}
anchors.verticalCenter: parent.verticalCenter
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
// TextStyle9
@ -171,7 +171,7 @@ Rectangle {
text: 'T'
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
anchors.verticalCenter: parent.verticalCenter
Layout.alignment: Qt.AlignVCenter
}
}
@ -368,8 +368,7 @@ Rectangle {
InputTextStyle4 {
id: avatarAnimationUrlInputText
font.pixelSize: 17
anchors.left: parent.left
anchors.right: parent.right
Layout.fillWidth: true
placeholderText: 'user\\file\\dir'
onFocusChanged: {
@ -398,8 +397,7 @@ Rectangle {
InputTextStyle4 {
id: avatarCollisionSoundUrlInputText
font.pixelSize: 17
anchors.left: parent.left
anchors.right: parent.right
Layout.fillWidth: true
placeholderText: 'https://hifi-public.s3.amazonaws.com/sounds/Collisions-'
onFocusChanged: {

View file

@ -724,6 +724,8 @@ const QString TEST_RESULTS_LOCATION_COMMAND{ "--testResultsLocation" };
bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
const char** constArgv = const_cast<const char**>(argv);
qInstallMessageHandler(messageHandler);
// HRS: I could not figure out how to move these any earlier in startup, so when using this option, be sure to also supply
// --allowMultipleInstances
auto reportAndQuit = [&](const char* commandSwitch, std::function<void(FILE* fp)> report) {
@ -974,6 +976,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
QApplication(argc, argv),
_window(new MainWindow(desktop())),
_sessionRunTimer(startupTimer),
_logger(new FileLogger(this)),
_previousSessionCrashed(setupEssentials(argc, argv, runningMarkerExisted)),
_entitySimulation(new PhysicalEntitySimulation()),
_physicsEngine(new PhysicsEngine(Vectors::ZERO)),
@ -1063,9 +1066,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
installNativeEventFilter(&MyNativeEventFilter::getInstance());
#endif
_logger = new FileLogger(this);
qInstallMessageHandler(messageHandler);
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "styles/Inconsolata.otf");
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/fontawesome-webfont.ttf");
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/hifi-glyphs.ttf");
@ -2060,7 +2060,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
properties["avatar_ping"] = avatarMixerNode ? avatarMixerNode->getPingMs() : -1;
properties["asset_ping"] = assetServerNode ? assetServerNode->getPingMs() : -1;
properties["messages_ping"] = messagesMixerNode ? messagesMixerNode->getPingMs() : -1;
properties["atp_in_kbps"] = messagesMixerNode ? assetServerNode->getInboundKbps() : 0.0f;
properties["atp_in_kbps"] = assetServerNode ? assetServerNode->getInboundKbps() : 0.0f;
auto loadingRequests = ResourceCache::getLoadingRequests();
@ -2459,6 +2459,9 @@ void Application::updateHeartbeat() const {
}
void Application::onAboutToQuit() {
// quickly save AvatarEntityData before the EntityTree is dismantled
getMyAvatar()->saveAvatarEntityDataToSettings();
emit beforeAboutToQuit();
if (getLoginDialogPoppedUp() && _firstRun.get()) {
@ -5972,6 +5975,8 @@ void Application::update(float deltaTime) {
if (deltaTime > FLT_EPSILON) {
myAvatar->setDriveKey(MyAvatar::PITCH, -1.0f * userInputMapper->getActionState(controller::Action::PITCH));
myAvatar->setDriveKey(MyAvatar::YAW, -1.0f * userInputMapper->getActionState(controller::Action::YAW));
myAvatar->setDriveKey(MyAvatar::DELTA_PITCH, -1.0f * userInputMapper->getActionState(controller::Action::DELTA_PITCH));
myAvatar->setDriveKey(MyAvatar::DELTA_YAW, -1.0f * userInputMapper->getActionState(controller::Action::DELTA_YAW));
myAvatar->setDriveKey(MyAvatar::STEP_YAW, -1.0f * userInputMapper->getActionState(controller::Action::STEP_YAW));
}
}
@ -6761,8 +6766,10 @@ void Application::updateWindowTitle() const {
}
void Application::clearDomainOctreeDetails() {
// before we delete all entities get MyAvatar's AvatarEntityData ready
getMyAvatar()->prepareAvatarEntityDataForReload();
// if we're about to quit, we really don't need to do any of these things...
// if we're about to quit, we really don't need to do the rest of these things...
if (_aboutToQuit) {
return;
}
@ -6790,8 +6797,6 @@ void Application::clearDomainOctreeDetails() {
ShaderCache::instance().clearUnusedResources();
DependencyManager::get<TextureCache>()->clearUnusedResources();
DependencyManager::get<recording::ClipCache>()->clearUnusedResources();
getMyAvatar()->setAvatarEntityDataChanged(true);
}
void Application::domainURLChanged(QUrl domainURL) {

View file

@ -594,6 +594,8 @@ private:
bool _aboutToQuit { false };
FileLogger* _logger { nullptr };
bool _previousSessionCrashed;
DisplayPluginPointer _displayPlugin;
@ -674,8 +676,6 @@ private:
QPointer<EntityScriptServerLogDialog> _entityScriptServerLogDialog;
QDir _defaultScriptsLocation;
FileLogger* _logger;
TouchEvent _lastTouchEvent;
quint64 _lastNackTime;

View file

@ -60,7 +60,6 @@ void addAvatarEntities(const QVariantList& avatarEntities) {
entityProperties.setParentID(myNodeID);
entityProperties.setEntityHostType(entity::HostType::AVATAR);
entityProperties.setOwningAvatarID(myNodeID);
entityProperties.setSimulationOwner(myNodeID, AVATAR_ENTITY_SIMULATION_PRIORITY);
entityProperties.markAllChanged();
EntityItemID id = EntityItemID(QUuid::createUuid());

View file

@ -50,6 +50,7 @@
#include <RecordingScriptingInterface.h>
#include <trackers/FaceTracker.h>
#include <RenderableModelEntityItem.h>
#include <VariantMapToScriptValue.h>
#include "MyHead.h"
#include "MySkeletonModel.h"
@ -282,6 +283,8 @@ MyAvatar::MyAvatar(QThread* thread) :
MyAvatar::~MyAvatar() {
_lookAtTargetAvatar.reset();
delete _myScriptEngine;
_myScriptEngine = nullptr;
}
QString MyAvatar::getDominantHand() const {
@ -688,7 +691,7 @@ void MyAvatar::update(float deltaTime) {
_clientTraitsHandler->sendChangedTraitsToMixer();
simulate(deltaTime);
simulate(deltaTime, true);
currentEnergy += energyChargeRate;
currentEnergy -= getAccelerationEnergy();
@ -760,7 +763,7 @@ void MyAvatar::updateChildCauterization(SpatiallyNestablePointer object, bool ca
}
}
void MyAvatar::simulate(float deltaTime) {
void MyAvatar::simulate(float deltaTime, bool inView) {
PerformanceTimer perfTimer("simulate");
animateScaleChanges(deltaTime);
@ -904,7 +907,7 @@ void MyAvatar::simulate(float deltaTime) {
_characterController.setCollisionlessAllowed(collisionlessAllowed);
}
updateAvatarEntities();
handleChangedAvatarEntityData();
updateFadingStatus();
}
@ -1268,7 +1271,7 @@ void MyAvatar::saveAvatarUrl() {
}
}
void MyAvatar::resizeAvatarEntitySettingHandles(unsigned int avatarEntityIndex) {
void MyAvatar::resizeAvatarEntitySettingHandles(uint32_t maxIndex) {
// The original Settings interface saved avatar-entity array data like this:
// Avatar/avatarEntityData/size: 5
// Avatar/avatarEntityData/1/id: ...
@ -1278,14 +1281,15 @@ void MyAvatar::resizeAvatarEntitySettingHandles(unsigned int avatarEntityIndex)
// Avatar/avatarEntityData/5/properties: ...
//
// Create Setting::Handles to mimic this.
while (_avatarEntityIDSettings.size() <= avatarEntityIndex) {
uint32_t settingsIndex = (uint32_t)_avatarEntityIDSettings.size() + 1;
while (settingsIndex <= maxIndex) {
Setting::Handle<QUuid> idHandle(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "avatarEntityData"
<< QString::number(avatarEntityIndex + 1) << "id", QUuid());
<< QString::number(settingsIndex) << "id", QUuid());
_avatarEntityIDSettings.push_back(idHandle);
Setting::Handle<QByteArray> dataHandle(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "avatarEntityData"
<< QString::number(avatarEntityIndex + 1) << "properties", QByteArray());
<< QString::number(settingsIndex) << "properties", QByteArray());
_avatarEntityDataSettings.push_back(dataHandle);
settingsIndex++;
}
}
@ -1316,26 +1320,54 @@ void MyAvatar::saveData() {
_flyingHMDSetting.set(getFlyingHMDPref());
auto hmdInterface = DependencyManager::get<HMDScriptingInterface>();
_avatarEntitiesLock.withReadLock([&] {
QList<QUuid> avatarEntityIDs = _avatarEntityData.keys();
unsigned int avatarEntityCount = avatarEntityIDs.size();
unsigned int previousAvatarEntityCount = _avatarEntityCountSetting.get(0);
resizeAvatarEntitySettingHandles(std::max<unsigned int>(avatarEntityCount, previousAvatarEntityCount));
_avatarEntityCountSetting.set(avatarEntityCount);
saveAvatarEntityDataToSettings();
}
unsigned int avatarEntityIndex = 0;
for (auto entityID : avatarEntityIDs) {
_avatarEntityIDSettings[avatarEntityIndex].set(entityID);
_avatarEntityDataSettings[avatarEntityIndex].set(_avatarEntityData.value(entityID));
avatarEntityIndex++;
}
void MyAvatar::saveAvatarEntityDataToSettings() {
if (!_needToSaveAvatarEntitySettings) {
return;
}
bool success = updateStaleAvatarEntityBlobs();
if (!success) {
return;
}
_needToSaveAvatarEntitySettings = false;
// clean up any left-over (due to the list shrinking) slots
for (; avatarEntityIndex < previousAvatarEntityCount; avatarEntityIndex++) {
_avatarEntityIDSettings[avatarEntityIndex].remove();
_avatarEntityDataSettings[avatarEntityIndex].remove();
uint32_t numEntities = (uint32_t)_cachedAvatarEntityBlobs.size();
uint32_t prevNumEntities = _avatarEntityCountSetting.get(0);
resizeAvatarEntitySettingHandles(std::max<uint32_t>(numEntities, prevNumEntities));
// save new Settings
if (numEntities > 0) {
// save all unfortunately-formatted-binary-blobs to Settings
_avatarEntitiesLock.withWriteLock([&] {
uint32_t i = 0;
AvatarEntityMap::const_iterator itr = _cachedAvatarEntityBlobs.begin();
while (itr != _cachedAvatarEntityBlobs.end()) {
_avatarEntityIDSettings[i].set(itr.key());
_avatarEntityDataSettings[i].set(itr.value());
++itr;
++i;
}
numEntities = i;
});
}
_avatarEntityCountSetting.set(numEntities);
// remove old Settings if any
if (numEntities < prevNumEntities) {
uint32_t numEntitiesToRemove = prevNumEntities - numEntities;
for (uint32_t i = 0; i < numEntitiesToRemove; ++i) {
if (_avatarEntityIDSettings.size() > numEntities) {
_avatarEntityIDSettings.back().remove();
_avatarEntityIDSettings.pop_back();
}
if (_avatarEntityDataSettings.size() > numEntities) {
_avatarEntityDataSettings.back().remove();
_avatarEntityDataSettings.pop_back();
}
}
});
}
}
float loadSetting(Settings& settings, const QString& name, float defaultValue) {
@ -1432,7 +1464,410 @@ void MyAvatar::setEnableInverseKinematics(bool isEnabled) {
_skeletonModel->getRig().setEnableInverseKinematics(isEnabled);
}
void MyAvatar::storeAvatarEntityDataPayload(const QUuid& entityID, const QByteArray& payload) {
AvatarData::storeAvatarEntityDataPayload(entityID, payload);
_avatarEntitiesLock.withWriteLock([&] {
_cachedAvatarEntityBlobsToAddOrUpdate.push_back(entityID);
});
}
void MyAvatar::clearAvatarEntity(const QUuid& entityID, bool requiresRemovalFromTree) {
AvatarData::clearAvatarEntity(entityID, requiresRemovalFromTree);
_avatarEntitiesLock.withWriteLock([&] {
_cachedAvatarEntityBlobsToDelete.push_back(entityID);
});
}
void MyAvatar::sanitizeAvatarEntityProperties(EntityItemProperties& properties) const {
properties.setEntityHostType(entity::HostType::AVATAR);
properties.setOwningAvatarID(getID());
// there's no entity-server to tell us we're the simulation owner, so always set the
// simulationOwner to the owningAvatarID and a high priority.
properties.setSimulationOwner(getID(), AVATAR_ENTITY_SIMULATION_PRIORITY);
if (properties.getParentID() == AVATAR_SELF_ID) {
properties.setParentID(getID());
}
// When grabbing avatar entities, they are parented to the joint moving them, then when un-grabbed
// they go back to the default parent (null uuid). When un-gripped, others saw the entity disappear.
// The thinking here is the local position was noticed as changing, but not the parentID (since it is now
// back to the default), and the entity flew off somewhere. Marking all changed definitely fixes this,
// and seems safe (per Seth).
properties.markAllChanged();
}
void MyAvatar::handleChangedAvatarEntityData() {
// NOTE: this is a per-frame update
if (getID().isNull() ||
getID() == AVATAR_SELF_ID ||
DependencyManager::get<NodeList>()->getSessionUUID() == QUuid()) {
// wait until MyAvatar and this Node gets an ID before doing this. Otherwise, various things go wrong:
// things get their parent fixed up from AVATAR_SELF_ID to a null uuid which means "no parent".
return;
}
if (_reloadAvatarEntityDataFromSettings) {
loadAvatarEntityDataFromSettings();
}
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
if (!entityTree) {
return;
}
// We collect changes to AvatarEntities and then handle them all in one spot per frame: handleChangedAvatarEntityData().
// Basically this is a "transaction pattern" with an extra complication: these changes can come from two
// "directions" and the "authoritative source" of each direction is different, so we maintain two distinct sets
// of transaction lists:
//
// The _entitiesToDelete/Add/Update lists are for changes whose "authoritative sources" are already
// correctly stored in _cachedAvatarEntityBlobs. These come from loadAvatarEntityDataFromSettings() and
// setAvatarEntityData(). These changes need to be extracted from _cachedAvatarEntityBlobs and applied to
// real EntityItems.
//
// The _cachedAvatarEntityBlobsToDelete/Add/Update lists are for changes whose "authoritative sources" are
// already reflected in real EntityItems. These changes need to be propagated to _cachedAvatarEntityBlobs
// and eventually to Settings.
//
// The DELETEs also need to be propagated to the traits, which will eventually propagate to
// AvatarData::_packedAvatarEntityData via deeper logic.
// move the lists to minimize lock time
std::vector<QUuid> cachedBlobsToDelete;
std::vector<QUuid> cachedBlobsToUpdate;
std::vector<QUuid> entitiesToDelete;
std::vector<QUuid> entitiesToAdd;
std::vector<QUuid> entitiesToUpdate;
_avatarEntitiesLock.withWriteLock([&] {
cachedBlobsToDelete = std::move(_cachedAvatarEntityBlobsToDelete);
cachedBlobsToUpdate = std::move(_cachedAvatarEntityBlobsToAddOrUpdate);
entitiesToDelete = std::move(_entitiesToDelete);
entitiesToAdd = std::move(_entitiesToAdd);
entitiesToUpdate = std::move(_entitiesToUpdate);
});
auto removeAllInstancesHelper = [] (const QUuid& id, std::vector<QUuid>& v) {
uint32_t i = 0;
while (i < v.size()) {
if (id == v[i]) {
v[i] = v.back();
v.pop_back();
} else {
++i;
}
}
};
// remove delete-add and delete-update overlap
for (const auto& id : entitiesToDelete) {
removeAllInstancesHelper(id, cachedBlobsToUpdate);
removeAllInstancesHelper(id, entitiesToAdd);
removeAllInstancesHelper(id, entitiesToUpdate);
}
for (const auto& id : cachedBlobsToDelete) {
removeAllInstancesHelper(id, entitiesToUpdate);
removeAllInstancesHelper(id, cachedBlobsToUpdate);
}
for (const auto& id : entitiesToAdd) {
removeAllInstancesHelper(id, entitiesToUpdate);
}
// DELETE real entities
for (const auto& id : entitiesToDelete) {
entityTree->withWriteLock([&] {
entityTree->deleteEntity(id);
});
}
// ADD real entities
EntityEditPacketSender* packetSender = qApp->getEntityEditPacketSender();
for (const auto& id : entitiesToAdd) {
bool blobFailed = false;
EntityItemProperties properties;
_avatarEntitiesLock.withReadLock([&] {
AvatarEntityMap::iterator itr = _cachedAvatarEntityBlobs.find(id);
if (itr == _cachedAvatarEntityBlobs.end()) {
blobFailed = true; // blob doesn't exist
return;
}
if (!EntityItemProperties::blobToProperties(*_myScriptEngine, itr.value(), properties)) {
blobFailed = true; // blob is corrupt
}
});
if (blobFailed) {
// remove from _cachedAvatarEntityBlobUpdatesToSkip just in case:
// avoids a resource leak when blob updates to be skipped are never actually skipped
// when the blob fails to result in a real EntityItem
_avatarEntitiesLock.withWriteLock([&] {
removeAllInstancesHelper(id, _cachedAvatarEntityBlobUpdatesToSkip);
});
continue;
}
sanitizeAvatarEntityProperties(properties);
entityTree->withWriteLock([&] {
EntityItemPointer entity = entityTree->addEntity(id, properties);
if (entity) {
packetSender->queueEditEntityMessage(PacketType::EntityAdd, entityTree, id, properties);
}
});
}
// CHANGE real entities
for (const auto& id : entitiesToUpdate) {
EntityItemProperties properties;
bool skip = false;
_avatarEntitiesLock.withReadLock([&] {
AvatarEntityMap::iterator itr = _cachedAvatarEntityBlobs.find(id);
if (itr == _cachedAvatarEntityBlobs.end()) {
skip = true;
return;
}
if (!EntityItemProperties::blobToProperties(*_myScriptEngine, itr.value(), properties)) {
skip = true;
}
});
sanitizeAvatarEntityProperties(properties);
entityTree->withWriteLock([&] {
entityTree->updateEntity(id, properties);
});
}
// DELETE cached blobs
_avatarEntitiesLock.withWriteLock([&] {
for (const auto& id : cachedBlobsToDelete) {
AvatarEntityMap::iterator itr = _cachedAvatarEntityBlobs.find(id);
// remove blob and remember to remove from settings
if (itr != _cachedAvatarEntityBlobs.end()) {
_cachedAvatarEntityBlobs.erase(itr);
_needToSaveAvatarEntitySettings = true;
}
// also remove from list of stale blobs to avoid failed entity lookup later
std::set<QUuid>::iterator blobItr = _staleCachedAvatarEntityBlobs.find(id);
if (blobItr != _staleCachedAvatarEntityBlobs.end()) {
_staleCachedAvatarEntityBlobs.erase(blobItr);
}
// also remove from _cachedAvatarEntityBlobUpdatesToSkip just in case:
// avoids a resource leak when things are deleted before they could be skipped
removeAllInstancesHelper(id, _cachedAvatarEntityBlobUpdatesToSkip);
}
});
// ADD/UPDATE cached blobs
for (const auto& id : cachedBlobsToUpdate) {
// computing the blobs is expensive and we want to avoid it when possible
// so we add these ids to _staleCachedAvatarEntityBlobs for later
// and only build the blobs when absolutely necessary
bool skip = false;
uint32_t i = 0;
_avatarEntitiesLock.withWriteLock([&] {
while (i < _cachedAvatarEntityBlobUpdatesToSkip.size()) {
if (id == _cachedAvatarEntityBlobUpdatesToSkip[i]) {
_cachedAvatarEntityBlobUpdatesToSkip[i] = _cachedAvatarEntityBlobUpdatesToSkip.back();
_cachedAvatarEntityBlobUpdatesToSkip.pop_back();
skip = true;
break; // assume no duplicates
} else {
++i;
}
}
});
if (!skip) {
_staleCachedAvatarEntityBlobs.insert(id);
_needToSaveAvatarEntitySettings = true;
}
}
// DELETE traits
// (no need to worry about the ADDs and UPDATEs: each will be handled when the interface
// tries to send a real update packet (via AvatarData::storeAvatarEntityDataPayload()))
if (_clientTraitsHandler) {
// we have a client traits handler
// flag removed entities as deleted so that changes are sent next frame
_avatarEntitiesLock.withWriteLock([&] {
for (const auto& id : entitiesToDelete) {
if (_packedAvatarEntityData.find(id) != _packedAvatarEntityData.end()) {
_clientTraitsHandler->markInstancedTraitDeleted(AvatarTraits::AvatarEntity, id);
}
}
for (const auto& id : cachedBlobsToDelete) {
if (_packedAvatarEntityData.find(id) != _packedAvatarEntityData.end()) {
_clientTraitsHandler->markInstancedTraitDeleted(AvatarTraits::AvatarEntity, id);
}
}
});
}
}
bool MyAvatar::updateStaleAvatarEntityBlobs() const {
// call this right before you actually need to use the blobs
//
// Note: this method is const (and modifies mutable data members)
// so we can call it at the Last Minute inside other const methods
//
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
if (!entityTree) {
return false;
}
std::set<QUuid> staleBlobs = std::move(_staleCachedAvatarEntityBlobs);
int32_t numFound = 0;
for (const auto& id : staleBlobs) {
bool found = false;
EntityItemProperties properties;
entityTree->withReadLock([&] {
EntityItemPointer entity = entityTree->findEntityByID(id);
if (entity) {
properties = entity->getProperties();
found = true;
}
});
if (found) {
++numFound;
QByteArray blob;
EntityItemProperties::propertiesToBlob(*_myScriptEngine, getID(), properties, blob);
_avatarEntitiesLock.withWriteLock([&] {
_cachedAvatarEntityBlobs[id] = blob;
});
}
}
return true;
}
void MyAvatar::prepareAvatarEntityDataForReload() {
saveAvatarEntityDataToSettings();
_avatarEntitiesLock.withWriteLock([&] {
_packedAvatarEntityData.clear();
_entitiesToDelete.clear();
_entitiesToAdd.clear();
_entitiesToUpdate.clear();
_cachedAvatarEntityBlobs.clear();
_cachedAvatarEntityBlobsToDelete.clear();
_cachedAvatarEntityBlobsToAddOrUpdate.clear();
_cachedAvatarEntityBlobUpdatesToSkip.clear();
});
_reloadAvatarEntityDataFromSettings = true;
}
AvatarEntityMap MyAvatar::getAvatarEntityData() const {
// NOTE: the return value is expected to be a map of unfortunately-formatted-binary-blobs
updateStaleAvatarEntityBlobs();
AvatarEntityMap result;
_avatarEntitiesLock.withReadLock([&] {
result = _cachedAvatarEntityBlobs;
});
return result;
}
void MyAvatar::setAvatarEntityData(const AvatarEntityMap& avatarEntityData) {
// Note: this is an invokable Script call
// avatarEntityData is expected to be a map of QByteArrays that represent EntityItemProperties objects from JavaScript,
// aka: unfortunately-formatted-binary-blobs because we store them in non-human-readable format in Settings.
//
if (avatarEntityData.size() > MAX_NUM_AVATAR_ENTITIES) {
// the data is suspect
qCDebug(interfaceapp) << "discard suspect AvatarEntityData with size =" << avatarEntityData.size();
return;
}
// this overwrites ALL AvatarEntityData so we clear pending operations
_avatarEntitiesLock.withWriteLock([&] {
_packedAvatarEntityData.clear();
_entitiesToDelete.clear();
_entitiesToAdd.clear();
_entitiesToUpdate.clear();
});
_needToSaveAvatarEntitySettings = true;
_avatarEntitiesLock.withWriteLock([&] {
// find new and updated IDs
AvatarEntityMap::const_iterator constItr = avatarEntityData.begin();
while (constItr != avatarEntityData.end()) {
QUuid id = constItr.key();
if (_cachedAvatarEntityBlobs.find(id) == _cachedAvatarEntityBlobs.end()) {
_entitiesToAdd.push_back(id);
} else {
_entitiesToUpdate.push_back(id);
}
++constItr;
}
// find and erase deleted IDs from _cachedAvatarEntityBlobs
std::vector<QUuid> deletedIDs;
AvatarEntityMap::iterator itr = _cachedAvatarEntityBlobs.begin();
while (itr != _cachedAvatarEntityBlobs.end()) {
QUuid id = itr.key();
if (std::find(_entitiesToUpdate.begin(), _entitiesToUpdate.end(), id) == _entitiesToUpdate.end()) {
deletedIDs.push_back(id);
itr = _cachedAvatarEntityBlobs.erase(itr);
} else {
++itr;
}
}
// copy new data
constItr = avatarEntityData.begin();
while (constItr != avatarEntityData.end()) {
_cachedAvatarEntityBlobs.insert(constItr.key(), constItr.value());
++constItr;
}
// erase deleted IDs from _packedAvatarEntityData
for (const auto& id : deletedIDs) {
itr = _packedAvatarEntityData.find(id);
if (itr != _packedAvatarEntityData.end()) {
_packedAvatarEntityData.erase(itr);
} else {
++itr;
}
_entitiesToDelete.push_back(id);
}
});
}
void MyAvatar::updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData) {
// NOTE: this is an invokable Script call
// TODO: we should handle the case where entityData is corrupt or invalid
// BEFORE we store into _cachedAvatarEntityBlobs
_needToSaveAvatarEntitySettings = true;
_avatarEntitiesLock.withWriteLock([&] {
AvatarEntityMap::iterator itr = _cachedAvatarEntityBlobs.find(entityID);
if (itr != _cachedAvatarEntityBlobs.end()) {
_entitiesToUpdate.push_back(entityID);
itr.value() = entityData;
} else {
_entitiesToAdd.push_back(entityID);
_cachedAvatarEntityBlobs.insert(entityID, entityData);
}
});
}
void MyAvatar::avatarEntityDataToJson(QJsonObject& root) const {
updateStaleAvatarEntityBlobs();
_avatarEntitiesLock.withReadLock([&] {
if (!_cachedAvatarEntityBlobs.empty()) {
QJsonArray avatarEntityJson;
int entityCount = 0;
AvatarEntityMap::const_iterator itr = _cachedAvatarEntityBlobs.begin();
while (itr != _cachedAvatarEntityBlobs.end()) {
QVariantMap entityData;
QUuid id = _avatarEntityForRecording.size() == _cachedAvatarEntityBlobs.size() ? _avatarEntityForRecording.values()[entityCount++] : itr.key();
entityData.insert("id", id);
entityData.insert("properties", itr.value().toBase64());
avatarEntityJson.push_back(QVariant(entityData).toJsonObject());
++itr;
}
const QString JSON_AVATAR_ENTITIES = QStringLiteral("attachedEntities");
root[JSON_AVATAR_ENTITIES] = avatarEntityJson;
}
});
}
void MyAvatar::loadData() {
if (!_myScriptEngine) {
_myScriptEngine = new QScriptEngine();
}
getHead()->setBasePitch(_headPitchSetting.get());
_yawSpeed = _yawSpeedSetting.get(_yawSpeed);
@ -1444,14 +1879,7 @@ void MyAvatar::loadData() {
useFullAvatarURL(_fullAvatarURLFromPreferences, _fullAvatarModelName);
int avatarEntityCount = _avatarEntityCountSetting.get(0);
for (int i = 0; i < avatarEntityCount; i++) {
resizeAvatarEntitySettingHandles(i);
// QUuid entityID = QUuid::createUuid(); // generate a new ID
QUuid entityID = _avatarEntityIDSettings[i].get(QUuid());
QByteArray properties = _avatarEntityDataSettings[i].get();
updateAvatarEntity(entityID, properties);
}
loadAvatarEntityDataFromSettings();
// Flying preferences must be loaded before calling setFlyingEnabled()
Setting::Handle<bool> firstRunVal { Settings::firstRun, true };
@ -1474,6 +1902,38 @@ void MyAvatar::loadData() {
setEnableDebugDrawPosition(Menu::getInstance()->isOptionChecked(MenuOption::AnimDebugDrawPosition));
}
void MyAvatar::loadAvatarEntityDataFromSettings() {
// this overwrites ALL AvatarEntityData so we clear pending operations
_avatarEntitiesLock.withWriteLock([&] {
_packedAvatarEntityData.clear();
_entitiesToDelete.clear();
_entitiesToAdd.clear();
_entitiesToUpdate.clear();
});
_reloadAvatarEntityDataFromSettings = false;
_needToSaveAvatarEntitySettings = false;
int numEntities = _avatarEntityCountSetting.get(0);
if (numEntities == 0) {
return;
}
resizeAvatarEntitySettingHandles(numEntities);
_avatarEntitiesLock.withWriteLock([&] {
_entitiesToAdd.reserve(numEntities);
// TODO: build map between old and new IDs so we can restitch parent-child relationships
for (int i = 0; i < numEntities; i++) {
QUuid id = QUuid::createUuid(); // generate a new ID
_cachedAvatarEntityBlobs[id] = _avatarEntityDataSettings[i].get();
_entitiesToAdd.push_back(id);
// this blob is the "authoritative source" for this AvatarEntity and we want to avoid overwriting it
// (the outgoing update packet will flag it for save-back into the blob)
// which is why we remember its id: to skip its save-back later
_cachedAvatarEntityBlobUpdatesToSkip.push_back(id);
}
});
}
void MyAvatar::saveAttachmentData(const AttachmentData& attachment) const {
Settings settings;
settings.beginGroup("savedAttachmentData");
@ -1925,8 +2385,11 @@ void MyAvatar::clearAvatarEntities() {
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
AvatarEntityMap avatarEntities = getAvatarEntityData();
for (auto entityID : avatarEntities.keys()) {
QList<QUuid> avatarEntityIDs;
_avatarEntitiesLock.withReadLock([&] {
avatarEntityIDs = _packedAvatarEntityData.keys();
});
for (const auto& entityID : avatarEntityIDs) {
entityTree->withWriteLock([&entityID, &entityTree] {
// remove this entity first from the entity tree
entityTree->deleteEntity(entityID, true, true);
@ -1941,10 +2404,12 @@ void MyAvatar::clearAvatarEntities() {
void MyAvatar::removeWearableAvatarEntities() {
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
if (entityTree) {
AvatarEntityMap avatarEntities = getAvatarEntityData();
for (auto entityID : avatarEntities.keys()) {
QList<QUuid> avatarEntityIDs;
_avatarEntitiesLock.withReadLock([&] {
avatarEntityIDs = _packedAvatarEntityData.keys();
});
for (const auto& entityID : avatarEntityIDs) {
auto entity = entityTree->findEntityByID(entityID);
if (entity && isWearableEntity(entity)) {
entityTree->withWriteLock([&entityID, &entityTree] {
@ -1961,13 +2426,16 @@ void MyAvatar::removeWearableAvatarEntities() {
}
QVariantList MyAvatar::getAvatarEntitiesVariant() {
// NOTE: this method is NOT efficient
QVariantList avatarEntitiesData;
QScriptEngine scriptEngine;
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
if (entityTree) {
AvatarEntityMap avatarEntities = getAvatarEntityData();
for (auto entityID : avatarEntities.keys()) {
QList<QUuid> avatarEntityIDs;
_avatarEntitiesLock.withReadLock([&] {
avatarEntityIDs = _packedAvatarEntityData.keys();
});
for (const auto& entityID : avatarEntityIDs) {
auto entity = entityTree->findEntityByID(entityID);
if (!entity) {
continue;
@ -1978,7 +2446,7 @@ QVariantList MyAvatar::getAvatarEntitiesVariant() {
desiredProperties += PROP_LOCAL_POSITION;
desiredProperties += PROP_LOCAL_ROTATION;
EntityItemProperties entityProperties = entity->getProperties(desiredProperties);
QScriptValue scriptProperties = EntityItemPropertiesToScriptValue(&scriptEngine, entityProperties);
QScriptValue scriptProperties = EntityItemPropertiesToScriptValue(_myScriptEngine, entityProperties);
avatarEntityData["properties"] = scriptProperties.toVariant();
avatarEntitiesData.append(QVariant(avatarEntityData));
}
@ -2367,17 +2835,17 @@ void MyAvatar::setAttachmentData(const QVector<AttachmentData>& attachmentData)
}
QVector<AttachmentData> MyAvatar::getAttachmentData() const {
QVector<AttachmentData> avatarData;
auto avatarEntities = getAvatarEntityData();
AvatarEntityMap::const_iterator dataItr = avatarEntities.begin();
while (dataItr != avatarEntities.end()) {
QUuid entityID = dataItr.key();
QVector<AttachmentData> attachmentData;
QList<QUuid> avatarEntityIDs;
_avatarEntitiesLock.withReadLock([&] {
avatarEntityIDs = _packedAvatarEntityData.keys();
});
for (const auto& entityID : avatarEntityIDs) {
auto properties = DependencyManager::get<EntityScriptingInterface>()->getEntityProperties(entityID);
AttachmentData data = entityPropertiesToAttachmentData(properties);
avatarData.append(data);
dataItr++;
attachmentData.append(data);
}
return avatarData;
return attachmentData;
}
QVariantList MyAvatar::getAttachmentsVariant() const {
@ -2406,16 +2874,16 @@ void MyAvatar::setAttachmentsVariant(const QVariantList& variant) {
}
bool MyAvatar::findAvatarEntity(const QString& modelURL, const QString& jointName, QUuid& entityID) {
auto avatarEntities = getAvatarEntityData();
AvatarEntityMap::const_iterator dataItr = avatarEntities.begin();
while (dataItr != avatarEntities.end()) {
entityID = dataItr.key();
QList<QUuid> avatarEntityIDs;
_avatarEntitiesLock.withReadLock([&] {
avatarEntityIDs = _packedAvatarEntityData.keys();
});
for (const auto& entityID : avatarEntityIDs) {
auto props = DependencyManager::get<EntityScriptingInterface>()->getEntityProperties(entityID);
if (props.getModelURL() == modelURL &&
(jointName.isEmpty() || props.getParentJointIndex() == getJointIndex(jointName))) {
return true;
}
dataItr++;
}
return false;
}
@ -2729,9 +3197,10 @@ void MyAvatar::updateOrientation(float deltaTime) {
_bodyYawDelta = 0.0f;
}
}
float totalBodyYaw = _bodyYawDelta * deltaTime;
// Rotate directly proportional to delta yaw and delta pitch from right-click mouse movement.
totalBodyYaw += getDriveKey(DELTA_YAW) * _yawSpeed / YAW_SPEED_DEFAULT;
// Comfort Mode: If you press any of the left/right rotation drive keys or input, you'll
// get an instantaneous 15 degree turn. If you keep holding the key down you'll get another
@ -2799,7 +3268,8 @@ void MyAvatar::updateOrientation(float deltaTime) {
head->setBaseRoll(ROLL(euler));
} else {
head->setBaseYaw(0.0f);
head->setBasePitch(getHead()->getBasePitch() + getDriveKey(PITCH) * _pitchSpeed * deltaTime);
head->setBasePitch(getHead()->getBasePitch() + getDriveKey(PITCH) * _pitchSpeed * deltaTime
+ getDriveKey(DELTA_PITCH) * _pitchSpeed / PITCH_SPEED_DEFAULT);
head->setBaseRoll(0.0f);
}
}

View file

@ -266,6 +266,8 @@ public:
STEP_YAW,
PITCH,
ZOOM,
DELTA_YAW,
DELTA_PITCH,
MAX_DRIVE_KEYS
};
Q_ENUM(DriveKeys)
@ -587,9 +589,11 @@ public:
float getHMDRollControlRate() const { return _hmdRollControlRate; }
// get/set avatar data
void resizeAvatarEntitySettingHandles(unsigned int avatarEntityIndex);
void resizeAvatarEntitySettingHandles(uint32_t maxIndex);
void saveData();
void saveAvatarEntityDataToSettings();
void loadData();
void loadAvatarEntityDataFromSettings();
void saveAttachmentData(const AttachmentData& attachment) const;
AttachmentData loadAttachmentData(const QUrl& modelURL, const QString& jointName = QString()) const;
@ -1196,6 +1200,7 @@ public:
virtual void setAttachmentsVariant(const QVariantList& variant) override;
glm::vec3 getNextPosition() { return _goToPending ? _goToPosition : getWorldPosition(); }
void prepareAvatarEntityDataForReload();
/**jsdoc
* Create a new grab.
@ -1216,6 +1221,11 @@ public:
*/
Q_INVOKABLE void releaseGrab(const QUuid& grabID);
AvatarEntityMap getAvatarEntityData() const override;
void setAvatarEntityData(const AvatarEntityMap& avatarEntityData) override;
void updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData) override;
void avatarEntityDataToJson(QJsonObject& root) const override;
public slots:
/**jsdoc
@ -1414,6 +1424,10 @@ public slots:
*/
bool getEnableMeshVisible() const override;
void storeAvatarEntityDataPayload(const QUuid& entityID, const QByteArray& payload) override;
void clearAvatarEntity(const QUuid& entityID, bool requiresRemovalFromTree = true) override;
void sanitizeAvatarEntityProperties(EntityItemProperties& properties) const;
/**jsdoc
* Set whether or not your avatar mesh is visible.
* @function MyAvatar.setEnableMeshVisible
@ -1620,23 +1634,24 @@ signals:
*/
void disableHandTouchForIDChanged(const QUuid& entityID, bool disable);
private slots:
void leaveDomain();
void updateCollisionCapsuleCache();
protected:
void handleChangedAvatarEntityData();
virtual void beParentOfChild(SpatiallyNestablePointer newChild) const override;
virtual void forgetChild(SpatiallyNestablePointer newChild) const override;
virtual void recalculateChildCauterization() const override;
private:
bool updateStaleAvatarEntityBlobs() const;
bool requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& positionOut);
virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking) override;
void simulate(float deltaTime);
void simulate(float deltaTime, bool inView) override;
void updateFromTrackers(float deltaTime);
void saveAvatarUrl();
virtual void render(RenderArgs* renderArgs) override;
@ -1940,6 +1955,7 @@ private:
bool _haveReceivedHeightLimitsFromDomain { false };
int _disableHandTouchCount { 0 };
bool _skeletonModelLoaded { false };
bool _reloadAvatarEntityDataFromSettings { true };
Setting::Handle<QString> _dominantHandSetting;
Setting::Handle<QString> _hmdAvatarAlignmentTypeSetting;
@ -1959,6 +1975,38 @@ private:
Setting::Handle<bool> _allowTeleportingSetting { "allowTeleporting", true };
std::vector<Setting::Handle<QUuid>> _avatarEntityIDSettings;
std::vector<Setting::Handle<QByteArray>> _avatarEntityDataSettings;
// AvatarEntities stuff:
// We cache the "map of unfortunately-formatted-binary-blobs" because they are expensive to compute
// Do not confuse these with AvatarData::_packedAvatarEntityData which are in wire-format.
mutable AvatarEntityMap _cachedAvatarEntityBlobs;
// We collect changes to AvatarEntities and then handle them all in one spot per frame: updateAvatarEntities().
// Basically this is a "transaction pattern" with an extra complication: these changes can come from two
// "directions" and the "authoritative source" of each direction is different, so maintain two distinct sets of
// transaction lists;
//
// The _entitiesToDelete/Add/Update lists are for changes whose "authoritative sources" are already
// correctly stored in _cachedAvatarEntityBlobs. These come from loadAvatarEntityDataFromSettings() and
// setAvatarEntityData(). These changes need to be extracted from _cachedAvatarEntityBlobs and applied to
// real EntityItems.
std::vector<QUuid> _entitiesToDelete;
std::vector<QUuid> _entitiesToAdd;
std::vector<QUuid> _entitiesToUpdate;
//
// The _cachedAvatarEntityBlobsToDelete/Add/Update lists are for changes whose "authoritative sources" are
// already reflected in real EntityItems. These changes need to be propagated to _cachedAvatarEntityBlobs
// and eventually to settings.
std::vector<QUuid> _cachedAvatarEntityBlobsToDelete;
std::vector<QUuid> _cachedAvatarEntityBlobsToAddOrUpdate;
std::vector<QUuid> _cachedAvatarEntityBlobUpdatesToSkip;
//
// Also these lists for tracking delayed changes to blobs and Settings
mutable std::set<QUuid> _staleCachedAvatarEntityBlobs;
//
// keep a ScriptEngine around so we don't have to instantiate on the fly (these are very slow to create/delete)
QScriptEngine* _myScriptEngine { nullptr };
bool _needToSaveAvatarEntitySettings { false };
};
QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode);

View file

@ -7,10 +7,18 @@
//
#include "OtherAvatar.h"
#include "Application.h"
#include <glm/gtx/norm.hpp>
#include <glm/gtx/vector_angle.hpp>
#include <AvatarLogging.h>
#include "Application.h"
#include "AvatarMotionState.h"
const float DISPLAYNAME_FADE_TIME = 0.5f;
const float DISPLAYNAME_FADE_FACTOR = pow(0.01f, 1.0f / DISPLAYNAME_FADE_TIME);
static glm::u8vec3 getLoadingOrbColor(Avatar::LoadingStatus loadingStatus) {
const glm::u8vec3 NO_MODEL_COLOR(0xe3, 0xe3, 0xe3);
@ -142,4 +150,293 @@ void OtherAvatar::updateCollisionGroup(bool myAvatarCollide) {
_motionState->addDirtyFlags(Simulation::DIRTY_COLLISION_GROUP);
}
}
}
}
void OtherAvatar::simulate(float deltaTime, bool inView) {
PROFILE_RANGE(simulation, "simulate");
_globalPosition = _transit.isActive() ? _transit.getCurrentPosition() : _serverPosition;
if (!hasParent()) {
setLocalPosition(_globalPosition);
}
_simulationRate.increment();
if (inView) {
_simulationInViewRate.increment();
}
PerformanceTimer perfTimer("simulate");
{
PROFILE_RANGE(simulation, "updateJoints");
if (inView) {
Head* head = getHead();
if (_hasNewJointData || _transit.isActive()) {
_skeletonModel->getRig().copyJointsFromJointData(_jointData);
glm::mat4 rootTransform = glm::scale(_skeletonModel->getScale()) * glm::translate(_skeletonModel->getOffset());
_skeletonModel->getRig().computeExternalPoses(rootTransform);
_jointDataSimulationRate.increment();
_skeletonModel->simulate(deltaTime, true);
locationChanged(); // joints changed, so if there are any children, update them.
_hasNewJointData = false;
glm::vec3 headPosition = getWorldPosition();
if (!_skeletonModel->getHeadPosition(headPosition)) {
headPosition = getWorldPosition();
}
head->setPosition(headPosition);
}
head->setScale(getModelScale());
head->simulate(deltaTime);
relayJointDataToChildren();
} else {
// a non-full update is still required so that the position, rotation, scale and bounds of the skeletonModel are updated.
_skeletonModel->simulate(deltaTime, false);
}
_skeletonModelSimulationRate.increment();
}
// update animation for display name fade in/out
if ( _displayNameTargetAlpha != _displayNameAlpha) {
// the alpha function is
// Fade out => alpha(t) = factor ^ t => alpha(t+dt) = alpha(t) * factor^(dt)
// Fade in => alpha(t) = 1 - factor^t => alpha(t+dt) = 1-(1-alpha(t))*coef^(dt)
// factor^(dt) = coef
float coef = pow(DISPLAYNAME_FADE_FACTOR, deltaTime);
if (_displayNameTargetAlpha < _displayNameAlpha) {
// Fading out
_displayNameAlpha *= coef;
} else {
// Fading in
_displayNameAlpha = 1.0f - (1.0f - _displayNameAlpha) * coef;
}
_displayNameAlpha = glm::abs(_displayNameAlpha - _displayNameTargetAlpha) < 0.01f ? _displayNameTargetAlpha : _displayNameAlpha;
}
{
PROFILE_RANGE(simulation, "misc");
measureMotionDerivatives(deltaTime);
simulateAttachments(deltaTime);
updatePalms();
}
{
PROFILE_RANGE(simulation, "entities");
handleChangedAvatarEntityData();
updateAttachedAvatarEntities();
}
{
PROFILE_RANGE(simulation, "grabs");
updateGrabs();
}
updateFadingStatus();
}
void OtherAvatar::handleChangedAvatarEntityData() {
PerformanceTimer perfTimer("attachments");
// 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
// - 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
// and here we are...
// AVATAR ENTITY DELETE FLOW
// - EntityScriptingInterface::deleteEntity() calls _myAvatar->clearAvatarEntity() for deleted avatar entities
// - clearAvatarEntity() removes the avatar entity and flags the trait instance for the entity as deleted
// - ClientTraitsHandler::sendChangedTraitsToMixer() sends a deletion to the mixer which relays to other interfaces
// - AvatarHashMap::processBulkAvatarTraits() on other interfaces calls avatar->processDeletedTraitInstace()
// - AvatarData::processDeletedTraitInstance() calls clearAvatarEntity()
// - AvatarData::clearAvatarEntity() sets _avatarEntityDataChanged = true and adds the ID to the detached list
// - (My)Avatar::simulate() calls handleChangedAvatarEntityData() every frame which checks _avatarEntityDataChanged
// and here we are...
if (!_avatarEntityDataChanged) {
return;
}
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
if (!entityTree) {
return;
}
PackedAvatarEntityMap packedAvatarEntityData;
_avatarEntitiesLock.withReadLock([&] {
packedAvatarEntityData = _packedAvatarEntityData;
});
entityTree->withWriteLock([&] {
AvatarEntityMap::const_iterator dataItr = packedAvatarEntityData.begin();
while (dataItr != packedAvatarEntityData.end()) {
// compute hash of data. TODO? cache this?
QByteArray data = dataItr.value();
uint32_t newHash = qHash(data);
// check to see if we recognize this hash and whether it was already successfully processed
QUuid entityID = dataItr.key();
MapOfAvatarEntityDataHashes::iterator stateItr = _avatarEntityDataHashes.find(entityID);
if (stateItr != _avatarEntityDataHashes.end()) {
if (stateItr.value().success) {
if (newHash == stateItr.value().hash) {
// data hasn't changed --> nothing to do
++dataItr;
continue;
}
} else {
// NOTE: if the data was unsuccessful in producing an entity in the past
// we will try again just in case something changed (unlikely).
// Unfortunately constantly trying to build the entity for this data costs
// CPU cycles that we'd rather not spend.
// TODO? put a maximum number of tries on this?
}
} else {
// sanity check data
QUuid id;
EntityTypes::EntityType type;
EntityTypes::extractEntityTypeAndID((unsigned char*)(data.data()), data.size(), type, id);
if (id != entityID || !EntityTypes::typeIsValid(type)) {
// skip for corrupt
++dataItr;
continue;
}
// remember this hash for the future
stateItr = _avatarEntityDataHashes.insert(entityID, AvatarEntityDataHash(newHash));
}
++dataItr;
EntityItemProperties properties;
int32_t bytesLeftToRead = data.size();
unsigned char* dataAt = (unsigned char*)(data.data());
if (!properties.constructFromBuffer(dataAt, bytesLeftToRead)) {
// properties are corrupt
continue;
}
properties.setEntityHostType(entity::HostType::AVATAR);
properties.setOwningAvatarID(getID());
// there's no entity-server to tell us we're the simulation owner, so always set the
// simulationOwner to the owningAvatarID and a high priority.
properties.setSimulationOwner(getID(), AVATAR_ENTITY_SIMULATION_PRIORITY);
if (properties.getParentID() == AVATAR_SELF_ID) {
properties.setParentID(getID());
}
// NOTE: if this avatar entity is not attached to us, strip its entity script completely...
auto attachedScript = properties.getScript();
if (!isMyAvatar() && !attachedScript.isEmpty()) {
QString noScript;
properties.setScript(noScript);
}
auto specifiedHref = properties.getHref();
if (!isMyAvatar() && !specifiedHref.isEmpty()) {
qCDebug(avatars) << "removing entity href from avatar attached entity:" << entityID << "old href:" << specifiedHref;
QString noHref;
properties.setHref(noHref);
}
// When grabbing avatar entities, they are parented to the joint moving them, then when un-grabbed
// they go back to the default parent (null uuid). When un-gripped, others saw the entity disappear.
// The thinking here is the local position was noticed as changing, but not the parentID (since it is now
// back to the default), and the entity flew off somewhere. Marking all changed definitely fixes this,
// and seems safe (per Seth).
properties.markAllChanged();
// try to build the entity
EntityItemPointer entity = entityTree->findEntityByEntityItemID(EntityItemID(entityID));
bool success = true;
if (entity) {
QUuid oldParentID = entity->getParentID();
if (entityTree->updateEntity(entityID, properties)) {
entity->updateLastEditedFromRemote();
} else {
success = false;
}
if (oldParentID != entity->getParentID()) {
if (entity->getParentID() == getID()) {
onAddAttachedAvatarEntity(entityID);
} else if (oldParentID == getID()) {
onRemoveAttachedAvatarEntity(entityID);
}
}
} else {
entity = entityTree->addEntity(entityID, properties);
if (!entity) {
success = false;
} else if (entity->getParentID() == getID()) {
onAddAttachedAvatarEntity(entityID);
}
}
stateItr.value().success = success;
}
AvatarEntityIDs recentlyRemovedAvatarEntities = getAndClearRecentlyRemovedIDs();
if (!recentlyRemovedAvatarEntities.empty()) {
// only lock this thread when absolutely necessary
AvatarEntityMap packedAvatarEntityData;
_avatarEntitiesLock.withReadLock([&] {
packedAvatarEntityData = _packedAvatarEntityData;
});
foreach (auto entityID, recentlyRemovedAvatarEntities) {
if (!packedAvatarEntityData.contains(entityID)) {
entityTree->deleteEntity(entityID, true, true);
}
}
// TODO: move this outside of tree lock
// remove stale data hashes
foreach (auto entityID, recentlyRemovedAvatarEntities) {
MapOfAvatarEntityDataHashes::iterator stateItr = _avatarEntityDataHashes.find(entityID);
if (stateItr != _avatarEntityDataHashes.end()) {
_avatarEntityDataHashes.erase(stateItr);
}
onRemoveAttachedAvatarEntity(entityID);
}
}
if (packedAvatarEntityData.size() != _avatarEntityForRecording.size()) {
createRecordingIDs();
}
});
setAvatarEntityDataChanged(false);
}
void OtherAvatar::onAddAttachedAvatarEntity(const QUuid& id) {
for (uint32_t i = 0; i < _attachedAvatarEntities.size(); ++i) {
if (_attachedAvatarEntities[i] == id) {
return;
}
}
_attachedAvatarEntities.push_back(id);
}
void OtherAvatar::onRemoveAttachedAvatarEntity(const QUuid& id) {
for (uint32_t i = 0; i < _attachedAvatarEntities.size(); ++i) {
if (_attachedAvatarEntities[i] == id) {
if (i != _attachedAvatarEntities.size() - 1) {
_attachedAvatarEntities[i] = _attachedAvatarEntities.back();
}
_attachedAvatarEntities.pop_back();
break;
}
}
}
void OtherAvatar::updateAttachedAvatarEntities() {
if (!_attachedAvatarEntities.empty()) {
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
if (!treeRenderer) {
return;
}
for (const QUuid& id : _attachedAvatarEntities) {
treeRenderer->onEntityChanged(id);
}
}
}

View file

@ -10,6 +10,7 @@
#define hifi_OtherAvatar_h
#include <memory>
#include <vector>
#include <avatars-renderer/Avatar.h>
#include <workload/Space.h>
@ -47,9 +48,17 @@ public:
void updateCollisionGroup(bool myAvatarCollide);
void simulate(float deltaTime, bool inView) override;
friend AvatarManager;
protected:
void handleChangedAvatarEntityData();
void updateAttachedAvatarEntities();
void onAddAttachedAvatarEntity(const QUuid& id);
void onRemoveAttachedAvatarEntity(const QUuid& id);
std::vector<QUuid> _attachedAvatarEntities;
std::shared_ptr<Sphere3DOverlay> _otherAvatarOrbMeshPlaceholder { nullptr };
OverlayID _otherAvatarOrbMeshPlaceholderID { UNKNOWN_OVERLAY_ID };
AvatarMotionState* _motionState { nullptr };

View file

@ -223,7 +223,7 @@ Pointer::Buttons PathPointer::getPressedButtons(const PickResultPointer& pickRes
std::string button = trigger.getButton();
TriggerState& state = _states[button];
// TODO: right now, LaserPointers don't support axes, only on/off buttons
if (trigger.getEndpoint()->peek() >= 1.0f) {
if (trigger.getEndpoint()->peek().value >= 1.0f) {
toReturn.insert(button);
if (_previousButtons.find(button) == _previousButtons.end()) {

View file

@ -104,7 +104,7 @@ class ScriptEngine;
* <ul>
* <li>{@link Controller.getValue|getValue}</li>
* <li>{@link Controller.getAxisValue|getAxisValue}</li>
* <li>{@link Controller.getPoseValue|getgetPoseValue}</li>
* <li>{@link Controller.getPoseValue|getPoseValue}</li>
* <li>{@link Controller.getActionValue|getActionValue}</li>
* </ul>
*

View file

@ -43,7 +43,7 @@ bool MenuScriptingInterface::menuExists(const QString& menu) {
if (QThread::currentThread() == qApp->thread()) {
return Menu::getInstance()->menuExists(menu);
}
bool result;
bool result { false };
BLOCKING_INVOKE_METHOD(Menu::getInstance(), "menuExists",
Q_RETURN_ARG(bool, result),
Q_ARG(const QString&, menu));
@ -86,7 +86,7 @@ bool MenuScriptingInterface::menuItemExists(const QString& menu, const QString&
if (QThread::currentThread() == qApp->thread()) {
return Menu::getInstance()->menuItemExists(menu, menuitem);
}
bool result;
bool result { false };
BLOCKING_INVOKE_METHOD(Menu::getInstance(), "menuItemExists",
Q_RETURN_ARG(bool, result),
Q_ARG(const QString&, menu),
@ -98,7 +98,7 @@ bool MenuScriptingInterface::isOptionChecked(const QString& menuOption) {
if (QThread::currentThread() == qApp->thread()) {
return Menu::getInstance()->isOptionChecked(menuOption);
}
bool result;
bool result { false };
BLOCKING_INVOKE_METHOD(Menu::getInstance(), "isOptionChecked",
Q_RETURN_ARG(bool, result),
Q_ARG(const QString&, menuOption));
@ -115,7 +115,7 @@ bool MenuScriptingInterface::isMenuEnabled(const QString& menuOption) {
if (QThread::currentThread() == qApp->thread()) {
return Menu::getInstance()->isOptionChecked(menuOption);
}
bool result;
bool result { false };
BLOCKING_INVOKE_METHOD(Menu::getInstance(), "isMenuEnabled",
Q_RETURN_ARG(bool, result),
Q_ARG(const QString&, menuOption));

View file

@ -27,6 +27,9 @@ Base3DOverlay::Base3DOverlay() :
_drawInFront(false),
_drawHUDLayer(false)
{
// HACK: queryAACube stuff not actually relevant for 3DOverlays, and by setting _queryAACubeSet true here
// we can avoid incorrect evaluation for sending updates for entities with 3DOverlays children.
_queryAACubeSet = true;
}
Base3DOverlay::Base3DOverlay(const Base3DOverlay* base3DOverlay) :
@ -41,6 +44,9 @@ Base3DOverlay::Base3DOverlay(const Base3DOverlay* base3DOverlay) :
_isVisibleInSecondaryCamera(base3DOverlay->_isVisibleInSecondaryCamera)
{
setTransform(base3DOverlay->getTransform());
// HACK: queryAACube stuff not actually relevant for 3DOverlays, and by setting _queryAACubeSet true here
// we can avoid incorrect evaluation for sending updates for entities with 3DOverlays children.
_queryAACubeSet = true;
}
QVariantMap convertOverlayLocationFromScriptSemantics(const QVariantMap& properties, bool scalesWithParent) {
@ -209,6 +215,7 @@ void Base3DOverlay::setProperties(const QVariantMap& originalProperties) {
transaction.updateItem(itemID);
scene->enqueueTransaction(transaction);
}
_queryAACubeSet = true; // HACK: just in case some SpatiallyNestable code accidentally set it false
}
}

View file

@ -25,6 +25,7 @@ public:
Base3DOverlay(const Base3DOverlay* base3DOverlay);
void setVisible(bool visible) override;
bool queryAACubeNeedsUpdate() const override { return false; } // HACK: queryAACube not relevant for Overlays
virtual OverlayID getOverlayID() const override { return OverlayID(getID().toString()); }
void setOverlayID(OverlayID overlayID) override { setID(overlayID); }

View file

@ -109,9 +109,6 @@ void AnimClip::copyFromNetworkAnim() {
jointMap.reserve(animJointCount);
for (int i = 0; i < animJointCount; i++) {
int skeletonJoint = _skeleton->nameToJointIndex(animSkeleton.getJointName(i));
if (skeletonJoint == -1) {
qCWarning(animation) << "animation contains joint =" << animSkeleton.getJointName(i) << " which is not in the skeleton";
}
jointMap.push_back(skeletonJoint);
}

View file

@ -425,7 +425,6 @@ int Rig::indexOfJoint(const QString& jointName) const {
// This is a content error, so we should issue a warning.
if (result < 0 && _jointNameWarningCount < MAX_JOINT_NAME_WARNING_COUNT) {
qCWarning(animation) << "Rig: Missing joint" << jointName << "in avatar model";
_jointNameWarningCount++;
}
return result;

View file

@ -1333,8 +1333,12 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) {
} else if (injector->isStereo()) {
// calculate distance, gain
glm::vec3 relativePosition = injector->getPosition() - _positionGetter();
float distance = glm::max(glm::length(relativePosition), EPSILON);
float gain = gainForSource(distance, injector->getVolume());
// stereo gets directly mixed into mixBuffer
float gain = injector->getVolume();
for (int i = 0; i < AudioConstants::NETWORK_FRAME_SAMPLES_STEREO; i++) {
mixBuffer[i] += convertToFloat(_localScratchBuffer[i]) * gain;
}

View file

@ -695,7 +695,7 @@ static void ifft_radix8_first(complex_t* x, complex_t* y, int n, int p) {
// n >= 4
static void rfft_post(complex_t* x, const complex_t* w, int n) {
size_t t = n/4;
int t = n/4;
assert(t >= 1);
// NOTE: x[n/2].re is packed into x[0].im
@ -707,7 +707,7 @@ static void rfft_post(complex_t* x, const complex_t* w, int n) {
complex_t* xp0 = &x[1];
complex_t* xp1 = &x[n/2 - 1];
for (size_t i = 0; i < t; i++) {
for (int i = 0; i < t; i++) {
float ar = xp0[i].re;
float ai = xp0[i].im;
@ -743,7 +743,7 @@ static void rfft_post(complex_t* x, const complex_t* w, int n) {
// n >= 4
static void rifft_pre(complex_t* x, const complex_t* w, int n) {
size_t t = n/4;
int t = n/4;
assert(t >= 1);
// NOTE: x[n/2].re is packed into x[0].im
@ -755,7 +755,7 @@ static void rifft_pre(complex_t* x, const complex_t* w, int n) {
complex_t* xp0 = &x[1];
complex_t* xp1 = &x[n/2 - 1];
for (size_t i = 0; i < t; i++) {
for (int i = 0; i < t; i++) {
float ar = xp0[i].re;
float ai = xp0[i].im;

View file

@ -973,8 +973,8 @@ FORCEINLINE static void ifft_radix8_first(complex_t* x, complex_t* y, int n, int
// n >= 32
static void rfft_post(complex_t* x, const complex_t* w, int n) {
size_t t = n/4;
assert(n/4 >= 8); // SIMD8
int t = n/4;
assert(t >= 8); // SIMD8
// NOTE: x[n/2].re is packed into x[0].im
float tr = x[0].re;
@ -985,7 +985,7 @@ static void rfft_post(complex_t* x, const complex_t* w, int n) {
complex_t* xp0 = &x[1];
complex_t* xp1 = &x[n/2 - 8];
for (size_t i = 0; i < t; i += 8) {
for (int i = 0; i < t; i += 8) {
__m256 z0 = _mm256_loadu_ps(&xp0[i+0].re);
__m256 z1 = _mm256_loadu_ps(&xp0[i+4].re);
@ -1033,8 +1033,8 @@ static void rfft_post(complex_t* x, const complex_t* w, int n) {
// n >= 32
static void rifft_pre(complex_t* x, const complex_t* w, int n) {
size_t t = n/4;
assert(n/4 >= 8); // SIMD8
int t = n/4;
assert(t >= 8); // SIMD8
// NOTE: x[n/2].re is packed into x[0].im
float tr = x[0].re;
@ -1045,7 +1045,7 @@ static void rifft_pre(complex_t* x, const complex_t* w, int n) {
complex_t* xp0 = &x[1];
complex_t* xp1 = &x[n/2 - 8];
for (size_t i = 0; i < t; i += 8) {
for (int i = 0; i < t; i += 8) {
__m256 z0 = _mm256_loadu_ps(&xp0[i+0].re);
__m256 z1 = _mm256_loadu_ps(&xp0[i+4].re);

View file

@ -308,175 +308,16 @@ void Avatar::setAvatarEntityDataChanged(bool value) {
_avatarEntityDataHashes.clear();
}
void Avatar::updateAvatarEntities() {
PerformanceTimer perfTimer("attachments");
// AVATAR ENTITY UPDATE FLOW
// - if queueEditEntityMessage sees avatarEntity flag it does _myAvatar->updateAvatarEntity()
// - updateAvatarEntity saves the bytes and flags the trait instance for the entity as updated
// - ClientTraitsHandler::sendChangedTraitsToMixer sends the entity bytes to the mixer which relays them to other interfaces
// - AvatarHashMap::processBulkAvatarTraits on other interfaces calls avatar->processTraitInstace
// - AvatarData::processTraitInstance calls updateAvatarEntity, which sets _avatarEntityDataChanged = true
// - (My)Avatar::simulate notices _avatarEntityDataChanged and here we are...
// AVATAR ENTITY DELETE FLOW
// - EntityScriptingInterface::deleteEntity calls _myAvatar->clearAvatarEntity() for deleted avatar entities
// - clearAvatarEntity removes the avatar entity and flags the trait instance for the entity as deleted
// - ClientTraitsHandler::sendChangedTraitsToMixer sends a deletion to the mixer which relays to other interfaces
// - AvatarHashMap::processBulkAvatarTraits on other interfaces calls avatar->processDeletedTraitInstace
// - AvatarData::processDeletedTraitInstance calls clearAvatarEntity
// - AvatarData::clearAvatarEntity sets _avatarEntityDataChanged = true and adds the ID to the detached list
// - Avatar::simulate notices _avatarEntityDataChanged and here we are...
if (!_avatarEntityDataChanged) {
return;
}
if (getID().isNull() ||
getID() == AVATAR_SELF_ID ||
DependencyManager::get<NodeList>()->getSessionUUID() == QUuid()) {
// wait until MyAvatar and this Node gets an ID before doing this. Otherwise, various things go wrong --
// things get their parent fixed up from AVATAR_SELF_ID to a null uuid which means "no parent".
return;
}
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
if (!entityTree) {
return;
}
QScriptEngine scriptEngine;
entityTree->withWriteLock([&] {
AvatarEntityMap avatarEntities = getAvatarEntityData();
AvatarEntityMap::const_iterator dataItr = avatarEntities.begin();
while (dataItr != avatarEntities.end()) {
// compute hash of data. TODO? cache this?
QByteArray data = dataItr.value();
uint32_t newHash = qHash(data);
// check to see if we recognize this hash and whether it was already successfully processed
QUuid entityID = dataItr.key();
MapOfAvatarEntityDataHashes::iterator stateItr = _avatarEntityDataHashes.find(entityID);
if (stateItr != _avatarEntityDataHashes.end()) {
if (stateItr.value().success) {
if (newHash == stateItr.value().hash) {
// data hasn't changed --> nothing to do
++dataItr;
continue;
}
} else {
// NOTE: if the data was unsuccessful in producing an entity in the past
// we will try again just in case something changed (unlikely).
// Unfortunately constantly trying to build the entity for this data costs
// CPU cycles that we'd rather not spend.
// TODO? put a maximum number of tries on this?
}
} else {
// remember this hash for the future
stateItr = _avatarEntityDataHashes.insert(entityID, AvatarEntityDataHash(newHash));
}
++dataItr;
// see EntityEditPacketSender::queueEditEntityMessage for the other end of this. unpack properties
// and either add or update the entity.
QJsonDocument jsonProperties = QJsonDocument::fromBinaryData(data);
if (!jsonProperties.isObject()) {
qCDebug(avatars_renderer) << "got bad avatarEntity json" << QString(data.toHex());
continue;
}
QVariant variantProperties = jsonProperties.toVariant();
QVariantMap asMap = variantProperties.toMap();
QScriptValue scriptProperties = variantMapToScriptValue(asMap, scriptEngine);
EntityItemProperties properties;
EntityItemPropertiesFromScriptValueIgnoreReadOnly(scriptProperties, properties);
properties.setEntityHostType(entity::HostType::AVATAR);
properties.setOwningAvatarID(getID());
// there's no entity-server to tell us we're the simulation owner, so always set the
// simulationOwner to the owningAvatarID and a high priority.
properties.setSimulationOwner(getID(), AVATAR_ENTITY_SIMULATION_PRIORITY);
if (properties.getParentID() == AVATAR_SELF_ID) {
properties.setParentID(getID());
}
// NOTE: if this avatar entity is not attached to us, strip its entity script completely...
auto attachedScript = properties.getScript();
if (!isMyAvatar() && !attachedScript.isEmpty()) {
QString noScript;
properties.setScript(noScript);
}
auto specifiedHref = properties.getHref();
if (!isMyAvatar() && !specifiedHref.isEmpty()) {
qCDebug(avatars_renderer) << "removing entity href from avatar attached entity:" << entityID << "old href:" << specifiedHref;
QString noHref;
properties.setHref(noHref);
}
// When grabbing avatar entities, they are parented to the joint moving them, then when un-grabbed
// they go back to the default parent (null uuid). When un-gripped, others saw the entity disappear.
// The thinking here is the local position was noticed as changing, but not the parentID (since it is now
// back to the default), and the entity flew off somewhere. Marking all changed definitely fixes this,
// and seems safe (per Seth).
properties.markAllChanged();
// try to build the entity
EntityItemPointer entity = entityTree->findEntityByEntityItemID(EntityItemID(entityID));
bool success = true;
if (entity) {
if (entityTree->updateEntity(entityID, properties)) {
entity->updateLastEditedFromRemote();
} else {
success = false;
}
} else {
entity = entityTree->addEntity(entityID, properties);
if (!entity) {
success = false;
}
}
stateItr.value().success = success;
}
AvatarEntityIDs recentlyDetachedAvatarEntities = getAndClearRecentlyDetachedIDs();
if (!recentlyDetachedAvatarEntities.empty()) {
// only lock this thread when absolutely necessary
AvatarEntityMap avatarEntityData;
_avatarEntitiesLock.withReadLock([&] {
avatarEntityData = _avatarEntityData;
});
foreach (auto entityID, recentlyDetachedAvatarEntities) {
if (!avatarEntityData.contains(entityID)) {
entityTree->deleteEntity(entityID, true, true);
}
}
// remove stale data hashes
foreach (auto entityID, recentlyDetachedAvatarEntities) {
MapOfAvatarEntityDataHashes::iterator stateItr = _avatarEntityDataHashes.find(entityID);
if (stateItr != _avatarEntityDataHashes.end()) {
_avatarEntityDataHashes.erase(stateItr);
}
}
}
if (avatarEntities.size() != _avatarEntityForRecording.size()) {
createRecordingIDs();
}
});
setAvatarEntityDataChanged(false);
}
void Avatar::removeAvatarEntitiesFromTree() {
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
if (entityTree) {
QList<QUuid> avatarEntityIDs;
_avatarEntitiesLock.withReadLock([&] {
avatarEntityIDs = _packedAvatarEntityData.keys();
});
entityTree->withWriteLock([&] {
AvatarEntityMap avatarEntities = getAvatarEntityData();
for (auto entityID : avatarEntities.keys()) {
for (const auto& entityID : avatarEntityIDs) {
entityTree->deleteEntity(entityID, true, true);
}
});
@ -651,87 +492,6 @@ void Avatar::relayJointDataToChildren() {
_reconstructSoftEntitiesJointMap = false;
}
void Avatar::simulate(float deltaTime, bool inView) {
PROFILE_RANGE(simulation, "simulate");
_globalPosition = _transit.isActive() ? _transit.getCurrentPosition() : _serverPosition;
if (!hasParent()) {
setLocalPosition(_globalPosition);
}
_simulationRate.increment();
if (inView) {
_simulationInViewRate.increment();
}
PerformanceTimer perfTimer("simulate");
{
PROFILE_RANGE(simulation, "updateJoints");
if (inView) {
Head* head = getHead();
if (_hasNewJointData || _transit.isActive()) {
_skeletonModel->getRig().copyJointsFromJointData(_jointData);
glm::mat4 rootTransform = glm::scale(_skeletonModel->getScale()) * glm::translate(_skeletonModel->getOffset());
_skeletonModel->getRig().computeExternalPoses(rootTransform);
_jointDataSimulationRate.increment();
_skeletonModel->simulate(deltaTime, true);
locationChanged(); // joints changed, so if there are any children, update them.
_hasNewJointData = false;
glm::vec3 headPosition = getWorldPosition();
if (!_skeletonModel->getHeadPosition(headPosition)) {
headPosition = getWorldPosition();
}
head->setPosition(headPosition);
}
head->setScale(getModelScale());
head->simulate(deltaTime);
relayJointDataToChildren();
} else {
// a non-full update is still required so that the position, rotation, scale and bounds of the skeletonModel are updated.
_skeletonModel->simulate(deltaTime, false);
}
_skeletonModelSimulationRate.increment();
}
// update animation for display name fade in/out
if ( _displayNameTargetAlpha != _displayNameAlpha) {
// the alpha function is
// Fade out => alpha(t) = factor ^ t => alpha(t+dt) = alpha(t) * factor^(dt)
// Fade in => alpha(t) = 1 - factor^t => alpha(t+dt) = 1-(1-alpha(t))*coef^(dt)
// factor^(dt) = coef
float coef = pow(DISPLAYNAME_FADE_FACTOR, deltaTime);
if (_displayNameTargetAlpha < _displayNameAlpha) {
// Fading out
_displayNameAlpha *= coef;
} else {
// Fading in
_displayNameAlpha = 1 - (1 - _displayNameAlpha) * coef;
}
_displayNameAlpha = abs(_displayNameAlpha - _displayNameTargetAlpha) < 0.01f ? _displayNameTargetAlpha : _displayNameAlpha;
}
{
PROFILE_RANGE(simulation, "misc");
measureMotionDerivatives(deltaTime);
simulateAttachments(deltaTime);
updatePalms();
}
{
PROFILE_RANGE(simulation, "entities");
updateAvatarEntities();
}
{
PROFILE_RANGE(simulation, "grabs");
updateGrabs();
}
updateFadingStatus();
}
float Avatar::getSimulationRate(const QString& rateName) const {
if (rateName == "") {
return _simulationRate.rate();
@ -1046,7 +806,6 @@ void Avatar::render(RenderArgs* renderArgs) {
}
}
void Avatar::setEnableMeshVisible(bool isEnabled) {
if (_isMeshVisible != isEnabled) {
_isMeshVisible = isEnabled;

View file

@ -139,9 +139,8 @@ public:
typedef render::Payload<AvatarData> Payload;
void init();
void updateAvatarEntities();
void removeAvatarEntitiesFromTree();
void simulate(float deltaTime, bool inView);
virtual void simulate(float deltaTime, bool inView) = 0;
virtual void simulateAttachments(float deltaTime);
virtual void render(RenderArgs* renderArgs);
@ -240,8 +239,6 @@ public:
static void renderJointConnectingCone(gpu::Batch& batch, glm::vec3 position1, glm::vec3 position2,
float radius1, float radius2, const glm::vec4& color);
virtual void applyCollision(const glm::vec3& contactPoint, const glm::vec3& penetration) { }
/**jsdoc
* Set the offset applied to the current avatar. The offset adjusts the position that the avatar is rendered. For example,
* with an offset of <code>{ x: 0, y: 0.1, z: 0 }</code>, your avatar will appear to be raised off the ground slightly.

View file

@ -14,16 +14,33 @@
#include "AvatarTraits.h"
// This templated class is admittedly fairly confusing to look at. It is used
// to hold some associated value of type T for both simple (non-instanced) and instanced traits.
// Most of the complexity comes from the fact that simple and instanced trait types are
// handled differently. For each simple trait type there can be a value T, but for
// each instance of each instanced trait
// (keyed by a TraitInstanceID, which at the time of this writing is a UUID) there can be a value T.
// There are separate methods in most cases for simple traits and instanced traits
// because of this different behaviour. This class is not used to hold the values
// of the traits themselves, but instead an associated value like the latest version
// of each trait (see TraitVersions) or a state associated with each trait (like added/changed/deleted).
namespace AvatarTraits {
template<typename T, T defaultValue>
class AssociatedTraitValues {
public:
// constructor that pre-fills _simpleTypes with the default value specified by the template
AssociatedTraitValues() : _simpleTypes(FirstInstancedTrait, defaultValue) {}
/// inserts the given value for the given simple trait type
void insert(TraitType type, T value) { _simpleTypes[type] = value; }
/// resets the simple trait type value to the default
void erase(TraitType type) { _simpleTypes[type] = defaultValue; }
/// returns a reference to the value for a given instance for a given instanced trait type
T& getInstanceValueRef(TraitType traitType, TraitInstanceID instanceID);
/// inserts the passed value for the given instance for the given instanced trait type
void instanceInsert(TraitType traitType, TraitInstanceID instanceID, T value);
struct InstanceIDValuePair {
@ -34,24 +51,30 @@ namespace AvatarTraits {
};
using InstanceIDValuePairs = std::vector<InstanceIDValuePair>;
/// returns a vector of InstanceIDValuePair objects for the given instanced trait type
InstanceIDValuePairs& getInstanceIDValuePairs(TraitType traitType);
/// erases the a given instance for a given instanced trait type
void instanceErase(TraitType traitType, TraitInstanceID instanceID);
/// erases the value for all instances for a given instanced trait type
void eraseAllInstances(TraitType traitType);
// will return defaultValue for instanced traits
/// value getters for simple trait types, will be default value if value has been erased or not set
T operator[](TraitType traitType) const { return _simpleTypes[traitType]; }
T& operator[](TraitType traitType) { return _simpleTypes[traitType]; }
/// resets all simple trait types to the default value and erases all values for instanced trait types
void reset() {
std::fill(_simpleTypes.begin(), _simpleTypes.end(), defaultValue);
_instancedTypes.clear();
}
/// 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(); }
/// 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(); }
@ -64,15 +87,18 @@ namespace AvatarTraits {
traitType(traitType), instances({{ instanceID, value }}) {};
};
/// const iterators for the vector of TraitWithInstances objects
typename std::vector<TraitWithInstances>::const_iterator instancedCBegin() const { return _instancedTypes.cbegin(); }
typename std::vector<TraitWithInstances>::const_iterator instancedCEnd() const { return _instancedTypes.cend(); }
/// non-const iterators for the vector of TraitWithInstances objects
typename std::vector<TraitWithInstances>::iterator instancedBegin() { return _instancedTypes.begin(); }
typename std::vector<TraitWithInstances>::iterator instancedEnd() { return _instancedTypes.end(); }
private:
std::vector<T> _simpleTypes;
/// return the iterator to the matching TraitWithInstances object for a given instanced trait type
typename std::vector<TraitWithInstances>::iterator instancesForTrait(TraitType traitType) {
return std::find_if(_instancedTypes.begin(), _instancedTypes.end(),
[traitType](TraitWithInstances& traitWithInstances){
@ -83,25 +109,34 @@ namespace AvatarTraits {
std::vector<TraitWithInstances> _instancedTypes;
};
/// returns a reference to the InstanceIDValuePairs object for a given instanced trait type
template <typename T, T defaultValue>
inline typename AssociatedTraitValues<T, defaultValue>::InstanceIDValuePairs&
AssociatedTraitValues<T, defaultValue>::getInstanceIDValuePairs(TraitType traitType) {
// first check if we already have some values for instances of this trait type
auto it = instancesForTrait(traitType);
if (it != _instancedTypes.end()) {
return it->instances;
} else {
// if we didn't have any values for instances of the instanced trait type
// add an empty InstanceIDValuePairs object first and then return the reference to it
_instancedTypes.emplace_back(traitType);
return _instancedTypes.back().instances;
}
}
// returns a reference to value for the given instance of the given instanced trait type
template <typename T, T defaultValue>
inline T& AssociatedTraitValues<T, defaultValue>::getInstanceValueRef(TraitType traitType, TraitInstanceID instanceID) {
// first check if we already have some values for instances of this trait type
auto it = instancesForTrait(traitType);
if (it != _instancedTypes.end()) {
// grab the matching vector of instances
auto& instancesVector = it->instances;
// check if we have a value for this specific instance ID
auto instanceIt = std::find_if(instancesVector.begin(), instancesVector.end(),
[instanceID](InstanceIDValuePair& idValuePair){
return idValuePair.id == instanceID;
@ -109,40 +144,53 @@ namespace AvatarTraits {
if (instanceIt != instancesVector.end()) {
return instanceIt->value;
} else {
// no value for this specific instance ID, insert the default value and return it
instancesVector.emplace_back(instanceID, defaultValue);
return instancesVector.back().value;
}
} else {
// no values for any instances of this trait type
// insert the default value for the specific instance for the instanced trait type
_instancedTypes.emplace_back(traitType, instanceID, defaultValue);
return _instancedTypes.back().instances.back().value;
}
}
/// inserts the passed value for the specific instance of the given instanced trait type
template <typename T, T defaultValue>
inline void AssociatedTraitValues<T, defaultValue>::instanceInsert(TraitType traitType, TraitInstanceID instanceID, T value) {
// first check if we already have some instances for this trait type
auto it = instancesForTrait(traitType);
if (it != _instancedTypes.end()) {
// found some instances for the instanced trait type, check if our specific instance is one of them
auto& instancesVector = it->instances;
auto instanceIt = std::find_if(instancesVector.begin(), instancesVector.end(),
[instanceID](InstanceIDValuePair& idValuePair){
return idValuePair.id == instanceID;
});
if (instanceIt != instancesVector.end()) {
// the instance already existed, update the value
instanceIt->value = value;
} else {
// the instance was not present, emplace the new value
instancesVector.emplace_back(instanceID, value);
}
} else {
// there were no existing instances for the given trait type
// setup the container for instances and insert the passed value for this instance ID
_instancedTypes.emplace_back(traitType, instanceID, value);
}
}
/// erases the value for a specific instance of the given instanced trait type
template <typename T, T defaultValue>
inline void AssociatedTraitValues<T, defaultValue>::instanceErase(TraitType traitType, TraitInstanceID instanceID) {
// check if we have any instances at all for this instanced trait type
auto it = instancesForTrait(traitType);
if (it != _instancedTypes.end()) {
// we have some instances, erase the value for the passed instance ID if it is present
auto& instancesVector = it->instances;
instancesVector.erase(std::remove_if(instancesVector.begin(),
instancesVector.end(),

View file

@ -1908,10 +1908,9 @@ qint64 AvatarData::packAvatarEntityTraitInstance(AvatarTraits::TraitType traitTy
// grab a read lock on the avatar entities and check for entity data for the given ID
QByteArray entityBinaryData;
_avatarEntitiesLock.withReadLock([this, &entityBinaryData, &traitInstanceID] {
if (_avatarEntityData.contains(traitInstanceID)) {
entityBinaryData = _avatarEntityData[traitInstanceID];
if (_packedAvatarEntityData.contains(traitInstanceID)) {
entityBinaryData = _packedAvatarEntityData[traitInstanceID];
}
});
@ -1987,9 +1986,9 @@ qint64 AvatarData::packTraitInstance(AvatarTraits::TraitType traitType, AvatarTr
qint64 bytesWritten = 0;
if (traitType == AvatarTraits::AvatarEntity) {
packAvatarEntityTraitInstance(traitType, traitInstanceID, destination, traitVersion);
bytesWritten += packAvatarEntityTraitInstance(traitType, traitInstanceID, destination, traitVersion);
} else if (traitType == AvatarTraits::Grab) {
packGrabTraitInstance(traitType, traitInstanceID, destination, traitVersion);
bytesWritten += packGrabTraitInstance(traitType, traitInstanceID, destination, traitVersion);
}
return bytesWritten;
@ -1998,7 +1997,7 @@ qint64 AvatarData::packTraitInstance(AvatarTraits::TraitType traitType, AvatarTr
void AvatarData::prepareResetTraitInstances() {
if (_clientTraitsHandler) {
_avatarEntitiesLock.withReadLock([this]{
foreach (auto entityID, _avatarEntityData.keys()) {
foreach (auto entityID, _packedAvatarEntityData.keys()) {
_clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::AvatarEntity, entityID);
}
foreach (auto grabID, _avatarGrabData.keys()) {
@ -2019,7 +2018,7 @@ void AvatarData::processTrait(AvatarTraits::TraitType traitType, QByteArray trai
void AvatarData::processTraitInstance(AvatarTraits::TraitType traitType,
AvatarTraits::TraitInstanceID instanceID, QByteArray traitBinaryData) {
if (traitType == AvatarTraits::AvatarEntity) {
updateAvatarEntity(instanceID, traitBinaryData);
storeAvatarEntityDataPayload(instanceID, traitBinaryData);
} else if (traitType == AvatarTraits::Grab) {
updateAvatarGrabData(instanceID, traitBinaryData);
}
@ -2367,7 +2366,7 @@ void AvatarData::setRecordingBasis(std::shared_ptr<Transform> recordingBasis) {
void AvatarData::createRecordingIDs() {
_avatarEntitiesLock.withReadLock([&] {
_avatarEntityForRecording.clear();
for (int i = 0; i < _avatarEntityData.size(); i++) {
for (int i = 0; i < _packedAvatarEntityData.size(); i++) {
_avatarEntityForRecording.insert(QUuid::createUuid());
}
});
@ -2422,6 +2421,10 @@ JointData jointDataFromJsonValue(int version, const QJsonValue& json) {
return result;
}
void AvatarData::avatarEntityDataToJson(QJsonObject& root) const {
// overridden where needed
}
QJsonObject AvatarData::toJson() const {
QJsonObject root;
@ -2433,20 +2436,8 @@ QJsonObject AvatarData::toJson() const {
if (!getDisplayName().isEmpty()) {
root[JSON_AVATAR_DISPLAY_NAME] = getDisplayName();
}
_avatarEntitiesLock.withReadLock([&] {
if (!_avatarEntityData.empty()) {
QJsonArray avatarEntityJson;
int entityCount = 0;
for (auto entityID : _avatarEntityData.keys()) {
QVariantMap entityData;
QUuid newId = _avatarEntityForRecording.size() == _avatarEntityData.size() ? _avatarEntityForRecording.values()[entityCount++] : entityID;
entityData.insert("id", newId);
entityData.insert("properties", _avatarEntityData.value(entityID).toBase64());
avatarEntityJson.push_back(QVariant(entityData).toJsonObject());
}
root[JSON_AVATAR_ENTITIES] = avatarEntityJson;
}
});
avatarEntityDataToJson(root);
auto recordingBasis = getRecordingBasis();
bool success;
@ -2568,9 +2559,9 @@ void AvatarData::fromJson(const QJsonObject& json, bool useFrameSkeleton) {
for (auto attachmentJson : attachmentsJson) {
if (attachmentJson.isObject()) {
QVariantMap entityData = attachmentJson.toObject().toVariantMap();
QUuid entityID = entityData.value("id").toUuid();
QByteArray properties = QByteArray::fromBase64(entityData.value("properties").toByteArray());
updateAvatarEntity(entityID, properties);
QUuid id = entityData.value("id").toUuid();
QByteArray data = QByteArray::fromBase64(entityData.value("properties").toByteArray());
updateAvatarEntity(id, data);
}
}
}
@ -2752,17 +2743,15 @@ void AvatarData::setAttachmentsVariant(const QVariantList& variant) {
setAttachmentData(newAttachments);
}
const int MAX_NUM_AVATAR_ENTITIES = 42;
void AvatarData::updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData) {
void AvatarData::storeAvatarEntityDataPayload(const QUuid& entityID, const QByteArray& data) {
_avatarEntitiesLock.withWriteLock([&] {
AvatarEntityMap::iterator itr = _avatarEntityData.find(entityID);
if (itr == _avatarEntityData.end()) {
if (_avatarEntityData.size() < MAX_NUM_AVATAR_ENTITIES) {
_avatarEntityData.insert(entityID, entityData);
PackedAvatarEntityMap::iterator itr = _packedAvatarEntityData.find(entityID);
if (itr == _packedAvatarEntityData.end()) {
if (_packedAvatarEntityData.size() < MAX_NUM_AVATAR_ENTITIES) {
_packedAvatarEntityData.insert(entityID, data);
}
} else {
itr.value() = entityData;
itr.value() = data;
}
});
@ -2775,15 +2764,20 @@ void AvatarData::updateAvatarEntity(const QUuid& entityID, const QByteArray& ent
}
}
void AvatarData::updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData) {
// overridden where needed
// expects 'entityData' to be a JavaScript EntityItemProperties Object in QByteArray form
}
void AvatarData::clearAvatarEntity(const QUuid& entityID, bool requiresRemovalFromTree) {
bool removedEntity = false;
_avatarEntitiesLock.withWriteLock([this, &removedEntity, &entityID] {
removedEntity = _avatarEntityData.remove(entityID);
removedEntity = _packedAvatarEntityData.remove(entityID);
});
insertDetachedEntityID(entityID);
insertRemovedEntityID(entityID);
if (removedEntity && _clientTraitsHandler) {
// we have a client traits handler, so we need to mark this removed instance trait as deleted
@ -2793,75 +2787,29 @@ void AvatarData::clearAvatarEntity(const QUuid& entityID, bool requiresRemovalFr
}
AvatarEntityMap AvatarData::getAvatarEntityData() const {
AvatarEntityMap result;
_avatarEntitiesLock.withReadLock([&] {
result = _avatarEntityData;
});
return result;
}
void AvatarData::insertDetachedEntityID(const QUuid entityID) {
_avatarEntitiesLock.withWriteLock([&] {
_avatarEntityDetached.insert(entityID);
});
_avatarEntityDataChanged = true;
// overridden where needed
// NOTE: the return value is expected to be a map of unfortunately-formatted-binary-blobs
return AvatarEntityMap();
}
void AvatarData::setAvatarEntityData(const AvatarEntityMap& avatarEntityData) {
if (avatarEntityData.size() > MAX_NUM_AVATAR_ENTITIES) {
// the data is suspect
qCDebug(avatars) << "discard suspect AvatarEntityData with size =" << avatarEntityData.size();
return;
}
std::vector<QUuid> deletedEntityIDs;
QList<QUuid> updatedEntityIDs;
_avatarEntitiesLock.withWriteLock([&] {
if (_avatarEntityData != avatarEntityData) {
// keep track of entities that were attached to this avatar but no longer are
AvatarEntityIDs previousAvatarEntityIDs = QSet<QUuid>::fromList(_avatarEntityData.keys());
_avatarEntityData = avatarEntityData;
setAvatarEntityDataChanged(true);
deletedEntityIDs.reserve(previousAvatarEntityIDs.size());
foreach (auto entityID, previousAvatarEntityIDs) {
if (!_avatarEntityData.contains(entityID)) {
_avatarEntityDetached.insert(entityID);
deletedEntityIDs.push_back(entityID);
}
}
updatedEntityIDs = _avatarEntityData.keys();
}
});
if (_clientTraitsHandler) {
// we have a client traits handler
// flag removed entities as deleted so that changes are sent next frame
for (auto& deletedEntityID : deletedEntityIDs) {
_clientTraitsHandler->markInstancedTraitDeleted(AvatarTraits::AvatarEntity, deletedEntityID);
}
// flag any updated or created entities so that we send changes for them next frame
for (auto& entityID : updatedEntityIDs) {
_clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::AvatarEntity, entityID);
}
}
// overridden where needed
// avatarEntityData is expected to be a map of QByteArrays
// each QByteArray represents an EntityItemProperties object from JavaScript
}
AvatarEntityIDs AvatarData::getAndClearRecentlyDetachedIDs() {
void AvatarData::insertRemovedEntityID(const QUuid entityID) {
_avatarEntitiesLock.withWriteLock([&] {
_avatarEntityRemoved.insert(entityID);
});
_avatarEntityDataChanged = true;
}
AvatarEntityIDs AvatarData::getAndClearRecentlyRemovedIDs() {
AvatarEntityIDs result;
_avatarEntitiesLock.withWriteLock([&] {
result = _avatarEntityDetached;
_avatarEntityDetached.clear();
result = _avatarEntityRemoved;
_avatarEntityRemoved.clear();
});
return result;
}

View file

@ -63,6 +63,7 @@ using AvatarWeakPointer = std::weak_ptr<AvatarData>;
using AvatarHash = QHash<QUuid, AvatarSharedPointer>;
using AvatarEntityMap = QMap<QUuid, QByteArray>;
using PackedAvatarEntityMap = QMap<QUuid, QByteArray>; // similar to AvatarEntityMap, but different internal format
using AvatarEntityIDs = QSet<QUuid>;
using AvatarGrabDataMap = QMap<QUuid, QByteArray>;
@ -71,6 +72,8 @@ using AvatarGrabMap = QMap<QUuid, GrabPointer>;
using AvatarDataSequenceNumber = uint16_t;
const int MAX_NUM_AVATAR_ENTITIES = 42;
// avatar motion behaviors
const quint32 AVATAR_MOTION_ACTION_MOTOR_ENABLED = 1U << 0;
const quint32 AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED = 1U << 1;
@ -952,19 +955,20 @@ public:
// FIXME: Can this name be improved? Can it be deprecated?
Q_INVOKABLE virtual void setAttachmentsVariant(const QVariantList& variant);
virtual void storeAvatarEntityDataPayload(const QUuid& entityID, const QByteArray& payload);
/**jsdoc
* @function MyAvatar.updateAvatarEntity
* @param {Uuid} entityID
* @param {string} entityData
*/
Q_INVOKABLE void updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData);
Q_INVOKABLE virtual void updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData);
/**jsdoc
* @function MyAvatar.clearAvatarEntity
* @param {Uuid} entityID
*/
Q_INVOKABLE void clearAvatarEntity(const QUuid& entityID, bool requiresRemovalFromTree = true);
Q_INVOKABLE virtual void clearAvatarEntity(const QUuid& entityID, bool requiresRemovalFromTree = true);
/**jsdoc
@ -1125,6 +1129,7 @@ public:
TransformPointer getRecordingBasis() const;
void setRecordingBasis(TransformPointer recordingBasis = TransformPointer());
void createRecordingIDs();
virtual void avatarEntityDataToJson(QJsonObject& root) const;
QJsonObject toJson() const;
void fromJson(const QJsonObject& json, bool useFrameSkeleton = true);
@ -1136,17 +1141,16 @@ public:
* @function MyAvatar.getAvatarEntityData
* @returns {object}
*/
Q_INVOKABLE AvatarEntityMap getAvatarEntityData() const;
Q_INVOKABLE virtual AvatarEntityMap getAvatarEntityData() const;
/**jsdoc
* @function MyAvatar.setAvatarEntityData
* @param {object} avatarEntityData
*/
Q_INVOKABLE void setAvatarEntityData(const AvatarEntityMap& avatarEntityData);
Q_INVOKABLE virtual void setAvatarEntityData(const AvatarEntityMap& avatarEntityData);
virtual void setAvatarEntityDataChanged(bool value) { _avatarEntityDataChanged = value; }
void insertDetachedEntityID(const QUuid entityID);
AvatarEntityIDs getAndClearRecentlyDetachedIDs();
AvatarEntityIDs getAndClearRecentlyRemovedIDs();
/**jsdoc
* @function MyAvatar.getSensorToWorldMatrix
@ -1333,6 +1337,7 @@ public slots:
void resetLastSent() { _lastToByteArray = 0; }
protected:
void insertRemovedEntityID(const QUuid entityID);
void lazyInitHeadData() const;
float getDistanceBasedMinRotationDOT(glm::vec3 viewerPosition) const;
@ -1461,9 +1466,9 @@ protected:
AABox _defaultBubbleBox;
mutable ReadWriteLockable _avatarEntitiesLock;
AvatarEntityIDs _avatarEntityDetached; // recently detached from this avatar
AvatarEntityIDs _avatarEntityRemoved; // recently removed AvatarEntity ids
AvatarEntityIDs _avatarEntityForRecording; // create new entities id for avatar recording
AvatarEntityMap _avatarEntityData;
PackedAvatarEntityMap _packedAvatarEntityData;
bool _avatarEntityDataChanged { false };
mutable ReadWriteLockable _avatarGrabsLock;

View file

@ -328,6 +328,19 @@ void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer<ReceivedMessage>
}
void AvatarHashMap::processBulkAvatarTraits(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
AvatarTraits::TraitMessageSequence seq;
message->readPrimitive(&seq);
auto traitsAckPacket = NLPacket::create(PacketType::BulkAvatarTraitsAck, sizeof(AvatarTraits::TraitMessageSequence), true);
traitsAckPacket->writePrimitive(seq);
auto nodeList = DependencyManager::get<LimitedNodeList>();
SharedNodePointer avatarMixer = nodeList->soloNodeOfType(NodeType::AvatarMixer);
if (!avatarMixer.isNull()) {
// we have a mixer to send to, acknowledge that we received these
// traits.
nodeList->sendPacket(std::move(traitsAckPacket), *avatarMixer);
}
while (message->getBytesLeftToRead()) {
// read the avatar ID to figure out which avatar this is for

View file

@ -42,6 +42,10 @@ namespace AvatarTraits {
const TraitWireSize DELETED_TRAIT_SIZE = -1;
const TraitWireSize MAXIMUM_TRAIT_SIZE = INT16_MAX;
using TraitMessageSequence = int64_t;
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;

View file

@ -52,11 +52,17 @@ namespace controller {
* <tr><td><code>TranslateZ</code></td><td>number</td><td>number</td><td>Move the user's avatar in the direction of its
* z-axis, if the camera isn't in independent or mirror modes.</td></tr>
* <tr><td><code>Pitch</code></td><td>number</td><td>number</td><td>Rotate the user's avatar head and attached camera
* about its negative x-axis (i.e., positive values pitch down), if the camera isn't in HMD, independent, or mirror
* modes.</td></tr>
* <tr><td><code>Yaw</code></td><td>number</td><td>number</td><td>Rotate the user's avatar about its y-axis, if the
* camera isn't in independent or mirror modes.</td></tr>
* about its negative x-axis (i.e., positive values pitch down) at a rate proportional to the control value, if the
* camera isn't in HMD, independent, or mirror modes.</td></tr>
* <tr><td><code>Yaw</code></td><td>number</td><td>number</td><td>Rotate the user's avatar about its y-axis at a rate
* proportional to the control value, if the camera isn't in independent or mirror modes.</td></tr>
* <tr><td><code>Roll</code></td><td>number</td><td>number</td><td>No action.</td></tr>
* <tr><td><code>DeltaPitch</code></td><td>number</td><td>number</td><td>Rotate the user's avatar head and attached
* camera about its negative x-axis (i.e., positive values pitch down) by an amount proportional to the control value,
* if the camera isn't in HMD, independent, or mirror modes.</td></tr>
* <tr><td><code>DeltaYaw</code></td><td>number</td><td>number</td><td>Rotate the user's avatar about its y-axis by an
* amount proportional to the control value, if the camera isn't in independent or mirror modes.</td></tr>
* <tr><td><code>DeltaRoll</code></td><td>number</td><td>number</td><td>No action.</td></tr>
* <tr><td><code>StepTranslateX</code></td><td>number</td><td>number</td><td>No action.</td></tr>
* <tr><td><code>StepTranslateY</code></td><td>number</td><td>number</td><td>No action.</td></tr>
* <tr><td><code>StepTranslateZ</code></td><td>number</td><td>number</td><td>No action.</td></tr>
@ -318,6 +324,9 @@ namespace controller {
makeAxisPair(Action::ROLL, "Roll"),
makeAxisPair(Action::PITCH, "Pitch"),
makeAxisPair(Action::YAW, "Yaw"),
makeAxisPair(Action::DELTA_YAW, "DeltaYaw"),
makeAxisPair(Action::DELTA_PITCH, "DeltaPitch"),
makeAxisPair(Action::DELTA_ROLL, "DeltaRoll"),
makeAxisPair(Action::STEP_YAW, "StepYaw"),
makeAxisPair(Action::STEP_PITCH, "StepPitch"),
makeAxisPair(Action::STEP_ROLL, "StepRoll"),

View file

@ -27,6 +27,10 @@ enum class Action {
ROTATE_Y, YAW = ROTATE_Y,
ROTATE_Z, ROLL = ROTATE_Z,
DELTA_PITCH,
DELTA_YAW,
DELTA_ROLL,
STEP_YAW,
// FIXME does this have a use case?
STEP_PITCH,

View file

@ -0,0 +1,21 @@
//
// AxisValue.cpp
//
// Created by David Rowe on 14 Dec 2018.
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "AxisValue.h"
namespace controller {
AxisValue::AxisValue(const float value, const quint64 timestamp) :
value(value), timestamp(timestamp) { }
bool AxisValue::operator==(const AxisValue& right) const {
return value == right.value && timestamp == right.timestamp;
}
}

View file

@ -0,0 +1,34 @@
//
// AxisValue.h
//
// Created by David Rowe on 13 Dec 2018.
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#pragma once
#ifndef hifi_controllers_AxisValue_h
#define hifi_controllers_AxisValue_h
#include <QtCore/qglobal.h>
namespace controller {
struct AxisValue {
public:
float value { 0.0f };
// The value can be timestamped to determine if consecutive identical values should be output (e.g., mouse movement).
quint64 timestamp { 0 };
AxisValue() {}
AxisValue(const float value, const quint64 timestamp);
bool operator ==(const AxisValue& right) const;
bool operator !=(const AxisValue& right) const { return !(*this == right); }
};
}
#endif // hifi_controllers_AxisValue_h

View file

@ -26,12 +26,12 @@ namespace controller {
return 0.0f;
}
float InputDevice::getAxis(int channel) const {
AxisValue InputDevice::getAxis(int channel) const {
auto axis = _axisStateMap.find(channel);
if (axis != _axisStateMap.end()) {
return (*axis).second;
} else {
return 0.0f;
return AxisValue();
}
}
@ -68,26 +68,25 @@ namespace controller {
return Input::NamedPair(makeInput(pose), name);
}
float InputDevice::getValue(ChannelType channelType, uint16_t channel) const {
AxisValue InputDevice::getValue(ChannelType channelType, uint16_t channel) const {
switch (channelType) {
case ChannelType::AXIS:
return getAxis(channel);
case ChannelType::BUTTON:
return getButton(channel);
return { getButton(channel), 0 };
case ChannelType::POSE:
return getPose(channel).valid ? 1.0f : 0.0f;
return { getPose(channel).valid ? 1.0f : 0.0f, 0 };
default:
break;
}
return 0.0f;
return { 0.0f, 0 };
}
float InputDevice::getValue(const Input& input) const {
AxisValue InputDevice::getValue(const Input& input) const {
return getValue(input.getType(), input.channel);
}

View file

@ -16,6 +16,7 @@
#include <QtCore/QString>
#include "AxisValue.h"
#include "Pose.h"
#include "Input.h"
#include "StandardControls.h"
@ -103,16 +104,16 @@ public:
using Pointer = std::shared_ptr<InputDevice>;
typedef std::unordered_set<int> ButtonPressedMap;
typedef std::map<int, float> AxisStateMap;
typedef std::map<int, AxisValue> AxisStateMap;
typedef std::map<int, Pose> PoseStateMap;
// Get current state for each channel
float getButton(int channel) const;
float getAxis(int channel) const;
AxisValue getAxis(int channel) const;
Pose getPose(int channel) const;
float getValue(const Input& input) const;
float getValue(ChannelType channelType, uint16_t channel) const;
AxisValue getValue(const Input& input) const;
AxisValue getValue(ChannelType channelType, uint16_t channel) const;
Pose getPoseValue(uint16_t channel) const;
const QString& getName() const { return _name; }

View file

@ -297,6 +297,13 @@ namespace controller {
return 0.0f;
}
InputRecorder::ActionStates InputRecorder::getActionstates() {
if (_actionStateList.size() > 0) {
return _actionStateList[_playCount];
}
return {};
}
controller::Pose InputRecorder::getPoseState(const QString& action) {
if (_poseStateList.size() > 0) {
return _poseStateList[_playCount][action];

View file

@ -45,6 +45,7 @@ namespace controller {
void setActionState(const QString& action, float value);
void setActionState(const QString& action, const controller::Pose& pose);
float getActionState(const QString& action);
ActionStates getActionstates();
controller::Pose getPoseState(const QString& action);
QString getSaveDirectory();
void frameTick();

View file

@ -89,17 +89,17 @@ namespace controller {
float ScriptingInterface::getValue(const int& source) const {
auto userInputMapper = DependencyManager::get<UserInputMapper>();
return userInputMapper->getValue(Input((uint32_t)source));
return userInputMapper->getValue(Input((uint32_t)source)).value;
}
float ScriptingInterface::getAxisValue(int source) const {
auto userInputMapper = DependencyManager::get<UserInputMapper>();
return userInputMapper->getValue(Input((uint32_t)source));
return userInputMapper->getValue(Input((uint32_t)source)).value;
}
Pose ScriptingInterface::getPoseValue(const int& source) const {
auto userInputMapper = DependencyManager::get<UserInputMapper>();
return userInputMapper->getPose(Input((uint32_t)source));
return userInputMapper->getPose(Input((uint32_t)source));
}
QVector<Action> ScriptingInterface::getAllActions() {

View file

@ -290,17 +290,17 @@ void UserInputMapper::update(float deltaTime) {
if ((int)_lastStandardStates.size() != standardInputs.size()) {
_lastStandardStates.resize(standardInputs.size());
for (auto& lastValue : _lastStandardStates) {
lastValue = 0;
lastValue = AxisValue();
}
}
for (int i = 0; i < standardInputs.size(); ++i) {
const auto& input = standardInputs[i].first;
float value = getValue(input);
float& oldValue = _lastStandardStates[i];
AxisValue value = getValue(input);
AxisValue& oldValue = _lastStandardStates[i];
if (value != oldValue) {
oldValue = value;
emit inputEvent(input.id, value);
emit inputEvent(input.id, value.value);
}
}
inputRecorder->frameTick();
@ -489,6 +489,21 @@ void UserInputMapper::runMappings() {
}
applyRoutes(_standardRoutes);
InputRecorder* inputRecorder = InputRecorder::getInstance();
if (inputRecorder->isPlayingback()) {
if (debugRoutes) {
qCDebug(controllers) << "Playing back recording actions";
}
// Play back each numeric action even if there is no current route active for the action.
auto actionStates = inputRecorder->getActionstates();
for (InputRecorder::ActionStates::iterator it = actionStates.begin(); it != actionStates.end(); ++it) {
setActionState((Action)findAction(it->first), it->second);
}
// Poses are played back in StandardEndpoint.
}
if (debugRoutes) {
qCDebug(controllers) << "Done with mappings";
}
@ -604,10 +619,10 @@ bool UserInputMapper::applyRoute(const Route::Pointer& route, bool force) {
destination->apply(value, source);
} else {
// Fetch the value, may have been overriden by previous loopback routes
float value = getValue(source, route->peek);
auto value = getValue(source, route->peek);
if (debugRoutes && route->debug) {
qCDebug(controllers) << "Value was " << value;
qCDebug(controllers) << "Value was " << value.value << value.timestamp;
}
// Apply each of the filters.
for (const auto& filter : route->filters) {
@ -615,7 +630,7 @@ bool UserInputMapper::applyRoute(const Route::Pointer& route, bool force) {
}
if (debugRoutes && route->debug) {
qCDebug(controllers) << "Filtered value was " << value;
qCDebug(controllers) << "Filtered value was " << value.value << value.timestamp;
}
destination->apply(value, source);
@ -741,15 +756,15 @@ void UserInputMapper::enableMapping(const QString& mappingName, bool enable) {
}
}
float UserInputMapper::getValue(const Endpoint::Pointer& endpoint, bool peek) {
AxisValue UserInputMapper::getValue(const Endpoint::Pointer& endpoint, bool peek) {
return peek ? endpoint->peek() : endpoint->value();
}
float UserInputMapper::getValue(const Input& input) const {
AxisValue UserInputMapper::getValue(const Input& input) const {
Locker locker(_lock);
auto endpoint = endpointFor(input);
if (!endpoint) {
return 0;
return AxisValue();
}
return endpoint->value();
}

View file

@ -121,7 +121,7 @@ namespace controller {
void unloadMappings(const QStringList& jsonFiles);
void unloadMapping(const QString& jsonFile);
float getValue(const Input& input) const;
AxisValue getValue(const Input& input) const;
Pose getPose(const Input& input) const;
// perform an action when the UserInputMapper mutex is acquired.
@ -147,9 +147,9 @@ namespace controller {
std::vector<float> _actionScales = std::vector<float>(toInt(Action::NUM_ACTIONS), 1.0f);
std::vector<float> _lastActionStates = std::vector<float>(toInt(Action::NUM_ACTIONS), 0.0f);
std::vector<Pose> _poseStates = std::vector<Pose>(toInt(Action::NUM_ACTIONS));
std::vector<float> _lastStandardStates = std::vector<float>();
std::vector<AxisValue> _lastStandardStates = std::vector<AxisValue>();
static float getValue(const EndpointPointer& endpoint, bool peek = false);
static AxisValue getValue(const EndpointPointer& endpoint, bool peek = false);
static Pose getPose(const EndpointPointer& endpoint, bool peek = false);
friend class RouteBuilderProxy;

View file

@ -16,6 +16,7 @@
#include <QtCore/QObject>
#include "../AxisValue.h"
#include "../Input.h"
#include "../Pose.h"
@ -36,9 +37,9 @@ namespace controller {
using WriteLambda = std::function<void(float)>;
Endpoint(const Input& input) : _input(input) {}
virtual float value() { return peek(); }
virtual float peek() const = 0;
virtual void apply(float value, const Pointer& source) = 0;
virtual AxisValue value() { return peek(); }
virtual AxisValue peek() const = 0;
virtual void apply(AxisValue value, const Pointer& source) = 0;
virtual Pose peekPose() const { return Pose(); };
virtual Pose pose() { return peekPose(); }
virtual void apply(const Pose& value, const Pointer& source) {}
@ -59,8 +60,8 @@ namespace controller {
LambdaEndpoint(ReadLambda readLambda, WriteLambda writeLambda = [](float) {})
: Endpoint(Input::INVALID_INPUT), _readLambda(readLambda), _writeLambda(writeLambda) { }
virtual float peek() const override { return _readLambda(); }
virtual void apply(float value, const Pointer& source) override { _writeLambda(value); }
virtual AxisValue peek() const override { return AxisValue(_readLambda(), 0); }
virtual void apply(AxisValue value, const Pointer& source) override { _writeLambda(value.value); }
private:
ReadLambda _readLambda;
@ -76,8 +77,8 @@ namespace controller {
: Endpoint(Input::INVALID_INPUT), _readLambda(readLambda), _writeLambda(writeLambda) {
}
virtual float peek() const override { return _readLambda(); }
virtual void apply(float value, const Pointer& source) override { _writeLambda(value); }
virtual AxisValue peek() const override { return AxisValue(_readLambda(), 0); }
virtual void apply(AxisValue value, const Pointer& source) override { _writeLambda(value.value); }
private:
const ReadLambda& _readLambda;
@ -91,15 +92,15 @@ namespace controller {
: Endpoint(id) {
}
virtual float peek() const override { return _currentValue; }
virtual void apply(float value, const Pointer& source) override { _currentValue = value; }
virtual AxisValue peek() const override { return _currentValue; }
virtual void apply(AxisValue value, const Pointer& source) override { _currentValue = value; }
virtual Pose peekPose() const override { return _currentPose; }
virtual void apply(const Pose& value, const Pointer& source) override {
_currentPose = value;
}
protected:
float _currentValue { 0.0f };
AxisValue _currentValue { 0.0f, 0 };
Pose _currentPose {};
};

View file

@ -21,6 +21,7 @@
#include <QtCore/QEasingCurve>
#include "../AxisValue.h"
#include "../Pose.h"
class QJsonValue;
@ -37,7 +38,7 @@ namespace controller {
virtual ~Filter() = default;
virtual float apply(float value) const = 0;
virtual AxisValue apply(AxisValue value) const = 0;
virtual Pose apply(Pose value) const = 0;
// Factory features

View file

@ -18,7 +18,7 @@ namespace controller {
class EndpointConditional : public Conditional {
public:
EndpointConditional(Endpoint::Pointer endpoint) : _endpoint(endpoint) {}
virtual bool satisfied() override { return _endpoint && _endpoint->peek() != 0.0f; }
virtual bool satisfied() override { return _endpoint && _endpoint->peek().value != 0.0f; }
private:
Endpoint::Pointer _endpoint;
};

View file

@ -15,22 +15,19 @@
using namespace controller;
void ActionEndpoint::apply(float newValue, const Pointer& source) {
void ActionEndpoint::apply(AxisValue newValue, const Pointer& source) {
auto userInputMapper = DependencyManager::get<UserInputMapper>();
InputRecorder* inputRecorder = InputRecorder::getInstance();
QString actionName;
if (inputRecorder->isPlayingback() || inputRecorder->isRecording()) {
actionName = userInputMapper->getActionName(Action(_input.getChannel()));
if (inputRecorder->isPlayingback()) {
newValue = inputRecorder->getActionState(actionName);
}
QString actionName = userInputMapper->getActionName(Action(_input.getChannel()));
inputRecorder->setActionState(actionName, newValue.value);
}
_currentValue += newValue;
_currentValue.value += newValue.value;
if (_input != Input::INVALID_INPUT) {
userInputMapper->deltaActionState(Action(_input.getChannel()), newValue);
userInputMapper->deltaActionState(Action(_input.getChannel()), newValue.value);
}
inputRecorder->setActionState(actionName, newValue);
}
void ActionEndpoint::apply(const Pose& value, const Pointer& source) {
@ -51,7 +48,7 @@ void ActionEndpoint::apply(const Pose& value, const Pointer& source) {
}
void ActionEndpoint::reset() {
_currentValue = 0.0f;
_currentValue = AxisValue();
_currentPose = Pose();
}

View file

@ -23,8 +23,8 @@ class ActionEndpoint : public Endpoint {
public:
ActionEndpoint(const Input& id = Input::INVALID_INPUT) : Endpoint(id) { }
virtual float peek() const override { return _currentValue; }
virtual void apply(float newValue, const Pointer& source) override;
virtual AxisValue peek() const override { return _currentValue; }
virtual void apply(AxisValue newValue, const Pointer& source) override;
virtual Pose peekPose() const override { return _currentPose; }
virtual void apply(const Pose& value, const Pointer& source) override;
@ -32,7 +32,7 @@ public:
virtual void reset() override;
private:
float _currentValue{ 0.0f };
AxisValue _currentValue { 0.0f, 0 };
Pose _currentPose{};
};

View file

@ -27,13 +27,13 @@ AnyEndpoint::AnyEndpoint(Endpoint::List children) : Endpoint(Input::INVALID_INPU
}
}
// The value of an any-point is considered to be the maxiumum absolute value,
// The value of an any-point is considered to be the maximum absolute value,
// this handles any's of multiple axis values as well as single values as well
float AnyEndpoint::peek() const {
float result = 0.0f;
AxisValue AnyEndpoint::peek() const {
auto result = AxisValue();
for (auto& child : _children) {
auto childValue = child->peek();
if (std::abs(childValue) > std::abs(result)) {
if (std::abs(childValue.value) > std::abs(result.value)) {
result = childValue;
}
}
@ -41,18 +41,18 @@ float AnyEndpoint::peek() const {
}
// Fetching the value must trigger any necessary side effects of value() on ALL the children.
float AnyEndpoint::value() {
float result = 0.0f;
AxisValue AnyEndpoint::value() {
auto result = AxisValue();
for (auto& child : _children) {
auto childValue = child->value();
if (std::abs(childValue) > std::abs(result)) {
if (std::abs(childValue.value) > std::abs(result.value)) {
result = childValue;
}
}
return result;
}
void AnyEndpoint::apply(float newValue, const Endpoint::Pointer& source) {
void AnyEndpoint::apply(AxisValue newValue, const Endpoint::Pointer& source) {
qFatal("AnyEndpoint is read only");
}

View file

@ -19,9 +19,9 @@ class AnyEndpoint : public Endpoint {
public:
using Endpoint::apply;
AnyEndpoint(Endpoint::List children);
virtual float peek() const override;
virtual float value() override;
virtual void apply(float newValue, const Endpoint::Pointer& source) override;
virtual AxisValue peek() const override;
virtual AxisValue value() override;
virtual void apply(AxisValue newValue, const Endpoint::Pointer& source) override;
virtual bool writeable() const override;
virtual bool readable() const override;

View file

@ -21,9 +21,9 @@ public:
using Pointer = std::shared_ptr<ArrayEndpoint>;
ArrayEndpoint() : Endpoint(Input::INVALID_INPUT) { }
virtual float peek() const override { return 0.0f; }
virtual AxisValue peek() const override { return AxisValue(); }
virtual void apply(float value, const Endpoint::Pointer& source) override {
virtual void apply(AxisValue value, const Endpoint::Pointer& source) override {
for (auto& child : _children) {
if (child->writeable()) {
child->apply(value, source);

View file

@ -24,18 +24,22 @@ bool CompositeEndpoint::readable() const {
return first->readable() && second->readable();
}
float CompositeEndpoint::peek() const {
float result = first->peek() * -1.0f + second->peek();
AxisValue CompositeEndpoint::peek() const {
auto negative = first->peek();
auto positive = second->peek();
auto result = AxisValue(positive.value - negative.value, std::max(positive.timestamp, negative.timestamp));
return result;
}
// Fetching via value() must trigger any side effects of value() on the children
float CompositeEndpoint::value() {
float result = first->value() * -1.0f + second->value();
AxisValue CompositeEndpoint::value() {
auto negative = first->value();
auto positive = second->value();
auto result = AxisValue(positive.value - negative.value, std::max(positive.timestamp, negative.timestamp));
return result;
}
void CompositeEndpoint::apply(float newValue, const Pointer& source) {
void CompositeEndpoint::apply(AxisValue newValue, const Pointer& source) {
// Composites are read only
}

View file

@ -18,9 +18,9 @@ namespace controller {
using Endpoint::apply;
CompositeEndpoint(Endpoint::Pointer first, Endpoint::Pointer second);
virtual float peek() const override;
virtual float value() override;
virtual void apply(float newValue, const Pointer& source) override;
virtual AxisValue peek() const override;
virtual AxisValue value() override;
virtual void apply(AxisValue newValue, const Pointer& source) override;
virtual bool readable() const override;
};

View file

@ -14,19 +14,19 @@
using namespace controller;
float InputEndpoint::peek() const {
AxisValue InputEndpoint::peek() const {
if (isPose()) {
return peekPose().valid ? 1.0f : 0.0f;
return peekPose().valid ? AxisValue(1.0f, 0) : AxisValue(0.0f, 0);
}
auto userInputMapper = DependencyManager::get<UserInputMapper>();
auto deviceProxy = userInputMapper->getDevice(_input);
if (!deviceProxy) {
return 0.0f;
return AxisValue();
}
return deviceProxy->getValue(_input);
}
float InputEndpoint::value(){
AxisValue InputEndpoint::value() {
_read = true;
return peek();
}

View file

@ -20,10 +20,11 @@ public:
: Endpoint(id) {
}
virtual float peek() const override;
virtual float value() override;
virtual AxisValue peek() const override;
virtual AxisValue value() override;
// FIXME need support for writing back to vibration / force feedback effects
virtual void apply(float newValue, const Pointer& source) override {}
virtual void apply(AxisValue newValue, const Pointer& source) override {}
virtual Pose peekPose() const override;
virtual Pose pose() override;
virtual void apply(const Pose& value, const Pointer& source) override { }

View file

@ -30,18 +30,18 @@ QString formatException(const QJSValue& exception) {
return result;
}
float JSEndpoint::peek() const {
AxisValue JSEndpoint::peek() const {
QJSValue result = _callable.call();
if (result.isError()) {
qCDebug(controllers).noquote() << formatException(result);
return 0.0f;
return AxisValue();
} else {
return (float)result.toNumber();
return AxisValue((float)result.toNumber(), 0);
}
}
void JSEndpoint::apply(float newValue, const Pointer& source) {
QJSValue result = _callable.call(QJSValueList({ QJSValue(newValue) }));
void JSEndpoint::apply(AxisValue newValue, const Pointer& source) {
QJSValue result = _callable.call(QJSValueList({ QJSValue(newValue.value) }));
if (result.isError()) {
qCDebug(controllers).noquote() << formatException(result);
}

View file

@ -24,8 +24,8 @@ public:
: Endpoint(Input::INVALID_INPUT), _callable(callable) {
}
virtual float peek() const override;
virtual void apply(float newValue, const Pointer& source) override;
virtual AxisValue peek() const override;
virtual void apply(AxisValue newValue, const Pointer& source) override;
private:
mutable QJSValue _callable;

View file

@ -34,9 +34,9 @@ QString formatException(const QScriptValue& exception) {
return result;
}
float ScriptEndpoint::peek() const {
AxisValue ScriptEndpoint::peek() const {
const_cast<ScriptEndpoint*>(this)->updateValue();
return _lastValueRead;
return AxisValue(_lastValueRead, 0);
}
void ScriptEndpoint::updateValue() {
@ -58,15 +58,15 @@ void ScriptEndpoint::updateValue() {
}
}
void ScriptEndpoint::apply(float value, const Pointer& source) {
void ScriptEndpoint::apply(AxisValue value, const Pointer& source) {
if (value == _lastValueWritten) {
return;
}
internalApply(value, source->getInput().getID());
_lastValueWritten = value;
internalApply(value.value, source->getInput().getID());
}
void ScriptEndpoint::internalApply(float value, int sourceID) {
_lastValueWritten = value;
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "internalApply", Qt::QueuedConnection,
Q_ARG(float, value),

View file

@ -24,9 +24,8 @@ public:
: Endpoint(Input::INVALID_INPUT), _callable(callable) {
}
virtual float peek() const override;
virtual void apply(float newValue, const Pointer& source) override;
virtual AxisValue peek() const override;
virtual void apply(AxisValue newValue, const Pointer& source) override;
virtual Pose peekPose() const override;
virtual void apply(const Pose& newValue, const Pointer& source) override;
@ -42,7 +41,7 @@ protected:
private:
QScriptValue _callable;
float _lastValueRead { 0.0f };
float _lastValueWritten { 0.0f };
AxisValue _lastValueWritten { 0.0f, 0 };
bool _returnPose { false };
Pose _lastPoseRead;

View file

@ -25,19 +25,19 @@ public:
virtual bool writeable() const override { return !_written; }
virtual bool readable() const override { return !_read; }
virtual void reset() override {
apply(0.0f, Endpoint::Pointer());
apply(AxisValue(), Endpoint::Pointer());
apply(Pose(), Endpoint::Pointer());
_written = _read = false;
}
virtual float value() override {
virtual AxisValue value() override {
_read = true;
return VirtualEndpoint::value();
}
virtual void apply(float value, const Pointer& source) override {
virtual void apply(AxisValue value, const Pointer& source) override {
// For standard endpoints, the first NON-ZERO write counts.
if (value != 0.0f) {
if (value != AxisValue()) {
_written = true;
}
VirtualEndpoint::apply(value, source);

View file

@ -19,7 +19,7 @@ namespace controller {
public:
AccelerationLimiterFilter() {}
float apply(float value) const override { return value; }
AxisValue apply(AxisValue value) const override { return value; }
Pose apply(Pose value) const override;
bool parseParameters(const QJsonValue& parameters) override;

View file

@ -18,8 +18,8 @@ class ClampFilter : public Filter {
REGISTER_FILTER_CLASS(ClampFilter);
public:
ClampFilter(float min = 0.0, float max = 1.0) : _min(min), _max(max) {};
virtual float apply(float value) const override {
return glm::clamp(value, _min, _max);
virtual AxisValue apply(AxisValue value) const override {
return { glm::clamp(value.value, _min, _max), value.timestamp };
}
virtual Pose apply(Pose value) const override { return value; }

View file

@ -19,8 +19,8 @@ class ConstrainToIntegerFilter : public Filter {
public:
ConstrainToIntegerFilter() = default;
virtual float apply(float value) const override {
return glm::sign(value);
virtual AxisValue apply(AxisValue value) const override {
return { glm::sign(value.value), value.timestamp };
}
virtual Pose apply(Pose value) const override { return value; }

View file

@ -19,8 +19,8 @@ class ConstrainToPositiveIntegerFilter : public Filter {
public:
ConstrainToPositiveIntegerFilter() = default;
virtual float apply(float value) const override {
return (value <= 0.0f) ? 0.0f : 1.0f;
virtual AxisValue apply(AxisValue value) const override {
return { (value.value <= 0.0f) ? 0.0f : 1.0f, value.timestamp };
}
virtual Pose apply(Pose value) const override { return value; }

View file

@ -12,13 +12,13 @@
#include <QtCore/QJsonArray>
using namespace controller;
float DeadZoneFilter::apply(float value) const {
float scale = ((value < 0.0f) ? -1.0f : 1.0f) / (1.0f - _min);
float magnitude = std::abs(value);
AxisValue DeadZoneFilter::apply(AxisValue value) const {
float scale = ((value.value < 0.0f) ? -1.0f : 1.0f) / (1.0f - _min);
float magnitude = std::abs(value.value);
if (magnitude < _min) {
return 0.0f;
return { 0.0f, value.timestamp };
}
return (magnitude - _min) * scale;
return { (magnitude - _min) * scale, value.timestamp };
}
bool DeadZoneFilter::parseParameters(const QJsonValue& parameters) {

View file

@ -19,7 +19,7 @@ class DeadZoneFilter : public Filter {
public:
DeadZoneFilter(float min = 0.0) : _min(min) {};
virtual float apply(float value) const override;
virtual AxisValue apply(AxisValue value) const override;
virtual Pose apply(Pose value) const override { return value; }

View file

@ -21,7 +21,7 @@ namespace controller {
ExponentialSmoothingFilter(float rotationConstant, float translationConstant) :
_translationConstant(translationConstant), _rotationConstant(rotationConstant) {}
float apply(float value) const override { return value; }
AxisValue apply(AxisValue value) const override { return value; }
Pose apply(Pose value) const override;
bool parseParameters(const QJsonValue& parameters) override;

View file

@ -20,17 +20,17 @@ HysteresisFilter::HysteresisFilter(float min, float max) : _min(min), _max(max)
};
float HysteresisFilter::apply(float value) const {
AxisValue HysteresisFilter::apply(AxisValue value) const {
if (_signaled) {
if (value <= _min) {
if (value.value <= _min) {
_signaled = false;
}
} else {
if (value >= _max) {
if (value.value >= _max) {
_signaled = true;
}
}
return _signaled ? 1.0f : 0.0f;
return { _signaled ? 1.0f : 0.0f, value.timestamp };
}
bool HysteresisFilter::parseParameters(const QJsonValue& parameters) {

View file

@ -18,7 +18,7 @@ class HysteresisFilter : public Filter {
REGISTER_FILTER_CLASS(HysteresisFilter);
public:
HysteresisFilter(float min = 0.25, float max = 0.75);
virtual float apply(float value) const override;
virtual AxisValue apply(AxisValue value) const override;
virtual Pose apply(Pose value) const override { return value; }

View file

@ -21,7 +21,7 @@ namespace controller {
LowVelocityFilter(float rotationConstant, float translationConstant) :
_translationConstant(translationConstant), _rotationConstant(rotationConstant) {}
float apply(float value) const override { return value; }
AxisValue apply(AxisValue value) const override { return value; }
Pose apply(Pose newPose) const override;
bool parseParameters(const QJsonValue& parameters) override;

View file

@ -5,6 +5,6 @@ using namespace controller;
NotFilter::NotFilter() {
}
float NotFilter::apply(float value) const {
return (value == 0.0f) ? 1.0f : 0.0f;
AxisValue NotFilter::apply(AxisValue value) const {
return { (value.value == 0.0f) ? 1.0f : 0.0f, value.timestamp };
}

View file

@ -11,7 +11,7 @@ class NotFilter : public Filter {
public:
NotFilter();
virtual float apply(float value) const override;
virtual AxisValue apply(AxisValue value) const override;
virtual Pose apply(Pose value) const override { return value; }
};

View file

@ -21,7 +21,7 @@ class PostTransformFilter : public Filter {
public:
PostTransformFilter() = default;
PostTransformFilter(glm::mat4 transform) : _transform(transform) {}
virtual float apply(float value) const override { return value; }
virtual AxisValue apply(AxisValue value) const override { return value; }
virtual Pose apply(Pose value) const override { return value.postTransform(_transform); }
virtual bool parseParameters(const QJsonValue& parameters) override { return parseMat4Parameter(parameters, _transform); }
private:

View file

@ -15,21 +15,21 @@ using namespace controller;
const float PulseFilter::DEFAULT_LAST_EMIT_TIME = -::std::numeric_limits<float>::max();
float PulseFilter::apply(float value) const {
AxisValue PulseFilter::apply(AxisValue value) const {
float result = 0.0f;
if (0.0f != value) {
if (0.0f != value.value) {
float now = secTimestampNow();
float delta = now - _lastEmitTime;
if (delta >= _interval) {
_lastEmitTime = now;
result = value;
result = value.value;
}
} else if (_resetOnZero) {
_lastEmitTime = DEFAULT_LAST_EMIT_TIME;
}
return result;
return { result, value.timestamp };
}
bool PulseFilter::parseParameters(const QJsonValue& parameters) {

View file

@ -21,7 +21,7 @@ public:
PulseFilter() = default;
PulseFilter(float interval) : _interval(interval) {}
virtual float apply(float value) const override;
virtual AxisValue apply(AxisValue value) const override;
virtual Pose apply(Pose value) const override { return value; }

View file

@ -22,7 +22,7 @@ public:
RotateFilter() = default;
RotateFilter(glm::quat rotation) : _rotation(rotation) {}
virtual float apply(float value) const override { return value; }
virtual AxisValue apply(AxisValue value) const override { return value; }
virtual Pose apply(Pose value) const override {
return value.transform(glm::mat4(glm::quat(_rotation)));

View file

@ -22,8 +22,8 @@ public:
ScaleFilter() = default;
ScaleFilter(float scale) : _scale(scale) {}
virtual float apply(float value) const override {
return value * _scale;
virtual AxisValue apply(AxisValue value) const override {
return { value.value * _scale, value.timestamp };
}
virtual Pose apply(Pose value) const override {

View file

@ -22,7 +22,7 @@ public:
TransformFilter() = default;
TransformFilter(glm::mat4 transform) : _transform(transform) {}
virtual float apply(float value) const override { return value; }
virtual AxisValue apply(AxisValue value) const override { return value; }
virtual Pose apply(Pose value) const override { return value.transform(_transform); }
virtual bool parseParameters(const QJsonValue& parameters) override { return parseMat4Parameter(parameters, _transform); }

View file

@ -22,7 +22,7 @@ public:
TranslateFilter() = default;
TranslateFilter(glm::vec3 translate) : _translate(translate) {}
virtual float apply(float value) const override { return value; }
virtual AxisValue apply(AxisValue value) const override { return value; }
virtual Pose apply(Pose value) const override { return value.transform(glm::translate(_translate)); }
virtual bool parseParameters(const QJsonValue& parameters) override { return parseVec3Parameter(parameters, _translate); }

View file

@ -15,7 +15,7 @@
// These properties have JSDoc documentation in HMDScriptingInterface.h.
class AbstractHMDScriptingInterface : public QObject {
Q_OBJECT
Q_PROPERTY(bool active READ isHMDMode)
Q_PROPERTY(bool active READ isHMDMode NOTIFY mountedChanged)
Q_PROPERTY(float ipd READ getIPD)
Q_PROPERTY(float eyeHeight READ getEyeHeight)
Q_PROPERTY(float playerHeight READ getPlayerHeight)
@ -43,7 +43,7 @@ signals:
/**jsdoc
* Triggered when Interface's display mode changes and when the user puts on or takes off their HMD.
* @function HMD.displayModeChanged
* @param {boolean} isHMDMode - <code>true</code> if the display mode is HMD, otherwise <code>false</code>. This is the
* @param {boolean} isHMDMode - <code>true</code> if the display mode is HMD, otherwise <code>false</code>. This is the
* same value as provided by <code>HMD.active</code>.
* @returns {Signal}
* @example <caption>Report when the display mode changes.</caption>

View file

@ -39,13 +39,12 @@ void EntityEditPacketSender::adjustEditPacketForClockSkew(PacketType type, QByte
}
}
void EntityEditPacketSender::queueEditAvatarEntityMessage(PacketType type,
EntityTreePointer entityTree,
void EntityEditPacketSender::queueEditAvatarEntityMessage(EntityTreePointer entityTree,
EntityItemID entityItemID,
const EntityItemProperties& properties) {
assert(_myAvatar);
if (!entityTree) {
qCDebug(entities) << "EntityEditPacketSender::queueEditEntityMessage null entityTree.";
qCDebug(entities) << "EntityEditPacketSender::queueEditAvatarEntityMessage null entityTree.";
return;
}
EntityItemPointer entity = entityTree->findEntityByEntityItemID(entityItemID);
@ -53,33 +52,27 @@ void EntityEditPacketSender::queueEditAvatarEntityMessage(PacketType type,
qCDebug(entities) << "EntityEditPacketSender::queueEditAvatarEntityMessage can't find entity: " << entityItemID;
return;
}
entity->setLastBroadcast(usecTimestampNow());
// the properties that get serialized into the avatar identity packet should be the entire set
// serialize ALL properties in an "AvatarEntity" packet
// rather than just the ones being edited.
EntityItemProperties entityProperties = entity->getProperties();
entityProperties.merge(properties);
std::lock_guard<std::mutex> lock(_mutex);
QScriptValue scriptProperties = EntityItemNonDefaultPropertiesToScriptValue(&_scriptEngine, entityProperties);
QVariant variantProperties = scriptProperties.toVariant();
QJsonDocument jsonProperties = QJsonDocument::fromVariant(variantProperties);
OctreePacketData packetData(false, AvatarTraits::MAXIMUM_TRAIT_SIZE);
EncodeBitstreamParams params;
EntityTreeElementExtraEncodeDataPointer extra { nullptr };
OctreeElement::AppendState appendState = entity->appendEntityData(&packetData, params, extra);
// the ID of the parent/avatar changes from session to session. use a special UUID to indicate the avatar
QJsonObject jsonObject = jsonProperties.object();
if (jsonObject.contains("parentID")) {
if (QUuid(jsonObject["parentID"].toString()) == _myAvatar->getID()) {
jsonObject["parentID"] = AVATAR_SELF_ID.toString();
}
if (appendState != OctreeElement::COMPLETED) {
// this entity's payload is too big
return;
}
jsonProperties = QJsonDocument(jsonObject);
QByteArray binaryProperties = jsonProperties.toBinaryData();
_myAvatar->updateAvatarEntity(entityItemID, binaryProperties);
entity->setLastBroadcast(usecTimestampNow());
QByteArray tempArray((const char*)packetData.getUncompressedData(), packetData.getUncompressedSize());
_myAvatar->storeAvatarEntityDataPayload(entityItemID, tempArray);
}
void EntityEditPacketSender::queueEditEntityMessage(PacketType type,
EntityTreePointer entityTree,
EntityItemID entityItemID,
@ -89,7 +82,7 @@ void EntityEditPacketSender::queueEditEntityMessage(PacketType type,
qCWarning(entities) << "Suppressing entity edit message: cannot send avatar entity edit with no myAvatar";
} else if (properties.getOwningAvatarID() == _myAvatar->getID()) {
// this is an avatar-based entity --> update our avatar-data rather than sending to the entity-server
queueEditAvatarEntityMessage(type, entityTree, entityItemID, properties);
queueEditAvatarEntityMessage(entityTree, entityItemID, properties);
} else {
qCWarning(entities) << "Suppressing entity edit message: cannot send avatar entity edit for another avatar";
}
@ -127,7 +120,13 @@ void EntityEditPacketSender::queueEditEntityMessage(PacketType type,
while (encodeResult == OctreeElement::PARTIAL) {
encodeResult = EntityItemProperties::encodeEntityEditPacket(type, entityItemID, propertiesCopy, bufferOut, requestedProperties, didntFitProperties);
if (encodeResult != OctreeElement::NONE) {
if (encodeResult == OctreeElement::NONE) {
// This can happen for two reasons:
// 1. One of the properties is too large to fit in a single packet.
// 2. The requested properties don't exist in this entity type (e.g., 'modelUrl' in a Zone Entity).
// Since case #1 is more likely (and more critical), that's the one we warn about.
qCWarning(entities).nospace() << "queueEditEntityMessage: some of the properties don't fit and can't be sent. entityID=" << uuidStringWithoutCurlyBraces(entityItemID);
} else {
#ifdef WANT_DEBUG
qCDebug(entities) << "calling queueOctreeEditMessage()...";
qCDebug(entities) << " id:" << entityItemID;

View file

@ -50,7 +50,7 @@ public slots:
void processEntityEditNackPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
private:
void queueEditAvatarEntityMessage(PacketType type, EntityTreePointer entityTree,
void queueEditAvatarEntityMessage(EntityTreePointer entityTree,
EntityItemID entityItemID, const EntityItemProperties& properties);
private:

View file

@ -28,6 +28,7 @@
#include <GLMHelpers.h>
#include <RegisteredMetaTypes.h>
#include <Extents.h>
#include <VariantMapToScriptValue.h>
#include "EntitiesLogging.h"
#include "EntityItem.h"
@ -90,6 +91,16 @@ void EntityItemProperties::setLastEdited(quint64 usecTime) {
_lastEdited = usecTime > _created ? usecTime : _created;
}
bool EntityItemProperties::constructFromBuffer(const unsigned char* data, int dataLength) {
ReadBitstreamToTreeParams args;
EntityItemPointer tempEntity = EntityTypes::constructEntityItem(data, dataLength);
if (!tempEntity) {
return false;
}
tempEntity->readEntityDataFromBuffer(data, dataLength, args);
(*this) = tempEntity->getProperties();
return true;
}
QHash<QString, ShapeType> stringToShapeTypeLookup;
@ -2023,6 +2034,18 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool
_lastEdited = usecTimestampNow();
}
void EntityItemProperties::copyFromJSONString(QScriptEngine& scriptEngine, const QString& jsonString) {
// DANGER: this method is expensive
QJsonDocument propertiesDoc = QJsonDocument::fromJson(jsonString.toUtf8());
QJsonObject propertiesObj = propertiesDoc.object();
QVariant propertiesVariant(propertiesObj);
QVariantMap propertiesMap = propertiesVariant.toMap();
QScriptValue propertiesScriptValue = variantMapToScriptValue(propertiesMap, scriptEngine);
bool honorReadOnly = true;
copyFromScriptValue(propertiesScriptValue, honorReadOnly);
}
void EntityItemProperties::merge(const EntityItemProperties& other) {
// Core
COPY_PROPERTY_IF_CHANGED(simulationOwner);
@ -2252,7 +2275,6 @@ void EntityItemPropertiesFromScriptValueHonorReadOnly(const QScriptValue &object
properties.copyFromScriptValue(object, true);
}
QScriptValue EntityPropertyFlagsToScriptValue(QScriptEngine* engine, const EntityPropertyFlags& flags) {
return EntityItemProperties::entityPropertyFlagsToScriptValue(engine, flags);
}
@ -4590,6 +4612,40 @@ void EntityItemProperties::convertToCloneProperties(const EntityItemID& entityID
setCloneAvatarEntity(ENTITY_ITEM_DEFAULT_CLONE_AVATAR_ENTITY);
}
bool EntityItemProperties::blobToProperties(QScriptEngine& scriptEngine, const QByteArray& blob, EntityItemProperties& properties) {
// DANGER: this method is NOT efficient.
// begin recipe for converting unfortunately-formatted-binary-blob to EntityItemProperties
QJsonDocument jsonProperties = QJsonDocument::fromBinaryData(blob);
if (!jsonProperties.isObject()) {
qCDebug(entities) << "bad avatarEntityData json" << QString(blob.toHex());
return false;
}
QVariant variant = jsonProperties.toVariant();
QVariantMap variantMap = variant.toMap();
QScriptValue scriptValue = variantMapToScriptValue(variantMap, scriptEngine);
EntityItemPropertiesFromScriptValueHonorReadOnly(scriptValue, properties);
// end recipe
return true;
}
void EntityItemProperties::propertiesToBlob(QScriptEngine& scriptEngine, const QUuid& myAvatarID, const EntityItemProperties& properties, QByteArray& blob) {
// DANGER: this method is NOT efficient.
// begin recipe for extracting unfortunately-formatted-binary-blob from EntityItem
QScriptValue scriptValue = EntityItemNonDefaultPropertiesToScriptValue(&scriptEngine, properties);
QVariant variantProperties = scriptValue.toVariant();
QJsonDocument jsonProperties = QJsonDocument::fromVariant(variantProperties);
// the ID of the parent/avatar changes from session to session. use a special UUID to indicate the avatar
QJsonObject jsonObject = jsonProperties.object();
if (jsonObject.contains("parentID")) {
if (QUuid(jsonObject["parentID"].toString()) == myAvatarID) {
jsonObject["parentID"] = AVATAR_SELF_ID.toString();
}
}
jsonProperties = QJsonDocument(jsonObject);
blob = jsonProperties.toBinaryData();
// end recipe
}
QDebug& operator<<(QDebug& dbg, const EntityPropertyFlags& f) {
QString result = "[ ";

View file

@ -98,6 +98,9 @@ class EntityItemProperties {
friend class ZoneEntityItem;
friend class MaterialEntityItem;
public:
static bool blobToProperties(QScriptEngine& scriptEngine, const QByteArray& blob, EntityItemProperties& properties);
static void propertiesToBlob(QScriptEngine& scriptEngine, const QUuid& myAvatarID, const EntityItemProperties& properties, QByteArray& blob);
EntityItemProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags());
virtual ~EntityItemProperties() = default;
@ -109,6 +112,7 @@ public:
virtual QScriptValue copyToScriptValue(QScriptEngine* engine, bool skipDefaults, bool allowUnknownCreateTime = false,
bool strictSemantics = false, EntityPsuedoPropertyFlags psueudoPropertyFlags = EntityPsuedoPropertyFlags()) const;
virtual void copyFromScriptValue(const QScriptValue& object, bool honorReadOnly);
void copyFromJSONString(QScriptEngine& scriptEngine, const QString& jsonString);
static QScriptValue entityPropertyFlagsToScriptValue(QScriptEngine* engine, const EntityPropertyFlags& flags);
static void entityPropertyFlagsFromScriptValue(const QScriptValue& object, EntityPropertyFlags& flags);
@ -135,6 +139,8 @@ public:
EntityPropertyFlags getDesiredProperties() { return _desiredProperties; }
void setDesiredProperties(EntityPropertyFlags properties) { _desiredProperties = properties; }
bool constructFromBuffer(const unsigned char* data, int dataLength);
// Note: DEFINE_PROPERTY(PROP_FOO, Foo, foo, type, value) creates the following methods and variables:
// type getFoo() const;
// void setFoo(type);
@ -157,7 +163,7 @@ public:
DEFINE_PROPERTY(PROP_CREATED, Created, created, quint64, UNKNOWN_CREATED_TIME);
DEFINE_PROPERTY_REF(PROP_LAST_EDITED_BY, LastEditedBy, lastEditedBy, QUuid, ENTITY_ITEM_DEFAULT_LAST_EDITED_BY);
DEFINE_PROPERTY_REF_ENUM(PROP_ENTITY_HOST_TYPE, EntityHostType, entityHostType, entity::HostType, entity::HostType::DOMAIN);
DEFINE_PROPERTY_REF(PROP_OWNING_AVATAR_ID, OwningAvatarID, owningAvatarID, QUuid, UNKNOWN_ENTITY_ID);
DEFINE_PROPERTY_REF_WITH_SETTER(PROP_OWNING_AVATAR_ID, OwningAvatarID, owningAvatarID, QUuid, UNKNOWN_ENTITY_ID);
DEFINE_PROPERTY_REF(PROP_PARENT_ID, ParentID, parentID, QUuid, UNKNOWN_ENTITY_ID);
DEFINE_PROPERTY_REF(PROP_PARENT_JOINT_INDEX, ParentJointIndex, parentJointIndex, quint16, -1);
DEFINE_PROPERTY_REF(PROP_QUERY_AA_CUBE, QueryAACube, queryAACube, AACube, AACube());
@ -499,6 +505,16 @@ void EntityPropertyInfoFromScriptValue(const QScriptValue& object, EntityPropert
inline void EntityItemProperties::setPosition(const glm::vec3& value)
{ _position = glm::clamp(value, (float)-HALF_TREE_SCALE, (float)HALF_TREE_SCALE); _positionChanged = true; }
inline void EntityItemProperties::setOwningAvatarID(const QUuid& id) {
_owningAvatarID = id;
if (!_owningAvatarID.isNull()) {
// for AvatarEntities there's no entity-server to tell us we're the simulation owner,
// so always set the simulationOwner to the owningAvatarID and a high priority.
setSimulationOwner(_owningAvatarID, AVATAR_ENTITY_SIMULATION_PRIORITY);
}
_owningAvatarIDChanged = true;
}
QDebug& operator<<(QDebug& dbg, const EntityPropertyFlags& f);
inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) {

View file

@ -174,7 +174,7 @@ int EntityTree::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
addToNeedsParentFixupList(entity);
}
} else {
entity = EntityTypes::constructEntityItem(dataAt, bytesLeftToRead, args);
entity = EntityTypes::constructEntityItem(dataAt, bytesLeftToRead);
if (entity) {
bytesForThisEntity = entity->readEntityDataFromBuffer(dataAt, bytesLeftToRead, args);
@ -490,7 +490,6 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti
}
EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const EntityItemProperties& properties, bool isClone) {
EntityItemPointer result = NULL;
EntityItemProperties props = properties;
auto nodeList = DependencyManager::get<NodeList>();
@ -517,12 +516,12 @@ EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const Enti
if (containingElement) {
qCWarning(entities) << "EntityTree::addEntity() on existing entity item with entityID=" << entityID
<< "containingElement=" << containingElement.get();
return result;
return nullptr;
}
// construct the instance of the entity
EntityTypes::EntityType type = props.getType();
result = EntityTypes::constructEntityItem(type, entityID, props);
EntityItemPointer result = EntityTypes::constructEntityItem(type, entityID, props);
if (result) {
if (recordCreationTime) {
@ -531,10 +530,6 @@ EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const Enti
// Recurse the tree and store the entity in the correct tree element
AddEntityOperator theOperator(getThisPointer(), result);
recurseTreeWithOperator(&theOperator);
if (!result->getParentID().isNull()) {
addToNeedsParentFixupList(result);
}
postAddEntity(result);
}
return result;
@ -2969,27 +2964,30 @@ void EntityTree::updateEntityQueryAACubeWorker(SpatiallyNestablePointer object,
MovingEntitiesOperator& moveOperator, bool force, bool tellServer) {
// if the queryBox has changed, tell the entity-server
EntityItemPointer entity = std::dynamic_pointer_cast<EntityItem>(object);
if (entity && (entity->updateQueryAACube() || force)) {
bool success;
AACube newCube = entity->getQueryAACube(success);
if (success) {
moveOperator.addEntityToMoveList(entity, newCube);
}
// send an edit packet to update the entity-server about the queryAABox. We do this for domain-hosted
// entities as well as for avatar-entities; the packet-sender will route the update accordingly
if (tellServer && packetSender && (entity->isDomainEntity() || entity->isAvatarEntity())) {
quint64 now = usecTimestampNow();
EntityItemProperties properties = entity->getProperties();
properties.setQueryAACubeDirty();
properties.setLocationDirty();
properties.setLastEdited(now);
if (entity) {
bool tellServerThis = tellServer && (entity->getEntityHostType() != entity::HostType::AVATAR);
if ((entity->updateQueryAACube() || force)) {
bool success;
AACube newCube = entity->getQueryAACube(success);
if (success) {
moveOperator.addEntityToMoveList(entity, newCube);
}
// send an edit packet to update the entity-server about the queryAABox. We do this for domain-hosted
// entities as well as for avatar-entities; the packet-sender will route the update accordingly
if (tellServerThis && packetSender && (entity->isDomainEntity() || entity->isAvatarEntity())) {
quint64 now = usecTimestampNow();
EntityItemProperties properties = entity->getProperties();
properties.setQueryAACubeDirty();
properties.setLocationDirty();
properties.setLastEdited(now);
packetSender->queueEditEntityMessage(PacketType::EntityEdit, getThisPointer(), entity->getID(), properties);
entity->setLastBroadcast(now); // for debug/physics status icons
}
packetSender->queueEditEntityMessage(PacketType::EntityEdit, getThisPointer(), entity->getID(), properties);
entity->setLastBroadcast(now); // for debug/physics status icons
}
entity->markDirtyFlags(Simulation::DIRTY_POSITION);
entityChanged(entity);
entity->markDirtyFlags(Simulation::DIRTY_POSITION);
entityChanged(entity);
}
}
object->forEachDescendant([&](SpatiallyNestablePointer descendant) {

View file

@ -58,6 +58,10 @@ REGISTER_ENTITY_TYPE(Light)
REGISTER_ENTITY_TYPE(Zone)
REGISTER_ENTITY_TYPE(Material)
bool EntityTypes::typeIsValid(EntityType type) {
return type > EntityType::Unknown && type <= EntityType::NUM_TYPES;
}
const QString& EntityTypes::getEntityTypeName(EntityType entityType) {
QMap<EntityType, QString>::iterator matchedTypeName = _typeToNameMap.find(entityType);
if (matchedTypeName != _typeToNameMap.end()) {
@ -107,8 +111,7 @@ EntityItemPointer EntityTypes::constructEntityItem(EntityType entityType, const
return newEntityItem;
}
EntityItemPointer EntityTypes::constructEntityItem(const unsigned char* data, int bytesToRead,
ReadBitstreamToTreeParams& args) {
void EntityTypes::extractEntityTypeAndID(const unsigned char* data, int dataLength, EntityTypes::EntityType& typeOut, QUuid& idOut) {
// Header bytes
// object ID [16 bytes]
@ -119,28 +122,36 @@ EntityItemPointer EntityTypes::constructEntityItem(const unsigned char* data, in
// ~27-35 bytes...
const int MINIMUM_HEADER_BYTES = 27;
int bytesRead = 0;
if (bytesToRead >= MINIMUM_HEADER_BYTES) {
int originalLength = bytesToRead;
QByteArray originalDataBuffer((const char*)data, originalLength);
if (dataLength >= MINIMUM_HEADER_BYTES) {
int bytesRead = 0;
QByteArray originalDataBuffer = QByteArray::fromRawData((const char*)data, dataLength);
// id
QByteArray encodedID = originalDataBuffer.mid(bytesRead, NUM_BYTES_RFC4122_UUID); // maximum possible size
QUuid actualID = QUuid::fromRfc4122(encodedID);
idOut = QUuid::fromRfc4122(encodedID);
bytesRead += encodedID.size();
// type
QByteArray encodedType = originalDataBuffer.mid(bytesRead); // maximum possible size
ByteCountCoded<quint32> typeCoder = encodedType;
encodedType = typeCoder; // determine true length
bytesRead += encodedType.size();
quint32 type = typeCoder;
EntityTypes::EntityType entityType = (EntityTypes::EntityType)type;
EntityItemID tempEntityID(actualID);
EntityItemProperties tempProperties;
return constructEntityItem(entityType, tempEntityID, tempProperties);
typeOut = (EntityTypes::EntityType)type;
}
return NULL;
}
EntityItemPointer EntityTypes::constructEntityItem(const unsigned char* data, int bytesToRead) {
QUuid id;
EntityTypes::EntityType type = EntityTypes::Unknown;
extractEntityTypeAndID(data, bytesToRead, type, id);
if (type > EntityTypes::Unknown && type <= EntityTypes::NUM_TYPES) {
EntityItemID tempEntityID(id);
EntityItemProperties tempProperties;
return constructEntityItem(type, tempEntityID, tempProperties);
}
return nullptr;
}
EntityItemPointer EntityTypes::constructEntityItem(const QUuid& id, const EntityItemProperties& properties) {
return constructEntityItem(properties.getType(), id, properties);
}

View file

@ -109,11 +109,14 @@ public:
NUM_TYPES
} EntityType;
static bool typeIsValid(EntityType type);
static const QString& getEntityTypeName(EntityType entityType);
static EntityTypes::EntityType getEntityTypeFromName(const QString& name);
static bool registerEntityType(EntityType entityType, const char* name, EntityTypeFactory factoryMethod);
static void extractEntityTypeAndID(const unsigned char* data, int dataLength, EntityTypes::EntityType& typeOut, QUuid& idOut);
static EntityItemPointer constructEntityItem(EntityType entityType, const EntityItemID& entityID, const EntityItemProperties& properties);
static EntityItemPointer constructEntityItem(const unsigned char* data, int bytesToRead, ReadBitstreamToTreeParams& args);
static EntityItemPointer constructEntityItem(const unsigned char* data, int bytesToRead);
static EntityItemPointer constructEntityItem(const QUuid& id, const EntityItemProperties& properties);
private:
static QMap<EntityType, QString> _typeToNameMap;

View file

@ -96,7 +96,7 @@ const uint8_t RECRUIT_SIMULATION_PRIORITY = VOLUNTEER_SIMULATION_PRIORITY + 1;
// When poking objects with scripts an observer will bid at SCRIPT_EDIT priority.
const uint8_t SCRIPT_GRAB_SIMULATION_PRIORITY = 128;
const uint8_t SCRIPT_POKE_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY - 1;
const uint8_t AVATAR_ENTITY_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY + 1;
const uint8_t AVATAR_ENTITY_SIMULATION_PRIORITY = 255;
// PERSONAL priority (needs a better name) is the level at which a simulation observer owns its own avatar
// which really just means: things that collide with it will be bid at a priority level one lower

View file

@ -96,6 +96,8 @@ class ExtractedMesh;
class FBXSerializer : public HFMSerializer {
public:
virtual ~FBXSerializer() {}
MediaType getMediaType() const override;
std::unique_ptr<hfm::Serializer::Factory> getFactory() const override;

View file

@ -23,6 +23,7 @@ class Serializer {
public:
class Factory {
public:
virtual ~Factory() {}
virtual std::shared_ptr<Serializer> get() = 0;
};

View file

@ -21,14 +21,27 @@
const char* KeyboardMouseDevice::NAME = "Keyboard/Mouse";
bool KeyboardMouseDevice::_enableTouch = true;
void KeyboardMouseDevice::updateDeltaAxisValue(int channel, float value) {
// Associate timestamps with non-zero delta values so that consecutive identical values can be output.
_inputDevice->_axisStateMap[channel] = { value, value != 0.0f ? usecTimestampNow() : 0 };
}
void KeyboardMouseDevice::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) {
auto userInputMapper = DependencyManager::get<controller::UserInputMapper>();
userInputMapper->withLock([&, this]() {
_inputDevice->update(deltaTime, inputCalibrationData);
eraseMouseClicked();
_inputDevice->_axisStateMap[MOUSE_AXIS_X] = _lastCursor.x();
_inputDevice->_axisStateMap[MOUSE_AXIS_Y] = _lastCursor.y();
_inputDevice->_axisStateMap[MOUSE_AXIS_X].value = _lastCursor.x();
_inputDevice->_axisStateMap[MOUSE_AXIS_Y].value = _lastCursor.y();
QPoint currentMove = _lastCursor - _previousCursor;
updateDeltaAxisValue(MOUSE_AXIS_X_POS, currentMove.x() > 0 ? currentMove.x() : 0.0f);
updateDeltaAxisValue(MOUSE_AXIS_X_NEG, currentMove.x() < 0 ? -currentMove.x() : 0.0f);
// Y mouse is inverted positive is pointing up the screen
updateDeltaAxisValue(MOUSE_AXIS_Y_POS, currentMove.y() < 0 ? -currentMove.y() : 0.0f);
updateDeltaAxisValue(MOUSE_AXIS_Y_NEG, currentMove.y() > 0 ? currentMove.y() : 0.0f);
_previousCursor = _lastCursor;
});
// For touch event, we need to check that the last event is not too long ago
@ -73,7 +86,6 @@ void KeyboardMouseDevice::mousePressEvent(QMouseEvent* event) {
}
_lastCursor = event->pos();
_mousePressTime = usecTimestampNow();
_mouseMoved = false;
_mousePressPos = event->pos();
_clickDeadspotActive = true;
@ -102,21 +114,12 @@ void KeyboardMouseDevice::eraseMouseClicked() {
void KeyboardMouseDevice::mouseMoveEvent(QMouseEvent* event) {
QPoint currentPos = event->pos();
QPoint currentMove = currentPos - _lastCursor;
_inputDevice->_axisStateMap[MOUSE_AXIS_X_POS] = (currentMove.x() > 0 ? currentMove.x() : 0.0f);
_inputDevice->_axisStateMap[MOUSE_AXIS_X_NEG] = (currentMove.x() < 0 ? -currentMove.x() : 0.0f);
// Y mouse is inverted positive is pointing up the screen
_inputDevice->_axisStateMap[MOUSE_AXIS_Y_POS] = (currentMove.y() < 0 ? -currentMove.y() : 0.0f);
_inputDevice->_axisStateMap[MOUSE_AXIS_Y_NEG] = (currentMove.y() > 0 ? currentMove.y() : 0.0f);
// FIXME - this has the characteristic that it will show large jumps when you move the cursor
// outside of the application window, because we don't get MouseEvents when the cursor is outside
// of the application window.
_lastCursor = currentPos;
_mouseMoved = true;
const int CLICK_EVENT_DEADSPOT = 6; // pixels
if (_clickDeadspotActive && (_mousePressPos - currentPos).manhattanLength() > CLICK_EVENT_DEADSPOT) {
_clickDeadspotActive = false;
@ -125,11 +128,10 @@ void KeyboardMouseDevice::mouseMoveEvent(QMouseEvent* event) {
void KeyboardMouseDevice::wheelEvent(QWheelEvent* event) {
auto currentMove = event->angleDelta() / 120.0f;
_inputDevice->_axisStateMap[_inputDevice->makeInput(MOUSE_AXIS_WHEEL_X_POS).getChannel()] = (currentMove.x() > 0 ? currentMove.x() : 0.0f);
_inputDevice->_axisStateMap[_inputDevice->makeInput(MOUSE_AXIS_WHEEL_X_NEG).getChannel()] = (currentMove.x() < 0 ? -currentMove.x() : 0.0f);
_inputDevice->_axisStateMap[_inputDevice->makeInput(MOUSE_AXIS_WHEEL_Y_POS).getChannel()] = (currentMove.y() > 0 ? currentMove.y() : 0.0f);
_inputDevice->_axisStateMap[_inputDevice->makeInput(MOUSE_AXIS_WHEEL_Y_NEG).getChannel()] = (currentMove.y() < 0 ? -currentMove.y() : 0.0f);
_inputDevice->_axisStateMap[_inputDevice->makeInput(MOUSE_AXIS_WHEEL_X_POS).getChannel()].value = currentMove.x() > 0 ? currentMove.x() : 0;
_inputDevice->_axisStateMap[_inputDevice->makeInput(MOUSE_AXIS_WHEEL_X_NEG).getChannel()].value = currentMove.x() < 0 ? -currentMove.x() : 0;
_inputDevice->_axisStateMap[_inputDevice->makeInput(MOUSE_AXIS_WHEEL_Y_POS).getChannel()].value = currentMove.y() > 0 ? currentMove.y() : 0;
_inputDevice->_axisStateMap[_inputDevice->makeInput(MOUSE_AXIS_WHEEL_Y_NEG).getChannel()].value = currentMove.y() < 0 ? -currentMove.y() : 0;
}
glm::vec2 evalAverageTouchPoints(const QList<QTouchEvent::TouchPoint>& points) {
@ -168,12 +170,11 @@ void KeyboardMouseDevice::touchUpdateEvent(const QTouchEvent* event) {
_isTouching = event->touchPointStates().testFlag(Qt::TouchPointPressed);
} else {
auto currentMove = currentPos - _lastTouch;
_inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_POS).getChannel()] = (currentMove.x > 0 ? currentMove.x : 0.0f);
_inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_NEG).getChannel()] = (currentMove.x < 0 ? -currentMove.x : 0.0f);
_inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_POS).getChannel()].value = (currentMove.x > 0 ? currentMove.x : 0.0f);
_inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_NEG).getChannel()].value = (currentMove.x < 0 ? -currentMove.x : 0.0f);
// Y mouse is inverted positive is pointing up the screen
_inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_Y_POS).getChannel()] = (currentMove.y < 0 ? -currentMove.y : 0.0f);
_inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_Y_NEG).getChannel()] = (currentMove.y > 0 ? currentMove.y : 0.0f);
_inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_Y_POS).getChannel()].value = (currentMove.y < 0 ? -currentMove.y : 0.0f);
_inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_Y_NEG).getChannel()].value = (currentMove.y > 0 ? currentMove.y : 0.0f);
}
_lastTouch = currentPos;
@ -247,16 +248,23 @@ controller::Input KeyboardMouseDevice::InputDevice::makeInput(KeyboardMouseDevic
* </td></tr>
* <tr><td><code>PgDown</code></td><td>number</td><td>number</td><td>The page down key on the keyboard or keypad is pressed.
* </td></tr>
* <tr><td><code>LeftMouseButton</code></td><td>number</td><td>number</td><td>The left mouse button pressed.</td></tr>
* <tr><td><code>MiddleMouseButton</code></td><td>number</td><td>number</td><td>The middle mouse button pressed.</td></tr>
* <tr><td><code>RightMouseButton</code></td><td>number</td><td>number</td><td>The right mouse button pressed.</td></tr>
* <tr><td><code>LeftMouseClicked</code></td><td>number</td><td>number</td><td>The left mouse button clicked.</td></tr>
* <tr><td><code>MiddleMouseClicked</code></td><td>number</td><td>number</td><td>The middle mouse button clicked.</td></tr>
* <tr><td><code>RightMouseClicked</code></td><td>number</td><td>number</td><td>The right mouse button clicked.</td></tr>
* <tr><td><code>MouseMoveRight</code></td><td>number</td><td>number</td><td>The mouse moved right.</td></tr>
* <tr><td><code>MouseMoveLeft</code></td><td>number</td><td>number</td><td>The mouse moved left.</td></tr>
* <tr><td><code>MouseMoveUp</code></td><td>number</td><td>number</td><td>The mouse moved up.</td></tr>
* <tr><td><code>MouseMoveDown</code></td><td>number</td><td>number</td><td>The mouse moved down.</td></tr>
* <tr><td><code>LeftMouseButton</code></td><td>number</td><td>number</td><td>The left mouse button is pressed.</td></tr>
* <tr><td><code>MiddleMouseButton</code></td><td>number</td><td>number</td><td>The middle mouse button is pressed.
* </td></tr>
* <tr><td><code>RightMouseButton</code></td><td>number</td><td>number</td><td>The right mouse button is pressed.</td></tr>
* <tr><td><code>LeftMouseClicked</code></td><td>number</td><td>number</td><td>The left mouse button was clicked.</td></tr>
* <tr><td><code>MiddleMouseClicked</code></td><td>number</td><td>number</td><td>The middle mouse button was clicked.
* </td></tr>
* <tr><td><code>RightMouseClicked</code></td><td>number</td><td>number</td><td>The right mouse button was clicked.
* </td></tr>
* <tr><td><code>MouseMoveRight</code></td><td>number</td><td>number</td><td>The mouse moved right. The data value is how
* far it moved.</td></tr>
* <tr><td><code>MouseMoveLeft</code></td><td>number</td><td>number</td><td>The mouse moved left. The data value is how far
* it moved.</td></tr>
* <tr><td><code>MouseMoveUp</code></td><td>number</td><td>number</td><td>The mouse moved up. The data value is how far it
* moved.</td></tr>
* <tr><td><code>MouseMoveDown</code></td><td>number</td><td>number</td><td>The mouse moved down. The data value is how far
* it moved.</td></tr>
* <tr><td><code>MouseX</code></td><td>number</td><td>number</td><td>The mouse x-coordinate changed. The data value is its
* new x-coordinate value.</td></tr>
* <tr><td><code>MouseY</code></td><td>number</td><td>number</td><td>The mouse y-coordinate changed. The data value is its

View file

@ -118,9 +118,9 @@ public:
protected:
QPoint _lastCursor;
QPoint _previousCursor;
QPoint _mousePressPos;
quint64 _mousePressTime;
bool _mouseMoved;
bool _clickDeadspotActive;
glm::vec2 _lastTouch;
std::shared_ptr<InputDevice> _inputDevice { std::make_shared<InputDevice>() };
@ -130,6 +130,9 @@ protected:
std::chrono::high_resolution_clock::time_point _lastTouchTime;
static bool _enableTouch;
private:
void updateDeltaAxisValue(int channel, float value);
};
#endif // hifi_KeyboardMouseDevice_h

Some files were not shown because too many files have changed in this diff Show more