diff --git a/interface/src/avatar/AvatarActionHold.cpp b/interface/src/avatar/AvatarActionHold.cpp index dbc82fd70e..230f8aa64b 100644 --- a/interface/src/avatar/AvatarActionHold.cpp +++ b/interface/src/avatar/AvatarActionHold.cpp @@ -48,6 +48,10 @@ AvatarActionHold::~AvatarActionHold() { myAvatar->removeHoldAction(this); } } + auto ownerEntity = _ownerEntity.lock(); + if (ownerEntity) { + ownerEntity->setTransitingWithAvatar(false); + } #if WANT_DEBUG qDebug() << "AvatarActionHold::~AvatarActionHold" << (void*)this; @@ -131,6 +135,15 @@ bool AvatarActionHold::getTarget(float deltaTimeStep, glm::quat& rotation, glm:: glm::vec3 palmPosition; glm::quat palmRotation; + bool isTransitingWithAvatar = holdingAvatar->getTransit()->isTransiting(); + if (isTransitingWithAvatar != _isTransitingWithAvatar) { + _isTransitingWithAvatar = isTransitingWithAvatar; + auto ownerEntity = _ownerEntity.lock(); + if (ownerEntity) { + ownerEntity->setTransitingWithAvatar(_isTransitingWithAvatar); + } + } + if (holdingAvatar->isMyAvatar()) { std::shared_ptr myAvatar = avatarManager->getMyAvatar(); @@ -404,11 +417,14 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) { _kinematicSetVelocity = kinematicSetVelocity; _ignoreIK = ignoreIK; _active = true; + + auto myAvatar = DependencyManager::get()->getMyAvatar(); auto ownerEntity = _ownerEntity.lock(); if (ownerEntity) { ownerEntity->setDynamicDataDirty(true); - ownerEntity->setDynamicDataNeedsTransmit(true); + ownerEntity->setDynamicDataNeedsTransmit(true); + ownerEntity->setTransitingWithAvatar(myAvatar->getTransit()->isTransiting()); } }); } diff --git a/interface/src/avatar/AvatarActionHold.h b/interface/src/avatar/AvatarActionHold.h index 6acc71b45c..ddc5808d67 100644 --- a/interface/src/avatar/AvatarActionHold.h +++ b/interface/src/avatar/AvatarActionHold.h @@ -59,6 +59,8 @@ private: bool _kinematicSetVelocity { false }; bool _previousSet { false }; bool _ignoreIK { false }; + bool _isTransitingWithAvatar { false }; + glm::vec3 _previousPositionalTarget; glm::quat _previousRotationalTarget; diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 5705e7347c..9b5aa4e415 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -78,6 +78,15 @@ AvatarManager::AvatarManager(QObject* parent) : removeAvatar(nodeID, KillAvatarReason::AvatarIgnored); } }); + + const float AVATAR_TRANSIT_TRIGGER_DISTANCE = 1.0f; + const int AVATAR_TRANSIT_FRAME_COUNT = 11; // Based on testing + const int AVATAR_TRANSIT_FRAMES_PER_METER = 1; // Based on testing + + _transitConfig._totalFrames = AVATAR_TRANSIT_FRAME_COUNT; + _transitConfig._triggerDistance = AVATAR_TRANSIT_TRIGGER_DISTANCE; + _transitConfig._framesPerMeter = AVATAR_TRANSIT_FRAMES_PER_METER; + _transitConfig._isDistanceBased = true; } AvatarSharedPointer AvatarManager::addAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer) { @@ -129,6 +138,10 @@ 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(), _transitConfig); + bool sendFirstTransitPackage = (status == AvatarTransit::Status::START_TRANSIT); + bool blockTransitData = (status == AvatarTransit::Status::TRANSITING); + _myAvatar->update(deltaTime); render::Transaction transaction; _myAvatar->updateRenderItem(transaction); @@ -137,9 +150,13 @@ void AvatarManager::updateMyAvatar(float deltaTime) { quint64 now = usecTimestampNow(); quint64 dt = now - _lastSendAvatarDataTime; - if (dt > MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS && !_myAvatarDataPacketsPaused) { + + if (sendFirstTransitPackage || (dt > MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS && !_myAvatarDataPacketsPaused && !blockTransitData)) { // send head/hand data to the avatar mixer and voxel server PerformanceTimer perfTimer("send"); + if (sendFirstTransitPackage) { + _myAvatar->overrideNextPackagePositionData(_myAvatar->getTransit()->getEndPosition()); + } _myAvatar->sendAvatarDataPacket(); _lastSendAvatarDataTime = now; _myAvatarSendRate.increment(); @@ -258,6 +275,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { if (inView && avatar->hasNewJointData()) { numAvatarsUpdated++; } + avatar->_transit.update(deltaTime, avatar->_globalPosition, _transitConfig); avatar->simulate(deltaTime, inView); avatar->updateRenderItem(renderTransaction); avatar->updateSpaceProxy(workloadTransaction); @@ -811,7 +829,7 @@ void AvatarManager::setAvatarSortCoefficient(const QString& name, const QScriptV } } - QVariantMap AvatarManager::getPalData(const QList specificAvatarIdentifiers) { +QVariantMap AvatarManager::getPalData(const QList specificAvatarIdentifiers) { QJsonArray palData; auto avatarMap = getHashCopy(); diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 9c4287728d..3ed156f673 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -31,6 +31,7 @@ #include "MyAvatar.h" #include "OtherAvatar.h" + using SortedAvatar = std::pair>; /**jsdoc @@ -232,6 +233,8 @@ private: mutable std::mutex _spaceLock; workload::SpacePointer _space; std::vector _spaceProxiesToDelete; + + AvatarTransit::TransitConfig _transitConfig; }; #endif // hifi_AvatarManager_h diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index f2e6b68a0f..17b7e6056e 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -638,9 +638,8 @@ void MyAvatar::updateChildCauterization(SpatiallyNestablePointer object, bool ca void MyAvatar::simulate(float deltaTime) { PerformanceTimer perfTimer("simulate"); - animateScaleChanges(deltaTime); - + setFlyingEnabled(getFlyingEnabled()); if (_cauterizationNeedsUpdate) { @@ -928,6 +927,7 @@ void MyAvatar::updateSensorToWorldMatrix() { updateJointFromController(controller::Action::RIGHT_HAND, _controllerRightHandMatrixCache); if (hasSensorToWorldScaleChanged) { + setTransitScale(sensorToWorldScale); emit sensorToWorldScaleChanged(sensorToWorldScale); } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index d7379a18c4..c861ee48a4 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1115,6 +1115,8 @@ public: virtual QVariantList getAttachmentsVariant() const override; virtual void setAttachmentsVariant(const QVariantList& variant) override; + glm::vec3 getNextPosition() { return _goToPending ? _goToPosition : getWorldPosition(); }; + public slots: /**jsdoc diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 914a3b7c6e..b4b213dc87 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -113,6 +113,80 @@ void Avatar::setShowNamesAboveHeads(bool show) { showNamesAboveHeads = show; } +AvatarTransit::Status AvatarTransit::update(float deltaTime, const glm::vec3& avatarPosition, const AvatarTransit::TransitConfig& config) { + glm::vec3 currentPosition = _isTransiting ? _currentPosition : avatarPosition; + float oneFrameDistance = glm::length(currentPosition - _lastPosition); + const float MAX_TRANSIT_DISTANCE = 30.0f; + float scaledMaxTransitDistance = MAX_TRANSIT_DISTANCE * _scale; + if (oneFrameDistance > config._triggerDistance && oneFrameDistance < scaledMaxTransitDistance && !_isTransiting) { + start(deltaTime, _lastPosition, currentPosition, config); + } + _lastPosition = currentPosition; + _status = updatePosition(deltaTime); + return _status; +} + +void AvatarTransit::start(float deltaTime, const glm::vec3& startPosition, const glm::vec3& endPosition, const AvatarTransit::TransitConfig& config) { + _startPosition = startPosition; + _endPosition = endPosition; + + _transitLine = endPosition - startPosition; + _totalDistance = glm::length(_transitLine); + _easeType = config._easeType; + const float REFERENCE_FRAMES_PER_SECOND = 30.0f; + + int transitFrames = (!config._isDistanceBased) ? config._totalFrames : config._framesPerMeter * _totalDistance; + _totalTime = (float)transitFrames / REFERENCE_FRAMES_PER_SECOND; + _currentTime = 0.0f; + _isTransiting = true; +} + +float AvatarTransit::getEaseValue(AvatarTransit::EaseType type, float value) { + switch (type) { + case EaseType::NONE: + return value; + break; + case EaseType::EASE_IN: + return value * value; + break; + case EaseType::EASE_OUT: + return value * (2.0f - value); + break; + case EaseType::EASE_IN_OUT: + return (value < 0.5f) ? 2.0f * value * value : -1.0f + (4.0f - 2.0f * value) * value; + break; + } + return value; +} + +AvatarTransit::Status AvatarTransit::updatePosition(float deltaTime) { + Status status = Status::IDLE; + if (_isTransiting) { + float nextTime = _currentTime + deltaTime; + glm::vec3 newPosition; + if (nextTime >= _totalTime) { + _currentPosition = _endPosition; + _isTransiting = false; + status = Status::END_TRANSIT; + } else { + if (_currentTime == 0) { + status = Status::START_TRANSIT; + } else { + status = Status::TRANSITING; + } + float percentageIntoTransit = nextTime / _totalTime; + _currentPosition = _startPosition + getEaseValue(_easeType, percentageIntoTransit) * _transitLine; + } + _currentTime = nextTime; + } + return status; +} + +bool AvatarTransit::getNextPosition(glm::vec3& nextPosition) { + nextPosition = _currentPosition; + return _isTransiting; +} + Avatar::Avatar(QThread* thread) : _voiceSphereID(GeometryCache::UNKNOWN_ID) { @@ -449,7 +523,18 @@ void Avatar::relayJointDataToChildren() { void Avatar::simulate(float deltaTime, bool inView) { PROFILE_RANGE(simulation, "simulate"); - + + if (_transit.isTransiting()) { + glm::vec3 nextPosition; + if (_transit.getNextPosition(nextPosition)) { + _globalPosition = nextPosition; + _globalPositionChanged = usecTimestampNow(); + if (!hasParent()) { + setLocalPosition(nextPosition); + } + } + } + _simulationRate.increment(); if (inView) { _simulationInViewRate.increment(); @@ -460,7 +545,7 @@ void Avatar::simulate(float deltaTime, bool inView) { PROFILE_RANGE(simulation, "updateJoints"); if (inView) { Head* head = getHead(); - if (_hasNewJointData) { + if (_hasNewJointData || _transit.isTransiting()) { _skeletonModel->getRig().copyJointsFromJointData(_jointData); glm::mat4 rootTransform = glm::scale(_skeletonModel->getScale()) * glm::translate(_skeletonModel->getOffset()); _skeletonModel->getRig().computeExternalPoses(rootTransform); @@ -1881,6 +1966,22 @@ float Avatar::getUnscaledEyeHeightFromSkeleton() const { } } +AvatarTransit::Status Avatar::updateTransit(float deltaTime, const glm::vec3& avatarPosition, const AvatarTransit::TransitConfig& config) { + std::lock_guard lock(_transitLock); + return _transit.update(deltaTime, avatarPosition, config); +} + +void Avatar::setTransitScale(float scale) { + std::lock_guard lock(_transitLock); + return _transit.setScale(scale); +} + +void Avatar::overrideNextPackagePositionData(const glm::vec3& position) { + std::lock_guard lock(_transitLock); + _overrideGlobalPosition = true; + _globalPositionOverride = position; +} + void Avatar::addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) { std::lock_guard lock(_materialsLock); _materials[parentMaterialName].push(material); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 4f1c010d84..fe5d310812 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -50,6 +50,62 @@ enum ScreenTintLayer { class Texture; +class AvatarTransit { +public: + enum Status { + IDLE = 0, + START_TRANSIT, + TRANSITING, + END_TRANSIT + }; + + enum EaseType { + NONE = 0, + EASE_IN, + EASE_OUT, + EASE_IN_OUT + }; + + struct TransitConfig { + TransitConfig() {}; + int _totalFrames { 0 }; + int _framesPerMeter { 0 }; + bool _isDistanceBased { false }; + float _triggerDistance { 0 }; + EaseType _easeType { EaseType::EASE_OUT }; + }; + + AvatarTransit() {}; + Status update(float deltaTime, const glm::vec3& avatarPosition, const TransitConfig& config); + Status getStatus() { return _status; } + bool isTransiting() { return _isTransiting; } + glm::vec3 getCurrentPosition() { return _currentPosition; } + bool getNextPosition(glm::vec3& nextPosition); + glm::vec3 getEndPosition() { return _endPosition; } + float getTransitTime() { return _totalTime; } + void setScale(float scale) { _scale = scale; } + +private: + Status updatePosition(float deltaTime); + void start(float deltaTime, const glm::vec3& startPosition, const glm::vec3& endPosition, const TransitConfig& config); + float getEaseValue(AvatarTransit::EaseType type, float value); + bool _isTransiting { false }; + + glm::vec3 _startPosition; + glm::vec3 _endPosition; + glm::vec3 _currentPosition; + + glm::vec3 _lastPosition; + + glm::vec3 _transitLine; + float _totalDistance { 0.0f }; + float _totalTime { 0.0f }; + float _currentTime { 0.0f }; + EaseType _easeType { EaseType::EASE_OUT }; + Status _status { Status::IDLE }; + float _scale { 1.0f }; +}; + class Avatar : public AvatarData, public scriptable::ModelProvider { Q_OBJECT @@ -370,6 +426,13 @@ public: virtual scriptable::ScriptableModelBase getScriptableModel() override; + std::shared_ptr getTransit() { return std::make_shared(_transit); }; + + AvatarTransit::Status updateTransit(float deltaTime, const glm::vec3& avatarPosition, const AvatarTransit::TransitConfig& config); + void setTransitScale(float scale); + + void overrideNextPackagePositionData(const glm::vec3& position); + signals: void targetScaleChanged(float targetScale); @@ -505,6 +568,7 @@ protected: RateCounter<> _skeletonModelSimulationRate; RateCounter<> _jointDataSimulationRate; + protected: class AvatarEntityDataHash { public: @@ -528,6 +592,9 @@ protected: bool _reconstructSoftEntitiesJointMap { false }; float _modelScale { 1.0f }; + AvatarTransit _transit; + std::mutex _transitLock; + static int _jointConesID; int _voiceSphereID; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index acf5696d65..2168dff1f6 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -369,7 +369,12 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent if (hasAvatarGlobalPosition) { auto startSection = destinationBuffer; - AVATAR_MEMCPY(_globalPosition); + if (_overrideGlobalPosition) { + AVATAR_MEMCPY(_globalPositionOverride); + } else { + AVATAR_MEMCPY(_globalPosition); + } + int numBytes = destinationBuffer - startSection; @@ -2088,6 +2093,10 @@ void AvatarData::sendAvatarDataPacket(bool sendAll) { } } + if (_overrideGlobalPosition) { + _overrideGlobalPosition = false; + } + doneEncoding(cullSmallData); static AvatarDataSequenceNumber sequenceNumber = 0; diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 39f0ea34f6..a890482e9a 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -1372,7 +1372,8 @@ protected: // where Entities are located. This is currently only used by the mixer to decide how often to send // updates about one avatar to another. glm::vec3 _globalPosition { 0, 0, 0 }; - + glm::vec3 _globalPositionOverride { 0, 0, 0 }; + bool _overrideGlobalPosition { false }; quint64 _globalPositionChanged { 0 }; quint64 _avatarBoundingBoxChanged { 0 }; diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index 01557e307e..c8ea68dae4 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -266,6 +266,7 @@ AvatarSharedPointer AvatarHashMap::parseAvatarData(QSharedPointerparseDataFromBuffer(byteArray); message->seek(positionBeforeRead + bytesRead); @@ -316,7 +317,6 @@ void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer // In this case, the "sendingNode" is the Avatar Mixer. avatar->processAvatarIdentity(message->getMessage(), identityChanged, displayNameChanged); _replicas.processAvatarIdentity(identityUUID, message->getMessage(), identityChanged, displayNameChanged); - } } @@ -329,6 +329,7 @@ void AvatarHashMap::processBulkAvatarTraits(QSharedPointer mess // grab the avatar so we can ask it to process trait data bool isNewAvatar; auto avatar = newOrExistingAvatar(avatarID, sendingNode, isNewAvatar); + // read the first trait type for this avatar AvatarTraits::TraitType traitType; message->readPrimitive(&traitType); diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 2139c1f6b1..0e5cae66b6 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -441,6 +441,8 @@ public: void setDynamicDataNeedsTransmit(bool value) const { _dynamicDataNeedsTransmit = value; } bool dynamicDataNeedsTransmit() const { return _dynamicDataNeedsTransmit; } + void setTransitingWithAvatar(bool value) { _transitingWithAvatar = value; } + bool getTransitingWithAvatar() { return _transitingWithAvatar; } bool shouldSuppressLocationEdits() const; @@ -668,6 +670,7 @@ protected: QUuid _sourceUUID; /// the server node UUID we came from bool _clientOnly { false }; + bool _transitingWithAvatar{ false }; QUuid _owningAvatarID; // physics related changes from the network to suppress any duplicates and make diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 5478221607..8162bf4e18 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -432,6 +432,9 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) { // this case is prevented by setting _ownershipState to UNOWNABLE in EntityMotionState::ctor assert(!(_entity->getClientOnly() && _entity->getOwningAvatarID() != Physics::getSessionUUID())); + if (_entity->getTransitingWithAvatar()) { + return false; + } if (_entity->dynamicDataNeedsTransmit()) { return true; }