// // 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 #include #include #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); 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(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()->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()->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.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()->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()->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& 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::simulate(float deltaTime, bool inView) { PROFILE_RANGE(simulation, "simulate"); _globalPosition = _transit.isActive() ? _transit.getCurrentPosition() : _serverPosition; if (!hasParent()) { setLocalPosition(_globalPosition); } _simulationRate.increment(); if (inView) { _simulationInViewRate.increment(); } PerformanceTimer perfTimer("simulate"); { PROFILE_RANGE(simulation, "updateJoints"); if (inView) { Head* head = getHead(); if (_hasNewJointData || _transit.isActive()) { _skeletonModel->getRig().copyJointsFromJointData(_jointData); glm::mat4 rootTransform = glm::scale(_skeletonModel->getScale()) * glm::translate(_skeletonModel->getOffset()); _skeletonModel->getRig().computeExternalPoses(rootTransform); _jointDataSimulationRate.increment(); head->simulate(deltaTime); _skeletonModel->simulate(deltaTime, true); locationChanged(); // joints changed, so if there are any children, update them. _hasNewJointData = false; glm::vec3 headPosition = getWorldPosition(); if (!_skeletonModel->getHeadPosition(headPosition)) { headPosition = getWorldPosition(); } head->setPosition(headPosition); } else { head->simulate(deltaTime); _skeletonModel->simulate(deltaTime, false); } head->setScale(getModelScale()); relayJointDataToChildren(); } else { // a non-full update is still required so that the position, rotation, scale and bounds of the skeletonModel are updated. _skeletonModel->simulate(deltaTime, false); } _skeletonModelSimulationRate.increment(); } // update animation for display name fade in/out if ( _displayNameTargetAlpha != _displayNameAlpha) { // the alpha function is // Fade out => alpha(t) = factor ^ t => alpha(t+dt) = alpha(t) * factor^(dt) // Fade in => alpha(t) = 1 - factor^t => alpha(t+dt) = 1-(1-alpha(t))*coef^(dt) // factor^(dt) = coef float coef = pow(DISPLAYNAME_FADE_FACTOR, deltaTime); if (_displayNameTargetAlpha < _displayNameAlpha) { // Fading out _displayNameAlpha *= coef; } else { // Fading in _displayNameAlpha = 1.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); DebugDraw::getInstance().drawRay(jointPose.trans(), jointPose.trans() + AXIS_LENGTH * yAxis, jointData[i].rotationIsDefaultPose ? LIGHT_GREEN : GREEN); DebugDraw::getInstance().drawRay(jointPose.trans(), jointPose.trans() + AXIS_LENGTH * zAxis, jointData[i].rotationIsDefaultPose ? LIGHT_BLUE : BLUE); if (!isHips) { DebugDraw::getInstance().drawRay(jointPose.trans(), parentPose.trans(), jointData[i].translationIsDefaultPose ? WHITE : GREY); } } absoluteJointPoses.push_back(absoluteJointPose); } } } void OtherAvatar::handleChangedAvatarEntityData() { PerformanceTimer perfTimer("avatarEntities"); // AVATAR ENTITY UPDATE FLOW // - if queueEditEntityMessage() sees "AvatarEntity" HostType it calls _myAvatar->storeAvatarEntityDataPayload() // - storeAvatarEntityDataPayload() saves the payload and flags the trait instance for the entity as updated, // - ClientTraitsHandler::sendChangedTraitsToMixer() sends the entity bytes to the mixer which relays them to other interfaces // - AvatarHashMap::processBulkAvatarTraits() on other interfaces calls avatar->processTraitInstance() // - AvatarData::processTraitInstance() calls storeAvatarEntityDataPayload(), which sets _avatarEntityDataChanged = true // - (My)Avatar::simulate() calls handleChangedAvatarEntityData() every frame which checks _avatarEntityDataChanged // and here we are... // AVATAR ENTITY DELETE FLOW // - EntityScriptingInterface::deleteEntity() calls _myAvatar->clearAvatarEntity() for deleted avatar entities // - clearAvatarEntity() removes the avatar entity and flags the trait instance for the entity as deleted // - ClientTraitsHandler::sendChangedTraitsToMixer() sends a deletion to the mixer which relays to other interfaces // - AvatarHashMap::processBulkAvatarTraits() on other interfaces calls avatar->processDeletedTraitInstace() // - AvatarData::processDeletedTraitInstance() calls clearAvatarEntity() // - AvatarData::clearAvatarEntity() sets _avatarEntityDataChanged = true and adds the ID to the detached list // - (My)Avatar::simulate() calls handleChangedAvatarEntityData() every frame which checks _avatarEntityDataChanged // and here we are... if (!_avatarEntityDataChanged) { return; } auto treeRenderer = DependencyManager::get(); EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; if (!entityTree) { return; } PackedAvatarEntityMap packedAvatarEntityData; _avatarEntitiesLock.withReadLock([&] { packedAvatarEntityData = _packedAvatarEntityData; }); entityTree->withWriteLock([&] { AvatarEntityMap::const_iterator dataItr = packedAvatarEntityData.begin(); while (dataItr != packedAvatarEntityData.end()) { // compute hash of data. TODO? cache this? QByteArray data = dataItr.value(); uint32_t newHash = qHash(data); // check to see if we recognize this hash and whether it was already successfully processed QUuid entityID = dataItr.key(); MapOfAvatarEntityDataHashes::iterator stateItr = _avatarEntityDataHashes.find(entityID); if (stateItr != _avatarEntityDataHashes.end()) { if (stateItr.value().success) { if (newHash == stateItr.value().hash) { // data hasn't changed --> nothing to do ++dataItr; continue; } } else { // NOTE: if the data was unsuccessful in producing an entity in the past // we will try again just in case something changed (unlikely). // Unfortunately constantly trying to build the entity for this data costs // CPU cycles that we'd rather not spend. // TODO? put a maximum number of tries on this? } } else { // sanity check data QUuid id; EntityTypes::EntityType type; EntityTypes::extractEntityTypeAndID((unsigned char*)(data.data()), data.size(), type, id); if (id != entityID || !EntityTypes::typeIsValid(type)) { // skip for corrupt ++dataItr; continue; } // remember this hash for the future stateItr = _avatarEntityDataHashes.insert(entityID, AvatarEntityDataHash(newHash)); } ++dataItr; EntityItemProperties properties; int32_t bytesLeftToRead = data.size(); unsigned char* dataAt = (unsigned char*)(data.data()); // FIXME: This function will cause unintented changes in SpaillyNestable // E.g overriding the ID index of an exisiting entity to temporary entity // in the following map QHash _children; // Andrew Meadows will address this issue if (!properties.constructFromBuffer(dataAt, bytesLeftToRead)) { // properties are corrupt continue; } properties.setEntityHostType(entity::HostType::AVATAR); properties.setOwningAvatarID(getID()); // there's no entity-server to tell us we're the simulation owner, so always set the // simulationOwner to the owningAvatarID and a high priority. properties.setSimulationOwner(getID(), AVATAR_ENTITY_SIMULATION_PRIORITY); if (properties.getParentID() == AVATAR_SELF_ID) { properties.setParentID(getID()); } // NOTE: if this avatar entity is not attached to us, strip its entity script completely... auto attachedScript = properties.getScript(); if (!isMyAvatar() && !attachedScript.isEmpty()) { QString noScript; properties.setScript(noScript); } auto specifiedHref = properties.getHref(); if (!isMyAvatar() && !specifiedHref.isEmpty()) { qCDebug(avatars) << "removing entity href from avatar attached entity:" << entityID << "old href:" << specifiedHref; QString noHref; properties.setHref(noHref); } // When grabbing avatar entities, they are parented to the joint moving them, then when un-grabbed // they go back to the default parent (null uuid). When un-gripped, others saw the entity disappear. // The thinking here is the local position was noticed as changing, but not the parentID (since it is now // back to the default), and the entity flew off somewhere. Marking all changed definitely fixes this, // and seems safe (per Seth). properties.markAllChanged(); // try to build the entity EntityItemPointer entity = entityTree->findEntityByEntityItemID(EntityItemID(entityID)); bool success = true; if (entity) { QUuid oldParentID = entity->getParentID(); // Since has overwrtiiten the back pointer // from the parent children map (see comment for function call above), // we need to for reset the back pointer in the map correctly by setting the parentID, but // since the parentID of the entity has not changed we first need to set it some ither ID, // then set the the original ID for the changes to take effect // TODO: This is a horrible hack and once properties.constructFromBuffer no longer causes // side effects...remove the following three lines const QUuid NULL_ID = QUuid("{00000000-0000-0000-0000-000000000005}"); entity->setParentID(NULL_ID); entity->setParentID(oldParentID); if (entity->stillHasMyGrab()) { // For this case: we want to ignore transform+velocities coming from authoritative OtherAvatar // because the MyAvatar is grabbing and we expect the local grab state // to have enough information to prevent simulation drift. // // Clever readers might realize this could cause problems. For example, // if an ignored OtherAvatar were to simultanously grab the object then there would be // a noticeable discrepancy between participants in the distributed physics simulation, // however the difference would be stable and would not drift. properties.clearTransformOrVelocityChanges(); } if (entityTree->updateEntity(entityID, properties)) { entity->updateLastEditedFromRemote(); } else { success = false; } if (oldParentID != entity->getParentID()) { if (entity->getParentID() == getID()) { onAddAttachedAvatarEntity(entityID); } else if (oldParentID == getID()) { onRemoveAttachedAvatarEntity(entityID); } } } else { entity = entityTree->addEntity(entityID, properties); if (!entity) { success = false; } else if (entity->getParentID() == getID()) { onAddAttachedAvatarEntity(entityID); } } stateItr.value().success = success; if (success) { stateItr.value().hash = newHash; } else { stateItr.value().hash = 0; } } AvatarEntityIDs recentlyRemovedAvatarEntities = getAndClearRecentlyRemovedIDs(); if (!recentlyRemovedAvatarEntities.empty()) { // only lock this thread when absolutely necessary AvatarEntityMap packedAvatarEntityData; _avatarEntitiesLock.withReadLock([&] { packedAvatarEntityData = _packedAvatarEntityData; }); if (!recentlyRemovedAvatarEntities.empty()) { std::vector idsToDelete; idsToDelete.reserve(recentlyRemovedAvatarEntities.size()); foreach (auto entityID, recentlyRemovedAvatarEntities) { if (!packedAvatarEntityData.contains(entityID)) { idsToDelete.push_back(entityID); } } if (!idsToDelete.empty()) { bool force = true; bool ignoreWarnings = true; entityTree->deleteEntitiesByID(idsToDelete, force, ignoreWarnings); } } // TODO: move this outside of tree lock // remove stale data hashes foreach (auto entityID, recentlyRemovedAvatarEntities) { MapOfAvatarEntityDataHashes::iterator stateItr = _avatarEntityDataHashes.find(entityID); if (stateItr != _avatarEntityDataHashes.end()) { _avatarEntityDataHashes.erase(stateItr); } onRemoveAttachedAvatarEntity(entityID); } } if (packedAvatarEntityData.size() != _avatarEntityForRecording.size()) { createRecordingIDs(); } }); setAvatarEntityDataChanged(false); } void OtherAvatar::onAddAttachedAvatarEntity(const QUuid& id) { for (uint32_t i = 0; i < _attachedAvatarEntities.size(); ++i) { if (_attachedAvatarEntities[i] == id) { return; } } _attachedAvatarEntities.push_back(id); } void OtherAvatar::onRemoveAttachedAvatarEntity(const QUuid& id) { for (uint32_t i = 0; i < _attachedAvatarEntities.size(); ++i) { if (_attachedAvatarEntities[i] == id) { if (i != _attachedAvatarEntities.size() - 1) { _attachedAvatarEntities[i] = _attachedAvatarEntities.back(); } _attachedAvatarEntities.pop_back(); break; } } } void OtherAvatar::updateAttachedAvatarEntities() { if (!_attachedAvatarEntities.empty()) { auto treeRenderer = DependencyManager::get(); if (!treeRenderer) { return; } for (const QUuid& id : _attachedAvatarEntities) { treeRenderer->onEntityChanged(id); } } }