//// Avatar.cpp// interface/src/avatar//// Created by Philip Rosedale on 9/11/12.// Copyright 2013 High Fidelity, Inc.//// Distributed under the Apache License, Version 2.0.// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html// #include <vector> #include <QDesktopWidget>#include <QWindow> #include <glm/glm.hpp>#include <glm/gtx/quaternion.hpp>#include <glm/gtx/vector_angle.hpp>#include <glm/gtc/type_ptr.hpp>#include <glm/gtx/vector_query.hpp> #include <DeferredLightingEffect.h>#include <GeometryUtil.h>#include <LODManager.h>#include <NodeList.h>#include <NumericalConstants.h>#include <OctreeUtils.h>#include <udt/PacketHeaders.h>#include <PerfStat.h>#include <SharedUtil.h>#include <TextRenderer3D.h>#include <TextureCache.h>#include <VariantMapToScriptValue.h>#include <DebugDraw.h> #include "Application.h"#include "Avatar.h"#include "AvatarManager.h"#include "AvatarMotionState.h"#include "Head.h"#include "Menu.h"#include "Physics.h"#include "Util.h"#include "world.h"#include "InterfaceLogging.h"#include "SoftAttachmentModel.h"#include <Rig.h> using namespace std; const glm::vec3 DEFAULT_UP_DIRECTION(0.0f, 1.0f, 0.0f);const int NUM_BODY_CONE_SIDES = 9;const float CHAT_MESSAGE_SCALE = 0.0015f;const float CHAT_MESSAGE_HEIGHT = 0.1f;const float DISPLAYNAME_FADE_TIME = 0.5f;const float DISPLAYNAME_FADE_FACTOR = pow(0.01f, 1.0f / DISPLAYNAME_FADE_TIME);const float DISPLAYNAME_ALPHA = 1.0f;const float DISPLAYNAME_BACKGROUND_ALPHA = 0.4f;const glm::vec3 HAND_TO_PALM_OFFSET(0.0f, 0.12f, 0.08f); namespace render { template <> const ItemKey payloadGetKey(const AvatarSharedPointer& avatar) { return ItemKey::Builder::opaqueShape(); } template <> const Item::Bound payloadGetBound(const AvatarSharedPointer& avatar) { return static_pointer_cast<Avatar>(avatar)->getBounds(); } template <> void payloadRender(const AvatarSharedPointer& avatar, RenderArgs* args) { auto avatarPtr = static_pointer_cast<Avatar>(avatar); if (avatarPtr->isInitialized() && args) { PROFILE_RANGE_BATCH(*args->_batch, "renderAvatarPayload"); avatarPtr->render(args, qApp->getCamera()->getPosition()); } }} Avatar::Avatar(RigPointer rig) : AvatarData(), _skeletonOffset(0.0f), _bodyYawDelta(0.0f), _positionDeltaAccumulator(0.0f), _lastVelocity(0.0f), _acceleration(0.0f), _lastAngularVelocity(0.0f), _lastOrientation(), _worldUpDirection(DEFAULT_UP_DIRECTION), _moving(false), _initialized(false), _voiceSphereID(GeometryCache::UNKNOWN_ID){ // we may have been created in the network thread, but we live in the main thread moveToThread(qApp->thread()); setScale(glm::vec3(1.0f)); // avatar scale is uniform // give the pointer to our head to inherited _headData variable from AvatarData _headData = static_cast<HeadData*>(new Head(this)); _skeletonModel = std::make_shared<SkeletonModel>(this, nullptr, rig);} Avatar::~Avatar() { assert(isDead()); // mark dead before calling the dtor EntityTreeRenderer* treeRenderer = qApp->getEntities(); EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; if (entityTree) { entityTree->withWriteLock([&] { AvatarEntityMap avatarEntities = getAvatarEntityData(); for (auto entityID : avatarEntities.keys()) { entityTree->deleteEntity(entityID, true, true); } }); } if (_motionState) { delete _motionState; _motionState = nullptr; }} void Avatar::init() { getHead()->init(); _skeletonModel->init(); _initialized = true;} glm::vec3 Avatar::getChestPosition() const { // for now, let's just assume that the "chest" is halfway between the root and the neck glm::vec3 neckPosition; return _skeletonModel->getNeckPosition(neckPosition) ? (getPosition() + neckPosition) * 0.5f : getPosition();} glm::vec3 Avatar::getNeckPosition() const { glm::vec3 neckPosition; return _skeletonModel->getNeckPosition(neckPosition) ? neckPosition : getPosition();} glm::quat Avatar::getWorldAlignedOrientation () const { return computeRotationFromBodyToWorldUp() * getOrientation();} AABox Avatar::getBounds() const { if (!_skeletonModel->isRenderable() || _skeletonModel->needsFixupInScene()) { // approximately 2m tall, scaled to user request. return AABox(getPosition() - glm::vec3(getUniformScale()), getUniformScale() * 2.0f); } return _skeletonModel->getRenderableMeshBound();} void Avatar::animateScaleChanges(float deltaTime) { float currentScale = getUniformScale(); if (currentScale != _targetScale) { // use exponential decay toward _targetScale const float SCALE_ANIMATION_TIMESCALE = 0.5f; float blendFactor = glm::clamp(deltaTime / SCALE_ANIMATION_TIMESCALE, 0.0f, 1.0f); float animatedScale = (1.0f - blendFactor) * currentScale + blendFactor * _targetScale; // snap to the end when we get close enough const float MIN_RELATIVE_SCALE_ERROR = 0.03f; if (fabsf(_targetScale - currentScale) / _targetScale < MIN_RELATIVE_SCALE_ERROR) { animatedScale = _targetScale; } setScale(glm::vec3(animatedScale)); // avatar scale is uniform rebuildCollisionShape(); }} void Avatar::updateAvatarEntities() { // - if queueEditEntityMessage sees clientOnly flag it does _myAvatar->updateAvatarEntity() // - updateAvatarEntity saves the bytes and sets _avatarEntityDataLocallyEdited // - MyAvatar::update notices _avatarEntityDataLocallyEdited and calls sendIdentityPacket // - sendIdentityPacket sends the entity bytes to the server which relays them to other interfaces // - AvatarHashMap::processAvatarIdentityPacket on other interfaces call avatar->setAvatarEntityData() // - setAvatarEntityData saves the bytes and sets _avatarEntityDataChanged = true // - (My)Avatar::simulate notices _avatarEntityDataChanged and here we are... if (!_avatarEntityDataChanged) { return; } if (getID() == QUuid()) { return; // wait until MyAvatar gets an ID before doing this. } EntityTreeRenderer* treeRenderer = qApp->getEntities(); EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; if (!entityTree) { return; } bool success = true; QScriptEngine scriptEngine; entityTree->withWriteLock([&] { AvatarEntityMap avatarEntities = getAvatarEntityData(); for (auto entityID : avatarEntities.keys()) { // see EntityEditPacketSender::queueEditEntityMessage for the other end of this. unpack properties // and either add or update the entity. QByteArray jsonByteArray = avatarEntities.value(entityID); QJsonDocument jsonProperties = QJsonDocument::fromBinaryData(jsonByteArray); if (!jsonProperties.isObject()) { qCDebug(interfaceapp) << "got bad avatarEntity json" << QString(jsonByteArray.toHex()); continue; } QVariant variantProperties = jsonProperties.toVariant(); QVariantMap asMap = variantProperties.toMap(); QScriptValue scriptProperties = variantMapToScriptValue(asMap, scriptEngine); EntityItemProperties properties; EntityItemPropertiesFromScriptValueHonorReadOnly(scriptProperties, properties); properties.setClientOnly(true); 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()); } EntityItemPointer entity = entityTree->findEntityByEntityItemID(EntityItemID(entityID)); if (entity) { if (entityTree->updateEntity(entityID, properties)) { entity->updateLastEditedFromRemote(); } else { success = false; } } else { entity = entityTree->addEntity(entityID, properties); if (!entity) { success = false; } } } AvatarEntityIDs recentlyDettachedAvatarEntities = getAndClearRecentlyDetachedIDs(); _avatarEntitiesLock.withReadLock([&] { foreach (auto entityID, recentlyDettachedAvatarEntities) { if (!_avatarEntityData.contains(entityID)) { entityTree->deleteEntity(entityID, true, true); } } }); }); if (success) { setAvatarEntityDataChanged(false); }} void Avatar::simulate(float deltaTime) { PerformanceTimer perfTimer("simulate"); if (!isDead() && !_motionState) { DependencyManager::get<AvatarManager>()->addAvatarToSimulation(this); } animateScaleChanges(deltaTime); // update the shouldAnimate flag to match whether or not we will render the avatar. const float MINIMUM_VISIBILITY_FOR_ON = 0.4f; const float MAXIMUM_VISIBILITY_FOR_OFF = 0.6f; ViewFrustum viewFrustum; qApp->copyViewFrustum(viewFrustum); float visibility = calculateRenderAccuracy(viewFrustum.getPosition(), getBounds(), DependencyManager::get<LODManager>()->getOctreeSizeScale()); if (!_shouldAnimate) { if (visibility > MINIMUM_VISIBILITY_FOR_ON) { _shouldAnimate = true; qCDebug(interfaceapp) << "Restoring" << (isMyAvatar() ? "myself" : getSessionUUID()) << "for visibility" << visibility; } } else if (visibility < MAXIMUM_VISIBILITY_FOR_OFF) { _shouldAnimate = false; qCDebug(interfaceapp) << "Optimizing" << (isMyAvatar() ? "myself" : getSessionUUID()) << "for visibility" << visibility; } // simple frustum check float boundingRadius = getBoundingRadius(); qApp->copyDisplayViewFrustum(viewFrustum); bool avatarPositionInView = viewFrustum.sphereIntersectsFrustum(getPosition(), boundingRadius); bool avatarMeshInView = viewFrustum.boxIntersectsFrustum(_skeletonModel->getRenderableMeshBound()); if (_shouldAnimate && !_shouldSkipRender && (avatarPositionInView || avatarMeshInView)) { { PerformanceTimer perfTimer("skeleton"); _skeletonModel->getRig()->copyJointsFromJointData(_jointData); _skeletonModel->simulate(deltaTime, _hasNewJointRotations || _hasNewJointTranslations); locationChanged(); // joints changed, so if there are any children, update them. _hasNewJointRotations = false; _hasNewJointTranslations = false; } { PerformanceTimer perfTimer("head"); glm::vec3 headPosition = getPosition(); _skeletonModel->getHeadPosition(headPosition); Head* head = getHead(); head->setPosition(headPosition); head->setScale(getUniformScale()); head->simulate(deltaTime, false, !_shouldAnimate); } } 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); } // 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; } measureMotionDerivatives(deltaTime); simulateAttachments(deltaTime); updatePalms(); updateAvatarEntities();} bool Avatar::isLookingAtMe(AvatarSharedPointer avatar) const { const float HEAD_SPHERE_RADIUS = 0.1f; glm::vec3 theirLookAt = dynamic_pointer_cast<Avatar>(avatar)->getHead()->getLookAtPosition(); glm::vec3 myEyePosition = getHead()->getEyePosition(); return glm::distance(theirLookAt, myEyePosition) <= (HEAD_SPHERE_RADIUS * getUniformScale());} void Avatar::slamPosition(const glm::vec3& newPosition) { setPosition(newPosition); _positionDeltaAccumulator = glm::vec3(0.0f); setVelocity(glm::vec3(0.0f)); _lastVelocity = glm::vec3(0.0f);} void Avatar::applyPositionDelta(const glm::vec3& delta) { setPosition(getPosition() + delta); _positionDeltaAccumulator += delta;} void Avatar::measureMotionDerivatives(float deltaTime) { // linear float invDeltaTime = 1.0f / deltaTime; // Floating point error prevents us from computing velocity in a naive way // (e.g. vel = (pos - oldPos) / dt) so instead we use _positionOffsetAccumulator. glm::vec3 velocity = _positionDeltaAccumulator * invDeltaTime; _positionDeltaAccumulator = glm::vec3(0.0f); _acceleration = (velocity - _lastVelocity) * invDeltaTime; _lastVelocity = velocity; setVelocity(velocity); // angular glm::quat orientation = getOrientation(); glm::quat delta = glm::inverse(_lastOrientation) * orientation; glm::vec3 angularVelocity = glm::axis(delta) * glm::angle(delta) * invDeltaTime; setAngularVelocity(angularVelocity); _lastOrientation = getOrientation();} enum TextRendererType { CHAT, DISPLAYNAME}; static TextRenderer3D* textRenderer(TextRendererType type) { static TextRenderer3D* chatRenderer = TextRenderer3D::getInstance(SANS_FONT_FAMILY, -1, false, SHADOW_EFFECT); static TextRenderer3D* displayNameRenderer = TextRenderer3D::getInstance(SANS_FONT_FAMILY); switch(type) { case CHAT: return chatRenderer; case DISPLAYNAME: return displayNameRenderer; } return displayNameRenderer;} bool Avatar::addToScene(AvatarSharedPointer self, std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges) { auto avatarPayload = new render::Payload<AvatarData>(self); auto avatarPayloadPointer = Avatar::PayloadPointer(avatarPayload); _renderItemID = scene->allocateID(); pendingChanges.resetItem(_renderItemID, avatarPayloadPointer); _skeletonModel->addToScene(scene, pendingChanges); for (auto& attachmentModel : _attachmentModels) { attachmentModel->addToScene(scene, pendingChanges); } return true;} void Avatar::removeFromScene(AvatarSharedPointer self, std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges) { pendingChanges.removeItem(_renderItemID); render::Item::clearID(_renderItemID); _skeletonModel->removeFromScene(scene, pendingChanges); for (auto& attachmentModel : _attachmentModels) { attachmentModel->removeFromScene(scene, pendingChanges); }} void Avatar::updateRenderItem(render::PendingChanges& pendingChanges) { if (render::Item::isValidID(_renderItemID)) { pendingChanges.updateItem<render::Payload<AvatarData>>(_renderItemID, [](render::Payload<AvatarData>& p) {}); }} void Avatar::postUpdate(float deltaTime) { bool renderLookAtVectors; if (isMyAvatar()) { renderLookAtVectors = Menu::getInstance()->isOptionChecked(MenuOption::RenderMyLookAtVectors); } else { renderLookAtVectors = Menu::getInstance()->isOptionChecked(MenuOption::RenderOtherLookAtVectors); } if (renderLookAtVectors) { const float EYE_RAY_LENGTH = 10.0; const glm::vec4 BLUE(0.0f, 0.0f, 1.0f, 1.0f); const glm::vec4 RED(1.0f, 0.0f, 0.0f, 1.0f); int leftEyeJoint = getJointIndex("LeftEye"); glm::vec3 leftEyePosition; glm::quat leftEyeRotation; if (_skeletonModel->getJointPositionInWorldFrame(leftEyeJoint, leftEyePosition) && _skeletonModel->getJointRotationInWorldFrame(leftEyeJoint, leftEyeRotation)) { DebugDraw::getInstance().drawRay(leftEyePosition, leftEyePosition + leftEyeRotation * Vectors::UNIT_Z * EYE_RAY_LENGTH, BLUE); } int rightEyeJoint = getJointIndex("RightEye"); glm::vec3 rightEyePosition; glm::quat rightEyeRotation; if (_skeletonModel->getJointPositionInWorldFrame(rightEyeJoint, rightEyePosition) && _skeletonModel->getJointRotationInWorldFrame(rightEyeJoint, rightEyeRotation)) { DebugDraw::getInstance().drawRay(rightEyePosition, rightEyePosition + rightEyeRotation * Vectors::UNIT_Z * EYE_RAY_LENGTH, RED); } }} void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) { auto& batch = *renderArgs->_batch; PROFILE_RANGE_BATCH(batch, __FUNCTION__); if (glm::distance(DependencyManager::get<AvatarManager>()->getMyAvatar()->getPosition(), getPosition()) < 10.0f) { auto geometryCache = DependencyManager::get<GeometryCache>(); // render pointing lasers glm::vec3 laserColor = glm::vec3(1.0f, 0.0f, 1.0f); float laserLength = 50.0f; glm::vec3 position; glm::quat rotation; bool havePosition, haveRotation; if (_handState & LEFT_HAND_POINTING_FLAG) { if (_handState & IS_FINGER_POINTING_FLAG) { int leftIndexTip = getJointIndex("LeftHandIndex4"); int leftIndexTipJoint = getJointIndex("LeftHandIndex3"); havePosition = _skeletonModel->getJointPositionInWorldFrame(leftIndexTip, position); haveRotation = _skeletonModel->getJointRotationInWorldFrame(leftIndexTipJoint, rotation); } else { int leftHand = _skeletonModel->getLeftHandJointIndex(); havePosition = _skeletonModel->getJointPositionInWorldFrame(leftHand, position); haveRotation = _skeletonModel->getJointRotationInWorldFrame(leftHand, rotation); } if (havePosition && haveRotation) { PROFILE_RANGE_BATCH(batch, __FUNCTION__":leftHandPointer"); Transform pointerTransform; pointerTransform.setTranslation(position); pointerTransform.setRotation(rotation); batch.setModelTransform(pointerTransform); geometryCache->bindSimpleProgram(batch); geometryCache->renderLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, laserLength, 0.0f), laserColor); } } if (_handState & RIGHT_HAND_POINTING_FLAG) { if (_handState & IS_FINGER_POINTING_FLAG) { int rightIndexTip = getJointIndex("RightHandIndex4"); int rightIndexTipJoint = getJointIndex("RightHandIndex3"); havePosition = _skeletonModel->getJointPositionInWorldFrame(rightIndexTip, position);Lines 1–500 of 1207. Load more
// // Avatar.cpp // interface/src/avatar // // Created by Philip Rosedale on 9/11/12. // Copyright 2013 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include <vector> #include <QDesktopWidget> #include <QWindow> #include <glm/glm.hpp> #include <glm/gtx/quaternion.hpp> #include <glm/gtx/vector_angle.hpp> #include <glm/gtc/type_ptr.hpp> #include <glm/gtx/vector_query.hpp> #include <DeferredLightingEffect.h> #include <GeometryUtil.h> #include <LODManager.h> #include <NodeList.h> #include <NumericalConstants.h> #include <OctreeUtils.h> #include <udt/PacketHeaders.h> #include <PerfStat.h> #include <SharedUtil.h> #include <TextRenderer3D.h> #include <TextureCache.h> #include <VariantMapToScriptValue.h> #include <DebugDraw.h> #include "Application.h" #include "Avatar.h" #include "AvatarManager.h" #include "AvatarMotionState.h" #include "Head.h" #include "Menu.h" #include "Physics.h" #include "Util.h" #include "world.h" #include "InterfaceLogging.h" #include "SoftAttachmentModel.h" #include <Rig.h> using namespace std; const glm::vec3 DEFAULT_UP_DIRECTION(0.0f, 1.0f, 0.0f); const int NUM_BODY_CONE_SIDES = 9; const float CHAT_MESSAGE_SCALE = 0.0015f; const float CHAT_MESSAGE_HEIGHT = 0.1f; const float DISPLAYNAME_FADE_TIME = 0.5f; const float DISPLAYNAME_FADE_FACTOR = pow(0.01f, 1.0f / DISPLAYNAME_FADE_TIME); const float DISPLAYNAME_ALPHA = 1.0f; const float DISPLAYNAME_BACKGROUND_ALPHA = 0.4f; const glm::vec3 HAND_TO_PALM_OFFSET(0.0f, 0.12f, 0.08f); namespace render { template <> const ItemKey payloadGetKey(const AvatarSharedPointer& avatar) { return ItemKey::Builder::opaqueShape(); } template <> const Item::Bound payloadGetBound(const AvatarSharedPointer& avatar) { return static_pointer_cast<Avatar>(avatar)->getBounds(); } template <> void payloadRender(const AvatarSharedPointer& avatar, RenderArgs* args) { auto avatarPtr = static_pointer_cast<Avatar>(avatar); if (avatarPtr->isInitialized() && args) { PROFILE_RANGE_BATCH(*args->_batch, "renderAvatarPayload"); avatarPtr->render(args, qApp->getCamera()->getPosition()); } } } Avatar::Avatar(RigPointer rig) : AvatarData(), _skeletonOffset(0.0f), _bodyYawDelta(0.0f), _positionDeltaAccumulator(0.0f), _lastVelocity(0.0f), _acceleration(0.0f), _lastAngularVelocity(0.0f), _lastOrientation(), _worldUpDirection(DEFAULT_UP_DIRECTION), _moving(false), _initialized(false), _voiceSphereID(GeometryCache::UNKNOWN_ID) { // we may have been created in the network thread, but we live in the main thread moveToThread(qApp->thread()); setScale(glm::vec3(1.0f)); // avatar scale is uniform // give the pointer to our head to inherited _headData variable from AvatarData _headData = static_cast<HeadData*>(new Head(this)); _skeletonModel = std::make_shared<SkeletonModel>(this, nullptr, rig); } Avatar::~Avatar() { assert(isDead()); // mark dead before calling the dtor EntityTreeRenderer* treeRenderer = qApp->getEntities(); EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; if (entityTree) { entityTree->withWriteLock([&] { AvatarEntityMap avatarEntities = getAvatarEntityData(); for (auto entityID : avatarEntities.keys()) { entityTree->deleteEntity(entityID, true, true); } }); } if (_motionState) { delete _motionState; _motionState = nullptr; } } void Avatar::init() { getHead()->init(); _skeletonModel->init(); _initialized = true; } glm::vec3 Avatar::getChestPosition() const { // for now, let's just assume that the "chest" is halfway between the root and the neck glm::vec3 neckPosition; return _skeletonModel->getNeckPosition(neckPosition) ? (getPosition() + neckPosition) * 0.5f : getPosition(); } glm::vec3 Avatar::getNeckPosition() const { glm::vec3 neckPosition; return _skeletonModel->getNeckPosition(neckPosition) ? neckPosition : getPosition(); } glm::quat Avatar::getWorldAlignedOrientation () const { return computeRotationFromBodyToWorldUp() * getOrientation(); } AABox Avatar::getBounds() const { if (!_skeletonModel->isRenderable() || _skeletonModel->needsFixupInScene()) { // approximately 2m tall, scaled to user request. return AABox(getPosition() - glm::vec3(getUniformScale()), getUniformScale() * 2.0f); } return _skeletonModel->getRenderableMeshBound(); } void Avatar::animateScaleChanges(float deltaTime) { float currentScale = getUniformScale(); if (currentScale != _targetScale) { // use exponential decay toward _targetScale const float SCALE_ANIMATION_TIMESCALE = 0.5f; float blendFactor = glm::clamp(deltaTime / SCALE_ANIMATION_TIMESCALE, 0.0f, 1.0f); float animatedScale = (1.0f - blendFactor) * currentScale + blendFactor * _targetScale; // snap to the end when we get close enough const float MIN_RELATIVE_SCALE_ERROR = 0.03f; if (fabsf(_targetScale - currentScale) / _targetScale < MIN_RELATIVE_SCALE_ERROR) { animatedScale = _targetScale; } setScale(glm::vec3(animatedScale)); // avatar scale is uniform rebuildCollisionShape(); } } void Avatar::updateAvatarEntities() { // - if queueEditEntityMessage sees clientOnly flag it does _myAvatar->updateAvatarEntity() // - updateAvatarEntity saves the bytes and sets _avatarEntityDataLocallyEdited // - MyAvatar::update notices _avatarEntityDataLocallyEdited and calls sendIdentityPacket // - sendIdentityPacket sends the entity bytes to the server which relays them to other interfaces // - AvatarHashMap::processAvatarIdentityPacket on other interfaces call avatar->setAvatarEntityData() // - setAvatarEntityData saves the bytes and sets _avatarEntityDataChanged = true // - (My)Avatar::simulate notices _avatarEntityDataChanged and here we are... if (!_avatarEntityDataChanged) { return; } if (getID() == QUuid()) { return; // wait until MyAvatar gets an ID before doing this. } EntityTreeRenderer* treeRenderer = qApp->getEntities(); EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; if (!entityTree) { return; } bool success = true; QScriptEngine scriptEngine; entityTree->withWriteLock([&] { AvatarEntityMap avatarEntities = getAvatarEntityData(); for (auto entityID : avatarEntities.keys()) { // see EntityEditPacketSender::queueEditEntityMessage for the other end of this. unpack properties // and either add or update the entity. QByteArray jsonByteArray = avatarEntities.value(entityID); QJsonDocument jsonProperties = QJsonDocument::fromBinaryData(jsonByteArray); if (!jsonProperties.isObject()) { qCDebug(interfaceapp) << "got bad avatarEntity json" << QString(jsonByteArray.toHex()); continue; } QVariant variantProperties = jsonProperties.toVariant(); QVariantMap asMap = variantProperties.toMap(); QScriptValue scriptProperties = variantMapToScriptValue(asMap, scriptEngine); EntityItemProperties properties; EntityItemPropertiesFromScriptValueHonorReadOnly(scriptProperties, properties); properties.setClientOnly(true); 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()); } EntityItemPointer entity = entityTree->findEntityByEntityItemID(EntityItemID(entityID)); if (entity) { if (entityTree->updateEntity(entityID, properties)) { entity->updateLastEditedFromRemote(); } else { success = false; } } else { entity = entityTree->addEntity(entityID, properties); if (!entity) { success = false; } } } AvatarEntityIDs recentlyDettachedAvatarEntities = getAndClearRecentlyDetachedIDs(); _avatarEntitiesLock.withReadLock([&] { foreach (auto entityID, recentlyDettachedAvatarEntities) { if (!_avatarEntityData.contains(entityID)) { entityTree->deleteEntity(entityID, true, true); } } }); }); if (success) { setAvatarEntityDataChanged(false); } } void Avatar::simulate(float deltaTime) { PerformanceTimer perfTimer("simulate"); if (!isDead() && !_motionState) { DependencyManager::get<AvatarManager>()->addAvatarToSimulation(this); } animateScaleChanges(deltaTime); // update the shouldAnimate flag to match whether or not we will render the avatar. const float MINIMUM_VISIBILITY_FOR_ON = 0.4f; const float MAXIMUM_VISIBILITY_FOR_OFF = 0.6f; ViewFrustum viewFrustum; qApp->copyViewFrustum(viewFrustum); float visibility = calculateRenderAccuracy(viewFrustum.getPosition(), getBounds(), DependencyManager::get<LODManager>()->getOctreeSizeScale()); if (!_shouldAnimate) { if (visibility > MINIMUM_VISIBILITY_FOR_ON) { _shouldAnimate = true; qCDebug(interfaceapp) << "Restoring" << (isMyAvatar() ? "myself" : getSessionUUID()) << "for visibility" << visibility; } } else if (visibility < MAXIMUM_VISIBILITY_FOR_OFF) { _shouldAnimate = false; qCDebug(interfaceapp) << "Optimizing" << (isMyAvatar() ? "myself" : getSessionUUID()) << "for visibility" << visibility; } // simple frustum check float boundingRadius = getBoundingRadius(); qApp->copyDisplayViewFrustum(viewFrustum); bool avatarPositionInView = viewFrustum.sphereIntersectsFrustum(getPosition(), boundingRadius); bool avatarMeshInView = viewFrustum.boxIntersectsFrustum(_skeletonModel->getRenderableMeshBound()); if (_shouldAnimate && !_shouldSkipRender && (avatarPositionInView || avatarMeshInView)) { { PerformanceTimer perfTimer("skeleton"); _skeletonModel->getRig()->copyJointsFromJointData(_jointData); _skeletonModel->simulate(deltaTime, _hasNewJointRotations || _hasNewJointTranslations); locationChanged(); // joints changed, so if there are any children, update them. _hasNewJointRotations = false; _hasNewJointTranslations = false; } { PerformanceTimer perfTimer("head"); glm::vec3 headPosition = getPosition(); _skeletonModel->getHeadPosition(headPosition); Head* head = getHead(); head->setPosition(headPosition); head->setScale(getUniformScale()); head->simulate(deltaTime, false, !_shouldAnimate); } } 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); } // 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; } measureMotionDerivatives(deltaTime); simulateAttachments(deltaTime); updatePalms(); updateAvatarEntities(); } bool Avatar::isLookingAtMe(AvatarSharedPointer avatar) const { const float HEAD_SPHERE_RADIUS = 0.1f; glm::vec3 theirLookAt = dynamic_pointer_cast<Avatar>(avatar)->getHead()->getLookAtPosition(); glm::vec3 myEyePosition = getHead()->getEyePosition(); return glm::distance(theirLookAt, myEyePosition) <= (HEAD_SPHERE_RADIUS * getUniformScale()); } void Avatar::slamPosition(const glm::vec3& newPosition) { setPosition(newPosition); _positionDeltaAccumulator = glm::vec3(0.0f); setVelocity(glm::vec3(0.0f)); _lastVelocity = glm::vec3(0.0f); } void Avatar::applyPositionDelta(const glm::vec3& delta) { setPosition(getPosition() + delta); _positionDeltaAccumulator += delta; } void Avatar::measureMotionDerivatives(float deltaTime) { // linear float invDeltaTime = 1.0f / deltaTime; // Floating point error prevents us from computing velocity in a naive way // (e.g. vel = (pos - oldPos) / dt) so instead we use _positionOffsetAccumulator. glm::vec3 velocity = _positionDeltaAccumulator * invDeltaTime; _positionDeltaAccumulator = glm::vec3(0.0f); _acceleration = (velocity - _lastVelocity) * invDeltaTime; _lastVelocity = velocity; setVelocity(velocity); // angular glm::quat orientation = getOrientation(); glm::quat delta = glm::inverse(_lastOrientation) * orientation; glm::vec3 angularVelocity = glm::axis(delta) * glm::angle(delta) * invDeltaTime; setAngularVelocity(angularVelocity); _lastOrientation = getOrientation(); } enum TextRendererType { CHAT, DISPLAYNAME }; static TextRenderer3D* textRenderer(TextRendererType type) { static TextRenderer3D* chatRenderer = TextRenderer3D::getInstance(SANS_FONT_FAMILY, -1, false, SHADOW_EFFECT); static TextRenderer3D* displayNameRenderer = TextRenderer3D::getInstance(SANS_FONT_FAMILY); switch(type) { case CHAT: return chatRenderer; case DISPLAYNAME: return displayNameRenderer; } return displayNameRenderer; } bool Avatar::addToScene(AvatarSharedPointer self, std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges) { auto avatarPayload = new render::Payload<AvatarData>(self); auto avatarPayloadPointer = Avatar::PayloadPointer(avatarPayload); _renderItemID = scene->allocateID(); pendingChanges.resetItem(_renderItemID, avatarPayloadPointer); _skeletonModel->addToScene(scene, pendingChanges); for (auto& attachmentModel : _attachmentModels) { attachmentModel->addToScene(scene, pendingChanges); } return true; } void Avatar::removeFromScene(AvatarSharedPointer self, std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges) { pendingChanges.removeItem(_renderItemID); render::Item::clearID(_renderItemID); _skeletonModel->removeFromScene(scene, pendingChanges); for (auto& attachmentModel : _attachmentModels) { attachmentModel->removeFromScene(scene, pendingChanges); } } void Avatar::updateRenderItem(render::PendingChanges& pendingChanges) { if (render::Item::isValidID(_renderItemID)) { pendingChanges.updateItem<render::Payload<AvatarData>>(_renderItemID, [](render::Payload<AvatarData>& p) {}); } } void Avatar::postUpdate(float deltaTime) { bool renderLookAtVectors; if (isMyAvatar()) { renderLookAtVectors = Menu::getInstance()->isOptionChecked(MenuOption::RenderMyLookAtVectors); } else { renderLookAtVectors = Menu::getInstance()->isOptionChecked(MenuOption::RenderOtherLookAtVectors); } if (renderLookAtVectors) { const float EYE_RAY_LENGTH = 10.0; const glm::vec4 BLUE(0.0f, 0.0f, 1.0f, 1.0f); const glm::vec4 RED(1.0f, 0.0f, 0.0f, 1.0f); int leftEyeJoint = getJointIndex("LeftEye"); glm::vec3 leftEyePosition; glm::quat leftEyeRotation; if (_skeletonModel->getJointPositionInWorldFrame(leftEyeJoint, leftEyePosition) && _skeletonModel->getJointRotationInWorldFrame(leftEyeJoint, leftEyeRotation)) { DebugDraw::getInstance().drawRay(leftEyePosition, leftEyePosition + leftEyeRotation * Vectors::UNIT_Z * EYE_RAY_LENGTH, BLUE); } int rightEyeJoint = getJointIndex("RightEye"); glm::vec3 rightEyePosition; glm::quat rightEyeRotation; if (_skeletonModel->getJointPositionInWorldFrame(rightEyeJoint, rightEyePosition) && _skeletonModel->getJointRotationInWorldFrame(rightEyeJoint, rightEyeRotation)) { DebugDraw::getInstance().drawRay(rightEyePosition, rightEyePosition + rightEyeRotation * Vectors::UNIT_Z * EYE_RAY_LENGTH, RED); } } } void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) { auto& batch = *renderArgs->_batch; PROFILE_RANGE_BATCH(batch, __FUNCTION__); if (glm::distance(DependencyManager::get<AvatarManager>()->getMyAvatar()->getPosition(), getPosition()) < 10.0f) { auto geometryCache = DependencyManager::get<GeometryCache>(); // render pointing lasers glm::vec3 laserColor = glm::vec3(1.0f, 0.0f, 1.0f); float laserLength = 50.0f; glm::vec3 position; glm::quat rotation; bool havePosition, haveRotation; if (_handState & LEFT_HAND_POINTING_FLAG) { if (_handState & IS_FINGER_POINTING_FLAG) { int leftIndexTip = getJointIndex("LeftHandIndex4"); int leftIndexTipJoint = getJointIndex("LeftHandIndex3"); havePosition = _skeletonModel->getJointPositionInWorldFrame(leftIndexTip, position); haveRotation = _skeletonModel->getJointRotationInWorldFrame(leftIndexTipJoint, rotation); } else { int leftHand = _skeletonModel->getLeftHandJointIndex(); havePosition = _skeletonModel->getJointPositionInWorldFrame(leftHand, position); haveRotation = _skeletonModel->getJointRotationInWorldFrame(leftHand, rotation); } if (havePosition && haveRotation) { PROFILE_RANGE_BATCH(batch, __FUNCTION__":leftHandPointer"); Transform pointerTransform; pointerTransform.setTranslation(position); pointerTransform.setRotation(rotation); batch.setModelTransform(pointerTransform); geometryCache->bindSimpleProgram(batch); geometryCache->renderLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, laserLength, 0.0f), laserColor); } } if (_handState & RIGHT_HAND_POINTING_FLAG) { if (_handState & IS_FINGER_POINTING_FLAG) { int rightIndexTip = getJointIndex("RightHandIndex4"); int rightIndexTipJoint = getJointIndex("RightHandIndex3"); havePosition = _skeletonModel->getJointPositionInWorldFrame(rightIndexTip, position);