/** * @file llhudnametag.cpp * @brief Name tags for avatars * @author James Cook * * $LicenseInfo:firstyear=2010&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ #include "llviewerprecompiledheaders.h" #include "llhudnametag.h" #include "llrender.h" #include "lltracerecording.h" #include "llagent.h" #include "llviewercontrol.h" #include "llcriticaldamp.h" #include "lldrawable.h" #include "llfontgl.h" #include "llglheaders.h" #include "llhudrender.h" #include "llui.h" #include "llviewercamera.h" #include "llviewertexturelist.h" #include "llviewerobject.h" #include "llvovolume.h" #include "llviewerwindow.h" #include "llstatusbar.h" #include "llmenugl.h" #include "pipeline.h" #include const F32 SPRING_STRENGTH = 0.7f; const F32 HORIZONTAL_PADDING = 16.f; const F32 VERTICAL_PADDING = 12.f; const F32 LINE_PADDING = 3.f; // aka "leading" const F32 BUFFER_SIZE = 2.f; const S32 NUM_OVERLAP_ITERATIONS = 10; const F32 POSITION_DAMPING_TC = 0.2f; const F32 MAX_STABLE_CAMERA_VELOCITY = 0.1f; const F32 LOD_0_SCREEN_COVERAGE = 0.15f; const F32 LOD_1_SCREEN_COVERAGE = 0.30f; const F32 LOD_2_SCREEN_COVERAGE = 0.40f; std::set > LLHUDNameTag::sTextObjects; std::vector > LLHUDNameTag::sVisibleTextObjects; bool LLHUDNameTag::sDisplayText = true ; const F32 LLHUDNameTag::NAMETAG_MAX_WIDTH = 298.f; const F32 LLHUDNameTag::HUD_TEXT_MAX_WIDTH = 190.f; bool llhudnametag_further_away::operator()(const LLPointer& lhs, const LLPointer& rhs) const { return lhs->getDistance() > rhs->getDistance(); } LLHUDNameTag::LLHUDNameTag(const U8 type) : LLHUDObject(type), mDoFade(true), mFadeDistance(8.f), mFadeRange(4.f), mLastDistance(0.f), mZCompare(true), mVisibleOffScreen(false), mOffscreen(false), mColor(1.f, 1.f, 1.f, 1.f), // mScale(), mWidth(0.f), mHeight(0.f), mFontp(LLFontGL::getFontSansSerifSmall()), mBoldFontp(LLFontGL::getFontSansSerifBold()), mSoftScreenRect(), mPositionAgent(), mPositionOffset(), mMass(10.f), mMaxLines(10), mOffsetY(0), mRadius(0.1f), mTextSegments(), mLabelSegments(), mTextAlignment(ALIGN_TEXT_CENTER), mVertAlignment(ALIGN_VERT_CENTER), mLOD(0), mHidden(false) { LLPointer ptr(this); sTextObjects.insert(ptr); mRoundedRectImgp = LLUI::getUIImage("Rounded_Rect"); mRoundedRectTopImgp = LLUI::getUIImage("Rounded_Rect_Top"); } LLHUDNameTag::~LLHUDNameTag() { } bool LLHUDNameTag::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, LLVector4a& intersection, bool debug_render) { if (!mVisible || mHidden) { return false; } // don't pick text that isn't bound to a viewerobject if (!mSourceObject || mSourceObject->mDrawable.isNull()) { return false; } F32 alpha_factor = 1.f; LLColor4 text_color = mColor; if (mDoFade) { if (mLastDistance > mFadeDistance) { alpha_factor = llmax(0.f, 1.f - (mLastDistance - mFadeDistance)/mFadeRange); text_color.mV[3] = text_color.mV[3]*alpha_factor; } } if (text_color.mV[3] < 0.01f) { return false; } mOffsetY = lltrunc(mHeight * ((mVertAlignment == ALIGN_VERT_CENTER) ? 0.5f : 1.f)); LLVector3 position = mPositionAgent; if (mSourceObject) { //get intersection of eye through mPositionAgent to plane of source object //using this position keeps the camera from focusing on some seemingly random //point several meters in front of the nametag const LLVector3& p = mSourceObject->getPositionAgent(); const LLVector3& n = LLViewerCamera::getInstance()->getAtAxis(); const LLVector3& eye = LLViewerCamera::getInstance()->getOrigin(); LLVector3 ray = position-eye; ray.normalize(); LLVector3 delta = p-position; F32 dist = delta*n; F32 dt = dist/(ray*n); position += ray*dt; } // scale screen size of borders down LLVector3 x_pixel_vec; LLVector3 y_pixel_vec; LLViewerCamera::getInstance()->getPixelVectors(position, y_pixel_vec, x_pixel_vec); LLVector3 width_vec = mWidth * x_pixel_vec; LLVector3 height_vec = mHeight * y_pixel_vec; LLCoordGL screen_pos; LLViewerCamera::getInstance()->projectPosAgentToScreen(position, screen_pos, false); LLVector2 screen_offset; screen_offset = updateScreenPos(mPositionOffset); LLVector3 render_position = position + (x_pixel_vec * screen_offset.mV[VX]) + (y_pixel_vec * screen_offset.mV[VY]); LLVector3 bg_pos = render_position + (F32)mOffsetY * y_pixel_vec - (width_vec / 2.f) - (height_vec); LLVector3 v[] = { bg_pos, bg_pos + width_vec, bg_pos + width_vec + height_vec, bg_pos + height_vec, }; LLVector4a dir; dir.setSub(end,start); F32 a, b, t; LLVector4a v0,v1,v2,v3; v0.load3(v[0].mV); v1.load3(v[1].mV); v2.load3(v[2].mV); v3.load3(v[3].mV); if (LLTriangleRayIntersect(v0, v1, v2, start, dir, a, b, t) || LLTriangleRayIntersect(v2, v3, v0, start, dir, a, b, t) ) { if (t <= 1.f) { dir.mul(t); intersection.setAdd(start, dir); return true; } } return false; } void LLHUDNameTag::render() { LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; if (sDisplayText) { LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE); //LLGLDisable gls_stencil(GL_STENCIL_TEST); renderText(false); } } void LLHUDNameTag::renderText(bool for_select) { if (!mVisible || mHidden) { return; } // don't pick text that isn't bound to a viewerobject if (for_select && (!mSourceObject || mSourceObject->mDrawable.isNull())) { return; } if (for_select) { gGL.getTexUnit(0)->disable(); } else { gGL.getTexUnit(0)->enable(LLTexUnit::TT_TEXTURE); } LLGLState gls_blend(GL_BLEND, !for_select); LLColor4 shadow_color(0.f, 0.f, 0.f, 1.f); F32 alpha_factor = 1.f; LLColor4 text_color = mColor; if (mDoFade) { if (mLastDistance > mFadeDistance) { alpha_factor = llmax(0.f, 1.f - (mLastDistance - mFadeDistance)/mFadeRange); text_color.mV[3] = text_color.mV[3]*alpha_factor; } } if (text_color.mV[3] < 0.01f) { return; } shadow_color.mV[3] = text_color.mV[3]; mOffsetY = lltrunc(mHeight * ((mVertAlignment == ALIGN_VERT_CENTER) ? 0.5f : 1.f)); // *TODO: make this a per-text setting LLColor4 bg_color = LLUIColorTable::instance().getColor("NameTagBackground"); bg_color.setAlpha(gSavedSettings.getF32("ChatBubbleOpacity") * alpha_factor); // scale screen size of borders down //RN: for now, text on hud objects is never occluded LLVector3 x_pixel_vec; LLVector3 y_pixel_vec; LLViewerCamera::getInstance()->getPixelVectors(mPositionAgent, y_pixel_vec, x_pixel_vec); LLVector3 width_vec = mWidth * x_pixel_vec; LLVector3 height_vec = mHeight * y_pixel_vec; mRadius = (width_vec + height_vec).magVec() * 0.5f; LLCoordGL screen_pos; LLViewerCamera::getInstance()->projectPosAgentToScreen(mPositionAgent, screen_pos, false); LLVector2 screen_offset = updateScreenPos(mPositionOffset); LLVector3 render_position = mPositionAgent + (x_pixel_vec * screen_offset.mV[VX]) + (y_pixel_vec * screen_offset.mV[VY]); LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE); LLRect screen_rect; screen_rect.setCenterAndSize(0, static_cast(lltrunc(-mHeight / 2 + mOffsetY)), static_cast(lltrunc(mWidth)), static_cast(lltrunc(mHeight))); mRoundedRectImgp->draw3D(render_position, x_pixel_vec, y_pixel_vec, screen_rect, bg_color); if (mLabelSegments.size()) { LLRect label_top_rect = screen_rect; const S32 label_height = ll_round((mFontp->getLineHeight() * (F32)mLabelSegments.size() + (VERTICAL_PADDING / 3.f))); label_top_rect.mBottom = label_top_rect.mTop - label_height; LLColor4 label_top_color = text_color; label_top_color.mV[VALPHA] = gSavedSettings.getF32("ChatBubbleOpacity") * alpha_factor; mRoundedRectTopImgp->draw3D(render_position, x_pixel_vec, y_pixel_vec, label_top_rect, label_top_color); } F32 y_offset = (F32)mOffsetY; // Render label { for(std::vector::iterator segment_iter = mLabelSegments.begin(); segment_iter != mLabelSegments.end(); ++segment_iter ) { // Label segments use default font const LLFontGL* fontp = (segment_iter->mStyle == LLFontGL::BOLD) ? mBoldFontp : mFontp; y_offset -= fontp->getLineHeight(); F32 x_offset; if (mTextAlignment == ALIGN_TEXT_CENTER) { x_offset = -0.5f*segment_iter->getWidth(fontp); } else // ALIGN_LEFT { x_offset = -0.5f * mWidth + (HORIZONTAL_PADDING / 2.f); } LLColor4 label_color(0.f, 0.f, 0.f, 1.f); label_color.mV[VALPHA] = alpha_factor; hud_render_text(segment_iter->getText(), render_position, *fontp, segment_iter->mStyle, LLFontGL::NO_SHADOW, x_offset, y_offset, label_color, false); } } // Render text { // -1 mMaxLines means unlimited lines. S32 start_segment; S32 max_lines = getMaxLines(); if (max_lines < 0) { start_segment = 0; } else { start_segment = llmax((S32)0, (S32)mTextSegments.size() - max_lines); } for (std::vector::iterator segment_iter = mTextSegments.begin() + start_segment; segment_iter != mTextSegments.end(); ++segment_iter ) { const LLFontGL* fontp = segment_iter->mFont; y_offset -= fontp->getLineHeight(); y_offset -= LINE_PADDING; U8 style = segment_iter->mStyle; LLFontGL::ShadowType shadow = LLFontGL::DROP_SHADOW; F32 x_offset; if (mTextAlignment== ALIGN_TEXT_CENTER) { x_offset = -0.5f*segment_iter->getWidth(fontp); } else // ALIGN_LEFT { x_offset = -0.5f * mWidth + (HORIZONTAL_PADDING / 2.f); // *HACK x_offset += 1; } text_color = segment_iter->mColor; text_color.mV[VALPHA] *= alpha_factor; hud_render_text(segment_iter->getText(), render_position, *fontp, style, shadow, x_offset, y_offset, text_color, false); } } /// Reset the default color to white. The renderer expects this to be the default. gGL.color4f(1.0f, 1.0f, 1.0f, 1.0f); if (for_select) { gGL.getTexUnit(0)->enable(LLTexUnit::TT_TEXTURE); } } void LLHUDNameTag::setString(const std::string &text_utf8) { mTextSegments.clear(); addLine(text_utf8, mColor); } void LLHUDNameTag::clearString() { mTextSegments.clear(); } void LLHUDNameTag::addLine(const std::string &text_utf8, const LLColor4& color, const LLFontGL::StyleFlags style, const LLFontGL* font, const bool use_ellipses, F32 max_pixels) { LLWString wline = utf8str_to_wstring(text_utf8); if (!wline.empty()) { // use default font for segment if custom font not specified if (!font) { font = mFontp; } typedef boost::tokenizer, LLWString::const_iterator, LLWString > tokenizer; LLWString seps(utf8str_to_wstring("\r\n")); boost::char_separator sep(seps.c_str()); tokenizer tokens(wline, sep); tokenizer::iterator iter = tokens.begin(); max_pixels = llmin(max_pixels, NAMETAG_MAX_WIDTH); while (iter != tokens.end()) { U32 line_length = 0; if (use_ellipses) { // "QualityAssuranceAssuresQuality1" will end up like "QualityAssuranceAssuresQual..." // "QualityAssuranceAssuresQuality QualityAssuranceAssuresQuality" will end up like "QualityAssuranceAssuresQual..." // "QualityAssurance AssuresQuality1" will end up as "QualityAssurance AssuresQua..." because we are enforcing single line do { S32 segment_length = font->maxDrawableChars(iter->substr(line_length).c_str(), max_pixels, wline.length(), LLFontGL::ANYWHERE); if (segment_length + line_length < wline.length()) // since we only draw one string, line_length should be 0 { // token does does not fit into signle line, need to draw "...". // Use four dots for ellipsis width to generate padding const LLWString dots_pad(utf8str_to_wstring(std::string("...."))); S32 elipses_width = font->getWidthF32(dots_pad.c_str()); // truncated string length segment_length = font->maxDrawableChars(iter->substr(line_length).c_str(), max_pixels - elipses_width, wline.length(), LLFontGL::ANYWHERE); const LLWString dots(utf8str_to_wstring(std::string("..."))); LLHUDTextSegment segment(iter->substr(line_length, segment_length) + dots, style, color, font); mTextSegments.push_back(segment); break; // consider it to be complete } else { // token fits fully into string LLHUDTextSegment segment(iter->substr(line_length, segment_length), style, color, font); mTextSegments.push_back(segment); line_length += segment_length; } } while (line_length != iter->size()); } else { // "QualityAssuranceAssuresQuality 1" will be split into two lines "QualityAssuranceAssuresQualit" and "y 1" // "QualityAssurance AssuresQuality 1" will be split into two lines "QualityAssurance" and "AssuresQuality" do { S32 segment_length = font->maxDrawableChars(iter->substr(line_length).c_str(), max_pixels, wline.length(), LLFontGL::WORD_BOUNDARY_IF_POSSIBLE); LLHUDTextSegment segment(iter->substr(line_length, segment_length), style, color, font); mTextSegments.push_back(segment); line_length += segment_length; } while (line_length != iter->size()); } ++iter; } } } void LLHUDNameTag::setLabel(const std::string &label_utf8) { mLabelSegments.clear(); addLabel(label_utf8); } void LLHUDNameTag::addLabel(const std::string& label_utf8, F32 max_pixels) { LLWString wstr = utf8string_to_wstring(label_utf8); if (!wstr.empty()) { LLWString seps(utf8str_to_wstring("\r\n")); LLWString empty; typedef boost::tokenizer, LLWString::const_iterator, LLWString > tokenizer; boost::char_separator sep(seps.c_str(), empty.c_str(), boost::keep_empty_tokens); tokenizer tokens(wstr, sep); tokenizer::iterator iter = tokens.begin(); max_pixels = llmin(max_pixels, NAMETAG_MAX_WIDTH); while (iter != tokens.end()) { U32 line_length = 0; do { S32 segment_length = mFontp->maxDrawableChars(iter->substr(line_length).c_str(), max_pixels, wstr.length(), LLFontGL::WORD_BOUNDARY_IF_POSSIBLE); LLHUDTextSegment segment(iter->substr(line_length, segment_length), LLFontGL::NORMAL, mColor, mFontp); mLabelSegments.push_back(segment); line_length += segment_length; } while (line_length != iter->size()); ++iter; } } } void LLHUDNameTag::setZCompare(const bool zcompare) { mZCompare = zcompare; } void LLHUDNameTag::setFont(const LLFontGL* font) { mFontp = font; } void LLHUDNameTag::setColor(const LLColor4 &color) { mColor = color; for (std::vector::iterator segment_iter = mTextSegments.begin(); segment_iter != mTextSegments.end(); ++segment_iter ) { segment_iter->mColor = color; } } void LLHUDNameTag::setAlpha(F32 alpha) { mColor.mV[VALPHA] = alpha; for (std::vector::iterator segment_iter = mTextSegments.begin(); segment_iter != mTextSegments.end(); ++segment_iter ) { segment_iter->mColor.mV[VALPHA] = alpha; } } void LLHUDNameTag::setDoFade(const bool do_fade) { mDoFade = do_fade; } void LLHUDNameTag::updateVisibility() { if (mSourceObject) { mSourceObject->updateText(); } mPositionAgent = gAgent.getPosAgentFromGlobal(mPositionGlobal); if (!mSourceObject) { //LL_WARNS() << "LLHUDNameTag::updateScreenPos -- mSourceObject is NULL!" << LL_ENDL; mVisible = true; sVisibleTextObjects.push_back(LLPointer (this)); return; } // Not visible if parent object is dead if (mSourceObject->isDead()) { mVisible = false; return; } // push text towards camera by radius of object, but not past camera LLVector3 vec_from_camera = mPositionAgent - LLViewerCamera::getInstance()->getOrigin(); LLVector3 dir_from_camera = vec_from_camera; dir_from_camera.normVec(); if (dir_from_camera * LLViewerCamera::getInstance()->getAtAxis() <= 0.f) { //text is behind camera, don't render mVisible = false; return; } if (vec_from_camera * LLViewerCamera::getInstance()->getAtAxis() <= LLViewerCamera::getInstance()->getNear() + 0.1f + mSourceObject->getVObjRadius()) { mPositionAgent = LLViewerCamera::getInstance()->getOrigin() + vec_from_camera * ((LLViewerCamera::getInstance()->getNear() + 0.1f) / (vec_from_camera * LLViewerCamera::getInstance()->getAtAxis())); } else { mPositionAgent -= dir_from_camera * mSourceObject->getVObjRadius(); } mLastDistance = (mPositionAgent - LLViewerCamera::getInstance()->getOrigin()).magVec(); if (mLOD >= 3 || !mTextSegments.size() || (mDoFade && (mLastDistance > mFadeDistance + mFadeRange))) { mVisible = false; return; } LLVector3 x_pixel_vec; LLVector3 y_pixel_vec; LLViewerCamera::getInstance()->getPixelVectors(mPositionAgent, y_pixel_vec, x_pixel_vec); LLVector3 render_position = mPositionAgent + (x_pixel_vec * mPositionOffset.mV[VX]) + (y_pixel_vec * mPositionOffset.mV[VY]); mOffscreen = false; if (!LLViewerCamera::getInstance()->sphereInFrustum(render_position, mRadius)) { if (!mVisibleOffScreen) { mVisible = false; return; } else { mOffscreen = true; } } mVisible = true; sVisibleTextObjects.push_back(LLPointer (this)); } LLVector2 LLHUDNameTag::updateScreenPos(LLVector2 &offset) { LLCoordGL screen_pos; LLVector2 screen_pos_vec; LLVector3 x_pixel_vec; LLVector3 y_pixel_vec; LLViewerCamera::getInstance()->getPixelVectors(mPositionAgent, y_pixel_vec, x_pixel_vec); LLVector3 world_pos = mPositionAgent + (offset.mV[VX] * x_pixel_vec) + (offset.mV[VY] * y_pixel_vec); if (!LLViewerCamera::getInstance()->projectPosAgentToScreen(world_pos, screen_pos, false) && mVisibleOffScreen) { // bubble off-screen, so find a spot for it along screen edge LLViewerCamera::getInstance()->projectPosAgentToScreenEdge(world_pos, screen_pos); } screen_pos_vec.setVec((F32)screen_pos.mX, (F32)screen_pos.mY); LLRect world_rect = gViewerWindow->getWorldViewRectScaled(); S32 bottom = world_rect.mBottom + STATUS_BAR_HEIGHT; LLVector2 screen_center; screen_center.mV[VX] = llclamp((F32)screen_pos_vec.mV[VX], (F32)world_rect.mLeft + mWidth * 0.5f, (F32)world_rect.mRight - mWidth * 0.5f); if(mVertAlignment == ALIGN_VERT_TOP) { screen_center.mV[VY] = llclamp((F32)screen_pos_vec.mV[VY], (F32)bottom, (F32)world_rect.mTop - mHeight - (F32)MENU_BAR_HEIGHT); mSoftScreenRect.setLeftTopAndSize(screen_center.mV[VX] - (mWidth + BUFFER_SIZE) * 0.5f, screen_center.mV[VY] + (mHeight + BUFFER_SIZE), mWidth + BUFFER_SIZE, mHeight + BUFFER_SIZE); } else { screen_center.mV[VY] = llclamp((F32)screen_pos_vec.mV[VY], (F32)bottom + mHeight * 0.5f, (F32)world_rect.mTop - mHeight * 0.5f - (F32)MENU_BAR_HEIGHT); mSoftScreenRect.setCenterAndSize(screen_center.mV[VX], screen_center.mV[VY], mWidth + BUFFER_SIZE, mHeight + BUFFER_SIZE); } return offset + (screen_center - LLVector2((F32)screen_pos.mX, (F32)screen_pos.mY)); } void LLHUDNameTag::updateSize() { F32 height = 0.f; F32 width = 0.f; S32 max_lines = getMaxLines(); //S32 lines = (max_lines < 0) ? (S32)mTextSegments.size() : llmin((S32)mTextSegments.size(), max_lines); //F32 height = (F32)mFontp->getLineHeight() * (lines + mLabelSegments.size()); S32 start_segment; if (max_lines < 0) start_segment = 0; else start_segment = llmax((S32)0, (S32)mTextSegments.size() - max_lines); std::vector::iterator iter = mTextSegments.begin() + start_segment; while (iter != mTextSegments.end()) { const LLFontGL* fontp = iter->mFont; height += fontp->getLineHeight(); height += LINE_PADDING; width = llmax(width, llmin(iter->getWidth(fontp), NAMETAG_MAX_WIDTH)); ++iter; } // Don't want line spacing under the last line if (height > 0.f) { height -= LINE_PADDING; } iter = mLabelSegments.begin(); while (iter != mLabelSegments.end()) { height += mFontp->getLineHeight(); width = llmax(width, llmin(iter->getWidth(mFontp), NAMETAG_MAX_WIDTH)); ++iter; } if (width == 0.f) { return; } width += HORIZONTAL_PADDING; height += VERTICAL_PADDING; // *TODO: Could do a timer-based resize here //mWidth = llmax(width, lerp(mWidth, (F32)width, u)); //mHeight = llmax(height, lerp(mHeight, (F32)height, u)); mWidth = width; mHeight = height; } void LLHUDNameTag::updateAll() { LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; // iterate over all text objects, calculate their restoration forces, // and add them to the visible set if they are on screen and close enough sVisibleTextObjects.clear(); TextObjectIterator text_it; for (text_it = sTextObjects.begin(); text_it != sTextObjects.end(); ++text_it) { LLHUDNameTag* textp = (*text_it); textp->mTargetPositionOffset.clearVec(); textp->updateSize(); textp->updateVisibility(); } // sort back to front for rendering purposes std::sort(sVisibleTextObjects.begin(), sVisibleTextObjects.end(), llhudnametag_further_away()); // iterate from front to back, and set LOD based on current screen coverage F32 screen_area = (F32)(gViewerWindow->getWindowWidthScaled() * gViewerWindow->getWindowHeightScaled()); F32 current_screen_area = 0.f; std::vector >::reverse_iterator r_it; for (r_it = sVisibleTextObjects.rbegin(); r_it != sVisibleTextObjects.rend(); ++r_it) { LLHUDNameTag* textp = (*r_it); if (current_screen_area / screen_area > LOD_2_SCREEN_COVERAGE) { textp->setLOD(3); } else if (current_screen_area / screen_area > LOD_1_SCREEN_COVERAGE) { textp->setLOD(2); } else if (current_screen_area / screen_area > LOD_0_SCREEN_COVERAGE) { textp->setLOD(1); } else { textp->setLOD(0); } textp->updateSize(); // find on-screen position and initialize collision rectangle textp->mTargetPositionOffset = textp->updateScreenPos(LLVector2::zero); current_screen_area += (F32)(textp->mSoftScreenRect.getWidth() * textp->mSoftScreenRect.getHeight()); } LLTrace::CountStatHandle<>* camera_vel_stat = LLViewerCamera::getVelocityStat(); F32 camera_vel = LLTrace::get_frame_recording().getLastRecording().getPerSec(*camera_vel_stat); if (camera_vel > MAX_STABLE_CAMERA_VELOCITY) { return; } VisibleTextObjectIterator src_it; for (S32 i = 0; i < NUM_OVERLAP_ITERATIONS; i++) { for (src_it = sVisibleTextObjects.begin(); src_it != sVisibleTextObjects.end(); ++src_it) { LLHUDNameTag* src_textp = (*src_it); VisibleTextObjectIterator dst_it = src_it; ++dst_it; for (; dst_it != sVisibleTextObjects.end(); ++dst_it) { LLHUDNameTag* dst_textp = (*dst_it); if (src_textp->mSoftScreenRect.overlaps(dst_textp->mSoftScreenRect)) { LLRectf intersect_rect = src_textp->mSoftScreenRect; intersect_rect.intersectWith(dst_textp->mSoftScreenRect); intersect_rect.stretch(-BUFFER_SIZE * 0.5f); F32 src_center_x = src_textp->mSoftScreenRect.getCenterX(); F32 src_center_y = src_textp->mSoftScreenRect.getCenterY(); F32 dst_center_x = dst_textp->mSoftScreenRect.getCenterX(); F32 dst_center_y = dst_textp->mSoftScreenRect.getCenterY(); F32 intersect_center_x = intersect_rect.getCenterX(); F32 intersect_center_y = intersect_rect.getCenterY(); LLVector2 force = lerp(LLVector2(dst_center_x - intersect_center_x, dst_center_y - intersect_center_y), LLVector2(intersect_center_x - src_center_x, intersect_center_y - src_center_y), 0.5f); force.setVec(dst_center_x - src_center_x, dst_center_y - src_center_y); force.normVec(); LLVector2 src_force = -1.f * force; LLVector2 dst_force = force; LLVector2 force_strength; F32 src_mult = dst_textp->mMass / (dst_textp->mMass + src_textp->mMass); F32 dst_mult = 1.f - src_mult; F32 src_aspect_ratio = src_textp->mSoftScreenRect.getWidth() / src_textp->mSoftScreenRect.getHeight(); F32 dst_aspect_ratio = dst_textp->mSoftScreenRect.getWidth() / dst_textp->mSoftScreenRect.getHeight(); src_force.mV[VY] *= src_aspect_ratio; src_force.normVec(); dst_force.mV[VY] *= dst_aspect_ratio; dst_force.normVec(); src_force.mV[VX] *= llmin(intersect_rect.getWidth() * src_mult, intersect_rect.getHeight() * SPRING_STRENGTH); src_force.mV[VY] *= llmin(intersect_rect.getHeight() * src_mult, intersect_rect.getWidth() * SPRING_STRENGTH); dst_force.mV[VX] *= llmin(intersect_rect.getWidth() * dst_mult, intersect_rect.getHeight() * SPRING_STRENGTH); dst_force.mV[VY] *= llmin(intersect_rect.getHeight() * dst_mult, intersect_rect.getWidth() * SPRING_STRENGTH); src_textp->mTargetPositionOffset += src_force; dst_textp->mTargetPositionOffset += dst_force; src_textp->mTargetPositionOffset = src_textp->updateScreenPos(src_textp->mTargetPositionOffset); dst_textp->mTargetPositionOffset = dst_textp->updateScreenPos(dst_textp->mTargetPositionOffset); } } } } VisibleTextObjectIterator this_object_it; for (this_object_it = sVisibleTextObjects.begin(); this_object_it != sVisibleTextObjects.end(); ++this_object_it) { (*this_object_it)->mPositionOffset = lerp((*this_object_it)->mPositionOffset, (*this_object_it)->mTargetPositionOffset, LLSmoothInterpolation::getInterpolant(POSITION_DAMPING_TC)); } } void LLHUDNameTag::setLOD(S32 lod) { mLOD = lod; //RN: uncomment this to visualize LOD levels //std::string label = llformat("%d", lod); //setLabel(label); } S32 LLHUDNameTag::getMaxLines() { switch(mLOD) { case 0: return mMaxLines; case 1: return mMaxLines > 0 ? mMaxLines / 2 : 5; case 2: return mMaxLines > 0 ? mMaxLines / 3 : 2; default: // label only return 0; } } void LLHUDNameTag::markDead() { sTextObjects.erase(LLPointer(this)); LLHUDObject::markDead(); } void LLHUDNameTag::shiftAll(const LLVector3& offset) { TextObjectIterator text_it; for (text_it = sTextObjects.begin(); text_it != sTextObjects.end(); ++text_it) { LLHUDNameTag *textp = text_it->get(); textp->shift(offset); } } void LLHUDNameTag::shift(const LLVector3& offset) { mPositionAgent += offset; } F32 LLHUDNameTag::getWorldHeight() const { const LLViewerCamera* camera = LLViewerCamera::getInstance(); F32 height_meters = mLastDistance * (F32)tan(camera->getView() / 2.f); F32 height_pixels = camera->getViewHeightInPixels() / 2.f; F32 meters_per_pixel = height_meters / height_pixels; return mHeight * meters_per_pixel * gViewerWindow->getDisplayScale().mV[VY]; } //static void LLHUDNameTag::addPickable(std::set &pick_list) { //this might put an object on the pick list a second time, overriding it's mGLName, which is ok // *FIX: we should probably cull against pick frustum VisibleTextObjectIterator text_it; for (text_it = sVisibleTextObjects.begin(); text_it != sVisibleTextObjects.end(); ++text_it) { pick_list.insert((*text_it)->mSourceObject); } } //static // called when UI scale changes, to flush font width caches void LLHUDNameTag::reshape() { TextObjectIterator text_it; for (text_it = sTextObjects.begin(); text_it != sTextObjects.end(); ++text_it) { LLHUDNameTag* textp = (*text_it); std::vector::iterator segment_iter; for (segment_iter = textp->mTextSegments.begin(); segment_iter != textp->mTextSegments.end(); ++segment_iter ) { segment_iter->clearFontWidthMap(); } for(segment_iter = textp->mLabelSegments.begin(); segment_iter != textp->mLabelSegments.end(); ++segment_iter ) { segment_iter->clearFontWidthMap(); } } } //============================================================================ F32 LLHUDNameTag::LLHUDTextSegment::getWidth(const LLFontGL* font) { std::map::iterator iter = mFontWidthMap.find(font); if (iter != mFontWidthMap.end()) { return iter->second; } else { F32 width = font->getWidthF32(mText.c_str()); mFontWidthMap[font] = width; return width; } }