diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index c67e998dd4..3ca924b007 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -870,8 +870,8 @@ AvatarMixerClientData* AvatarMixer::getOrCreateClientData(SharedNodePointer node node->setLinkedData(std::unique_ptr { new AvatarMixerClientData(node->getUUID()) }); clientData = dynamic_cast(node->getLinkedData()); auto& avatar = clientData->getAvatar(); - avatar.setDomainMinimumScale(_domainMinimumScale); - avatar.setDomainMaximumScale(_domainMaximumScale); + avatar.setDomainMinimumHeight(_domainMinimumHeight); + avatar.setDomainMaximumHeight(_domainMaximumHeight); } return clientData; @@ -939,21 +939,21 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) { const QString AVATARS_SETTINGS_KEY = "avatars"; - static const QString MIN_SCALE_OPTION = "min_avatar_scale"; - float settingMinScale = domainSettings[AVATARS_SETTINGS_KEY].toObject()[MIN_SCALE_OPTION].toDouble(MIN_AVATAR_SCALE); - _domainMinimumScale = glm::clamp(settingMinScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE); + static const QString MIN_HEIGHT_OPTION = "min_avatar_height"; + float settingMinHeight = domainSettings[AVATARS_SETTINGS_KEY].toObject()[MIN_HEIGHT_OPTION].toDouble(MIN_AVATAR_HEIGHT); + _domainMinimumHeight = glm::clamp(settingMinHeight, MIN_AVATAR_HEIGHT, MAX_AVATAR_HEIGHT); - static const QString MAX_SCALE_OPTION = "max_avatar_scale"; - float settingMaxScale = domainSettings[AVATARS_SETTINGS_KEY].toObject()[MAX_SCALE_OPTION].toDouble(MAX_AVATAR_SCALE); - _domainMaximumScale = glm::clamp(settingMaxScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE); + static const QString MAX_HEIGHT_OPTION = "max_avatar_height"; + float settingMaxHeight = domainSettings[AVATARS_SETTINGS_KEY].toObject()[MAX_HEIGHT_OPTION].toDouble(MAX_AVATAR_HEIGHT); + _domainMaximumHeight = glm::clamp(settingMaxHeight, MIN_AVATAR_HEIGHT, MAX_AVATAR_HEIGHT); // make sure that the domain owner didn't flip min and max - if (_domainMinimumScale > _domainMaximumScale) { - std::swap(_domainMinimumScale, _domainMaximumScale); + if (_domainMinimumHeight > _domainMaximumHeight) { + std::swap(_domainMinimumHeight, _domainMaximumHeight); } - qCDebug(avatars) << "This domain requires a minimum avatar scale of" << _domainMinimumScale - << "and a maximum avatar scale of" << _domainMaximumScale; + qCDebug(avatars) << "This domain requires a minimum avatar height of" << _domainMinimumHeight + << "and a maximum avatar height of" << _domainMaximumHeight; const QString AVATAR_WHITELIST_DEFAULT{ "" }; static const QString AVATAR_WHITELIST_OPTION = "avatar_whitelist"; diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index 610da8ad57..cb5f536faa 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -90,8 +90,8 @@ private: float _maxKbpsPerNode = 0.0f; - float _domainMinimumScale { MIN_AVATAR_SCALE }; - float _domainMaximumScale { MAX_AVATAR_SCALE }; + float _domainMinimumHeight { MIN_AVATAR_HEIGHT }; + float _domainMaximumHeight { MAX_AVATAR_HEIGHT }; RateCounter<> _broadcastRate; p_high_resolution_clock::time_point _lastDebugMessage; diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 31f224667c..c6d118a87b 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -1,5 +1,5 @@ { - "version": 2.0, + "version": 2.1, "settings": [ { "name": "label", @@ -1015,20 +1015,20 @@ "assignment-types": [ 1, 2 ], "settings": [ { - "name": "min_avatar_scale", + "name": "min_avatar_height", "type": "double", - "label": "Minimum Avatar Scale", - "help": "Limits the scale of avatars in your domain. Must be at least 0.005.", - "placeholder": 0.25, - "default": 0.25 + "label": "Minimum Avatar Height (meters)", + "help": "Limits the height of avatars in your domain. Must be at least 0.009.", + "placeholder": 0.4, + "default": 0.4 }, { - "name": "max_avatar_scale", + "name": "max_avatar_height", "type": "double", - "label": "Maximum Avatar Scale", - "help": "Limits the scale of avatars in your domain. Cannot be greater than 1000.", - "placeholder": 3.0, - "default": 3.0 + "label": "Maximum Avatar Height (meters)", + "help": "Limits the scale of avatars in your domain. Cannot be greater than 1755.", + "placeholder": 5.2, + "default": 5.2 }, { "name": "avatar_whitelist", diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index a4d3e675aa..05227d35b7 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -304,6 +304,26 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList *wizardCompletedOnce = QVariant(true); } + if (oldVersion < 2.1) { + // convert old avatar scale settings into avatar height. + + const QString AVATAR_MIN_SCALE_KEYPATH = "avatars.min_avatar_scale"; + const QString AVATAR_MAX_SCALE_KEYPATH = "avatars.max_avatar_scale"; + const QString AVATAR_MIN_HEIGHT_KEYPATH = "avatars.min_avatar_height"; + const QString AVATAR_MAX_HEIGHT_KEYPATH = "avatars.max_avatar_height"; + + QVariant* avatarMinScale = _configMap.valueForKeyPath(AVATAR_MIN_SCALE_KEYPATH); + if (avatarMinScale) { + float scale = avatarMinScale->toFloat(); + _configMap.valueForKeyPath(AVATAR_MIN_HEIGHT_KEYPATH, scale * DEFAULT_AVATAR_HEIGHT); + } + + QVariant* avatarMaxScale = _configMap.valueForKeyPath(AVATAR_MAX_SCALE_KEYPATH); + if (avatarMaxScale) { + float scale = avatarMaxScale->toFloat(); + _configMap.valueForKeyPath(AVATAR_MAX_HEIGHT_KEYPATH, scale * DEFAULT_AVATAR_HEIGHT); + } + } // write the current description version to our settings *versionVariant = _descriptionVersion; diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 7bb88b0445..0440d2af3a 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2812,10 +2812,10 @@ static int getEventQueueSize(QThread* thread) { static void dumpEventQueue(QThread* thread) { auto threadData = QThreadData::get2(thread); QMutexLocker locker(&threadData->postEventList.mutex); - qDebug() << "AJT: event list, size =" << threadData->postEventList.size(); + qDebug() << "Event list, size =" << threadData->postEventList.size(); for (auto& postEvent : threadData->postEventList) { QEvent::Type type = (postEvent.event ? postEvent.event->type() : QEvent::None); - qDebug() << "AJT: " << type; + qDebug() << " " << type; } } #endif // DEBUG_EVENT_QUEUE diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index df2089223b..f2ce1963c2 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1799,6 +1799,7 @@ void MyAvatar::postUpdate(float deltaTime, const render::ScenePointer& scene) { _skeletonModel->setCauterizeBoneSet(_headBoneSet); _fstAnimGraphOverrideUrl = _skeletonModel->getGeometry()->getAnimGraphOverrideUrl(); initAnimGraph(); + _isAnimatingScale = true; } if (_enableDebugDrawDefaultPose || _enableDebugDrawAnimPose) { @@ -2161,41 +2162,6 @@ bool findAvatarAvatarPenetration(const glm::vec3 positionA, float radiusA, float // target scale to match the new scale they have chosen. When they leave the domain they will not return to the scale they were // before they entered the limiting domain. -void MyAvatar::clampTargetScaleToDomainLimits() { - // when we're about to change the target scale because the user has asked to increase or decrease their scale, - // we first make sure that we're starting from a target scale that is allowed by the current domain - - auto clampedTargetScale = glm::clamp(_targetScale, _domainMinimumScale, _domainMaximumScale); - - if (clampedTargetScale != _targetScale) { - qCDebug(interfaceapp, "Clamped scale to %f since original target scale %f was not allowed by domain", - (double)clampedTargetScale, (double)_targetScale); - - setTargetScale(clampedTargetScale); - } -} - -void MyAvatar::clampScaleChangeToDomainLimits(float desiredScale) { - auto clampedTargetScale = glm::clamp(desiredScale, _domainMinimumScale, _domainMaximumScale); - - if (clampedTargetScale != desiredScale) { - qCDebug(interfaceapp, "Forcing scale to %f since %f is not allowed by domain", - clampedTargetScale, desiredScale); - } - - setTargetScale(clampedTargetScale); - qCDebug(interfaceapp, "Changed scale to %f", (double)_targetScale); - emit(scaleChanged()); -} - -float MyAvatar::getDomainMinScale() { - return _domainMinimumScale; -} - -float MyAvatar::getDomainMaxScale() { - return _domainMaximumScale; -} - void MyAvatar::setGravity(float gravity) { _characterController.setGravity(gravity); } @@ -2205,70 +2171,58 @@ float MyAvatar::getGravity() { } void MyAvatar::increaseSize() { - // make sure we're starting from an allowable scale - clampTargetScaleToDomainLimits(); + float minScale = getDomainMinScale(); + float maxScale = getDomainMaxScale(); - // calculate what our new scale should be - float updatedTargetScale = _targetScale * (1.0f + SCALING_RATIO); + float clampedTargetScale = glm::clamp(_targetScale, minScale, maxScale); + float newTargetScale = glm::clamp(clampedTargetScale * (1.0f + SCALING_RATIO), minScale, maxScale); - // attempt to change to desired scale (clamped to the domain limits) - clampScaleChangeToDomainLimits(updatedTargetScale); + setTargetScale(newTargetScale); } void MyAvatar::decreaseSize() { - // make sure we're starting from an allowable scale - clampTargetScaleToDomainLimits(); + float minScale = getDomainMinScale(); + float maxScale = getDomainMaxScale(); - // calculate what our new scale should be - float updatedTargetScale = _targetScale * (1.0f - SCALING_RATIO); + float clampedTargetScale = glm::clamp(_targetScale, minScale, maxScale); + float newTargetScale = glm::clamp(clampedTargetScale * (1.0f - SCALING_RATIO), minScale, maxScale); - // attempt to change to desired scale (clamped to the domain limits) - clampScaleChangeToDomainLimits(updatedTargetScale); + setTargetScale(newTargetScale); } void MyAvatar::resetSize() { // attempt to reset avatar size to the default (clamped to domain limits) const float DEFAULT_AVATAR_SCALE = 1.0f; - - clampScaleChangeToDomainLimits(DEFAULT_AVATAR_SCALE); + setTargetScale(DEFAULT_AVATAR_SCALE); } void MyAvatar::restrictScaleFromDomainSettings(const QJsonObject& domainSettingsObject) { - // pull out the minimum and maximum scale and set them to restrict our scale + // pull out the minimum and maximum height and set them to restrict our scale static const QString AVATAR_SETTINGS_KEY = "avatars"; auto avatarsObject = domainSettingsObject[AVATAR_SETTINGS_KEY].toObject(); - static const QString MIN_SCALE_OPTION = "min_avatar_scale"; - float settingMinScale = avatarsObject[MIN_SCALE_OPTION].toDouble(MIN_AVATAR_SCALE); - setDomainMinimumScale(settingMinScale); + static const QString MIN_HEIGHT_OPTION = "min_avatar_height"; + float settingMinHeight = avatarsObject[MIN_HEIGHT_OPTION].toDouble(MIN_AVATAR_HEIGHT); + setDomainMinimumHeight(settingMinHeight); - static const QString MAX_SCALE_OPTION = "max_avatar_scale"; - float settingMaxScale = avatarsObject[MAX_SCALE_OPTION].toDouble(MAX_AVATAR_SCALE); - setDomainMaximumScale(settingMaxScale); + static const QString MAX_HEIGHT_OPTION = "max_avatar_height"; + float settingMaxHeight = avatarsObject[MAX_HEIGHT_OPTION].toDouble(MAX_AVATAR_HEIGHT); + setDomainMaximumHeight(settingMaxHeight); // make sure that the domain owner didn't flip min and max - if (_domainMinimumScale > _domainMaximumScale) { - std::swap(_domainMinimumScale, _domainMaximumScale); + if (_domainMinimumHeight > _domainMaximumHeight) { + std::swap(_domainMinimumHeight, _domainMaximumHeight); } // Set avatar current scale Settings settings; settings.beginGroup("Avatar"); _targetScale = loadSetting(settings, "scale", 1.0f); - qCDebug(interfaceapp) << "This domain requires a minimum avatar scale of " << _domainMinimumScale - << " and a maximum avatar scale of " << _domainMaximumScale - << ". Current avatar scale is " << _targetScale; + qCDebug(interfaceapp) << "This domain requires a minimum avatar scale of " << _domainMinimumHeight + << " and a maximum avatar scale of " << _domainMaximumHeight; - // debug to log if this avatar's scale in this domain will be clamped - float clampedScale = glm::clamp(_targetScale, _domainMinimumScale, _domainMaximumScale); - - if (_targetScale != clampedScale) { - qCDebug(interfaceapp) << "Current avatar scale is clamped to " << clampedScale - << " because " << _targetScale << " is not allowed by current domain"; - // The current scale of avatar should not be more than domain's max_avatar_scale and not less than domain's min_avatar_scale . - _targetScale = clampedScale; - } + _isAnimatingScale = true; setModelScale(_targetScale); rebuildCollisionShape(); @@ -2288,8 +2242,8 @@ void MyAvatar::saveAvatarScale() { } void MyAvatar::clearScaleRestriction() { - _domainMinimumScale = MIN_AVATAR_SCALE; - _domainMaximumScale = MAX_AVATAR_SCALE; + _domainMinimumHeight = MIN_AVATAR_HEIGHT; + _domainMaximumHeight = MAX_AVATAR_HEIGHT; } void MyAvatar::goToLocation(const QVariant& propertiesVar) { @@ -3248,6 +3202,7 @@ void MyAvatar::setModelScale(float scale) { if (changed) { float sensorToWorldScale = getEyeHeight() / getUserEyeHeight(); emit sensorToWorldScaleChanged(sensorToWorldScale); + emit scaleChanged(); } } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 563fb7fccd..c3ac84ed0f 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -558,8 +558,6 @@ public slots: void increaseSize(); void decreaseSize(); void resetSize(); - float getDomainMinScale(); - float getDomainMaxScale(); void setGravity(float gravity); float getGravity(); diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index e9cc444bd4..e738ad1c19 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -231,6 +231,9 @@ public: const glm::mat4& getGeometryToRigTransform() const { return _geometryToRigTransform; } + const AnimPose& getModelOffsetPose() const { return _modelOffset; } + const AnimPose& getGeometryOffsetPose() const { return _geometryOffset; } + void setEnableDebugDrawIKTargets(bool enableDebugDrawIKTargets) { _enableDebugDrawIKTargets = enableDebugDrawIKTargets; } void setEnableDebugDrawIKConstraints(bool enableDebugDrawIKConstraints) { _enableDebugDrawIKConstraints = enableDebugDrawIKConstraints; } void setEnableDebugDrawIKChains(bool enableDebugDrawIKChains) { _enableDebugDrawIKChains = enableDebugDrawIKChains; } diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 055a3cf24a..437ac623e3 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -162,6 +162,7 @@ AABox Avatar::getBounds() const { } void Avatar::animateScaleChanges(float deltaTime) { + if (_isAnimatingScale) { float currentScale = getModelScale(); float desiredScale = getDomainLimitedScale(); @@ -172,7 +173,7 @@ void Avatar::animateScaleChanges(float deltaTime) { float animatedScale = (1.0f - blendFactor) * currentScale + blendFactor * desiredScale; // snap to the end when we get close enough - const float MIN_RELATIVE_ERROR = 0.03f; + const float MIN_RELATIVE_ERROR = 0.001f; float relativeError = fabsf(desiredScale - currentScale) / desiredScale; if (relativeError < MIN_RELATIVE_ERROR) { animatedScale = desiredScale; @@ -698,6 +699,7 @@ void Avatar::fixupModelsInScene(const render::ScenePointer& scene) { _skeletonModel->removeFromScene(scene, transaction); _skeletonModel->addToScene(scene, transaction); canTryFade = true; + _isAnimatingScale = true; } for (auto attachmentModel : _attachmentModels) { if (attachmentModel->isRenderable() && attachmentModel->needsFixupInScene()) { @@ -1195,6 +1197,8 @@ void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { void Avatar::setModelURLFinished(bool success) { invalidateJointIndicesCache(); + _isAnimatingScale = true; + if (!success && _skeletonModelURL != AvatarData::defaultFullAvatarModelUrl()) { const int MAX_SKELETON_DOWNLOAD_ATTEMPTS = 4; // NOTE: we don't want to be as generous as ResourceCache is, we only want 4 attempts if (_skeletonModel->getResourceDownloadAttemptsRemaining() <= 0 || @@ -1588,45 +1592,80 @@ float Avatar::getEyeHeight() const { return result; } + return getModelScale() * getUnscaledEyeHeight(); +} + +float Avatar::getUnscaledEyeHeight() const { + float skeletonHeight = getUnscaledEyeHeightFromSkeleton(); + + // Sanity check by looking at the model extents. + Extents meshExtents = _skeletonModel->getUnscaledMeshExtents(); + float meshHeight = meshExtents.size().y; + + // if we determine the mesh is much larger then the skeleton, then we use the mesh height instead. + // This helps prevent absurdly large avatars from exceeding the domain height limit. + const float MESH_SLOP_RATIO = 1.5f; + if (meshHeight > skeletonHeight * MESH_SLOP_RATIO) { + return meshHeight; + } else { + return skeletonHeight; + } +} + +float Avatar::getUnscaledEyeHeightFromSkeleton() const { + // TODO: if performance becomes a concern we can cache this value rather then computing it everytime. - // Makes assumption that the y = 0 plane in geometry is the ground plane. - // We also make that assumption in Rig::computeAvatarBoundingCapsule() - float avatarScale = getModelScale(); + if (_skeletonModel) { auto& rig = _skeletonModel->getRig(); + + // Normally the model offset transform will contain the avatar scale factor, we explicitly remove it here. + AnimPose modelOffsetWithoutAvatarScale(glm::vec3(1.0f), rig.getModelOffsetPose().rot(), rig.getModelOffsetPose().trans()); + AnimPose geomToRigWithoutAvatarScale = modelOffsetWithoutAvatarScale * rig.getGeometryOffsetPose(); + + // This factor can be used to scale distances in the geometry frame into the unscaled rig frame. + // Typically it will be the unit conversion from cm to m. + float scaleFactor = geomToRigWithoutAvatarScale.scale().x; // in practice this always a uniform scale factor. + int headTopJoint = rig.indexOfJoint("HeadTop_End"); int headJoint = rig.indexOfJoint("Head"); int eyeJoint = rig.indexOfJoint("LeftEye") != -1 ? rig.indexOfJoint("LeftEye") : rig.indexOfJoint("RightEye"); int toeJoint = rig.indexOfJoint("LeftToeBase") != -1 ? rig.indexOfJoint("LeftToeBase") : rig.indexOfJoint("RightToeBase"); + + // Makes assumption that the y = 0 plane in geometry is the ground plane. + // We also make that assumption in Rig::computeAvatarBoundingCapsule() + const float GROUND_Y = 0.0f; + + // Values from the skeleton are in the geometry coordinate frame. + auto skeleton = rig.getAnimSkeleton(); if (eyeJoint >= 0 && toeJoint >= 0) { - // measure from eyes to toes. - float eyeHeight = rig.getAbsoluteDefaultPose(eyeJoint).trans().y - rig.getAbsoluteDefaultPose(toeJoint).trans().y; - return eyeHeight; + // Measure from eyes to toes. + float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - skeleton->getAbsoluteDefaultPose(toeJoint).trans().y; + return scaleFactor * eyeHeight; } else if (eyeJoint >= 0) { - // measure eyes to y = 0 plane. - float groundHeight = transformPoint(rig.getGeometryToRigTransform(), glm::vec3(0.0f)).y; - float eyeHeight = rig.getAbsoluteDefaultPose(eyeJoint).trans().y - groundHeight; - return eyeHeight; + // Measure Eye joint to y = 0 plane. + float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - GROUND_Y; + return scaleFactor * eyeHeight; } else if (headTopJoint >= 0 && toeJoint >= 0) { - // measure toe to top of head. Note: default poses already include avatar scale factor + // Measure from ToeBase joint to HeadTop_End joint, then remove forehead distance. const float ratio = DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD / DEFAULT_AVATAR_HEIGHT; - float height = rig.getAbsoluteDefaultPose(headTopJoint).trans().y - rig.getAbsoluteDefaultPose(toeJoint).trans().y; - return height - height * ratio; + float height = skeleton->getAbsoluteDefaultPose(headTopJoint).trans().y - skeleton->getAbsoluteDefaultPose(toeJoint).trans().y; + return scaleFactor * (height - height * ratio); } else if (headTopJoint >= 0) { + // Measure from HeadTop_End joint to the ground, then remove forehead distance. const float ratio = DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD / DEFAULT_AVATAR_HEIGHT; - float groundHeight = transformPoint(rig.getGeometryToRigTransform(), glm::vec3(0.0f)).y; - float headHeight = rig.getAbsoluteDefaultPose(headTopJoint).trans().y - groundHeight; - return headHeight - headHeight * ratio; + float headHeight = skeleton->getAbsoluteDefaultPose(headTopJoint).trans().y - GROUND_Y; + return scaleFactor * (headHeight - headHeight * ratio); } else if (headJoint >= 0) { - float groundHeight = transformPoint(rig.getGeometryToRigTransform(), glm::vec3(0.0f)).y; + // Measure Head joint to the ground, then add in distance from neck to eye. const float DEFAULT_AVATAR_NECK_TO_EYE = DEFAULT_AVATAR_NECK_TO_TOP_OF_HEAD - DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD; const float ratio = DEFAULT_AVATAR_NECK_TO_EYE / DEFAULT_AVATAR_NECK_HEIGHT; - float neckHeight = rig.getAbsoluteDefaultPose(headJoint).trans().y - groundHeight; - return neckHeight + neckHeight * ratio; + float neckHeight = skeleton->getAbsoluteDefaultPose(headJoint).trans().y - GROUND_Y; + return scaleFactor * (neckHeight + neckHeight * ratio); } else { - return avatarScale * DEFAULT_AVATAR_EYE_HEIGHT; + return DEFAULT_AVATAR_EYE_HEIGHT; } } else { - return avatarScale * DEFAULT_AVATAR_EYE_HEIGHT; + return DEFAULT_AVATAR_EYE_HEIGHT; } } diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index a723a8e1b0..44d4ef4c51 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -255,12 +255,16 @@ public: bool isFading() const { return _isFading; } void updateFadingStatus(render::ScenePointer scene); - /**jsdoc - * Provides read only access to the current eye height of the avatar. - * @function Avatar.getEyeHeight - * @returns {number} eye height of avatar in meters - */ - Q_INVOKABLE float getEyeHeight() const; + Q_INVOKABLE virtual float getEyeHeight() const override; + + // returns eye height of avatar in meters, ignoreing avatar scale. + // if _targetScale is 1 then this will be identical to getEyeHeight; + virtual float getUnscaledEyeHeight() const override; + + // returns true, if an acurate eye height estimage can be obtained by inspecting the avatar model skeleton and geometry, + // not all subclasses of AvatarData have access to this data. + virtual bool canMeasureEyeHeight() const override { return true; } + virtual float getModelScale() const { return _modelScale; } virtual void setModelScale(float scale) { _modelScale = scale; } @@ -279,6 +283,7 @@ public slots: void setModelURLFinished(bool success); protected: + float getUnscaledEyeHeightFromSkeleton() const; virtual const QString& getSessionDisplayNameForTransport() const override { return _empty; } // Save a tiny bit of bandwidth. Mixer won't look at what we send. QString _empty{}; virtual void maybeUpdateSessionDisplayNameFromTransport(const QString& sessionDisplayName) override { _sessionDisplayName = sessionDisplayName; } // don't use no-op setter! @@ -349,7 +354,7 @@ protected: RateCounter<> _skeletonModelSimulationRate; RateCounter<> _jointDataSimulationRate; -private: +protected: class AvatarEntityDataHash { public: AvatarEntityDataHash(uint32_t h) : hash(h) {}; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index f68dce8d26..f2053e29d7 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -117,6 +117,55 @@ void AvatarData::setTargetScale(float targetScale) { } } +float AvatarData::getDomainLimitedScale() const { + if (canMeasureEyeHeight()) { + const float minScale = getDomainMinScale(); + const float maxScale = getDomainMaxScale(); + return glm::clamp(_targetScale, minScale, maxScale); + } else { + // We can't make a good estimate. + return _targetScale; + } +} + +void AvatarData::setDomainMinimumHeight(float domainMinimumHeight) { + _domainMinimumHeight = glm::clamp(domainMinimumHeight, MIN_AVATAR_HEIGHT, MAX_AVATAR_HEIGHT); +} + +void AvatarData::setDomainMaximumHeight(float domainMaximumHeight) { + _domainMaximumHeight = glm::clamp(domainMaximumHeight, MIN_AVATAR_HEIGHT, MAX_AVATAR_HEIGHT); +} + +float AvatarData::getDomainMinScale() const { + float unscaledHeight = getUnscaledHeight(); + const float EPSILON = 1.0e-4f; + if (unscaledHeight <= EPSILON) { + unscaledHeight = DEFAULT_AVATAR_HEIGHT; + } + return _domainMinimumHeight / unscaledHeight; +} + +float AvatarData::getDomainMaxScale() const { + float unscaledHeight = getUnscaledHeight(); + const float EPSILON = 1.0e-4f; + if (unscaledHeight <= EPSILON) { + unscaledHeight = DEFAULT_AVATAR_HEIGHT; + } + return _domainMaximumHeight / unscaledHeight; +} + +float AvatarData::getUnscaledHeight() const { + const float eyeHeight = getUnscaledEyeHeight(); + const float ratio = eyeHeight / DEFAULT_AVATAR_HEIGHT; + return eyeHeight + ratio * DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD; +} + +float AvatarData::getHeight() const { + const float eyeHeight = getEyeHeight(); + const float ratio = eyeHeight / DEFAULT_AVATAR_HEIGHT; + return eyeHeight + ratio * DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD; +} + glm::vec3 AvatarData::getHandPosition() const { return getWorldOrientation() * _handPosition + getWorldPosition(); } diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 1df0e94496..de98675733 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -35,6 +35,7 @@ #include #include +#include #include #include #include @@ -257,9 +258,6 @@ namespace AvatarDataPacket { size_t maxJointDataSize(size_t numJoints); } -static const float MAX_AVATAR_SCALE = 1000.0f; -static const float MIN_AVATAR_SCALE = .005f; - const float MAX_AUDIO_LOUDNESS = 1000.0f; // close enough for mouth animation const int AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS = 1000; @@ -484,12 +482,38 @@ public: // Scale virtual void setTargetScale(float targetScale); - float getDomainLimitedScale() const { return glm::clamp(_targetScale, _domainMinimumScale, _domainMaximumScale); } + float getDomainLimitedScale() const; + float getDomainMinScale() const; + float getDomainMaxScale() const; - void setDomainMinimumScale(float domainMinimumScale) - { _domainMinimumScale = glm::clamp(domainMinimumScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE); _scaleChanged = usecTimestampNow(); } - void setDomainMaximumScale(float domainMaximumScale) - { _domainMaximumScale = glm::clamp(domainMaximumScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE); _scaleChanged = usecTimestampNow(); } + // returns eye height of avatar in meters, ignoreing avatar scale. + // if _targetScale is 1 then this will be identical to getEyeHeight; + virtual float getUnscaledEyeHeight() const { return DEFAULT_AVATAR_EYE_HEIGHT; } + + // returns true, if an acurate eye height estimage can be obtained by inspecting the avatar model skeleton and geometry, + // not all subclasses of AvatarData have access to this data. + virtual bool canMeasureEyeHeight() const { return false; } + + /**jsdoc + * Provides read only access to the current eye height of the avatar. + * This height is only an estimate and might be incorrect for avatars that are missing standard joints. + * @function AvatarData.getEyeHeight + * @returns {number} eye height of avatar in meters + */ + Q_INVOKABLE virtual float getEyeHeight() const { return _targetScale * getUnscaledEyeHeight(); } + + /**jsdoc + * Provides read only access to the current height of the avatar. + * This height is only an estimate and might be incorrect for avatars that are missing standard joints. + * @function AvatarData.getHeight + * @returns {number} height of avatar in meters + */ + Q_INVOKABLE virtual float getHeight() const; + + float getUnscaledHeight() const; + + void setDomainMinimumHeight(float domainMinimumHeight); + void setDomainMaximumHeight(float domainMaximumHeight); // Hand State Q_INVOKABLE void setHandState(char s) { _handState = s; } @@ -698,8 +722,8 @@ protected: // Body scale float _targetScale; - float _domainMinimumScale { MIN_AVATAR_SCALE }; - float _domainMaximumScale { MAX_AVATAR_SCALE }; + float _domainMinimumHeight { MIN_AVATAR_HEIGHT }; + float _domainMaximumHeight { MAX_AVATAR_HEIGHT }; // Hand state (are we grabbing something or not) char _handState; diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index fa5028e1bb..207ddf6bbb 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -74,6 +74,8 @@ PacketVersion versionForPacketType(PacketType packetType) { return static_cast(AudioVersion::HighDynamicRangeVolume); case PacketType::ICEPing: return static_cast(IcePingVersion::SendICEPeerID); + case PacketType::DomainSettings: + return 18; // replace min_avatar_scale and max_avatar_scale with min_avatar_height and max_avatar_height default: return 17; } diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 428fcc7a54..199cb29f53 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -163,7 +163,7 @@ void Model::setScale(const glm::vec3& scale) { _scaledToFit = false; } -const float SCALE_CHANGE_EPSILON = 0.01f; +const float SCALE_CHANGE_EPSILON = 0.001f; void Model::setScaleInternal(const glm::vec3& scale) { if (glm::distance(_scale, scale) > SCALE_CHANGE_EPSILON) { diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index c537a928b3..fadd44b745 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -204,6 +204,9 @@ public: /// Returns the extents of the model's mesh Extents getMeshExtents() const; + /// Returns the unscaled extents of the model's mesh + Extents getUnscaledMeshExtents() const; + void setTranslation(const glm::vec3& translation); void setRotation(const glm::quat& rotation); void setTransformNoUpdateRenderItems(const Transform& transform); // temporary HACK @@ -276,9 +279,6 @@ protected: void setBlendshapeCoefficients(const QVector& coefficients) { _blendshapeCoefficients = coefficients; } const QVector& getBlendshapeCoefficients() const { return _blendshapeCoefficients; } - /// Returns the unscaled extents of the model's mesh - Extents getUnscaledMeshExtents() const; - /// Clear the joint states void clearJointState(int index); diff --git a/libraries/shared/src/AvatarConstants.h b/libraries/shared/src/AvatarConstants.h index a7a80471be..4942c63e27 100644 --- a/libraries/shared/src/AvatarConstants.h +++ b/libraries/shared/src/AvatarConstants.h @@ -12,6 +12,8 @@ #ifndef hifi_AvatarConstants_h #define hifi_AvatarConstants_h +#include "GLMHelpers.h" + // 50th Percentile Man const float DEFAULT_AVATAR_HEIGHT = 1.755f; // meters const float DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD = 0.11f; // meters @@ -52,5 +54,10 @@ const float DEFAULT_AVATAR_JUMP_HEIGHT = (DEFAULT_AVATAR_JUMP_SPEED * DEFAULT_AV const float DEFAULT_AVATAR_FALL_HEIGHT = 20.0f; // meters const float DEFAULT_AVATAR_MIN_HOVER_HEIGHT = 2.5f; // meters +static const float MAX_AVATAR_SCALE = 1000.0f; +static const float MIN_AVATAR_SCALE = 0.005f; + +static const float MAX_AVATAR_HEIGHT = 1000.0f * DEFAULT_AVATAR_HEIGHT; // meters +static const float MIN_AVATAR_HEIGHT = 0.005f * DEFAULT_AVATAR_HEIGHT; // meters #endif // hifi_AvatarConstants_h