mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-10 00:13:21 +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) {
|
||||
_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
|
||||
|
|
|
@ -674,7 +674,7 @@ void MyAvatar::update(float deltaTime) {
|
|||
|
||||
_clientTraitsHandler->sendChangedTraitsToMixer();
|
||||
|
||||
simulate(deltaTime);
|
||||
simulate(deltaTime, true);
|
||||
|
||||
currentEnergy += energyChargeRate;
|
||||
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");
|
||||
animateScaleChanges(deltaTime);
|
||||
|
||||
|
@ -890,7 +890,7 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
_characterController.setCollisionlessAllowed(collisionlessAllowed);
|
||||
}
|
||||
|
||||
updateAvatarEntities();
|
||||
handleChangedAvatarEntityData();
|
||||
|
||||
updateFadingStatus();
|
||||
}
|
||||
|
@ -1480,7 +1480,7 @@ void MyAvatar::sanitizeAvatarEntityProperties(EntityItemProperties& properties)
|
|||
properties.markAllChanged();
|
||||
}
|
||||
|
||||
void MyAvatar::updateAvatarEntities() {
|
||||
void MyAvatar::handleChangedAvatarEntityData() {
|
||||
// NOTE: this is a per-frame update
|
||||
if (getID().isNull() ||
|
||||
getID() == AVATAR_SELF_ID ||
|
||||
|
@ -1499,7 +1499,7 @@ void MyAvatar::updateAvatarEntities() {
|
|||
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
|
||||
// "directions" and the "authoritative source" of each direction is different, so we maintain two distinct sets
|
||||
// of transaction lists:
|
||||
|
|
|
@ -1186,7 +1186,6 @@ public:
|
|||
virtual void setAttachmentsVariant(const QVariantList& variant) override;
|
||||
|
||||
glm::vec3 getNextPosition() { return _goToPending ? _goToPosition : getWorldPosition(); }
|
||||
void updateAvatarEntities() override;
|
||||
void prepareAvatarEntityDataForReload();
|
||||
|
||||
/**jsdoc
|
||||
|
@ -1619,6 +1618,7 @@ private slots:
|
|||
void updateCollisionCapsuleCache();
|
||||
|
||||
protected:
|
||||
void handleChangedAvatarEntityData();
|
||||
virtual void beParentOfChild(SpatiallyNestablePointer newChild) const override;
|
||||
virtual void forgetChild(SpatiallyNestablePointer newChild) const override;
|
||||
virtual void recalculateChildCauterization() const override;
|
||||
|
@ -1630,7 +1630,7 @@ private:
|
|||
|
||||
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;
|
||||
|
|
|
@ -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 - (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
|
||||
|
||||
#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 };
|
||||
|
|
|
@ -308,164 +308,6 @@ void Avatar::setAvatarEntityDataChanged(bool value) {
|
|||
_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() {
|
||||
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
||||
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
|
||||
|
@ -650,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();
|
||||
|
@ -1045,7 +806,6 @@ void Avatar::render(RenderArgs* renderArgs) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
void Avatar::setEnableMeshVisible(bool isEnabled) {
|
||||
if (_isMeshVisible != isEnabled) {
|
||||
_isMeshVisible = isEnabled;
|
||||
|
|
|
@ -139,9 +139,8 @@ public:
|
|||
typedef render::Payload<AvatarData> Payload;
|
||||
|
||||
void init();
|
||||
virtual 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);
|
||||
|
|
|
@ -2777,7 +2777,7 @@ void AvatarData::clearAvatarEntity(const QUuid& entityID, bool requiresRemovalFr
|
|||
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
|
||||
|
@ -2798,18 +2798,18 @@ void AvatarData::setAvatarEntityData(const AvatarEntityMap& avatarEntityData) {
|
|||
// each QByteArray represents an EntityItemProperties object from JavaScript
|
||||
}
|
||||
|
||||
void AvatarData::insertDetachedEntityID(const QUuid entityID) {
|
||||
void AvatarData::insertRemovedEntityID(const QUuid entityID) {
|
||||
_avatarEntitiesLock.withWriteLock([&] {
|
||||
_avatarEntityDetached.insert(entityID);
|
||||
_avatarEntityRemoved.insert(entityID);
|
||||
});
|
||||
_avatarEntityDataChanged = true;
|
||||
}
|
||||
|
||||
AvatarEntityIDs AvatarData::getAndClearRecentlyDetachedIDs() {
|
||||
AvatarEntityIDs AvatarData::getAndClearRecentlyRemovedIDs() {
|
||||
AvatarEntityIDs result;
|
||||
_avatarEntitiesLock.withWriteLock([&] {
|
||||
result = _avatarEntityDetached;
|
||||
_avatarEntityDetached.clear();
|
||||
result = _avatarEntityRemoved;
|
||||
_avatarEntityRemoved.clear();
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -1150,8 +1150,7 @@ public:
|
|||
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
|
||||
|
@ -1338,6 +1337,7 @@ public slots:
|
|||
void resetLastSent() { _lastToByteArray = 0; }
|
||||
|
||||
protected:
|
||||
void insertRemovedEntityID(const QUuid entityID);
|
||||
void lazyInitHeadData() const;
|
||||
|
||||
float getDistanceBasedMinRotationDOT(glm::vec3 viewerPosition) const;
|
||||
|
@ -1466,7 +1466,7 @@ 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
|
||||
PackedAvatarEntityMap _packedAvatarEntityData;
|
||||
bool _avatarEntityDataChanged { false };
|
||||
|
|
Loading…
Reference in a new issue