768 lines · 33.9 KB · C++
RawDownload
////  Created by Bradley Austin Davis on 2017/04/27//  Copyright 2013-2017 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 "OtherAvatar.h" #include <glm/gtx/norm.hpp>#include <glm/gtx/vector_angle.hpp> #include <AvatarLogging.h> #include "Application.h"#include "AvatarMotionState.h"#include "DetailedMotionState.h"#include "DebugDraw.h" const float DISPLAYNAME_FADE_TIME = 0.5f;const float DISPLAYNAME_FADE_FACTOR = pow(0.01f, 1.0f / DISPLAYNAME_FADE_TIME); /// Maximum length of joint history storage before it gets reallocated./// Only small number of past entries in history are used, but larger number is stored to avoid frequent reallocations.constexpr size_t OTHER_AVATAR_JOINT_HISTORY_SIZE = 2000; /// Number of previous samples used to determine latency./// Must be less than OTHER_AVATAR_JOINT_HISTORY_LOOK_BACK.constexpr size_t OTHER_AVATAR_JOINT_LATENCY_WINDOW = 20; /// How many entries backwards in history are searched before interpolation.constexpr size_t OTHER_AVATAR_JOINT_HISTORY_LOOK_BACK = 45; static_assert(OTHER_AVATAR_JOINT_LATENCY_WINDOW < OTHER_AVATAR_JOINT_HISTORY_LOOK_BACK); /// Maximum allowed latency for the avatar motion interpolation in microseconds./// Joint positions are only transmitted when the joint moves, so the latency needs to be determined from previous data.constexpr quint64 OTHER_AVATAR_JOINT_HISTORY_MAX_LATENCY = 600000; /// For smooth motion assumed latency needs to be slightly higher than estimated one./// Estimated latency will be multiplied by this coefficient.constexpr double OTHER_AVATAR_JOINT_LATENCY_COEFFICIENT = 1.3;  static glm::u8vec3 getLoadingOrbColor(Avatar::LoadingStatus loadingStatus) {     const glm::u8vec3 NO_MODEL_COLOR(0xe3, 0xe3, 0xe3);    const glm::u8vec3 LOAD_MODEL_COLOR(0xef, 0x93, 0xd1);    const glm::u8vec3 LOAD_SUCCESS_COLOR(0x1f, 0xc6, 0xa6);    const glm::u8vec3 LOAD_FAILURE_COLOR(0xc6, 0x21, 0x47);    switch (loadingStatus) {    case Avatar::LoadingStatus::NoModel:        return NO_MODEL_COLOR;    case Avatar::LoadingStatus::LoadModel:        return LOAD_MODEL_COLOR;    case Avatar::LoadingStatus::LoadSuccess:        return LOAD_SUCCESS_COLOR;    case Avatar::LoadingStatus::LoadFailure:    default:        return LOAD_FAILURE_COLOR;    }} OtherAvatar::OtherAvatar(QThread* thread) : Avatar(thread) {    // give the pointer to our head to inherited _headData variable from AvatarData    _headData = new Head(this);    _skeletonModel = std::make_shared<SkeletonModel>(this, nullptr);    _skeletonModel->setLoadingPriorityOperator([]() { return OTHERAVATAR_LOADING_PRIORITY; });    connect(_skeletonModel.get(), &Model::setURLFinished, this, &Avatar::setModelURLFinished);    connect(_skeletonModel.get(), &Model::rigReady, this, &Avatar::rigReady);    connect(_skeletonModel.get(), &Model::rigReset, this, &Avatar::rigReset);} OtherAvatar::~OtherAvatar() {    removeOrb();} void OtherAvatar::removeOrb() {    if (!_otherAvatarOrbMeshPlaceholderID.isNull()) {        DependencyManager::get<EntityScriptingInterface>()->deleteEntity(_otherAvatarOrbMeshPlaceholderID);        _otherAvatarOrbMeshPlaceholderID = UNKNOWN_ENTITY_ID;    }} void OtherAvatar::updateOrbPosition() {    if (!_otherAvatarOrbMeshPlaceholderID.isNull()) {        EntityItemProperties properties;        glm::vec3 headPosition;        if (!_skeletonModel->getHeadPosition(headPosition)) {            headPosition = getWorldPosition();        }        properties.setPosition(headPosition);        DependencyManager::get<EntityScriptingInterface>()->editEntity(_otherAvatarOrbMeshPlaceholderID, properties);    }} void OtherAvatar::createOrb() {    if (_otherAvatarOrbMeshPlaceholderID.isNull()) {        EntityItemProperties properties;        properties.setType(EntityTypes::Sphere);        properties.setAlpha(1.0f);        properties.setColor(getLoadingOrbColor(_loadingStatus));        properties.setName("Loading Avatar " + getID().toString());        properties.setPrimitiveMode(PrimitiveMode::LINES);        properties.getPulse().setMin(0.5f);        properties.getPulse().setMax(1.0f);        properties.getPulse().setColorMode(PulseMode::IN_PHASE);        properties.setIgnorePickIntersection(true);        properties.getGrab().setGrabbable(false);        properties.setFadeInMode(COMPONENT_MODE_DISABLED);        properties.setFadeOutMode(COMPONENT_MODE_DISABLED);         properties.setPosition(getHead()->getPosition());        properties.setRotation(glm::quat(0.0f, 0.0f, 0.0f, 1.0));        properties.setDimensions(glm::vec3(0.5f, 0.5f, 0.5f));        properties.setVisible(true);         _otherAvatarOrbMeshPlaceholderID = DependencyManager::get<EntityScriptingInterface>()->addEntityInternal(properties, entity::HostType::LOCAL);    }} void OtherAvatar::indicateLoadingStatus(LoadingStatus loadingStatus) {    Avatar::indicateLoadingStatus(loadingStatus);     if (_otherAvatarOrbMeshPlaceholderID != UNKNOWN_ENTITY_ID) {        EntityItemProperties properties;        properties.setColor(getLoadingOrbColor(_loadingStatus));        DependencyManager::get<EntityScriptingInterface>()->editEntity(_otherAvatarOrbMeshPlaceholderID, properties);    }} void OtherAvatar::setSpaceIndex(int32_t index) {    assert(_spaceIndex == -1);    _spaceIndex = index;} void OtherAvatar::updateSpaceProxy(workload::Transaction& transaction) const {    if (_spaceIndex > -1) {        float approximateBoundingRadius = glm::length(getTargetScale());        workload::Sphere sphere(getWorldPosition(), approximateBoundingRadius);        transaction.update(_spaceIndex, sphere);    }} int OtherAvatar::parseDataFromBuffer(const QByteArray& buffer) {    int32_t bytesRead = Avatar::parseDataFromBuffer(buffer);    for (auto& detailedMotionState : _detailedMotionStates) {        // NOTE: we activate _detailedMotionStates is because they are KINEMATIC        // and Bullet will automagically call DetailedMotionState::getWorldTransform() when active.        detailedMotionState->forceActive();    }    if (_moving && _motionState) {        _motionState->addDirtyFlags(Simulation::DIRTY_POSITION);    }    return bytesRead;} const btCollisionShape* OtherAvatar::createCollisionShape(int32_t jointIndex, bool& isBound, std::vector<int32_t>& boundJoints) {    ShapeInfo shapeInfo;    isBound = false;    QString jointName = "";    if (jointIndex > -1 && jointIndex < (int32_t)_multiSphereShapes.size()) {        jointName = _multiSphereShapes[jointIndex].getJointName();    }    switch (_bodyLOD) {    case BodyLOD::Sphere:        shapeInfo.setSphere(0.5f * getFitBounds().getDimensions().y);        boundJoints.clear();        for (auto &spheres : _multiSphereShapes) {            if (spheres.isValid()) {                boundJoints.push_back(spheres.getJointIndex());            }        }        isBound = true;        break;    case BodyLOD::MultiSphereLow:        if (jointName.contains("RightHand", Qt::CaseInsensitive) || jointName.contains("LeftHand", Qt::CaseInsensitive))  {            if (jointName.size() <= QString("RightHand").size()) {                AABox handBound;                for (auto &spheres : _multiSphereShapes) {                    if (spheres.isValid() && spheres.getJointName().contains(jointName, Qt::CaseInsensitive)) {                        boundJoints.push_back(spheres.getJointIndex());                        handBound += spheres.getBoundingBox();                    }                }                shapeInfo.setSphere(0.5f * handBound.getLargestDimension());                glm::vec3 jointPosition;                glm::quat jointRotation;                _skeletonModel->getJointPositionInWorldFrame(jointIndex, jointPosition);                _skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRotation);                glm::vec3 positionOffset = glm::inverse(jointRotation) * (handBound.calcCenter() - jointPosition);                shapeInfo.setOffset(positionOffset);                isBound = true;            }            break;        }        // Note: MultiSphereLow case really means: "skip fingers and use spheres for hands,        // else fall through to MultiSphereHigh case"        /* fall-thru */    case BodyLOD::MultiSphereHigh:        computeDetailedShapeInfo(shapeInfo, jointIndex);        break;    default:        assert(false); // should never reach here        break;    }    return ObjectMotionState::getShapeManager()->getShape(shapeInfo);} void OtherAvatar::forgetDetailedMotionStates() {    // NOTE: the DetailedMotionStates are deleted after being added to PhysicsEngine::Transaction::_objectsToRemove    // See AvatarManager::handleProcessedPhysicsTransaction()    _detailedMotionStates.clear();} void OtherAvatar::setWorkloadRegion(uint8_t region) {    _workloadRegion = region;    computeShapeLOD();} void OtherAvatar::computeShapeLOD() {    // auto newBodyLOD = _workloadRegion < workload::Region::R3 ? BodyLOD::MultiSphereShapes : BodyLOD::CapsuleShape;    // auto newBodyLOD = BodyLOD::CapsuleShape;    BodyLOD newLOD;    switch (_workloadRegion) {    case workload::Region::R1:        newLOD = BodyLOD::MultiSphereHigh;        break;    case workload::Region::R2:        newLOD = BodyLOD::MultiSphereLow;        break;    case workload::Region::UNKNOWN:    case workload::Region::INVALID:    case workload::Region::R4:    case workload::Region::R3:    default:        newLOD = BodyLOD::Sphere;        break;    }    if (newLOD != _bodyLOD) {        _bodyLOD = newLOD;        if (isInPhysicsSimulation()) {            _needsDetailedRebuild = true;        }    }} bool OtherAvatar::isInPhysicsSimulation() const {    return _motionState && _motionState->getRigidBody();} bool OtherAvatar::shouldBeInPhysicsSimulation() const {    return !isDead() && _workloadRegion <= workload::Region::R3;} bool OtherAvatar::needsPhysicsUpdate() const {    constexpr uint32_t FLAGS_OF_INTEREST = Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS | Simulation::DIRTY_POSITION | Simulation::DIRTY_COLLISION_GROUP;    return (_needsDetailedRebuild || (_motionState && (bool)(_motionState->getIncomingDirtyFlags() & FLAGS_OF_INTEREST)));} void OtherAvatar::rebuildCollisionShape() {    if (_motionState) {        // do not actually rebuild here, instead flag for later        _motionState->addDirtyFlags(Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS);        _needsDetailedRebuild = true;    }} void OtherAvatar::setCollisionWithOtherAvatarsFlags() {    if (_motionState) {        _motionState->addDirtyFlags(Simulation::DIRTY_COLLISION_GROUP);    }} void OtherAvatar::interpolateJoints() {    auto now = usecTimestampNow();     // there's no history to interpolate from,    // just set the rig poses to whatever we have    if ((size_t)_jointData.size() != _jointHistory.size()) {        goto finishRigSetup;    }     for (int i = 0; i < _jointData.size(); i++) {        const auto &history{_jointHistory[i]};        // We don't process very old samples for performance reasons, but we remove them only sporadically to avoid excessive reallocations.        const size_t historyStart = history.size() > OTHER_AVATAR_JOINT_HISTORY_LOOK_BACK ? history.size() - OTHER_AVATAR_JOINT_HISTORY_LOOK_BACK : 0;        // Calculate latency in microseconds and new time point.        quint64 latency = 0;        // We cannot calculate latency from just one sample.        if (history.size() >= 2) {            size_t latencySamples = 0;            for (size_t historyIndex = historyStart; historyIndex < history.size() - 1; historyIndex++) {                latency += std::min(history[historyIndex + 1].first - history[historyIndex].first, OTHER_AVATAR_JOINT_HISTORY_MAX_LATENCY);                latencySamples++;            }            latency /= latencySamples;            // Real latency may differ from estimated one, so we need some safety margin.            latency *= OTHER_AVATAR_JOINT_LATENCY_COEFFICIENT;        } else {            // Latency cannot be determined yet since we have only one entry.            latency = 0;        }        // Limit latency to a defined value.        latency = std::min(OTHER_AVATAR_JOINT_HISTORY_MAX_LATENCY, latency);        // We don't know the future, so avatar motions need to be delayed to be interpolated.        auto timePoint = now - latency;        // Retrieve previous and current joint data.        Q_ASSERT(history.size() > 0);        if (history.size() == 0) continue;        size_t oldKeyframeIndex = history.size() - 1;        size_t newKeyframeIndex = history.size() - 1;        // Find the first keyframe that's in the "future".        // Searching backwards is more efficient here.        for (size_t historyIndex = history.size() - 1; historyIndex >= historyStart; historyIndex--) {            if (timePoint > history[historyIndex].first) {                if (historyIndex < history.size() - 1) {                    oldKeyframeIndex = historyIndex;                    newKeyframeIndex = historyIndex + 1;                    break;                } else {                    // There are no past entries yet.                    oldKeyframeIndex = historyIndex;                    newKeyframeIndex = historyIndex;                    break;                }            }        }         const std::pair<quint64, JointData> &oldKeyframe{ history[oldKeyframeIndex] };        const std::pair<quint64, JointData> &newKeyframe{ history[newKeyframeIndex] };         quint64 timeDiff = timePoint >= oldKeyframe.first ? timePoint - oldKeyframe.first : 0;        // We need to avoid division by zero:        const float theta = std::min(1.0f, static_cast<float>(timeDiff) /            static_cast<float>(std::max(newKeyframe.first - oldKeyframe.first, 1ULL)));        _jointData[i].rotation = glm::slerp(oldKeyframe.second.rotation, newKeyframe.second.rotation, theta);        _jointData[i].translation = glm::mix(oldKeyframe.second.translation, newKeyframe.second.translation, theta);    } finishRigSetup:    glm::mat4 rootTransform = glm::scale(_skeletonModel->getScale()) * glm::translate(_skeletonModel->getOffset());    _skeletonModel->getRig().copyJointsFromJointData(_jointData);    _skeletonModel->getRig().computeExternalPoses(rootTransform);    locationChanged(); // joints changed, so if there are any children, update them.    relayJointDataToChildren();} void OtherAvatar::simulate(float deltaTime, bool inView) {    PROFILE_RANGE(simulation, "simulate");     if (!_lerpServerPosition) { _lerpServerPosition = _serverPosition; }     // TODO: This is wrong (exponential ease-out rather than linear),    // but looks nicer than low-FPS jittering. The correct solution    // would be to have a "position changed" field and use a similar    // snapshot-interpolation mechanism to what joint interpolations use.    _lerpServerPosition = glm::mix(*_lerpServerPosition, _serverPosition, std::min(deltaTime * 10.0f, 1.0f));     _globalPosition = *_lerpServerPosition;    if (!hasParent()) {        setLocalPosition(_globalPosition);    }     _simulationRate.increment();    if (inView) {        _simulationInViewRate.increment();    }     PerformanceTimer perfTimer("simulate");    {        PROFILE_RANGE(simulation, "updateJoints");        if (inView) {            Head* head = getHead();            if (_hasNewJointData) {                quint64 currentTime = usecTimestampNow();                 QReadLocker readLock(&_jointDataLock);                Q_ASSERT(_hasNewJointDataVec.size() == static_cast<size_t>(_jointData.size()));                // Reset joint history if joint count changed.                if (_jointHistory.size() != static_cast<size_t>(_jointData.size())) {                    _jointHistory.clear();                    _jointHistory.resize(_jointData.size());                    for (size_t i = 0; i < _jointHistory.size(); i++) {                        _jointHistory[i].push_back({currentTime, _jointData[i]});                    }                    qDebug() << "clearing joint history";                }                 for (size_t i = 0; i < _hasNewJointDataVec.size(); i++) {                    if (_hasNewJointDataVec[i]) {                        _hasNewJointDataVec[i] = false;                        _jointHistory[i].push_back({currentTime, _jointData[i]});                        // Cleanup old data and shorten the vector.                        if (_jointHistory[i].size() > OTHER_AVATAR_JOINT_HISTORY_SIZE) {                            for (size_t historyIndex = 0; historyIndex < OTHER_AVATAR_JOINT_HISTORY_LOOK_BACK; historyIndex++) {                                _jointHistory[i][historyIndex] = _jointHistory[i][static_cast<qsizetype>(historyIndex + _jointHistory[i].size() - OTHER_AVATAR_JOINT_HISTORY_LOOK_BACK)];                            }                            _jointHistory[i].resize(OTHER_AVATAR_JOINT_HISTORY_LOOK_BACK);                        }                    }                }                 _jointDataSimulationRate.increment();                _hasNewJointData = false;            }             glm::vec3 headPosition = getWorldPosition();            if (!_skeletonModel->getHeadPosition(headPosition)) {                headPosition = getWorldPosition();            }            head->setPosition(headPosition);            head->setScale(getModelScale());            head->simulate(deltaTime);            _skeletonModel->simulate(deltaTime, true);        } 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();    }     interpolateJoints();     // 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.0f - (1.0f - _displayNameAlpha) * coef;        }        _displayNameAlpha = glm::abs(_displayNameAlpha - _displayNameTargetAlpha) < 0.01f ? _displayNameTargetAlpha : _displayNameAlpha;    }     {        PROFILE_RANGE(simulation, "misc");        measureMotionDerivatives(deltaTime);        updatePalms();    }    {        PROFILE_RANGE(simulation, "entities");        handleChangedAvatarEntityData();        updateAttachedAvatarEntities();    }     {        PROFILE_RANGE(simulation, "grabs");        applyGrabChanges();    }} void OtherAvatar::debugJointData() const {    // Get a copy of the joint data    auto jointData = getJointData();    auto skeletonData = getSkeletonData();    if ((int)skeletonData.size() == jointData.size() && jointData.size() != 0) {        const vec4 RED(1.0f, 0.0f, 0.0f, 1.0f);        const vec4 GREEN(0.0f, 1.0f, 0.0f, 1.0f);        const vec4 BLUE(0.0f, 0.0f, 1.0f, 1.0f);        const vec4 LIGHT_RED(1.0f, 0.5f, 0.5f, 1.0f);        const vec4 LIGHT_GREEN(0.5f, 1.0f, 0.5f, 1.0f);        const vec4 LIGHT_BLUE(0.5f, 0.5f, 1.0f, 1.0f);        const vec4 GREY(0.3f, 0.3f, 0.3f, 1.0f);        const vec4 WHITE(1.0f, 1.0f, 1.0f, 1.0f);        const float AXIS_LENGTH = 0.1f;            AnimPoseVec absoluteJointPoses;        AnimPose rigToAvatar = AnimPose(Quaternions::Y_180 * getWorldOrientation(), getWorldPosition());        bool drawBones = false;        for (int i = 0; i < jointData.size(); i++) {            float jointScale = skeletonData[i].defaultScale * getTargetScale() * METERS_PER_CENTIMETER;            auto absoluteRotation = jointData[i].rotationIsDefaultPose ? skeletonData[i].defaultRotation : jointData[i].rotation;            auto localJointTranslation = jointScale * (jointData[i].translationIsDefaultPose ? skeletonData[i].defaultTranslation : jointData[i].translation);            bool isHips = skeletonData[i].jointName == "Hips";            if (isHips) {                 localJointTranslation = glm::vec3(0.0f);                drawBones = true;            }            AnimPose absoluteParentPose;            int parentIndex = skeletonData[i].parentIndex;            if (parentIndex != -1 && parentIndex < (int)absoluteJointPoses.size()) {                absoluteParentPose = absoluteJointPoses[parentIndex];            }            AnimPose absoluteJointPose = AnimPose(absoluteRotation, absoluteParentPose.trans() + absoluteParentPose.rot() * localJointTranslation);            auto jointPose = rigToAvatar * absoluteJointPose;            auto parentPose = rigToAvatar * absoluteParentPose;            if (drawBones) {                glm::vec3 xAxis = jointPose.rot() * Vectors::UNIT_X;                glm::vec3 yAxis = jointPose.rot() * Vectors::UNIT_Y;                glm::vec3 zAxis = jointPose.rot() * Vectors::UNIT_Z;                 DebugDraw::getInstance().drawRay(jointPose.trans(), jointPose.trans() + AXIS_LENGTH * xAxis, jointData[i].rotationIsDefaultPose ? LIGHT_RED : RED);

Lines 1–500 of 768.