//// AvatarManager.cpp// interface/src/avatar//// Created by Stephen Birarda on 1/23/2014.// Copyright 2014 High Fidelity, Inc.// Copyright 2023 Overte e.V.//// Distributed under the Apache License, Version 2.0.// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html// SPDX-License-Identifier: Apache-2.0// #include "AvatarManager.h" #include <string> #include <ScriptEngine.h>#include <ScriptValue.h> #include "AvatarLogging.h" #if defined(__GNUC__) && !defined(__clang__)#pragma GCC diagnostic push#pragma GCC diagnostic ignored "-Wdouble-promotion"#endif #include <glm/gtx/string_cast.hpp> #if defined(__GNUC__) && !defined(__clang__)#pragma GCC diagnostic pop#endif #include <shared/QtHelpers.h>#include <AvatarData.h>#include <PerfStat.h>#include <PrioritySortUtil.h>#include <RegisteredMetaTypes.h>#include <Rig.h>#include <SettingHandle.h>#include <UsersScriptingInterface.h>#include <UUID.h>#include <shared/ConicalViewFrustum.h>#include <ui/AvatarInputs.h> #include "Application.h"#include "InterfaceLogging.h"#include "Menu.h"#include "MyAvatar.h"#include "DebugDraw.h"#include "SceneScriptingInterface.h"#include "ui/AvatarCertifyBanner.h" // 50 times per second - target is 45hz, but this helps account for any small deviations// in the update loop - this also results in ~30hz when in desktop mode which is essentially// what we want // We add _myAvatar into the hash with all the other AvatarData, and we use the default NULL QUid as the key.const QUuid MY_AVATAR_KEY; // NULL key AvatarManager::AvatarManager(QObject* parent) : _myAvatar(new MyAvatar(qApp->thread()), [](MyAvatar* ptr) { ptr->deleteLater(); }){ // register a meta type for the weak pointer we'll use for the owning avatar mixer for each avatar qRegisterMetaType<QWeakPointer<Node> >("NodeWeakPointer"); auto nodeList = DependencyManager::get<NodeList>(); // when we hear that the user has ignored an avatar by session UUID // immediately remove that avatar instead of waiting for the absence of packets from avatar mixer connect(nodeList.data(), &NodeList::ignoredNode, this, [this](const QUuid& nodeID, bool enabled) { if (enabled) { removeAvatar(nodeID, KillAvatarReason::AvatarIgnored); } else { auto avatar = std::static_pointer_cast<Avatar>(getAvatarBySessionID(nodeID)); if (avatar) { avatar->createOrb(); } } }); _transitConfig._totalFrames = AVATAR_TRANSIT_FRAME_COUNT; _transitConfig._minTriggerDistance = AVATAR_TRANSIT_MIN_TRIGGER_DISTANCE; _transitConfig._maxTriggerDistance = AVATAR_TRANSIT_MAX_TRIGGER_DISTANCE; _transitConfig._framesPerMeter = AVATAR_TRANSIT_FRAMES_PER_METER; _transitConfig._isDistanceBased = AVATAR_TRANSIT_DISTANCE_BASED; _transitConfig._abortDistance = AVATAR_TRANSIT_ABORT_DISTANCE;} AvatarSharedPointer AvatarManager::addAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer) { AvatarSharedPointer avatar = AvatarHashMap::addAvatar(sessionUUID, mixerWeakPointer); const auto otherAvatar = std::static_pointer_cast<OtherAvatar>(avatar); if (otherAvatar && _space) { std::unique_lock<std::mutex> lock(_spaceLock); auto spaceIndex = _space->allocateID(); otherAvatar->setSpaceIndex(spaceIndex); workload::Sphere sphere(otherAvatar->getWorldPosition(), otherAvatar->getBoundingRadius()); workload::Transaction transaction; SpatiallyNestablePointer nestable = std::static_pointer_cast<SpatiallyNestable>(otherAvatar); transaction.reset(spaceIndex, sphere, workload::Owner(nestable)); _space->enqueueTransaction(transaction); } return avatar;} AvatarManager::~AvatarManager() { assert(_otherAvatarsToChangeInPhysics.empty());} void AvatarManager::init() { _myAvatar->init(); { QWriteLocker locker(&_hashLock); _avatarHash.insert(MY_AVATAR_KEY, _myAvatar); } _shouldRender = DependencyManager::get<SceneScriptingInterface>()->shouldRenderAvatars(); connect(DependencyManager::get<SceneScriptingInterface>().data(), &SceneScriptingInterface::shouldRenderAvatarsChanged, this, &AvatarManager::updateAvatarRenderStatus, Qt::QueuedConnection); if (_shouldRender) { const render::ScenePointer& scene = qApp->getMain3DScene(); render::Transaction transaction; _myAvatar->addToScene(_myAvatar, scene, transaction); scene->enqueueTransaction(transaction); } setEnableDebugDrawOtherSkeletons(Menu::getInstance()->isOptionChecked(MenuOption::AnimDebugDrawOtherSkeletons));} void AvatarManager::setSpace(workload::SpacePointer& space ) { assert(!_space); _space = space;} void AvatarManager::handleTransitAnimations(AvatarTransit::Status status) { switch (status) { case AvatarTransit::Status::STARTED: _myAvatar->getSkeletonModel()->getRig().triggerNetworkRole("preTransitAnim"); break; case AvatarTransit::Status::START_TRANSIT: _myAvatar->getSkeletonModel()->getRig().triggerNetworkRole("transitAnim"); break; case AvatarTransit::Status::END_TRANSIT: _myAvatar->getSkeletonModel()->getRig().triggerNetworkRole("postTransitAnim"); break; case AvatarTransit::Status::ENDED: _myAvatar->getSkeletonModel()->getRig().triggerNetworkRole("idleAnim"); break; case AvatarTransit::Status::PRE_TRANSIT: break; case AvatarTransit::Status::POST_TRANSIT: break; case AvatarTransit::Status::IDLE: break; case AvatarTransit::Status::TRANSITING: break; case AvatarTransit::Status::ABORT_TRANSIT: break; }} void AvatarManager::updateMyAvatar(float deltaTime) { bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "AvatarManager::updateMyAvatar()"); AvatarTransit::Status status = _myAvatar->updateTransit(deltaTime, _myAvatar->getNextPosition(), _myAvatar->getSensorToWorldScale(), _transitConfig); handleTransitAnimations(status); _myAvatar->update(deltaTime); render::Transaction transaction; _myAvatar->updateRenderItem(transaction); qApp->getMain3DScene()->enqueueTransaction(transaction); quint64 now = usecTimestampNow(); quint64 dt = now - _lastSendAvatarDataTime; if (dt > MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS && !_myAvatarDataPacketsPaused) { // send head/hand data to the avatar mixer and voxel server PerformanceTimer perfTimer("send"); _myAvatar->sendAvatarDataPacket(); _lastSendAvatarDataTime = now; _myAvatarSendRate.increment(); } static AvatarCertifyBanner theftBanner; if (_myAvatar->isCertifyFailed()) { theftBanner.show(_myAvatar->getSessionUUID()); } else { theftBanner.clear(); }} Q_LOGGING_CATEGORY(trace_simulation_avatar, "trace.simulation.avatar"); float AvatarManager::getAvatarDataRate(const QUuid& sessionID, const QString& rateName) const { auto avatar = getAvatarBySessionID(sessionID); return avatar ? avatar->getDataRate(rateName) : 0.0f;} void AvatarManager::setMyAvatarDataPacketsPaused(bool pause) { if (_myAvatarDataPacketsPaused != pause) { _myAvatarDataPacketsPaused = pause; if (!_myAvatarDataPacketsPaused) { _myAvatar->sendAvatarDataPacket(true); } }} float AvatarManager::getAvatarUpdateRate(const QUuid& sessionID, const QString& rateName) const { auto avatar = getAvatarBySessionID(sessionID); return avatar ? avatar->getUpdateRate(rateName) : 0.0f;} float AvatarManager::getAvatarSimulationRate(const QUuid& sessionID, const QString& rateName) const { auto avatar = std::static_pointer_cast<Avatar>(getAvatarBySessionID(sessionID)); return avatar ? avatar->getSimulationRate(rateName) : 0.0f;} void AvatarManager::updateOtherAvatars(float deltaTime) { { // lock the hash for read to check the size QReadLocker lock(&_hashLock); if (_avatarHash.size() < 2) { return; } } PerformanceTimer perfTimer("otherAvatars"); class SortableAvatar: public PrioritySortUtil::Sortable { public: SortableAvatar() = delete; SortableAvatar(const std::shared_ptr<Avatar>& avatar) : _avatar(avatar) {} glm::vec3 getPosition() const override { return _avatar->getWorldPosition(); } float getRadius() const override { return _avatar->getBoundingRadius(); } uint64_t getTimestamp() const override { return _avatar->getLastRenderUpdateTime(); } std::shared_ptr<Avatar> getAvatar() const { return _avatar; } private: std::shared_ptr<Avatar> _avatar; }; auto avatarMap = getHashCopy(); const auto& views = qApp->getConicalViews(); // Prepare 2 queues for heros and for crowd avatars using AvatarPriorityQueue = PrioritySortUtil::PriorityQueue<SortableAvatar>; // Keep two independent queues, one for heroes and one for the riff-raff. enum PriorityVariants { kHero = 0, kNonHero, NumVariants }; AvatarPriorityQueue avatarPriorityQueues[NumVariants] = { { views, AvatarData::_avatarSortCoefficientSize, AvatarData::_avatarSortCoefficientCenter, AvatarData::_avatarSortCoefficientAge }, { views, AvatarData::_avatarSortCoefficientSize, AvatarData::_avatarSortCoefficientCenter, AvatarData::_avatarSortCoefficientAge } }; // Reserve space //avatarPriorityQueues[kHero].reserve(10); // just few avatarPriorityQueues[kNonHero].reserve(avatarMap.size() - 1); // don't include MyAvatar // Build vector and compute priorities auto nodeList = DependencyManager::get<NodeList>(); AvatarHash::iterator itr = avatarMap.begin(); while (itr != avatarMap.end()) { auto avatar = std::static_pointer_cast<Avatar>(*itr); // DO NOT update _myAvatar! Its update has already been done earlier in the main loop. // DO NOT update or fade out uninitialized Avatars if (avatar != _myAvatar && avatar->isInitialized() && !nodeList->isPersonalMutingNode(avatar->getID())) { if (avatar->getHasPriority()) { avatarPriorityQueues[kHero].push(SortableAvatar(avatar)); } else { avatarPriorityQueues[kNonHero].push(SortableAvatar(avatar)); } } ++itr; } _numHeroAvatars = (int)avatarPriorityQueues[kHero].size(); // process in sorted order uint64_t startTime = usecTimestampNow(); const uint64_t MAX_UPDATE_HEROS_TIME_BUDGET = uint64_t(0.8 * MAX_UPDATE_AVATARS_TIME_BUDGET); uint64_t updatePriorityExpiries[NumVariants] = { startTime + MAX_UPDATE_HEROS_TIME_BUDGET, startTime + MAX_UPDATE_AVATARS_TIME_BUDGET }; int numHerosUpdated = 0; int numAvatarsUpdated = 0; int numAvatarsNotUpdated = 0; render::Transaction renderTransaction; workload::Transaction workloadTransaction; for (int p = kHero; p < NumVariants; p++) { auto& priorityQueue = avatarPriorityQueues[p]; // Sorting the current queue HERE as part of the measured timing. const auto& sortedAvatarVector = priorityQueue.getSortedVector(); auto passExpiry = updatePriorityExpiries[p]; for (auto it = sortedAvatarVector.begin(); it != sortedAvatarVector.end(); ++it) { const SortableAvatar& sortData = *it; const auto avatar = std::static_pointer_cast<OtherAvatar>(sortData.getAvatar()); if (!avatar->_isClientAvatar) { avatar->setIsClientAvatar(true); } // TODO: to help us scale to more avatars it would be nice to not have to poll this stuff every update if (avatar->getSkeletonModel()->isLoaded()) { // remove the orb if it is there avatar->removeOrb(); if (avatar->needsPhysicsUpdate()) { _otherAvatarsToChangeInPhysics.insert(avatar); } } else { avatar->updateOrbPosition(); } // for ALL avatars... if (_shouldRender) { avatar->ensureInScene(avatar, qApp->getMain3DScene()); } avatar->animateScaleChanges(deltaTime); uint64_t now = usecTimestampNow(); if (now < passExpiry) { // we're within budget bool inView = sortData.getPriority() > OUT_OF_VIEW_THRESHOLD; if (inView && avatar->hasNewJointData()) { numAvatarsUpdated++; } auto transitStatus = avatar->_transit.update(deltaTime, avatar->_serverPosition, _transitConfig); if (avatar->getIsNewAvatar() && (transitStatus == AvatarTransit::Status::START_TRANSIT || transitStatus == AvatarTransit::Status::ABORT_TRANSIT)) { avatar->_transit.reset(); avatar->setIsNewAvatar(false); } avatar->simulate(deltaTime, inView); if (avatar->getSkeletonModel()->isLoaded() && avatar->getWorkloadRegion() == workload::Region::R1) { _myAvatar->addAvatarHandsToFlow(avatar); } if (_drawOtherAvatarSkeletons) { avatar->debugJointData(); } avatar->setEnableMeshVisible(!_drawOtherAvatarSkeletons); avatar->updateRenderItem(renderTransaction); avatar->updateSpaceProxy(workloadTransaction); avatar->setLastRenderUpdateTime(startTime); } else { // we've spent our time budget for this priority bucket // let's deal with the reminding avatars if this pass and BREAK from the for loop if (p == kHero) { // Hero, // --> put them back in the non hero queue auto& crowdQueue = avatarPriorityQueues[kNonHero]; while (it != sortedAvatarVector.end()) { crowdQueue.push(SortableAvatar((*it).getAvatar())); ++it; } } else { // Non Hero // --> bail on the rest of the avatar updates // --> more avatars may freeze until their priority trickles up // --> some scale animations may glitch // --> some avatar velocity measurements may be a little off // no time to simulate, but we take the time to count how many were tragically missed numAvatarsNotUpdated = sortedAvatarVector.end() - it; } // We had to cut short this pass, we must break out of the for loop here break; } } if (p == kHero) { numHerosUpdated = numAvatarsUpdated; } } if (_shouldRender) { qApp->getMain3DScene()->enqueueTransaction(renderTransaction); } _space->enqueueTransaction(workloadTransaction); _numAvatarsUpdated = numAvatarsUpdated; _numAvatarsNotUpdated = numAvatarsNotUpdated; _numHeroAvatarsUpdated = numHerosUpdated; _avatarSimulationTime = (float)(usecTimestampNow() - startTime) / (float)USECS_PER_MSEC;} void AvatarManager::postUpdate(float deltaTime, const render::ScenePointer& scene) { auto hashCopy = getHashCopy(); AvatarHash::iterator avatarIterator = hashCopy.begin(); for (avatarIterator = hashCopy.begin(); avatarIterator != hashCopy.end(); avatarIterator++) { auto avatar = std::static_pointer_cast<Avatar>(avatarIterator.value()); avatar->postUpdate(deltaTime, scene); }} AvatarSharedPointer AvatarManager::newSharedAvatar(const QUuid& sessionUUID) { auto otherAvatar = new OtherAvatar(qApp->thread()); otherAvatar->setSessionUUID(sessionUUID); auto nodeList = DependencyManager::get<NodeList>(); if (nodeList && !nodeList->isIgnoringNode(sessionUUID)) { otherAvatar->createOrb(); } return AvatarSharedPointer(otherAvatar, [](OtherAvatar* ptr) { ptr->deleteLater(); });} void AvatarManager::queuePhysicsChange(const OtherAvatarPointer& avatar) { _otherAvatarsToChangeInPhysics.insert(avatar);} DetailedMotionState* AvatarManager::createDetailedMotionState(OtherAvatarPointer avatar, int32_t jointIndex) { bool isBound = false; std::vector<int32_t> boundJoints; const btCollisionShape* shape = avatar->createCollisionShape(jointIndex, isBound, boundJoints); if (shape) { DetailedMotionState* motionState = new DetailedMotionState(avatar, shape, jointIndex); motionState->setMass(0.0f); // DetailedMotionState has KINEMATIC MotionType, so zero mass is ok motionState->setIsBound(isBound, boundJoints); return motionState; } return nullptr;} void AvatarManager::rebuildAvatarPhysics(PhysicsEngine::Transaction& transaction, const OtherAvatarPointer& avatar) { if (!avatar->_motionState) { avatar->_motionState = new AvatarMotionState(avatar, nullptr); } AvatarMotionState* motionState = avatar->_motionState; ShapeInfo shapeInfo; avatar->computeShapeInfo(shapeInfo); const btCollisionShape* shape = ObjectMotionState::getShapeManager()->getShape(shapeInfo); assert(shape); motionState->setShape(shape); motionState->setMass(avatar->computeMass()); if (motionState->getRigidBody()) { transaction.objectsToReinsert.push_back(motionState); } else { transaction.objectsToAdd.push_back(motionState); } motionState->clearIncomingDirtyFlags();} void AvatarManager::removeDetailedAvatarPhysics(PhysicsEngine::Transaction& transaction, const OtherAvatarPointer& avatar) { // delete old detailedMotionStates auto& detailedMotionStates = avatar->getDetailedMotionStates(); if (detailedMotionStates.size() != 0) { for (auto& detailedMotionState : detailedMotionStates) { transaction.objectsToRemove.push_back(detailedMotionState); } avatar->forgetDetailedMotionStates(); }} void AvatarManager::rebuildDetailedAvatarPhysics(PhysicsEngine::Transaction& transaction, const OtherAvatarPointer& avatar) { // Rather than reconcile numbers of joints after change to model or LOD // we blow away old detailedMotionStates and create anew all around. removeDetailedAvatarPhysics(transaction, avatar); auto& detailedMotionStates = avatar->getDetailedMotionStates(); OtherAvatar::BodyLOD lod = avatar->getBodyLOD(); if (lod == OtherAvatar::BodyLOD::Sphere) { auto dMotionState = createDetailedMotionState(avatar, -1); if (dMotionState) { detailedMotionStates.push_back(dMotionState); transaction.objectsToAdd.push_back(dMotionState); } } else { int32_t numJoints = avatar->getJointCount(); for (int32_t i = 0; i < numJoints; i++) { auto dMotionState = createDetailedMotionState(avatar, i); if (dMotionState) { detailedMotionStates.push_back(dMotionState); transaction.objectsToAdd.push_back(dMotionState); } } } avatar->_needsDetailedRebuild = false;} void AvatarManager::buildPhysicsTransaction(PhysicsEngine::Transaction& transaction) { _myAvatar->getCharacterController()->buildPhysicsTransaction(transaction); for (auto avatar : _otherAvatarsToChangeInPhysics) { bool isInPhysics = avatar->isInPhysicsSimulation(); if (isInPhysics != avatar->shouldBeInPhysicsSimulation()) {Lines 1–500 of 1098. Load more
// // AvatarManager.cpp // interface/src/avatar // // Created by Stephen Birarda on 1/23/2014. // Copyright 2014 High Fidelity, Inc. // Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // SPDX-License-Identifier: Apache-2.0 // #include "AvatarManager.h" #include <string> #include <ScriptEngine.h> #include <ScriptValue.h> #include "AvatarLogging.h" #if defined(__GNUC__) && !defined(__clang__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdouble-promotion" #endif #include <glm/gtx/string_cast.hpp> #if defined(__GNUC__) && !defined(__clang__) #pragma GCC diagnostic pop #endif #include <shared/QtHelpers.h> #include <AvatarData.h> #include <PerfStat.h> #include <PrioritySortUtil.h> #include <RegisteredMetaTypes.h> #include <Rig.h> #include <SettingHandle.h> #include <UsersScriptingInterface.h> #include <UUID.h> #include <shared/ConicalViewFrustum.h> #include <ui/AvatarInputs.h> #include "Application.h" #include "InterfaceLogging.h" #include "Menu.h" #include "MyAvatar.h" #include "DebugDraw.h" #include "SceneScriptingInterface.h" #include "ui/AvatarCertifyBanner.h" // 50 times per second - target is 45hz, but this helps account for any small deviations // in the update loop - this also results in ~30hz when in desktop mode which is essentially // what we want // We add _myAvatar into the hash with all the other AvatarData, and we use the default NULL QUid as the key. const QUuid MY_AVATAR_KEY; // NULL key AvatarManager::AvatarManager(QObject* parent) : _myAvatar(new MyAvatar(qApp->thread()), [](MyAvatar* ptr) { ptr->deleteLater(); }) { // register a meta type for the weak pointer we'll use for the owning avatar mixer for each avatar qRegisterMetaType<QWeakPointer<Node> >("NodeWeakPointer"); auto nodeList = DependencyManager::get<NodeList>(); // when we hear that the user has ignored an avatar by session UUID // immediately remove that avatar instead of waiting for the absence of packets from avatar mixer connect(nodeList.data(), &NodeList::ignoredNode, this, [this](const QUuid& nodeID, bool enabled) { if (enabled) { removeAvatar(nodeID, KillAvatarReason::AvatarIgnored); } else { auto avatar = std::static_pointer_cast<Avatar>(getAvatarBySessionID(nodeID)); if (avatar) { avatar->createOrb(); } } }); _transitConfig._totalFrames = AVATAR_TRANSIT_FRAME_COUNT; _transitConfig._minTriggerDistance = AVATAR_TRANSIT_MIN_TRIGGER_DISTANCE; _transitConfig._maxTriggerDistance = AVATAR_TRANSIT_MAX_TRIGGER_DISTANCE; _transitConfig._framesPerMeter = AVATAR_TRANSIT_FRAMES_PER_METER; _transitConfig._isDistanceBased = AVATAR_TRANSIT_DISTANCE_BASED; _transitConfig._abortDistance = AVATAR_TRANSIT_ABORT_DISTANCE; } AvatarSharedPointer AvatarManager::addAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer) { AvatarSharedPointer avatar = AvatarHashMap::addAvatar(sessionUUID, mixerWeakPointer); const auto otherAvatar = std::static_pointer_cast<OtherAvatar>(avatar); if (otherAvatar && _space) { std::unique_lock<std::mutex> lock(_spaceLock); auto spaceIndex = _space->allocateID(); otherAvatar->setSpaceIndex(spaceIndex); workload::Sphere sphere(otherAvatar->getWorldPosition(), otherAvatar->getBoundingRadius()); workload::Transaction transaction; SpatiallyNestablePointer nestable = std::static_pointer_cast<SpatiallyNestable>(otherAvatar); transaction.reset(spaceIndex, sphere, workload::Owner(nestable)); _space->enqueueTransaction(transaction); } return avatar; } AvatarManager::~AvatarManager() { assert(_otherAvatarsToChangeInPhysics.empty()); } void AvatarManager::init() { _myAvatar->init(); { QWriteLocker locker(&_hashLock); _avatarHash.insert(MY_AVATAR_KEY, _myAvatar); } _shouldRender = DependencyManager::get<SceneScriptingInterface>()->shouldRenderAvatars(); connect(DependencyManager::get<SceneScriptingInterface>().data(), &SceneScriptingInterface::shouldRenderAvatarsChanged, this, &AvatarManager::updateAvatarRenderStatus, Qt::QueuedConnection); if (_shouldRender) { const render::ScenePointer& scene = qApp->getMain3DScene(); render::Transaction transaction; _myAvatar->addToScene(_myAvatar, scene, transaction); scene->enqueueTransaction(transaction); } setEnableDebugDrawOtherSkeletons(Menu::getInstance()->isOptionChecked(MenuOption::AnimDebugDrawOtherSkeletons)); } void AvatarManager::setSpace(workload::SpacePointer& space ) { assert(!_space); _space = space; } void AvatarManager::handleTransitAnimations(AvatarTransit::Status status) { switch (status) { case AvatarTransit::Status::STARTED: _myAvatar->getSkeletonModel()->getRig().triggerNetworkRole("preTransitAnim"); break; case AvatarTransit::Status::START_TRANSIT: _myAvatar->getSkeletonModel()->getRig().triggerNetworkRole("transitAnim"); break; case AvatarTransit::Status::END_TRANSIT: _myAvatar->getSkeletonModel()->getRig().triggerNetworkRole("postTransitAnim"); break; case AvatarTransit::Status::ENDED: _myAvatar->getSkeletonModel()->getRig().triggerNetworkRole("idleAnim"); break; case AvatarTransit::Status::PRE_TRANSIT: break; case AvatarTransit::Status::POST_TRANSIT: break; case AvatarTransit::Status::IDLE: break; case AvatarTransit::Status::TRANSITING: break; case AvatarTransit::Status::ABORT_TRANSIT: break; } } void AvatarManager::updateMyAvatar(float deltaTime) { bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "AvatarManager::updateMyAvatar()"); AvatarTransit::Status status = _myAvatar->updateTransit(deltaTime, _myAvatar->getNextPosition(), _myAvatar->getSensorToWorldScale(), _transitConfig); handleTransitAnimations(status); _myAvatar->update(deltaTime); render::Transaction transaction; _myAvatar->updateRenderItem(transaction); qApp->getMain3DScene()->enqueueTransaction(transaction); quint64 now = usecTimestampNow(); quint64 dt = now - _lastSendAvatarDataTime; if (dt > MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS && !_myAvatarDataPacketsPaused) { // send head/hand data to the avatar mixer and voxel server PerformanceTimer perfTimer("send"); _myAvatar->sendAvatarDataPacket(); _lastSendAvatarDataTime = now; _myAvatarSendRate.increment(); } static AvatarCertifyBanner theftBanner; if (_myAvatar->isCertifyFailed()) { theftBanner.show(_myAvatar->getSessionUUID()); } else { theftBanner.clear(); } } Q_LOGGING_CATEGORY(trace_simulation_avatar, "trace.simulation.avatar"); float AvatarManager::getAvatarDataRate(const QUuid& sessionID, const QString& rateName) const { auto avatar = getAvatarBySessionID(sessionID); return avatar ? avatar->getDataRate(rateName) : 0.0f; } void AvatarManager::setMyAvatarDataPacketsPaused(bool pause) { if (_myAvatarDataPacketsPaused != pause) { _myAvatarDataPacketsPaused = pause; if (!_myAvatarDataPacketsPaused) { _myAvatar->sendAvatarDataPacket(true); } } } float AvatarManager::getAvatarUpdateRate(const QUuid& sessionID, const QString& rateName) const { auto avatar = getAvatarBySessionID(sessionID); return avatar ? avatar->getUpdateRate(rateName) : 0.0f; } float AvatarManager::getAvatarSimulationRate(const QUuid& sessionID, const QString& rateName) const { auto avatar = std::static_pointer_cast<Avatar>(getAvatarBySessionID(sessionID)); return avatar ? avatar->getSimulationRate(rateName) : 0.0f; } void AvatarManager::updateOtherAvatars(float deltaTime) { { // lock the hash for read to check the size QReadLocker lock(&_hashLock); if (_avatarHash.size() < 2) { return; } } PerformanceTimer perfTimer("otherAvatars"); class SortableAvatar: public PrioritySortUtil::Sortable { public: SortableAvatar() = delete; SortableAvatar(const std::shared_ptr<Avatar>& avatar) : _avatar(avatar) {} glm::vec3 getPosition() const override { return _avatar->getWorldPosition(); } float getRadius() const override { return _avatar->getBoundingRadius(); } uint64_t getTimestamp() const override { return _avatar->getLastRenderUpdateTime(); } std::shared_ptr<Avatar> getAvatar() const { return _avatar; } private: std::shared_ptr<Avatar> _avatar; }; auto avatarMap = getHashCopy(); const auto& views = qApp->getConicalViews(); // Prepare 2 queues for heros and for crowd avatars using AvatarPriorityQueue = PrioritySortUtil::PriorityQueue<SortableAvatar>; // Keep two independent queues, one for heroes and one for the riff-raff. enum PriorityVariants { kHero = 0, kNonHero, NumVariants }; AvatarPriorityQueue avatarPriorityQueues[NumVariants] = { { views, AvatarData::_avatarSortCoefficientSize, AvatarData::_avatarSortCoefficientCenter, AvatarData::_avatarSortCoefficientAge }, { views, AvatarData::_avatarSortCoefficientSize, AvatarData::_avatarSortCoefficientCenter, AvatarData::_avatarSortCoefficientAge } }; // Reserve space //avatarPriorityQueues[kHero].reserve(10); // just few avatarPriorityQueues[kNonHero].reserve(avatarMap.size() - 1); // don't include MyAvatar // Build vector and compute priorities auto nodeList = DependencyManager::get<NodeList>(); AvatarHash::iterator itr = avatarMap.begin(); while (itr != avatarMap.end()) { auto avatar = std::static_pointer_cast<Avatar>(*itr); // DO NOT update _myAvatar! Its update has already been done earlier in the main loop. // DO NOT update or fade out uninitialized Avatars if (avatar != _myAvatar && avatar->isInitialized() && !nodeList->isPersonalMutingNode(avatar->getID())) { if (avatar->getHasPriority()) { avatarPriorityQueues[kHero].push(SortableAvatar(avatar)); } else { avatarPriorityQueues[kNonHero].push(SortableAvatar(avatar)); } } ++itr; } _numHeroAvatars = (int)avatarPriorityQueues[kHero].size(); // process in sorted order uint64_t startTime = usecTimestampNow(); const uint64_t MAX_UPDATE_HEROS_TIME_BUDGET = uint64_t(0.8 * MAX_UPDATE_AVATARS_TIME_BUDGET); uint64_t updatePriorityExpiries[NumVariants] = { startTime + MAX_UPDATE_HEROS_TIME_BUDGET, startTime + MAX_UPDATE_AVATARS_TIME_BUDGET }; int numHerosUpdated = 0; int numAvatarsUpdated = 0; int numAvatarsNotUpdated = 0; render::Transaction renderTransaction; workload::Transaction workloadTransaction; for (int p = kHero; p < NumVariants; p++) { auto& priorityQueue = avatarPriorityQueues[p]; // Sorting the current queue HERE as part of the measured timing. const auto& sortedAvatarVector = priorityQueue.getSortedVector(); auto passExpiry = updatePriorityExpiries[p]; for (auto it = sortedAvatarVector.begin(); it != sortedAvatarVector.end(); ++it) { const SortableAvatar& sortData = *it; const auto avatar = std::static_pointer_cast<OtherAvatar>(sortData.getAvatar()); if (!avatar->_isClientAvatar) { avatar->setIsClientAvatar(true); } // TODO: to help us scale to more avatars it would be nice to not have to poll this stuff every update if (avatar->getSkeletonModel()->isLoaded()) { // remove the orb if it is there avatar->removeOrb(); if (avatar->needsPhysicsUpdate()) { _otherAvatarsToChangeInPhysics.insert(avatar); } } else { avatar->updateOrbPosition(); } // for ALL avatars... if (_shouldRender) { avatar->ensureInScene(avatar, qApp->getMain3DScene()); } avatar->animateScaleChanges(deltaTime); uint64_t now = usecTimestampNow(); if (now < passExpiry) { // we're within budget bool inView = sortData.getPriority() > OUT_OF_VIEW_THRESHOLD; if (inView && avatar->hasNewJointData()) { numAvatarsUpdated++; } auto transitStatus = avatar->_transit.update(deltaTime, avatar->_serverPosition, _transitConfig); if (avatar->getIsNewAvatar() && (transitStatus == AvatarTransit::Status::START_TRANSIT || transitStatus == AvatarTransit::Status::ABORT_TRANSIT)) { avatar->_transit.reset(); avatar->setIsNewAvatar(false); } avatar->simulate(deltaTime, inView); if (avatar->getSkeletonModel()->isLoaded() && avatar->getWorkloadRegion() == workload::Region::R1) { _myAvatar->addAvatarHandsToFlow(avatar); } if (_drawOtherAvatarSkeletons) { avatar->debugJointData(); } avatar->setEnableMeshVisible(!_drawOtherAvatarSkeletons); avatar->updateRenderItem(renderTransaction); avatar->updateSpaceProxy(workloadTransaction); avatar->setLastRenderUpdateTime(startTime); } else { // we've spent our time budget for this priority bucket // let's deal with the reminding avatars if this pass and BREAK from the for loop if (p == kHero) { // Hero, // --> put them back in the non hero queue auto& crowdQueue = avatarPriorityQueues[kNonHero]; while (it != sortedAvatarVector.end()) { crowdQueue.push(SortableAvatar((*it).getAvatar())); ++it; } } else { // Non Hero // --> bail on the rest of the avatar updates // --> more avatars may freeze until their priority trickles up // --> some scale animations may glitch // --> some avatar velocity measurements may be a little off // no time to simulate, but we take the time to count how many were tragically missed numAvatarsNotUpdated = sortedAvatarVector.end() - it; } // We had to cut short this pass, we must break out of the for loop here break; } } if (p == kHero) { numHerosUpdated = numAvatarsUpdated; } } if (_shouldRender) { qApp->getMain3DScene()->enqueueTransaction(renderTransaction); } _space->enqueueTransaction(workloadTransaction); _numAvatarsUpdated = numAvatarsUpdated; _numAvatarsNotUpdated = numAvatarsNotUpdated; _numHeroAvatarsUpdated = numHerosUpdated; _avatarSimulationTime = (float)(usecTimestampNow() - startTime) / (float)USECS_PER_MSEC; } void AvatarManager::postUpdate(float deltaTime, const render::ScenePointer& scene) { auto hashCopy = getHashCopy(); AvatarHash::iterator avatarIterator = hashCopy.begin(); for (avatarIterator = hashCopy.begin(); avatarIterator != hashCopy.end(); avatarIterator++) { auto avatar = std::static_pointer_cast<Avatar>(avatarIterator.value()); avatar->postUpdate(deltaTime, scene); } } AvatarSharedPointer AvatarManager::newSharedAvatar(const QUuid& sessionUUID) { auto otherAvatar = new OtherAvatar(qApp->thread()); otherAvatar->setSessionUUID(sessionUUID); auto nodeList = DependencyManager::get<NodeList>(); if (nodeList && !nodeList->isIgnoringNode(sessionUUID)) { otherAvatar->createOrb(); } return AvatarSharedPointer(otherAvatar, [](OtherAvatar* ptr) { ptr->deleteLater(); }); } void AvatarManager::queuePhysicsChange(const OtherAvatarPointer& avatar) { _otherAvatarsToChangeInPhysics.insert(avatar); } DetailedMotionState* AvatarManager::createDetailedMotionState(OtherAvatarPointer avatar, int32_t jointIndex) { bool isBound = false; std::vector<int32_t> boundJoints; const btCollisionShape* shape = avatar->createCollisionShape(jointIndex, isBound, boundJoints); if (shape) { DetailedMotionState* motionState = new DetailedMotionState(avatar, shape, jointIndex); motionState->setMass(0.0f); // DetailedMotionState has KINEMATIC MotionType, so zero mass is ok motionState->setIsBound(isBound, boundJoints); return motionState; } return nullptr; } void AvatarManager::rebuildAvatarPhysics(PhysicsEngine::Transaction& transaction, const OtherAvatarPointer& avatar) { if (!avatar->_motionState) { avatar->_motionState = new AvatarMotionState(avatar, nullptr); } AvatarMotionState* motionState = avatar->_motionState; ShapeInfo shapeInfo; avatar->computeShapeInfo(shapeInfo); const btCollisionShape* shape = ObjectMotionState::getShapeManager()->getShape(shapeInfo); assert(shape); motionState->setShape(shape); motionState->setMass(avatar->computeMass()); if (motionState->getRigidBody()) { transaction.objectsToReinsert.push_back(motionState); } else { transaction.objectsToAdd.push_back(motionState); } motionState->clearIncomingDirtyFlags(); } void AvatarManager::removeDetailedAvatarPhysics(PhysicsEngine::Transaction& transaction, const OtherAvatarPointer& avatar) { // delete old detailedMotionStates auto& detailedMotionStates = avatar->getDetailedMotionStates(); if (detailedMotionStates.size() != 0) { for (auto& detailedMotionState : detailedMotionStates) { transaction.objectsToRemove.push_back(detailedMotionState); } avatar->forgetDetailedMotionStates(); } } void AvatarManager::rebuildDetailedAvatarPhysics(PhysicsEngine::Transaction& transaction, const OtherAvatarPointer& avatar) { // Rather than reconcile numbers of joints after change to model or LOD // we blow away old detailedMotionStates and create anew all around. removeDetailedAvatarPhysics(transaction, avatar); auto& detailedMotionStates = avatar->getDetailedMotionStates(); OtherAvatar::BodyLOD lod = avatar->getBodyLOD(); if (lod == OtherAvatar::BodyLOD::Sphere) { auto dMotionState = createDetailedMotionState(avatar, -1); if (dMotionState) { detailedMotionStates.push_back(dMotionState); transaction.objectsToAdd.push_back(dMotionState); } } else { int32_t numJoints = avatar->getJointCount(); for (int32_t i = 0; i < numJoints; i++) { auto dMotionState = createDetailedMotionState(avatar, i); if (dMotionState) { detailedMotionStates.push_back(dMotionState); transaction.objectsToAdd.push_back(dMotionState); } } } avatar->_needsDetailedRebuild = false; } void AvatarManager::buildPhysicsTransaction(PhysicsEngine::Transaction& transaction) { _myAvatar->getCharacterController()->buildPhysicsTransaction(transaction); for (auto avatar : _otherAvatarsToChangeInPhysics) { bool isInPhysics = avatar->isInPhysicsSimulation(); if (isInPhysics != avatar->shouldBeInPhysicsSimulation()) {