mirror of
https://github.com/overte-org/overte.git
synced 2025-08-11 06:53:00 +02:00
fix bug where attached AvatarEntities do not update in timely fashion
This commit is contained in:
parent
b9667a0679
commit
9ea6968e35
9 changed files with 326 additions and 261 deletions
|
@ -157,7 +157,7 @@ void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message,
|
||||||
if (traitSize == AvatarTraits::DELETED_TRAIT_SIZE) {
|
if (traitSize == AvatarTraits::DELETED_TRAIT_SIZE) {
|
||||||
_avatar->processDeletedTraitInstance(traitType, instanceID);
|
_avatar->processDeletedTraitInstance(traitType, instanceID);
|
||||||
// Mixer doesn't need deleted IDs.
|
// Mixer doesn't need deleted IDs.
|
||||||
_avatar->getAndClearRecentlyDetachedIDs();
|
_avatar->getAndClearRecentlyRemovedIDs();
|
||||||
|
|
||||||
// to track a deleted instance but keep version information
|
// to track a deleted instance but keep version information
|
||||||
// the avatar mixer uses the negative value of the sent version
|
// the avatar mixer uses the negative value of the sent version
|
||||||
|
|
|
@ -674,7 +674,7 @@ void MyAvatar::update(float deltaTime) {
|
||||||
|
|
||||||
_clientTraitsHandler->sendChangedTraitsToMixer();
|
_clientTraitsHandler->sendChangedTraitsToMixer();
|
||||||
|
|
||||||
simulate(deltaTime);
|
simulate(deltaTime, true);
|
||||||
|
|
||||||
currentEnergy += energyChargeRate;
|
currentEnergy += energyChargeRate;
|
||||||
currentEnergy -= getAccelerationEnergy();
|
currentEnergy -= getAccelerationEnergy();
|
||||||
|
@ -746,7 +746,7 @@ void MyAvatar::updateChildCauterization(SpatiallyNestablePointer object, bool ca
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MyAvatar::simulate(float deltaTime) {
|
void MyAvatar::simulate(float deltaTime, bool inView) {
|
||||||
PerformanceTimer perfTimer("simulate");
|
PerformanceTimer perfTimer("simulate");
|
||||||
animateScaleChanges(deltaTime);
|
animateScaleChanges(deltaTime);
|
||||||
|
|
||||||
|
@ -890,7 +890,7 @@ void MyAvatar::simulate(float deltaTime) {
|
||||||
_characterController.setCollisionlessAllowed(collisionlessAllowed);
|
_characterController.setCollisionlessAllowed(collisionlessAllowed);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateAvatarEntities();
|
handleChangedAvatarEntityData();
|
||||||
|
|
||||||
updateFadingStatus();
|
updateFadingStatus();
|
||||||
}
|
}
|
||||||
|
@ -1480,7 +1480,7 @@ void MyAvatar::sanitizeAvatarEntityProperties(EntityItemProperties& properties)
|
||||||
properties.markAllChanged();
|
properties.markAllChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MyAvatar::updateAvatarEntities() {
|
void MyAvatar::handleChangedAvatarEntityData() {
|
||||||
// NOTE: this is a per-frame update
|
// NOTE: this is a per-frame update
|
||||||
if (getID().isNull() ||
|
if (getID().isNull() ||
|
||||||
getID() == AVATAR_SELF_ID ||
|
getID() == AVATAR_SELF_ID ||
|
||||||
|
@ -1499,7 +1499,7 @@ void MyAvatar::updateAvatarEntities() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We collect changes to AvatarEntities and then handle them all in one spot per frame: updateAvatarEntities().
|
// 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
|
// 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
|
// "directions" and the "authoritative source" of each direction is different, so we maintain two distinct sets
|
||||||
// of transaction lists:
|
// of transaction lists:
|
||||||
|
|
|
@ -1186,7 +1186,6 @@ public:
|
||||||
virtual void setAttachmentsVariant(const QVariantList& variant) override;
|
virtual void setAttachmentsVariant(const QVariantList& variant) override;
|
||||||
|
|
||||||
glm::vec3 getNextPosition() { return _goToPending ? _goToPosition : getWorldPosition(); }
|
glm::vec3 getNextPosition() { return _goToPending ? _goToPosition : getWorldPosition(); }
|
||||||
void updateAvatarEntities() override;
|
|
||||||
void prepareAvatarEntityDataForReload();
|
void prepareAvatarEntityDataForReload();
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
|
@ -1619,6 +1618,7 @@ private slots:
|
||||||
void updateCollisionCapsuleCache();
|
void updateCollisionCapsuleCache();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
void handleChangedAvatarEntityData();
|
||||||
virtual void beParentOfChild(SpatiallyNestablePointer newChild) const override;
|
virtual void beParentOfChild(SpatiallyNestablePointer newChild) const override;
|
||||||
virtual void forgetChild(SpatiallyNestablePointer newChild) const override;
|
virtual void forgetChild(SpatiallyNestablePointer newChild) const override;
|
||||||
virtual void recalculateChildCauterization() const override;
|
virtual void recalculateChildCauterization() const override;
|
||||||
|
@ -1630,7 +1630,7 @@ private:
|
||||||
|
|
||||||
virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking) override;
|
virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking) override;
|
||||||
|
|
||||||
void simulate(float deltaTime);
|
void simulate(float deltaTime, bool inView) override;
|
||||||
void updateFromTrackers(float deltaTime);
|
void updateFromTrackers(float deltaTime);
|
||||||
void saveAvatarUrl();
|
void saveAvatarUrl();
|
||||||
virtual void render(RenderArgs* renderArgs) override;
|
virtual void render(RenderArgs* renderArgs) override;
|
||||||
|
|
|
@ -7,10 +7,18 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "OtherAvatar.h"
|
#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"
|
#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) {
|
static glm::u8vec3 getLoadingOrbColor(Avatar::LoadingStatus loadingStatus) {
|
||||||
|
|
||||||
const glm::u8vec3 NO_MODEL_COLOR(0xe3, 0xe3, 0xe3);
|
const glm::u8vec3 NO_MODEL_COLOR(0xe3, 0xe3, 0xe3);
|
||||||
|
@ -142,4 +150,293 @@ void OtherAvatar::updateCollisionGroup(bool myAvatarCollide) {
|
||||||
_motionState->addDirtyFlags(Simulation::DIRTY_COLLISION_GROUP);
|
_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 - (1 - _displayNameAlpha) * coef;
|
||||||
|
}
|
||||||
|
_displayNameAlpha = 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#define hifi_OtherAvatar_h
|
#define hifi_OtherAvatar_h
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include <avatars-renderer/Avatar.h>
|
#include <avatars-renderer/Avatar.h>
|
||||||
#include <workload/Space.h>
|
#include <workload/Space.h>
|
||||||
|
@ -47,9 +48,17 @@ public:
|
||||||
|
|
||||||
void updateCollisionGroup(bool myAvatarCollide);
|
void updateCollisionGroup(bool myAvatarCollide);
|
||||||
|
|
||||||
|
void simulate(float deltaTime, bool inView) override;
|
||||||
|
|
||||||
friend AvatarManager;
|
friend AvatarManager;
|
||||||
|
|
||||||
protected:
|
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 };
|
std::shared_ptr<Sphere3DOverlay> _otherAvatarOrbMeshPlaceholder { nullptr };
|
||||||
OverlayID _otherAvatarOrbMeshPlaceholderID { UNKNOWN_OVERLAY_ID };
|
OverlayID _otherAvatarOrbMeshPlaceholderID { UNKNOWN_OVERLAY_ID };
|
||||||
AvatarMotionState* _motionState { nullptr };
|
AvatarMotionState* _motionState { nullptr };
|
||||||
|
|
|
@ -308,164 +308,6 @@ void Avatar::setAvatarEntityDataChanged(bool value) {
|
||||||
_avatarEntityDataHashes.clear();
|
_avatarEntityDataHashes.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Avatar::updateAvatarEntities() {
|
|
||||||
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::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 storeAvatarEntityDataPayload, which sets _avatarEntityDataChanged = true
|
|
||||||
// - (My)Avatar::simulate calls updateAvatarEntities 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 updateAvatarEntities 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());
|
|
||||||
|
|
||||||
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 packedAvatarEntityData;
|
|
||||||
_avatarEntitiesLock.withReadLock([&] {
|
|
||||||
packedAvatarEntityData = _packedAvatarEntityData;
|
|
||||||
});
|
|
||||||
foreach (auto entityID, recentlyDetachedAvatarEntities) {
|
|
||||||
if (!packedAvatarEntityData.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 (packedAvatarEntityData.size() != _avatarEntityForRecording.size()) {
|
|
||||||
createRecordingIDs();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
setAvatarEntityDataChanged(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Avatar::removeAvatarEntitiesFromTree() {
|
void Avatar::removeAvatarEntitiesFromTree() {
|
||||||
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
||||||
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
|
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
|
||||||
|
@ -650,87 +492,6 @@ void Avatar::relayJointDataToChildren() {
|
||||||
_reconstructSoftEntitiesJointMap = false;
|
_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 {
|
float Avatar::getSimulationRate(const QString& rateName) const {
|
||||||
if (rateName == "") {
|
if (rateName == "") {
|
||||||
return _simulationRate.rate();
|
return _simulationRate.rate();
|
||||||
|
@ -1045,7 +806,6 @@ void Avatar::render(RenderArgs* renderArgs) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Avatar::setEnableMeshVisible(bool isEnabled) {
|
void Avatar::setEnableMeshVisible(bool isEnabled) {
|
||||||
if (_isMeshVisible != isEnabled) {
|
if (_isMeshVisible != isEnabled) {
|
||||||
_isMeshVisible = isEnabled;
|
_isMeshVisible = isEnabled;
|
||||||
|
|
|
@ -139,9 +139,8 @@ public:
|
||||||
typedef render::Payload<AvatarData> Payload;
|
typedef render::Payload<AvatarData> Payload;
|
||||||
|
|
||||||
void init();
|
void init();
|
||||||
virtual void updateAvatarEntities();
|
|
||||||
void removeAvatarEntitiesFromTree();
|
void removeAvatarEntitiesFromTree();
|
||||||
void simulate(float deltaTime, bool inView);
|
virtual void simulate(float deltaTime, bool inView) = 0;
|
||||||
virtual void simulateAttachments(float deltaTime);
|
virtual void simulateAttachments(float deltaTime);
|
||||||
|
|
||||||
virtual void render(RenderArgs* renderArgs);
|
virtual void render(RenderArgs* renderArgs);
|
||||||
|
|
|
@ -2777,7 +2777,7 @@ void AvatarData::clearAvatarEntity(const QUuid& entityID, bool requiresRemovalFr
|
||||||
removedEntity = _packedAvatarEntityData.remove(entityID);
|
removedEntity = _packedAvatarEntityData.remove(entityID);
|
||||||
});
|
});
|
||||||
|
|
||||||
insertDetachedEntityID(entityID);
|
insertRemovedEntityID(entityID);
|
||||||
|
|
||||||
if (removedEntity && _clientTraitsHandler) {
|
if (removedEntity && _clientTraitsHandler) {
|
||||||
// we have a client traits handler, so we need to mark this removed instance trait as deleted
|
// we have a client traits handler, so we need to mark this removed instance trait as deleted
|
||||||
|
@ -2798,18 +2798,18 @@ void AvatarData::setAvatarEntityData(const AvatarEntityMap& avatarEntityData) {
|
||||||
// each QByteArray represents an EntityItemProperties object from JavaScript
|
// each QByteArray represents an EntityItemProperties object from JavaScript
|
||||||
}
|
}
|
||||||
|
|
||||||
void AvatarData::insertDetachedEntityID(const QUuid entityID) {
|
void AvatarData::insertRemovedEntityID(const QUuid entityID) {
|
||||||
_avatarEntitiesLock.withWriteLock([&] {
|
_avatarEntitiesLock.withWriteLock([&] {
|
||||||
_avatarEntityDetached.insert(entityID);
|
_avatarEntityRemoved.insert(entityID);
|
||||||
});
|
});
|
||||||
_avatarEntityDataChanged = true;
|
_avatarEntityDataChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
AvatarEntityIDs AvatarData::getAndClearRecentlyDetachedIDs() {
|
AvatarEntityIDs AvatarData::getAndClearRecentlyRemovedIDs() {
|
||||||
AvatarEntityIDs result;
|
AvatarEntityIDs result;
|
||||||
_avatarEntitiesLock.withWriteLock([&] {
|
_avatarEntitiesLock.withWriteLock([&] {
|
||||||
result = _avatarEntityDetached;
|
result = _avatarEntityRemoved;
|
||||||
_avatarEntityDetached.clear();
|
_avatarEntityRemoved.clear();
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1150,8 +1150,7 @@ public:
|
||||||
Q_INVOKABLE virtual void setAvatarEntityData(const AvatarEntityMap& avatarEntityData);
|
Q_INVOKABLE virtual void setAvatarEntityData(const AvatarEntityMap& avatarEntityData);
|
||||||
|
|
||||||
virtual void setAvatarEntityDataChanged(bool value) { _avatarEntityDataChanged = value; }
|
virtual void setAvatarEntityDataChanged(bool value) { _avatarEntityDataChanged = value; }
|
||||||
void insertDetachedEntityID(const QUuid entityID);
|
AvatarEntityIDs getAndClearRecentlyRemovedIDs();
|
||||||
AvatarEntityIDs getAndClearRecentlyDetachedIDs();
|
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
* @function MyAvatar.getSensorToWorldMatrix
|
* @function MyAvatar.getSensorToWorldMatrix
|
||||||
|
@ -1338,6 +1337,7 @@ public slots:
|
||||||
void resetLastSent() { _lastToByteArray = 0; }
|
void resetLastSent() { _lastToByteArray = 0; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
void insertRemovedEntityID(const QUuid entityID);
|
||||||
void lazyInitHeadData() const;
|
void lazyInitHeadData() const;
|
||||||
|
|
||||||
float getDistanceBasedMinRotationDOT(glm::vec3 viewerPosition) const;
|
float getDistanceBasedMinRotationDOT(glm::vec3 viewerPosition) const;
|
||||||
|
@ -1466,7 +1466,7 @@ protected:
|
||||||
AABox _defaultBubbleBox;
|
AABox _defaultBubbleBox;
|
||||||
|
|
||||||
mutable ReadWriteLockable _avatarEntitiesLock;
|
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
|
AvatarEntityIDs _avatarEntityForRecording; // create new entities id for avatar recording
|
||||||
PackedAvatarEntityMap _packedAvatarEntityData;
|
PackedAvatarEntityMap _packedAvatarEntityData;
|
||||||
bool _avatarEntityDataChanged { false };
|
bool _avatarEntityDataChanged { false };
|
||||||
|
|
Loading…
Reference in a new issue