diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index ddee5dfc1f..c0636314b5 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -549,7 +549,12 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) { auto cameraMode = qApp->getCamera()->getMode(); if (!isMyAvatar() || cameraMode != CAMERA_MODE_FIRST_PERSON) { - renderDisplayName(batch, *renderArgs->_viewFrustum, renderArgs->_viewport); + auto& frustum = *renderArgs->_viewFrustum; + auto textPosition = getDisplayNamePosition(); + + if (frustum.pointInFrustum(textPosition, true) == ViewFrustum::INSIDE) { + renderDisplayName(batch, frustum, textPosition); + } } endRender(); } @@ -685,120 +690,85 @@ void Avatar::renderBillboard(RenderArgs* renderArgs) { } float Avatar::getBillboardSize() const { - return _scale * BILLBOARD_DISTANCE * tanf(glm::radians(BILLBOARD_FIELD_OF_VIEW / 2.0f)); + return _scale * BILLBOARD_DISTANCE * glm::tan(glm::radians(BILLBOARD_FIELD_OF_VIEW / 2.0f)); } +#ifdef DEBUG +void debugValue(const QString& str, const glm::vec3& value) { + if (glm::any(glm::isnan(value)) || glm::any(glm::isinf(value))) { + qCWarning(interfaceapp) << "debugValue() " << str << value; + } +}; +void debugValue(const QString& str, const float& value) { + if (glm::isnan(value) || glm::isinf(value)) { + qCWarning(interfaceapp) << "debugValue() " << str << value; + } +}; +#define DEBUG_VALUE(str, value) debugValue(str, value) +#else +#define DEBUG_VALUE(str, value) +#endif + glm::vec3 Avatar::getDisplayNamePosition() const { glm::vec3 namePosition(0.0f); + glm::vec3 bodyUpDirection = getBodyUpDirection(); + DEBUG_VALUE("bodyUpDirection =", bodyUpDirection); + if (getSkeletonModel().getNeckPosition(namePosition)) { - namePosition += getBodyUpDirection() * getHeadHeight() * 1.1f; + float headHeight = getHeadHeight(); + DEBUG_VALUE("namePosition =", namePosition); + DEBUG_VALUE("headHeight =", headHeight); + + static const float SLIGHTLY_ABOVE = 1.1f; + namePosition += bodyUpDirection * headHeight * SLIGHTLY_ABOVE; } else { const float HEAD_PROPORTION = 0.75f; - namePosition = _position + getBodyUpDirection() * (getBillboardSize() * HEAD_PROPORTION); + float billboardSize = getBillboardSize(); + + DEBUG_VALUE("_position =", _position); + DEBUG_VALUE("billboardSize =", billboardSize); + namePosition = _position + bodyUpDirection * (billboardSize * HEAD_PROPORTION); } -#ifdef DEBUG - // TODO: Temporary logging to track cause of invalid scale value; remove once cause has been fixed. - // See other TODO below. - if (glm::isnan(namePosition.x) || glm::isnan(namePosition.y) || glm::isnan(namePosition.z) - || glm::isinf(namePosition.x) || glm::isinf(namePosition.y) || glm::isinf(namePosition.z)) { - qDebug() << "namePosition =" << namePosition; - glm::vec3 tempPosition(0.0f); - if (getSkeletonModel().getNeckPosition(tempPosition)) { - qDebug() << "getBodyUpDirection() =" << getBodyUpDirection(); - qDebug() << "getHeadHeight() =" << getHeadHeight(); - } else { - qDebug() << "_position =" << _position; - qDebug() << "getBodyUpDirection() =" << getBodyUpDirection(); - qDebug() << "getBillboardSize() =" << getBillboardSize(); - } + + if (glm::any(glm::isnan(namePosition)) || glm::any(glm::isinf(namePosition))) { + qCWarning(interfaceapp) << "Invalid display name position" << namePosition + << ", setting is to (0.0f, 0.5f, 0.0f)"; + namePosition = glm::vec3(0.0f, 0.5f, 0.0f); } -#endif + return namePosition; } -Transform Avatar::calculateDisplayNameTransform(const ViewFrustum& frustum, float fontSize, const glm::ivec4& viewport) const { - Transform result; - // We assume textPosition is whithin the frustum - glm::vec3 textPosition = getDisplayNamePosition(); - - // Compute viewProjection matrix - glm::mat4 projMat, viewMat; - Transform view; - frustum.evalProjectionMatrix(projMat); - frustum.evalViewTransform(view); - glm::mat4 viewProj = projMat * view.getInverseMatrix(viewMat); - - // Used to determine correct scale - glm::vec3 testPoint0 = textPosition; - glm::vec3 testPoint1 = testPoint0 + glm::normalize(frustum.getUp()); - // testPoints projections - glm::vec4 p0 = viewProj * glm::vec4(testPoint0, 1.0); - glm::vec4 p1 = viewProj * glm::vec4(testPoint1, 1.0); - - float windowSizeY = viewport.w; - - const float DESIRED_HIGHT_ON_SCREEN = 20; // In pixels (this is double on retinas) - - // Projected point are between -1.0f and 1.0f, hence 0.5f * windowSizeY - float pixelHeight = 0.5f * windowSizeY * glm::abs((p1.y / p1.w) - (p0.y / p0.w)); // - // Handles pixel density (especially for macs retina displays) - float devicePixelRatio = (float)qApp->getDevicePixelRatio() * qApp->getRenderResolutionScale(); // pixels / unit - - // Compute correct scale to apply - float scale = DESIRED_HIGHT_ON_SCREEN / (fontSize * pixelHeight) * devicePixelRatio; -#ifdef DEBUG - // TODO: Temporary logging to track cause of invalid scale value; remove once cause has been fixed. - // Problem is probably due to an invalid getDisplayNamePosition(). See extra logging above. - if (scale == 0.0f || glm::isnan(scale) || glm::isinf(scale)) { - if (scale == 0.0f) { - qDebug() << "ASSERT because scale == 0.0f"; - } - if (glm::isnan(scale)) { - qDebug() << "ASSERT because isnan(scale)"; - } - if (glm::isinf(scale)) { - qDebug() << "ASSERT because isinf(scale)"; - } - qDebug() << "textPosition =" << textPosition; - qDebug() << "projMat =" << projMat; - qDebug() << "viewMat =" << viewMat; - qDebug() << "viewProj =" << viewProj; - qDebug() << "windowSizeY =" << windowSizeY; - qDebug() << "p1 =" << p1; - qDebug() << "p0 =" << p0; - qDebug() << "qApp->getDevicePixelRatio() =" << qApp->getDevicePixelRatio(); - qDebug() << "fontSize =" << fontSize; - qDebug() << "pixelHeight =" << pixelHeight; - qDebug() << "devicePixelRatio =" << devicePixelRatio; - } -#endif - - // Compute pixel alignment offset - float clipToPix = 0.5f * windowSizeY / p1.w; // Got from clip to pixel coordinates - glm::vec4 screenPos = clipToPix * p1; // in pixels coords - glm::vec4 screenOffset = (glm::round(screenPos) - screenPos) / clipToPix; // in clip coords - glm::vec3 worldOffset = glm::vec3(screenOffset.x, screenOffset.y, 0.0f) / (float)pixelHeight; +Transform Avatar::calculateDisplayNameTransform(const ViewFrustum& frustum, const glm::vec3& textPosition) const { + Q_ASSERT_X(frustum.pointInFrustum(textPosition, true) == ViewFrustum::INSIDE, + "Avatar::calculateDisplayNameTransform", "Text not in viewfrustum."); + glm::vec3 toFrustum = frustum.getPosition() - textPosition; // Compute orientation - glm::vec3 dPosition = frustum.getPosition() - getPosition(); - // If x and z are 0, atan(x, z) is undefined, so default to 0 degrees - float yawRotation = dPosition.x == 0.0f && dPosition.z == 0.0f ? 0.0f : glm::atan(dPosition.x, dPosition.z); + // If x and z are 0, atan(x, z) adais undefined, so default to 0 degrees + const float yawRotation = (toFrustum.x == 0.0f && toFrustum.z == 0.0f) ? 0.0f : glm::atan(toFrustum.x, toFrustum.z); glm::quat orientation = glm::quat(glm::vec3(0.0f, yawRotation, 0.0f)); - // Set transform (The order IS important) + // Compute correct scale to apply + static const float DESIRED_HEIGHT_RAD = glm::radians(1.5f); + float scale = glm::length(toFrustum) * glm::tan(DESIRED_HEIGHT_RAD); + + // Set transform + Transform result; result.setTranslation(textPosition); result.setRotation(orientation); // Always face the screen - result.postTranslate(worldOffset); // Pixel alignment result.setScale(scale); + // raise by half the scale up so that textPosition be the bottom + result.postTranslate(Vectors::UP / 2.0f); + return result; - } -void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& frustum, const glm::ivec4& viewport) const { +void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& frustum, const glm::vec3& textPosition) const { bool shouldShowReceiveStats = DependencyManager::get()->shouldShowReceiveStats() && !isMyAvatar(); // If we have nothing to draw, or it's totally transparent, or it's too close or behind the camera, return - const float CLIP_DISTANCE = 0.2f; + static const float CLIP_DISTANCE = 0.2f; if ((_displayName.isEmpty() && !shouldShowReceiveStats) || _displayNameAlpha == 0.0f || (glm::dot(frustum.getDirection(), getDisplayNamePosition() - frustum.getPosition()) <= CLIP_DISTANCE)) { return; @@ -818,39 +788,45 @@ void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& frustum, co } // Compute display name extent/position offset - glm::vec2 extent = renderer->computeExtent(renderedDisplayName); - QRect nameDynamicRect = QRect(0, 0, (int)extent.x, (int)extent.y); - const int text_x = -nameDynamicRect.width() / 2; - const int text_y = -nameDynamicRect.height() / 2; - - // Compute background position/size - static const float SLIGHTLY_IN_FRONT = 0.1f; - const int border = 0.1f * nameDynamicRect.height(); - const int left = text_x - border; - const int bottom = text_y - border; - const int width = nameDynamicRect.width() + 2.0f * border; - const int height = nameDynamicRect.height() + 2.0f * border; - const int bevelDistance = 0.1f * height; - - // Display name and background colors - glm::vec4 textColor(0.93f, 0.93f, 0.93f, _displayNameAlpha); - glm::vec4 backgroundColor(0.2f, 0.2f, 0.2f, - (_displayNameAlpha / DISPLAYNAME_ALPHA) * DISPLAYNAME_BACKGROUND_ALPHA); - - // Compute display name transform - auto textTransform = calculateDisplayNameTransform(frustum, renderer->getFontSize(), viewport); - batch.setModelTransform(textTransform); - - DependencyManager::get()->bindSimpleProgram(batch, false, true, true, true); - DependencyManager::get()->renderBevelCornersRect(batch, left, bottom, width, height, - bevelDistance, backgroundColor); - // Render actual name - QByteArray nameUTF8 = renderedDisplayName.toLocal8Bit(); - - // Render text slightly in front to avoid z-fighting - textTransform.postTranslate(glm::vec3(0.0f, 0.0f, SLIGHTLY_IN_FRONT * renderer->getFontSize())); - batch.setModelTransform(textTransform); - renderer->draw(batch, text_x, -text_y, nameUTF8.data(), textColor); + const glm::vec2 extent = renderer->computeExtent(renderedDisplayName); + if (!glm::any(glm::isCompNull(extent, EPSILON))) { + const QRect nameDynamicRect = QRect(0, 0, (int)extent.x, (int)extent.y); + const int text_x = -nameDynamicRect.width() / 2; + const int text_y = -nameDynamicRect.height() / 2; + + // Compute background position/size + static const float SLIGHTLY_IN_FRONT = 0.1f; + static const float BORDER_RELATIVE_SIZE = 0.1f; + static const float BEVEL_FACTOR = 0.1f; + const int border = BORDER_RELATIVE_SIZE * nameDynamicRect.height(); + const int left = text_x - border; + const int bottom = text_y - border; + const int width = nameDynamicRect.width() + 2.0f * border; + const int height = nameDynamicRect.height() + 2.0f * border; + const int bevelDistance = BEVEL_FACTOR * height; + + // Display name and background colors + glm::vec4 textColor(0.93f, 0.93f, 0.93f, _displayNameAlpha); + glm::vec4 backgroundColor(0.2f, 0.2f, 0.2f, + (_displayNameAlpha / DISPLAYNAME_ALPHA) * DISPLAYNAME_BACKGROUND_ALPHA); + + // Compute display name transform + auto textTransform = calculateDisplayNameTransform(frustum, textPosition); + // Test on extent above insures abs(height) > 0.0f + textTransform.postScale(1.0f / height); + batch.setModelTransform(textTransform); + + DependencyManager::get()->bindSimpleProgram(batch, false, true, true, true); + DependencyManager::get()->renderBevelCornersRect(batch, left, bottom, width, height, + bevelDistance, backgroundColor); + // Render actual name + QByteArray nameUTF8 = renderedDisplayName.toLocal8Bit(); + + // Render text slightly in front to avoid z-fighting + textTransform.postTranslate(glm::vec3(0.0f, 0.0f, SLIGHTLY_IN_FRONT * renderer->getFontSize())); + batch.setModelTransform(textTransform); + renderer->draw(batch, text_x, -text_y, nameUTF8.data(), textColor); + } } void Avatar::setSkeletonOffset(const glm::vec3& offset) { diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 1800f4fdd1..534aefd3cb 100644 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -217,8 +217,8 @@ protected: float getPelvisFloatingHeight() const; glm::vec3 getDisplayNamePosition() const; - Transform calculateDisplayNameTransform(const ViewFrustum& frustum, float fontSize, const glm::ivec4& viewport) const; - void renderDisplayName(gpu::Batch& batch, const ViewFrustum& frustum, const glm::ivec4& viewport) const; + Transform calculateDisplayNameTransform(const ViewFrustum& frustum, const glm::vec3& textPosition) const; + void renderDisplayName(gpu::Batch& batch, const ViewFrustum& frustum, const glm::vec3& textPosition) const; virtual void renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, float glowLevel = 0.0f); virtual bool shouldRenderHead(const RenderArgs* renderArgs) const; virtual void fixupModelsInScene(); diff --git a/libraries/octree/src/ViewFrustum.cpp b/libraries/octree/src/ViewFrustum.cpp index 820ab32b67..fd175a8e5b 100644 --- a/libraries/octree/src/ViewFrustum.cpp +++ b/libraries/octree/src/ViewFrustum.cpp @@ -241,20 +241,21 @@ ViewFrustum::location ViewFrustum::boxInKeyhole(const AABox& box) const { return result; } -ViewFrustum::location ViewFrustum::pointInFrustum(const glm::vec3& point) const { +ViewFrustum::location ViewFrustum::pointInFrustum(const glm::vec3& point, bool ignoreKeyhole) const { ViewFrustum::location regularResult = INSIDE; ViewFrustum::location keyholeResult = OUTSIDE; // If we have a keyholeRadius, check that first, since it's cheaper - if (_keyholeRadius >= 0.0f) { + if (!ignoreKeyhole && _keyholeRadius >= 0.0f) { keyholeResult = pointInKeyhole(point); - } - if (keyholeResult == INSIDE) { - return keyholeResult; + + if (keyholeResult == INSIDE) { + return keyholeResult; + } } // If we're not known to be INSIDE the keyhole, then check the regular frustum - for(int i=0; i < 6; i++) { + for(int i = 0; i < 6; ++i) { float distance = _planes[i].distance(point); if (distance < 0) { return keyholeResult; // escape early will be the value from checking the keyhole diff --git a/libraries/octree/src/ViewFrustum.h b/libraries/octree/src/ViewFrustum.h index fe40cfd53c..596d042506 100644 --- a/libraries/octree/src/ViewFrustum.h +++ b/libraries/octree/src/ViewFrustum.h @@ -79,7 +79,7 @@ public: typedef enum {OUTSIDE, INTERSECT, INSIDE} location; - ViewFrustum::location pointInFrustum(const glm::vec3& point) const; + ViewFrustum::location pointInFrustum(const glm::vec3& point, bool ignoreKeyhole = false) const; ViewFrustum::location sphereInFrustum(const glm::vec3& center, float radius) const; ViewFrustum::location cubeInFrustum(const AACube& cube) const; ViewFrustum::location boxInFrustum(const AABox& box) const;