Merge pull request #11865 from hyperlogic/feature/domain-limited-height

domain uses min/max avatar height instead of min/max scale
This commit is contained in:
Andrew Meadows 2017-12-07 16:29:15 -08:00 committed by GitHub
commit 4691a98509
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 246 additions and 144 deletions

View file

@ -870,8 +870,8 @@ AvatarMixerClientData* AvatarMixer::getOrCreateClientData(SharedNodePointer node
node->setLinkedData(std::unique_ptr<NodeData> { new AvatarMixerClientData(node->getUUID()) }); node->setLinkedData(std::unique_ptr<NodeData> { new AvatarMixerClientData(node->getUUID()) });
clientData = dynamic_cast<AvatarMixerClientData*>(node->getLinkedData()); clientData = dynamic_cast<AvatarMixerClientData*>(node->getLinkedData());
auto& avatar = clientData->getAvatar(); auto& avatar = clientData->getAvatar();
avatar.setDomainMinimumScale(_domainMinimumScale); avatar.setDomainMinimumHeight(_domainMinimumHeight);
avatar.setDomainMaximumScale(_domainMaximumScale); avatar.setDomainMaximumHeight(_domainMaximumHeight);
} }
return clientData; return clientData;
@ -939,21 +939,21 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) {
const QString AVATARS_SETTINGS_KEY = "avatars"; const QString AVATARS_SETTINGS_KEY = "avatars";
static const QString MIN_SCALE_OPTION = "min_avatar_scale"; static const QString MIN_HEIGHT_OPTION = "min_avatar_height";
float settingMinScale = domainSettings[AVATARS_SETTINGS_KEY].toObject()[MIN_SCALE_OPTION].toDouble(MIN_AVATAR_SCALE); float settingMinHeight = domainSettings[AVATARS_SETTINGS_KEY].toObject()[MIN_HEIGHT_OPTION].toDouble(MIN_AVATAR_HEIGHT);
_domainMinimumScale = glm::clamp(settingMinScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE); _domainMinimumHeight = glm::clamp(settingMinHeight, MIN_AVATAR_HEIGHT, MAX_AVATAR_HEIGHT);
static const QString MAX_SCALE_OPTION = "max_avatar_scale"; static const QString MAX_HEIGHT_OPTION = "max_avatar_height";
float settingMaxScale = domainSettings[AVATARS_SETTINGS_KEY].toObject()[MAX_SCALE_OPTION].toDouble(MAX_AVATAR_SCALE); float settingMaxHeight = domainSettings[AVATARS_SETTINGS_KEY].toObject()[MAX_HEIGHT_OPTION].toDouble(MAX_AVATAR_HEIGHT);
_domainMaximumScale = glm::clamp(settingMaxScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE); _domainMaximumHeight = glm::clamp(settingMaxHeight, MIN_AVATAR_HEIGHT, MAX_AVATAR_HEIGHT);
// make sure that the domain owner didn't flip min and max // make sure that the domain owner didn't flip min and max
if (_domainMinimumScale > _domainMaximumScale) { if (_domainMinimumHeight > _domainMaximumHeight) {
std::swap(_domainMinimumScale, _domainMaximumScale); std::swap(_domainMinimumHeight, _domainMaximumHeight);
} }
qCDebug(avatars) << "This domain requires a minimum avatar scale of" << _domainMinimumScale qCDebug(avatars) << "This domain requires a minimum avatar height of" << _domainMinimumHeight
<< "and a maximum avatar scale of" << _domainMaximumScale; << "and a maximum avatar height of" << _domainMaximumHeight;
const QString AVATAR_WHITELIST_DEFAULT{ "" }; const QString AVATAR_WHITELIST_DEFAULT{ "" };
static const QString AVATAR_WHITELIST_OPTION = "avatar_whitelist"; static const QString AVATAR_WHITELIST_OPTION = "avatar_whitelist";

View file

@ -90,8 +90,8 @@ private:
float _maxKbpsPerNode = 0.0f; float _maxKbpsPerNode = 0.0f;
float _domainMinimumScale { MIN_AVATAR_SCALE }; float _domainMinimumHeight { MIN_AVATAR_HEIGHT };
float _domainMaximumScale { MAX_AVATAR_SCALE }; float _domainMaximumHeight { MAX_AVATAR_HEIGHT };
RateCounter<> _broadcastRate; RateCounter<> _broadcastRate;
p_high_resolution_clock::time_point _lastDebugMessage; p_high_resolution_clock::time_point _lastDebugMessage;

View file

@ -1,5 +1,5 @@
{ {
"version": 2.0, "version": 2.1,
"settings": [ "settings": [
{ {
"name": "label", "name": "label",
@ -1015,20 +1015,20 @@
"assignment-types": [ 1, 2 ], "assignment-types": [ 1, 2 ],
"settings": [ "settings": [
{ {
"name": "min_avatar_scale", "name": "min_avatar_height",
"type": "double", "type": "double",
"label": "Minimum Avatar Scale", "label": "Minimum Avatar Height (meters)",
"help": "Limits the scale of avatars in your domain. Must be at least 0.005.", "help": "Limits the height of avatars in your domain. Must be at least 0.009.",
"placeholder": 0.25, "placeholder": 0.4,
"default": 0.25 "default": 0.4
}, },
{ {
"name": "max_avatar_scale", "name": "max_avatar_height",
"type": "double", "type": "double",
"label": "Maximum Avatar Scale", "label": "Maximum Avatar Height (meters)",
"help": "Limits the scale of avatars in your domain. Cannot be greater than 1000.", "help": "Limits the scale of avatars in your domain. Cannot be greater than 1755.",
"placeholder": 3.0, "placeholder": 5.2,
"default": 3.0 "default": 5.2
}, },
{ {
"name": "avatar_whitelist", "name": "avatar_whitelist",

View file

@ -304,6 +304,26 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
*wizardCompletedOnce = QVariant(true); *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 // write the current description version to our settings
*versionVariant = _descriptionVersion; *versionVariant = _descriptionVersion;

View file

@ -2812,10 +2812,10 @@ static int getEventQueueSize(QThread* thread) {
static void dumpEventQueue(QThread* thread) { static void dumpEventQueue(QThread* thread) {
auto threadData = QThreadData::get2(thread); auto threadData = QThreadData::get2(thread);
QMutexLocker locker(&threadData->postEventList.mutex); 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) { for (auto& postEvent : threadData->postEventList) {
QEvent::Type type = (postEvent.event ? postEvent.event->type() : QEvent::None); QEvent::Type type = (postEvent.event ? postEvent.event->type() : QEvent::None);
qDebug() << "AJT: " << type; qDebug() << " " << type;
} }
} }
#endif // DEBUG_EVENT_QUEUE #endif // DEBUG_EVENT_QUEUE

View file

@ -1799,6 +1799,7 @@ void MyAvatar::postUpdate(float deltaTime, const render::ScenePointer& scene) {
_skeletonModel->setCauterizeBoneSet(_headBoneSet); _skeletonModel->setCauterizeBoneSet(_headBoneSet);
_fstAnimGraphOverrideUrl = _skeletonModel->getGeometry()->getAnimGraphOverrideUrl(); _fstAnimGraphOverrideUrl = _skeletonModel->getGeometry()->getAnimGraphOverrideUrl();
initAnimGraph(); initAnimGraph();
_isAnimatingScale = true;
} }
if (_enableDebugDrawDefaultPose || _enableDebugDrawAnimPose) { 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 // 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. // 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) { void MyAvatar::setGravity(float gravity) {
_characterController.setGravity(gravity); _characterController.setGravity(gravity);
} }
@ -2205,70 +2171,58 @@ float MyAvatar::getGravity() {
} }
void MyAvatar::increaseSize() { void MyAvatar::increaseSize() {
// make sure we're starting from an allowable scale float minScale = getDomainMinScale();
clampTargetScaleToDomainLimits(); float maxScale = getDomainMaxScale();
// calculate what our new scale should be float clampedTargetScale = glm::clamp(_targetScale, minScale, maxScale);
float updatedTargetScale = _targetScale * (1.0f + SCALING_RATIO); float newTargetScale = glm::clamp(clampedTargetScale * (1.0f + SCALING_RATIO), minScale, maxScale);
// attempt to change to desired scale (clamped to the domain limits) setTargetScale(newTargetScale);
clampScaleChangeToDomainLimits(updatedTargetScale);
} }
void MyAvatar::decreaseSize() { void MyAvatar::decreaseSize() {
// make sure we're starting from an allowable scale float minScale = getDomainMinScale();
clampTargetScaleToDomainLimits(); float maxScale = getDomainMaxScale();
// calculate what our new scale should be float clampedTargetScale = glm::clamp(_targetScale, minScale, maxScale);
float updatedTargetScale = _targetScale * (1.0f - SCALING_RATIO); float newTargetScale = glm::clamp(clampedTargetScale * (1.0f - SCALING_RATIO), minScale, maxScale);
// attempt to change to desired scale (clamped to the domain limits) setTargetScale(newTargetScale);
clampScaleChangeToDomainLimits(updatedTargetScale);
} }
void MyAvatar::resetSize() { void MyAvatar::resetSize() {
// attempt to reset avatar size to the default (clamped to domain limits) // attempt to reset avatar size to the default (clamped to domain limits)
const float DEFAULT_AVATAR_SCALE = 1.0f; const float DEFAULT_AVATAR_SCALE = 1.0f;
setTargetScale(DEFAULT_AVATAR_SCALE);
clampScaleChangeToDomainLimits(DEFAULT_AVATAR_SCALE);
} }
void MyAvatar::restrictScaleFromDomainSettings(const QJsonObject& domainSettingsObject) { 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"; static const QString AVATAR_SETTINGS_KEY = "avatars";
auto avatarsObject = domainSettingsObject[AVATAR_SETTINGS_KEY].toObject(); auto avatarsObject = domainSettingsObject[AVATAR_SETTINGS_KEY].toObject();
static const QString MIN_SCALE_OPTION = "min_avatar_scale"; static const QString MIN_HEIGHT_OPTION = "min_avatar_height";
float settingMinScale = avatarsObject[MIN_SCALE_OPTION].toDouble(MIN_AVATAR_SCALE); float settingMinHeight = avatarsObject[MIN_HEIGHT_OPTION].toDouble(MIN_AVATAR_HEIGHT);
setDomainMinimumScale(settingMinScale); setDomainMinimumHeight(settingMinHeight);
static const QString MAX_SCALE_OPTION = "max_avatar_scale"; static const QString MAX_HEIGHT_OPTION = "max_avatar_height";
float settingMaxScale = avatarsObject[MAX_SCALE_OPTION].toDouble(MAX_AVATAR_SCALE); float settingMaxHeight = avatarsObject[MAX_HEIGHT_OPTION].toDouble(MAX_AVATAR_HEIGHT);
setDomainMaximumScale(settingMaxScale); setDomainMaximumHeight(settingMaxHeight);
// make sure that the domain owner didn't flip min and max // make sure that the domain owner didn't flip min and max
if (_domainMinimumScale > _domainMaximumScale) { if (_domainMinimumHeight > _domainMaximumHeight) {
std::swap(_domainMinimumScale, _domainMaximumScale); std::swap(_domainMinimumHeight, _domainMaximumHeight);
} }
// Set avatar current scale // Set avatar current scale
Settings settings; Settings settings;
settings.beginGroup("Avatar"); settings.beginGroup("Avatar");
_targetScale = loadSetting(settings, "scale", 1.0f); _targetScale = loadSetting(settings, "scale", 1.0f);
qCDebug(interfaceapp) << "This domain requires a minimum avatar scale of " << _domainMinimumScale qCDebug(interfaceapp) << "This domain requires a minimum avatar scale of " << _domainMinimumHeight
<< " and a maximum avatar scale of " << _domainMaximumScale << " and a maximum avatar scale of " << _domainMaximumHeight;
<< ". Current avatar scale is " << _targetScale;
// debug to log if this avatar's scale in this domain will be clamped _isAnimatingScale = true;
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;
}
setModelScale(_targetScale); setModelScale(_targetScale);
rebuildCollisionShape(); rebuildCollisionShape();
@ -2288,8 +2242,8 @@ void MyAvatar::saveAvatarScale() {
} }
void MyAvatar::clearScaleRestriction() { void MyAvatar::clearScaleRestriction() {
_domainMinimumScale = MIN_AVATAR_SCALE; _domainMinimumHeight = MIN_AVATAR_HEIGHT;
_domainMaximumScale = MAX_AVATAR_SCALE; _domainMaximumHeight = MAX_AVATAR_HEIGHT;
} }
void MyAvatar::goToLocation(const QVariant& propertiesVar) { void MyAvatar::goToLocation(const QVariant& propertiesVar) {
@ -3248,6 +3202,7 @@ void MyAvatar::setModelScale(float scale) {
if (changed) { if (changed) {
float sensorToWorldScale = getEyeHeight() / getUserEyeHeight(); float sensorToWorldScale = getEyeHeight() / getUserEyeHeight();
emit sensorToWorldScaleChanged(sensorToWorldScale); emit sensorToWorldScaleChanged(sensorToWorldScale);
emit scaleChanged();
} }
} }

View file

@ -558,8 +558,6 @@ public slots:
void increaseSize(); void increaseSize();
void decreaseSize(); void decreaseSize();
void resetSize(); void resetSize();
float getDomainMinScale();
float getDomainMaxScale();
void setGravity(float gravity); void setGravity(float gravity);
float getGravity(); float getGravity();

View file

@ -231,6 +231,9 @@ public:
const glm::mat4& getGeometryToRigTransform() const { return _geometryToRigTransform; } 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 setEnableDebugDrawIKTargets(bool enableDebugDrawIKTargets) { _enableDebugDrawIKTargets = enableDebugDrawIKTargets; }
void setEnableDebugDrawIKConstraints(bool enableDebugDrawIKConstraints) { _enableDebugDrawIKConstraints = enableDebugDrawIKConstraints; } void setEnableDebugDrawIKConstraints(bool enableDebugDrawIKConstraints) { _enableDebugDrawIKConstraints = enableDebugDrawIKConstraints; }
void setEnableDebugDrawIKChains(bool enableDebugDrawIKChains) { _enableDebugDrawIKChains = enableDebugDrawIKChains; } void setEnableDebugDrawIKChains(bool enableDebugDrawIKChains) { _enableDebugDrawIKChains = enableDebugDrawIKChains; }

View file

@ -162,6 +162,7 @@ AABox Avatar::getBounds() const {
} }
void Avatar::animateScaleChanges(float deltaTime) { void Avatar::animateScaleChanges(float deltaTime) {
if (_isAnimatingScale) { if (_isAnimatingScale) {
float currentScale = getModelScale(); float currentScale = getModelScale();
float desiredScale = getDomainLimitedScale(); float desiredScale = getDomainLimitedScale();
@ -172,7 +173,7 @@ void Avatar::animateScaleChanges(float deltaTime) {
float animatedScale = (1.0f - blendFactor) * currentScale + blendFactor * desiredScale; float animatedScale = (1.0f - blendFactor) * currentScale + blendFactor * desiredScale;
// snap to the end when we get close enough // 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; float relativeError = fabsf(desiredScale - currentScale) / desiredScale;
if (relativeError < MIN_RELATIVE_ERROR) { if (relativeError < MIN_RELATIVE_ERROR) {
animatedScale = desiredScale; animatedScale = desiredScale;
@ -698,6 +699,7 @@ void Avatar::fixupModelsInScene(const render::ScenePointer& scene) {
_skeletonModel->removeFromScene(scene, transaction); _skeletonModel->removeFromScene(scene, transaction);
_skeletonModel->addToScene(scene, transaction); _skeletonModel->addToScene(scene, transaction);
canTryFade = true; canTryFade = true;
_isAnimatingScale = true;
} }
for (auto attachmentModel : _attachmentModels) { for (auto attachmentModel : _attachmentModels) {
if (attachmentModel->isRenderable() && attachmentModel->needsFixupInScene()) { if (attachmentModel->isRenderable() && attachmentModel->needsFixupInScene()) {
@ -1195,6 +1197,8 @@ void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
void Avatar::setModelURLFinished(bool success) { void Avatar::setModelURLFinished(bool success) {
invalidateJointIndicesCache(); invalidateJointIndicesCache();
_isAnimatingScale = true;
if (!success && _skeletonModelURL != AvatarData::defaultFullAvatarModelUrl()) { 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 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 || if (_skeletonModel->getResourceDownloadAttemptsRemaining() <= 0 ||
@ -1588,45 +1592,80 @@ float Avatar::getEyeHeight() const {
return result; 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. // 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) { if (_skeletonModel) {
auto& rig = _skeletonModel->getRig(); 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 headTopJoint = rig.indexOfJoint("HeadTop_End");
int headJoint = rig.indexOfJoint("Head"); int headJoint = rig.indexOfJoint("Head");
int eyeJoint = rig.indexOfJoint("LeftEye") != -1 ? rig.indexOfJoint("LeftEye") : rig.indexOfJoint("RightEye"); int eyeJoint = rig.indexOfJoint("LeftEye") != -1 ? rig.indexOfJoint("LeftEye") : rig.indexOfJoint("RightEye");
int toeJoint = rig.indexOfJoint("LeftToeBase") != -1 ? rig.indexOfJoint("LeftToeBase") : rig.indexOfJoint("RightToeBase"); 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) { if (eyeJoint >= 0 && toeJoint >= 0) {
// measure from eyes to toes. // Measure from eyes to toes.
float eyeHeight = rig.getAbsoluteDefaultPose(eyeJoint).trans().y - rig.getAbsoluteDefaultPose(toeJoint).trans().y; float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - skeleton->getAbsoluteDefaultPose(toeJoint).trans().y;
return eyeHeight; return scaleFactor * eyeHeight;
} else if (eyeJoint >= 0) { } else if (eyeJoint >= 0) {
// measure eyes to y = 0 plane. // Measure Eye joint to y = 0 plane.
float groundHeight = transformPoint(rig.getGeometryToRigTransform(), glm::vec3(0.0f)).y; float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - GROUND_Y;
float eyeHeight = rig.getAbsoluteDefaultPose(eyeJoint).trans().y - groundHeight; return scaleFactor * eyeHeight;
return eyeHeight;
} else if (headTopJoint >= 0 && toeJoint >= 0) { } 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; 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; float height = skeleton->getAbsoluteDefaultPose(headTopJoint).trans().y - skeleton->getAbsoluteDefaultPose(toeJoint).trans().y;
return height - height * ratio; return scaleFactor * (height - height * ratio);
} else if (headTopJoint >= 0) { } 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; 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 = skeleton->getAbsoluteDefaultPose(headTopJoint).trans().y - GROUND_Y;
float headHeight = rig.getAbsoluteDefaultPose(headTopJoint).trans().y - groundHeight; return scaleFactor * (headHeight - headHeight * ratio);
return headHeight - headHeight * ratio;
} else if (headJoint >= 0) { } 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 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; const float ratio = DEFAULT_AVATAR_NECK_TO_EYE / DEFAULT_AVATAR_NECK_HEIGHT;
float neckHeight = rig.getAbsoluteDefaultPose(headJoint).trans().y - groundHeight; float neckHeight = skeleton->getAbsoluteDefaultPose(headJoint).trans().y - GROUND_Y;
return neckHeight + neckHeight * ratio; return scaleFactor * (neckHeight + neckHeight * ratio);
} else { } else {
return avatarScale * DEFAULT_AVATAR_EYE_HEIGHT; return DEFAULT_AVATAR_EYE_HEIGHT;
} }
} else { } else {
return avatarScale * DEFAULT_AVATAR_EYE_HEIGHT; return DEFAULT_AVATAR_EYE_HEIGHT;
} }
} }

View file

@ -255,12 +255,16 @@ public:
bool isFading() const { return _isFading; } bool isFading() const { return _isFading; }
void updateFadingStatus(render::ScenePointer scene); void updateFadingStatus(render::ScenePointer scene);
/**jsdoc Q_INVOKABLE virtual float getEyeHeight() const override;
* Provides read only access to the current eye height of the avatar.
* @function Avatar.getEyeHeight // returns eye height of avatar in meters, ignoreing avatar scale.
* @returns {number} eye height of avatar in meters // if _targetScale is 1 then this will be identical to getEyeHeight;
*/ virtual float getUnscaledEyeHeight() const override;
Q_INVOKABLE float getEyeHeight() const;
// 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 float getModelScale() const { return _modelScale; }
virtual void setModelScale(float scale) { _modelScale = scale; } virtual void setModelScale(float scale) { _modelScale = scale; }
@ -279,6 +283,7 @@ public slots:
void setModelURLFinished(bool success); void setModelURLFinished(bool success);
protected: 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. virtual const QString& getSessionDisplayNameForTransport() const override { return _empty; } // Save a tiny bit of bandwidth. Mixer won't look at what we send.
QString _empty{}; QString _empty{};
virtual void maybeUpdateSessionDisplayNameFromTransport(const QString& sessionDisplayName) override { _sessionDisplayName = sessionDisplayName; } // don't use no-op setter! virtual void maybeUpdateSessionDisplayNameFromTransport(const QString& sessionDisplayName) override { _sessionDisplayName = sessionDisplayName; } // don't use no-op setter!
@ -349,7 +354,7 @@ protected:
RateCounter<> _skeletonModelSimulationRate; RateCounter<> _skeletonModelSimulationRate;
RateCounter<> _jointDataSimulationRate; RateCounter<> _jointDataSimulationRate;
private: protected:
class AvatarEntityDataHash { class AvatarEntityDataHash {
public: public:
AvatarEntityDataHash(uint32_t h) : hash(h) {}; AvatarEntityDataHash(uint32_t h) : hash(h) {};

View file

@ -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 { glm::vec3 AvatarData::getHandPosition() const {
return getWorldOrientation() * _handPosition + getWorldPosition(); return getWorldOrientation() * _handPosition + getWorldPosition();
} }

View file

@ -35,6 +35,7 @@
#include <QtScript/QScriptValueIterator> #include <QtScript/QScriptValueIterator>
#include <QReadWriteLock> #include <QReadWriteLock>
#include <AvatarConstants.h>
#include <JointData.h> #include <JointData.h>
#include <NLPacket.h> #include <NLPacket.h>
#include <Node.h> #include <Node.h>
@ -257,9 +258,6 @@ namespace AvatarDataPacket {
size_t maxJointDataSize(size_t numJoints); 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 float MAX_AUDIO_LOUDNESS = 1000.0f; // close enough for mouth animation
const int AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS = 1000; const int AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS = 1000;
@ -484,12 +482,38 @@ public:
// Scale // Scale
virtual void setTargetScale(float targetScale); 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) // returns eye height of avatar in meters, ignoreing avatar scale.
{ _domainMinimumScale = glm::clamp(domainMinimumScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE); _scaleChanged = usecTimestampNow(); } // if _targetScale is 1 then this will be identical to getEyeHeight;
void setDomainMaximumScale(float domainMaximumScale) virtual float getUnscaledEyeHeight() const { return DEFAULT_AVATAR_EYE_HEIGHT; }
{ _domainMaximumScale = glm::clamp(domainMaximumScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE); _scaleChanged = usecTimestampNow(); }
// 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 // Hand State
Q_INVOKABLE void setHandState(char s) { _handState = s; } Q_INVOKABLE void setHandState(char s) { _handState = s; }
@ -698,8 +722,8 @@ protected:
// Body scale // Body scale
float _targetScale; float _targetScale;
float _domainMinimumScale { MIN_AVATAR_SCALE }; float _domainMinimumHeight { MIN_AVATAR_HEIGHT };
float _domainMaximumScale { MAX_AVATAR_SCALE }; float _domainMaximumHeight { MAX_AVATAR_HEIGHT };
// Hand state (are we grabbing something or not) // Hand state (are we grabbing something or not)
char _handState; char _handState;

View file

@ -74,6 +74,8 @@ PacketVersion versionForPacketType(PacketType packetType) {
return static_cast<PacketVersion>(AudioVersion::HighDynamicRangeVolume); return static_cast<PacketVersion>(AudioVersion::HighDynamicRangeVolume);
case PacketType::ICEPing: case PacketType::ICEPing:
return static_cast<PacketVersion>(IcePingVersion::SendICEPeerID); return static_cast<PacketVersion>(IcePingVersion::SendICEPeerID);
case PacketType::DomainSettings:
return 18; // replace min_avatar_scale and max_avatar_scale with min_avatar_height and max_avatar_height
default: default:
return 17; return 17;
} }

View file

@ -163,7 +163,7 @@ void Model::setScale(const glm::vec3& scale) {
_scaledToFit = false; _scaledToFit = false;
} }
const float SCALE_CHANGE_EPSILON = 0.01f; const float SCALE_CHANGE_EPSILON = 0.001f;
void Model::setScaleInternal(const glm::vec3& scale) { void Model::setScaleInternal(const glm::vec3& scale) {
if (glm::distance(_scale, scale) > SCALE_CHANGE_EPSILON) { if (glm::distance(_scale, scale) > SCALE_CHANGE_EPSILON) {

View file

@ -204,6 +204,9 @@ public:
/// Returns the extents of the model's mesh /// Returns the extents of the model's mesh
Extents getMeshExtents() const; Extents getMeshExtents() const;
/// Returns the unscaled extents of the model's mesh
Extents getUnscaledMeshExtents() const;
void setTranslation(const glm::vec3& translation); void setTranslation(const glm::vec3& translation);
void setRotation(const glm::quat& rotation); void setRotation(const glm::quat& rotation);
void setTransformNoUpdateRenderItems(const Transform& transform); // temporary HACK void setTransformNoUpdateRenderItems(const Transform& transform); // temporary HACK
@ -276,9 +279,6 @@ protected:
void setBlendshapeCoefficients(const QVector<float>& coefficients) { _blendshapeCoefficients = coefficients; } void setBlendshapeCoefficients(const QVector<float>& coefficients) { _blendshapeCoefficients = coefficients; }
const QVector<float>& getBlendshapeCoefficients() const { return _blendshapeCoefficients; } const QVector<float>& getBlendshapeCoefficients() const { return _blendshapeCoefficients; }
/// Returns the unscaled extents of the model's mesh
Extents getUnscaledMeshExtents() const;
/// Clear the joint states /// Clear the joint states
void clearJointState(int index); void clearJointState(int index);

View file

@ -12,6 +12,8 @@
#ifndef hifi_AvatarConstants_h #ifndef hifi_AvatarConstants_h
#define hifi_AvatarConstants_h #define hifi_AvatarConstants_h
#include "GLMHelpers.h"
// 50th Percentile Man // 50th Percentile Man
const float DEFAULT_AVATAR_HEIGHT = 1.755f; // meters const float DEFAULT_AVATAR_HEIGHT = 1.755f; // meters
const float DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD = 0.11f; // 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_FALL_HEIGHT = 20.0f; // meters
const float DEFAULT_AVATAR_MIN_HOVER_HEIGHT = 2.5f; // 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 #endif // hifi_AvatarConstants_h