void render_ui_2d() { LLGLSUIDefault gls_ui; ///////////////////////////////////////////////////////////// // // Render 2D UI elements that overlay the world (no z compare) // Disable wireframe mode below here, as this is HUD/menus glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); // Menu overlays, HUD, etc gViewerWindow->setup2DRender(); F32 zoom_factor = LLViewerCamera::getInstance()->getZoomFactor(); S16 sub_region = LLViewerCamera::getInstance()->getZoomSubRegion(); if (zoom_factor > 1.f) { //decompose subregion number to x and y values int pos_y = sub_region / llceil(zoom_factor); int pos_x = sub_region - (pos_y*llceil(zoom_factor)); // offset for this tile LLFontGL::sCurOrigin.mX -= llround((F32)gViewerWindow->getWindowWidth() * (F32)pos_x / zoom_factor); LLFontGL::sCurOrigin.mY -= llround((F32)gViewerWindow->getWindowHeight() * (F32)pos_y / zoom_factor); } stop_glerror(); gGL.getTexUnit(0)->setTextureBlendType(LLTexUnit::TB_MULT); // render outline for HUD if (gAgent.getAvatarObject() && gAgent.mHUDCurZoom < 0.98f) { glPushMatrix(); S32 half_width = (gViewerWindow->getWindowWidth() / 2); S32 half_height = (gViewerWindow->getWindowHeight() / 2); glScalef(LLUI::sGLScaleFactor.mV[0], LLUI::sGLScaleFactor.mV[1], 1.f); glTranslatef((F32)half_width, (F32)half_height, 0.f); F32 zoom = gAgent.mHUDCurZoom; glScalef(zoom,zoom,1.f); gGL.color4fv(LLColor4::white.mV); gl_rect_2d(-half_width, half_height, half_width, -half_height, FALSE); glPopMatrix(); stop_glerror(); } gViewerWindow->draw(); if (gDebugSelect) { gViewerWindow->drawPickBuffer(); } // reset current origin for font rendering, in case of tiling render LLFontGL::sCurOrigin.set(0, 0); }
// static void LLScreenClipRect::updateScissorRegion() { LLRect rect = sClipRectStack.top(); stop_glerror(); S32 x,y,w,h; x = llfloor(rect.mLeft * LLUI::getScaleFactor().mV[VX]); y = llfloor(rect.mBottom * LLUI::getScaleFactor().mV[VY]); w = llmax(0, llceil(rect.getWidth() * LLUI::getScaleFactor().mV[VX])) + 1; h = llmax(0, llceil(rect.getHeight() * LLUI::getScaleFactor().mV[VY])) + 1; gGL.setScissor( x,y,w,h ); stop_glerror(); }
void LLWorld::setLandFarClip(const F32 far_clip) { static S32 const rwidth = (S32)REGION_WIDTH_U32; S32 const n1 = (llceil(mLandFarClip) - 1) / rwidth; S32 const n2 = (llceil(far_clip) - 1) / rwidth; bool need_water_objects_update = n1 != n2; mLandFarClip = far_clip; if (need_water_objects_update) { updateWaterObjects(); } }
void LLWorld::setLandFarClip(const F32 far_clip) { static S32 const rwidth = (S32)getRegionWidthInMeters(); S32 const n1 = (llceil(mLandFarClip) - 1) / rwidth; S32 const n2 = (llceil(far_clip) - 1) / rwidth; bool need_water_objects_update = n1 != n2; mLandFarClip = far_clip; if (need_water_objects_update) { updateWaterObjects(); } }
bool LLAudioEngine_OpenAL::initWind() { ALenum error; llinfos << "LLAudioEngine_OpenAL::initWind() start" << llendl; mNumEmptyWindALBuffers = MAX_NUM_WIND_BUFFERS; alGetError(); /* clear error */ alGenSources(1,&mWindSource); if((error=alGetError()) != AL_NO_ERROR) { llwarns << "LLAudioEngine_OpenAL::initWind() Error creating wind sources: "<<error<<llendl; } mWindGen = new LLWindGen<WIND_SAMPLE_T>; mWindBufFreq = mWindGen->getInputSamplingRate(); mWindBufSamples = llceil(mWindBufFreq * WIND_BUFFER_SIZE_SEC); mWindBufBytes = mWindBufSamples * 2 /*stereo*/ * sizeof(WIND_SAMPLE_T); mWindBuf = new WIND_SAMPLE_T [mWindBufSamples * 2 /*stereo*/]; if(mWindBuf==NULL) { llerrs << "LLAudioEngine_OpenAL::initWind() Error creating wind memory buffer" << llendl; return false; } llinfos << "LLAudioEngine_OpenAL::initWind() done" << llendl; return true; }
LLDrawable *LLVOSurfacePatch::createDrawable(LLPipeline *pipeline) { pipeline->allocDrawable(this); mDrawable->setRenderType(LLPipeline::RENDER_TYPE_TERRAIN); mBaseComp = llfloor(mPatchp->getMinComposition()); S32 min_comp, max_comp, range; min_comp = llfloor(mPatchp->getMinComposition()); max_comp = llceil(mPatchp->getMaxComposition()); range = (max_comp - min_comp); range++; if (range > 3) { if ((mPatchp->getMinComposition() - min_comp) > (max_comp - mPatchp->getMaxComposition())) { // The top side runs over more mBaseComp++; } range = 3; } LLFacePool *poolp = getPool(); mDrawable->addFace(poolp, NULL); return mDrawable; }
void LLAudioEngine_OpenAL::initWind() { ALenum error; LL_INFOS("OpenAL") << "LLAudioEngine_OpenAL::initWind() start" << LL_ENDL; mNumEmptyWindALBuffers = MAX_NUM_WIND_BUFFERS; alGetError(); /* clear error */ alGenSources(1,&mWindSource); if((error=alGetError()) != AL_NO_ERROR) { LL_WARNS("OpenAL") << "LLAudioEngine_OpenAL::initWind() Error creating wind sources: "<<error<<LL_ENDL; } mWindGen = new LLWindGen<WIND_SAMPLE_T>; const float WIND_BUFFER_SIZE_SEC = 0.05f; // 1/20th sec mWindBufFreq = mWindGen->getInputSamplingRate(); mWindBufSamples = llceil(mWindBufFreq * WIND_BUFFER_SIZE_SEC); mWindBufBytes = mWindBufSamples * 2 /*stereo*/ * sizeof(WIND_SAMPLE_T); mWindBuf = new WIND_SAMPLE_T [mWindBufSamples * 2 /*stereo*/]; if(mWindBuf==NULL) { LL_ERRS("OpenAL") << "LLAudioEngine_OpenAL::initWind() Error creating wind memory buffer" << LL_ENDL; mEnableWind=false; } LL_INFOS("OpenAL") << "LLAudioEngine_OpenAL::initWind() done" << LL_ENDL; }
// virtual void LLPreviewTexture::reshape(S32 width, S32 height, BOOL called_from_parent) { LLPreview::reshape(width, height, called_from_parent); LLRect dim_rect; LLView *pView = findChildView( "dimensions" ); if( pView ) dim_rect = pView->getRect(); S32 horiz_pad = 2 * (LLPANEL_BORDER_WIDTH + PREVIEW_PAD) + PREVIEW_RESIZE_HANDLE_SIZE; // add space for dimensions and aspect ratio S32 info_height = dim_rect.mTop + CLIENT_RECT_VPAD; LLRect client_rect(horiz_pad, getRect().getHeight(), getRect().getWidth() - horiz_pad, 0); client_rect.mTop -= (PREVIEW_HEADER_SIZE + CLIENT_RECT_VPAD); client_rect.mBottom += PREVIEW_BORDER + CLIENT_RECT_VPAD + info_height ; S32 client_width = client_rect.getWidth(); S32 client_height = client_rect.getHeight(); if (mAspectRatio > 0.f) { if(mAspectRatio > 1.f) { client_height = llceil((F32)client_width / mAspectRatio); if(client_height > client_rect.getHeight()) { client_height = client_rect.getHeight(); client_width = llceil((F32)client_height * mAspectRatio); } } else//mAspectRatio < 1.f { client_width = llceil((F32)client_height * mAspectRatio); if(client_width > client_rect.getWidth()) { client_width = client_rect.getWidth(); client_height = llceil((F32)client_width / mAspectRatio); } } } mClientRect.setLeftTopAndSize(client_rect.getCenterX() - (client_width / 2), client_rect.getCenterY() + (client_height / 2), client_width, client_height); }
void LLWorld::setLandFarClip(const F32 far_clip) { // <FS:CR> Aurora Sim //static S32 const rwidth = (S32)REGION_WIDTH_U32; S32 const rwidth = (S32)getRegionWidthInMeters(); // </FS:CR> Aurora Sim S32 const n1 = (llceil(mLandFarClip) - 1) / rwidth; S32 const n2 = (llceil(far_clip) - 1) / rwidth; bool need_water_objects_update = n1 != n2; mLandFarClip = far_clip; if (need_water_objects_update) { updateWaterObjects(); } }
//static void LLScreenClipRect::updateScissorRegion() { if (sClipRectStack.empty()) return; // finish any deferred calls in the old clipping region gGL.flush(); LLRect rect = sClipRectStack.top(); stop_glerror(); S32 x,y,w,h; x = llfloor(rect.mLeft * LLUI::sGLScaleFactor.mV[VX]); y = llfloor(rect.mBottom * LLUI::sGLScaleFactor.mV[VY]); w = llmax(0, llceil(rect.getWidth() * LLUI::sGLScaleFactor.mV[VX])) + 1; h = llmax(0, llceil(rect.getHeight() * LLUI::sGLScaleFactor.mV[VY])) + 1; glScissor( x,y,w,h ); stop_glerror(); }
F32 lggBeamMaps::setUpAndGetDuration() { static LLCachedControl<std::string> settingNameCached(gSavedSettings, "FSBeamShape"); std::string settingName(settingNameCached); if(settingName != lastFileName) { lastFileName = settingName; if( settingName != "===OFF===" && settingName != "") { std::string path_name(gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "beams", "")); std::string path_name2(gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS , "beams", "")); std::string filename = path_name + settingName + ".xml"; if (!gDirUtilp->fileExists(filename)) { filename =path_name2 + settingName +".xml"; } LLSD mydata = getPic(filename); scale = (F32)mydata["scale"].asReal()/10.0f; LLSD myPicture = mydata["data"]; dots.clear(); for (int i = 0; i < myPicture.size(); i++) { LLSD beamData = myPicture[i]; lggBeamData dot; dot.p = LLVector3d(beamData["offset"]); // <FS:PP> Attempt to speed up things a little // dot.p *= (gSavedSettings.getF32("FSBeamShapeScale")*2.0f); static LLCachedControl<F32> FSBeamShapeScale(gSavedSettings, "FSBeamShapeScale"); dot.p *= (FSBeamShapeScale*2.0f); // </FS:PP> LLColor4 color = LLColor4(beamData["color"]); dot.c = LLColor4U(color); dots.push_back(dot); } // <FS:PP> Attempt to speed up things a little // F32 maxBPerQS = gSavedSettings.getF32("FSMaxBeamsPerSecond") / 4.0f; static LLCachedControl<F32> FSMaxBeamsPerSecond(gSavedSettings, "FSMaxBeamsPerSecond"); F32 maxBPerQS = FSMaxBeamsPerSecond / 4.0f; // </FS:PP> duration = llceil((F32)(myPicture.size()) / maxBPerQS) * 0.25f; llinfos << "reading it all now size is " << myPicture.size() << " and duration is " << duration << llendl; } else { dots.clear(); scale = 0.0f;//used as a flag too duration = 0.25f; } } //llinfos << "sent final dur of " << duration << llendl; return duration; }
F32 lggBeamMaps::setUpAndGetDuration() { static std::string* settingName = rebind_llcontrol<std::string>("EmeraldBeamShape", &gSavedSettings, true); if(*settingName != lastFileName) { lastFileName = *settingName; if( *settingName != "===OFF===" && *settingName != "") { std::string path_name(gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "beams", "")); std::string path_name2(gDirUtilp->getExpandedFilename( LL_PATH_USER_SETTINGS , "beams", "")); std::string filename = path_name + *settingName + ".xml"; if(gDirUtilp->fileExists(filename)) { }else { filename =path_name2 + *settingName +".xml"; } LLSD mydata = getPic(filename); scale = (F32)mydata["scale"].asReal()/10.0f; LLSD myPicture = mydata["data"]; dots.clear(); for(int i = 0; i < myPicture.size(); i++) { LLSD beamData = myPicture[i]; lggBeamData dot; dot.p = beamData["offset"]; dot.p *= (gSavedSettings.getF32("EmeraldBeamShapeScale")*2.0f); LLColor4 color = beamData["color"]; dot.c = LLColor4U(color); dots.push_back(dot); } F32 maxBPerQS = gSavedSettings.getF32("EmeraldMaxBeamsPerSecond") / 4.0f; duration = llceil((F32)(myPicture.size()) / maxBPerQS) * 0.25f; llinfos << "reading it all now size is " << myPicture.size() << " and duration is " << duration << llendl; }else { dots.clear(); scale = 0.0f;//used as a flag too duration = 0.25f; } } //llinfos << "sent final dur of " << duration << llendl; return duration; }
F32 lggBeamMaps::setUpAndGetDuration() { static LLCachedControl<std::string> settingNameCached(gSavedSettings, "FSBeamShape"); std::string settingName(settingNameCached); if (settingName != mLastFileName) { mLastFileName = settingName; if (!settingName.empty()) { std::string path_name(gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "beams", "")); std::string path_name2(gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS , "beams", "")); std::string filename = path_name + settingName + ".xml"; if (!gDirUtilp->fileExists(filename)) { filename = path_name2 + settingName + ".xml"; } LLSD mydata = getPic(filename); mScale = (F32)mydata["scale"].asReal() / 10.0f; LLSD myPicture = mydata["data"]; mDots.clear(); for (LLSD::array_iterator it = myPicture.beginArray(); it != myPicture.endArray(); ++it) { LLSD beamData = *it; lggBeamData dot; dot.p = LLVector3d(beamData["offset"]); static LLCachedControl<F32> FSBeamShapeScale(gSavedSettings, "FSBeamShapeScale"); dot.p *= (FSBeamShapeScale * 2.0f); LLColor4 color = LLColor4(beamData["color"]); dot.c = LLColor4U(color); mDots.push_back(dot); } static LLCachedControl<F32> FSMaxBeamsPerSecond(gSavedSettings, "FSMaxBeamsPerSecond"); F32 maxBPerQS = FSMaxBeamsPerSecond / 4.0f; mDuration = llceil((F32)(myPicture.size()) / maxBPerQS) * 0.25f; LL_INFOS("LGG_Beams") << "reading it all now size is " << myPicture.size() << " and duration is " << mDuration << LL_ENDL; } else { mDots.clear(); mScale = 0.0f; //used as a flag too mDuration = 0.25f; } } return mDuration; }
/*virtual*/ bool getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const { if (num_chars == 0) { width = 0; height = 0; } else { width = EMBEDDED_ITEM_LABEL_PADDING + mImage->getWidth() + mStyle->getFont()->getWidth(mLabel.c_str()); height = llmax(mImage->getHeight(), llceil(mStyle->getFont()->getLineHeight())); } return false; }
/*virtual*/ bool getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const { // more label always spans width of text box if (num_chars == 0) { width = 0; height = 0; } else { width = mEditor.getDocumentView()->getRect().getWidth() - mEditor.getHPad(); height = llceil(mStyle->getFont()->getLineHeight()); } return true; }
LLRect get_whole_screen_region() { LLRect whole_screen = gViewerWindow->getWorldViewRectScaled(); // apply camera zoom transform (for high res screenshots) F32 zoom_factor = LLViewerCamera::getInstance()->getZoomFactor(); S16 sub_region = LLViewerCamera::getInstance()->getZoomSubRegion(); if (zoom_factor > 1.f) { S32 num_horizontal_tiles = llceil(zoom_factor); S32 tile_width = ll_round((F32)gViewerWindow->getWorldViewWidthScaled() / zoom_factor); S32 tile_height = ll_round((F32)gViewerWindow->getWorldViewHeightScaled() / zoom_factor); int tile_y = sub_region / num_horizontal_tiles; int tile_x = sub_region - (tile_y * num_horizontal_tiles); whole_screen.setLeftTopAndSize(tile_x * tile_width, gViewerWindow->getWorldViewHeightScaled() - (tile_y * tile_height), tile_width, tile_height); } return whole_screen; }
BOOL setup_hud_matrices() { LLRect whole_screen = gViewerWindow->getVirtualWindowRect(); // apply camera zoom transform (for high res screenshots) F32 zoom_factor = LLViewerCamera::getInstance()->getZoomFactor(); S16 sub_region = LLViewerCamera::getInstance()->getZoomSubRegion(); if (zoom_factor > 1.f) { S32 num_horizontal_tiles = llceil(zoom_factor); S32 tile_width = llround((F32)gViewerWindow->getWindowWidth() / zoom_factor); S32 tile_height = llround((F32)gViewerWindow->getWindowHeight() / zoom_factor); int tile_y = sub_region / num_horizontal_tiles; int tile_x = sub_region - (tile_y * num_horizontal_tiles); glh::matrix4f mat; whole_screen.setLeftTopAndSize(tile_x * tile_width, gViewerWindow->getWindowHeight() - (tile_y * tile_height), tile_width, tile_height); } return setup_hud_matrices(whole_screen); }
// static LLRect LLNotifyBox::getNotifyTipRect(const std::string &utf8message) { LLWString message = utf8str_to_wstring(utf8message); S32 message_len = message.length(); const S32 NOTIFY_WIDTH = gSavedSettings.getS32("NotifyBoxWidth"); // Make room for the icon area. const S32 text_area_width = NOTIFY_WIDTH - HPAD * 4 - 32; const llwchar* wchars = message.c_str(); const llwchar* start = wchars; const llwchar* end; S32 total_drawn = 0; bool done = false; S32 line_count; for (line_count = 2; !done; ++line_count) { for (end = start; *end != 0 && *end != '\n'; end++); if (*end == 0) { end = wchars + message_len; done = true; } for (S32 remaining = end - start; remaining;) { S32 drawn = sFont->maxDrawableChars(start, (F32)text_area_width, remaining, LLFontGL::WORD_BOUNDARY_IF_POSSIBLE); if (0 == drawn) { drawn = 1; // Draw at least one character, even if it doesn't all fit. (avoids an infinite loop) } total_drawn += drawn; start += drawn; remaining -= drawn; if (total_drawn < message_len) { if (wchars[ total_drawn ] != '\n') { // wrap because line was too long line_count++; } } else { done = true; } } total_drawn++; // for '\n' start = ++end; } const S32 MIN_NOTIFY_HEIGHT = 72; const S32 MAX_NOTIFY_HEIGHT = 600; S32 notify_height = llceil((F32) (line_count+1) * sFont->getLineHeight()); if (gOverlayBar) { notify_height += gOverlayBar->getBoundingRect().mTop; } else { // *FIX: this is derived from the padding caused by the // rounded rects, shouldn't be a const here. notify_height += 10; } notify_height += VPAD; notify_height = llclamp(notify_height, MIN_NOTIFY_HEIGHT, MAX_NOTIFY_HEIGHT); const S32 RIGHT = gNotifyBoxView->getRect().getWidth(); const S32 LEFT = RIGHT - NOTIFY_WIDTH; // Make sure it goes slightly offscreen return LLRect(LEFT, notify_height-1, RIGHT, -1); }
S32 LLFontGL::render(const LLWString &wstr, const S32 begin_offset, const F32 x, const F32 y, const LLColor4 &color, const HAlign halign, const VAlign valign, U8 style, const S32 max_chars, S32 max_pixels, F32* right_x, BOOL use_embedded, BOOL use_ellipses) const { if(!sDisplayFont) //do not display texts { return wstr.length() ; } LLGLEnable tex(GL_TEXTURE_2D); if (wstr.empty()) { return 0; } S32 scaled_max_pixels = max_pixels == S32_MAX ? S32_MAX : llceil((F32)max_pixels * sScaleX); // HACK for better bolding if (style & BOLD) { if (this == LLFontGL::sSansSerif) { return LLFontGL::sSansSerifBold->render( wstr, begin_offset, x, y, color, halign, valign, (style & ~BOLD), max_chars, max_pixels, right_x, use_embedded); } } F32 drop_shadow_strength = 0.f; if (style & (DROP_SHADOW | DROP_SHADOW_SOFT)) { F32 luminance; color.calcHSL(NULL, NULL, &luminance); drop_shadow_strength = clamp_rescale(luminance, 0.35f, 0.6f, 0.f, 1.f); if (luminance < 0.35f) { style = style & ~(DROP_SHADOW | DROP_SHADOW_SOFT); } } gGL.pushMatrix(); glLoadIdentity(); gGL.translatef(floorf(sCurOrigin.mX*sScaleX), floorf(sCurOrigin.mY*sScaleY), sCurOrigin.mZ); //glScalef(sScaleX, sScaleY, 1.0f); // avoid half pixels // RN: if we're going to this trouble, might as well snap to nearest pixel all the time // but the plan is to get rid of this so that fonts "just work" //F32 half_pixel_distance = llabs(fmodf(sCurOrigin.mX * sScaleX, 1.f) - 0.5f); //if (half_pixel_distance < PIXEL_BORDER_THRESHOLD) //{ gGL.translatef(PIXEL_CORRECTION_DISTANCE*sScaleX, 0.f, 0.f); //} // this code would just snap to pixel grid, although it seems to introduce more jitter //F32 pixel_offset_x = llround(sCurOrigin.mX * sScaleX) - (sCurOrigin.mX * sScaleX); //F32 pixel_offset_y = llround(sCurOrigin.mY * sScaleY) - (sCurOrigin.mY * sScaleY); //gGL.translatef(-pixel_offset_x, -pixel_offset_y, 0.f); // scale back to native pixel size //glScalef(1.f / sScaleX, 1.f / sScaleY, 1.f); //glScaled(1.0 / (F64) sScaleX, 1.0 / (F64) sScaleY, 1.0f); LLFastTimer t(LLFastTimer::FTM_RENDER_FONTS); gGL.color4fv( color.mV ); S32 chars_drawn = 0; S32 i; S32 length; if (-1 == max_chars) { length = (S32)wstr.length() - begin_offset; } else { length = llmin((S32)wstr.length() - begin_offset, max_chars ); } F32 cur_x, cur_y, cur_render_x, cur_render_y; // Bind the font texture mImageGLp->bind(0); // Not guaranteed to be set correctly gGL.setSceneBlendType(LLRender::BT_ALPHA); cur_x = ((F32)x * sScaleX); cur_y = ((F32)y * sScaleY); // Offset y by vertical alignment. switch (valign) { case TOP: cur_y -= mAscender; break; case BOTTOM: cur_y += mDescender; break; case VCENTER: cur_y -= ((mAscender - mDescender)/2.f); break; case BASELINE: // Baseline, do nothing. break; default: break; } switch (halign) { case LEFT: break; case RIGHT: cur_x -= llmin(scaled_max_pixels, llround(getWidthF32(wstr.c_str(), 0, length) * sScaleX)); break; case HCENTER: cur_x -= llmin(scaled_max_pixels, llround(getWidthF32(wstr.c_str(), 0, length) * sScaleX)) / 2; break; default: break; } // Round properly. //cur_render_y = (F32)llfloor(cur_y/sScaleY + 0.5f)*sScaleY; //cur_render_x = (F32)llfloor(cur_x/sScaleX + 0.5f)*sScaleX; cur_render_y = cur_y; cur_render_x = cur_x; F32 start_x = cur_x; F32 inv_width = 1.f / mImageGLp->getWidth(); F32 inv_height = 1.f / mImageGLp->getHeight(); const S32 LAST_CHARACTER = LLFont::LAST_CHAR_FULL; BOOL draw_ellipses = FALSE; if (use_ellipses && halign == LEFT) { // check for too long of a string if (getWidthF32(wstr.c_str(), 0, max_chars) * sScaleX > scaled_max_pixels) { // use four dots for ellipsis width to generate padding const LLWString dots(utf8str_to_wstring(std::string("...."))); scaled_max_pixels = llmax(0, scaled_max_pixels - llround(getWidthF32(dots.c_str()))); draw_ellipses = TRUE; } } for (i = begin_offset; i < begin_offset + length; i++) { llwchar wch = wstr[i]; // Handle embedded characters first, if they're enabled. // Embedded characters are a hack for notecards const embedded_data_t* ext_data = use_embedded ? getEmbeddedCharData(wch) : NULL; if (ext_data) { LLImageGL* ext_image = ext_data->mImage; const LLWString& label = ext_data->mLabel; F32 ext_height = (F32)ext_image->getHeight() * sScaleY; F32 ext_width = (F32)ext_image->getWidth() * sScaleX; F32 ext_advance = (EXT_X_BEARING * sScaleX) + ext_width; if (!label.empty()) { ext_advance += (EXT_X_BEARING + gExtCharFont->getWidthF32( label.c_str() )) * sScaleX; } if (start_x + scaled_max_pixels < cur_x + ext_advance) { // Not enough room for this character. break; } ext_image->bind(); const F32 ext_x = cur_render_x + (EXT_X_BEARING * sScaleX); const F32 ext_y = cur_render_y + (EXT_Y_BEARING * sScaleY + mAscender - mLineHeight); LLRectf uv_rect(0.f, 1.f, 1.f, 0.f); LLRectf screen_rect(ext_x, ext_y + ext_height, ext_x + ext_width, ext_y); drawGlyph(screen_rect, uv_rect, LLColor4::white, style, drop_shadow_strength); if (!label.empty()) { gGL.pushMatrix(); //glLoadIdentity(); //gGL.translatef(sCurOrigin.mX, sCurOrigin.mY, 0.0f); //glScalef(sScaleX, sScaleY, 1.f); gExtCharFont->render(label, 0, /*llfloor*/((ext_x + (F32)ext_image->getWidth() + EXT_X_BEARING) / sScaleX), /*llfloor*/(cur_y / sScaleY), color, halign, BASELINE, NORMAL, S32_MAX, S32_MAX, NULL, TRUE ); gGL.popMatrix(); } gGL.color4fv(color.mV); chars_drawn++; cur_x += ext_advance; if (((i + 1) < length) && wstr[i+1]) { cur_x += EXT_KERNING * sScaleX; } cur_render_x = cur_x; // Bind the font texture mImageGLp->bind(); } else { if (!hasGlyph(wch)) { (const_cast<LLFontGL*>(this))->addChar(wch); } const LLFontGlyphInfo* fgi= getGlyphInfo(wch); if (!fgi) { llerrs << "Missing Glyph Info" << llendl; break; } if ((start_x + scaled_max_pixels) < (cur_x + fgi->mXBearing + fgi->mWidth)) { // Not enough room for this character. break; } // Draw the text at the appropriate location //Specify vertices and texture coordinates LLRectf uv_rect((fgi->mXBitmapOffset - PAD_AMT) * inv_width, (fgi->mYBitmapOffset + fgi->mHeight + PAD_AMT) * inv_height, (fgi->mXBitmapOffset + fgi->mWidth + PAD_AMT) * inv_width, (fgi->mYBitmapOffset - PAD_AMT) * inv_height); LLRectf screen_rect(cur_render_x + (F32)fgi->mXBearing - PAD_AMT, cur_render_y + (F32)fgi->mYBearing + PAD_AMT, cur_render_x + (F32)fgi->mXBearing + (F32)fgi->mWidth + PAD_AMT, cur_render_y + (F32)fgi->mYBearing - (F32)fgi->mHeight - PAD_AMT); drawGlyph(screen_rect, uv_rect, color, style, drop_shadow_strength); chars_drawn++; cur_x += fgi->mXAdvance; cur_y += fgi->mYAdvance; llwchar next_char = wstr[i+1]; if (next_char && (next_char < LAST_CHARACTER)) { // Kern this puppy. if (!hasGlyph(next_char)) { (const_cast<LLFontGL*>(this))->addChar(next_char); } cur_x += getXKerning(wch, next_char); } // Round after kerning. // Must do this to cur_x, not just to cur_render_x, otherwise you // will squish sub-pixel kerned characters too close together. // For example, "CCCCC" looks bad. cur_x = (F32)llfloor(cur_x + 0.5f); //cur_y = (F32)llfloor(cur_y + 0.5f); cur_render_x = cur_x; cur_render_y = cur_y; } } if (right_x) { *right_x = cur_x / sScaleX; } if (style & UNDERLINE) { LLGLSNoTexture no_texture; gGL.begin(LLVertexBuffer::LINES); gGL.vertex2f(start_x, cur_y - (mDescender)); gGL.vertex2f(cur_x, cur_y - (mDescender)); gGL.end(); } // *FIX: get this working in all alignment cases, etc. if (draw_ellipses) { // recursively render ellipses at end of string // we've already reserved enough room gGL.pushMatrix(); //glLoadIdentity(); //gGL.translatef(sCurOrigin.mX, sCurOrigin.mY, 0.0f); //glScalef(sScaleX, sScaleY, 1.f); renderUTF8(std::string("..."), 0, cur_x / sScaleX, (F32)y, color, LEFT, valign, style, S32_MAX, max_pixels, right_x, FALSE); gGL.popMatrix(); } gGL.popMatrix(); return chars_drawn; }
// Returns the max number of complete characters from text (up to max_chars) that can be drawn in max_pixels S32 LLFontGL::maxDrawableChars(const llwchar* wchars, F32 max_pixels, S32 max_chars, BOOL end_on_word_boundary, const BOOL use_embedded, F32* drawn_pixels) const { if (!wchars || !wchars[0] || max_chars == 0) { return 0; } llassert(max_pixels >= 0.f); llassert(max_chars >= 0); BOOL clip = FALSE; F32 cur_x = 0; F32 drawn_x = 0; S32 start_of_last_word = 0; BOOL in_word = FALSE; F32 scaled_max_pixels = (F32)llceil(max_pixels * sScaleX); S32 i; for (i=0; (i < max_chars); i++) { llwchar wch = wchars[i]; if(wch == 0) { // Null terminator. We're done. break; } const embedded_data_t* ext_data = use_embedded ? getEmbeddedCharData(wch) : NULL; if (ext_data) { if (in_word) { in_word = FALSE; } else { start_of_last_word = i; } cur_x += getEmbeddedCharAdvance(ext_data); if (scaled_max_pixels < cur_x) { clip = TRUE; break; } if (((i+1) < max_chars) && wchars[i+1]) { cur_x += EXT_KERNING * sScaleX; } if( scaled_max_pixels < cur_x ) { clip = TRUE; break; } } else { if (in_word) { if (iswspace(wch)) { in_word = FALSE; } } else { start_of_last_word = i; if (!iswspace(wch)) { in_word = TRUE; } } cur_x += getXAdvance(wch); if (scaled_max_pixels < cur_x) { clip = TRUE; break; } if (((i+1) < max_chars) && wchars[i+1]) { // Kern this puppy. cur_x += getXKerning(wch, wchars[i+1]); } } // Round after kerning. cur_x = (F32)llfloor(cur_x + 0.5f); drawn_x = cur_x; } if( clip && end_on_word_boundary && (start_of_last_word != 0) ) { i = start_of_last_word; } if (drawn_pixels) { *drawn_pixels = drawn_x; } return i; }
S32 LLFontGL::render(const LLWString &wstr, const S32 begin_offset, const F32 x, const F32 y, const LLColor4 &color, const HAlign halign, const VAlign valign, U8 style, const S32 max_chars, S32 max_pixels, F32* right_x, BOOL use_embedded, BOOL use_ellipses) const { if(!sDisplayFont) //do not display texts { return wstr.length() ; } if (wstr.empty()) { return 0; } gGL.getTexUnit(0)->enable(LLTexUnit::TT_TEXTURE); S32 scaled_max_pixels = max_pixels == S32_MAX ? S32_MAX : llceil((F32)max_pixels * sScaleX); // Strip off any style bits that are already accounted for by the font. style = style & (~getFontDesc().getStyle()); F32 drop_shadow_strength = 0.f; if (style & (DROP_SHADOW | DROP_SHADOW_SOFT)) { F32 luminance; color.calcHSL(NULL, NULL, &luminance); drop_shadow_strength = clamp_rescale(luminance, 0.35f, 0.6f, 0.f, 1.f); if (luminance < 0.35f) { style = style & ~(DROP_SHADOW | DROP_SHADOW_SOFT); } } gGL.pushMatrix(); glLoadIdentity(); gGL.translatef(floorf(sCurOrigin.mX*sScaleX), floorf(sCurOrigin.mY*sScaleY), sCurOrigin.mZ); // this code snaps the text origin to a pixel grid to start with F32 pixel_offset_x = llround((F32)sCurOrigin.mX) - (sCurOrigin.mX); F32 pixel_offset_y = llround((F32)sCurOrigin.mY) - (sCurOrigin.mY); gGL.translatef(-pixel_offset_x, -pixel_offset_y, 0.f); LLFastTimer t(LLFastTimer::FTM_RENDER_FONTS); gGL.color4fv( color.mV ); S32 chars_drawn = 0; S32 i; S32 length; if (-1 == max_chars) { length = (S32)wstr.length() - begin_offset; } else { length = llmin((S32)wstr.length() - begin_offset, max_chars ); } F32 cur_x, cur_y, cur_render_x, cur_render_y; // Not guaranteed to be set correctly gGL.setSceneBlendType(LLRender::BT_ALPHA); cur_x = ((F32)x * sScaleX); cur_y = ((F32)y * sScaleY); // Offset y by vertical alignment. switch (valign) { case TOP: cur_y -= mAscender; break; case BOTTOM: cur_y += mDescender; break; case VCENTER: cur_y -= ((mAscender - mDescender)/2.f); break; case BASELINE: // Baseline, do nothing. break; default: break; } switch (halign) { case LEFT: break; case RIGHT: cur_x -= llmin(scaled_max_pixels, llround(getWidthF32(wstr.c_str(), 0, length) * sScaleX)); break; case HCENTER: cur_x -= llmin(scaled_max_pixels, llround(getWidthF32(wstr.c_str(), 0, length) * sScaleX)) / 2; break; default: break; } cur_render_y = cur_y; cur_render_x = cur_x; F32 start_x = cur_x; F32 inv_width = 1.f / mFontBitmapCachep->getBitmapWidth(); F32 inv_height = 1.f / mFontBitmapCachep->getBitmapHeight(); const S32 LAST_CHARACTER = LLFont::LAST_CHAR_FULL; BOOL draw_ellipses = FALSE; if (use_ellipses && halign == LEFT) { // check for too long of a string if (getWidthF32(wstr.c_str(), 0, max_chars) * sScaleX > scaled_max_pixels) { // use four dots for ellipsis width to generate padding const LLWString dots(utf8str_to_wstring(std::string("...."))); scaled_max_pixels = llmax(0, scaled_max_pixels - llround(getWidthF32(dots.c_str()))); draw_ellipses = TRUE; } } // Remember last-used texture to avoid unnecesssary bind calls. LLImageGL *last_bound_texture = NULL; for (i = begin_offset; i < begin_offset + length; i++) { llwchar wch = wstr[i]; // Handle embedded characters first, if they're enabled. // Embedded characters are a hack for notecards const embedded_data_t* ext_data = use_embedded ? getEmbeddedCharData(wch) : NULL; if (ext_data) { LLImageGL* ext_image = ext_data->mImage; const LLWString& label = ext_data->mLabel; F32 ext_height = (F32)ext_image->getHeight() * sScaleY; F32 ext_width = (F32)ext_image->getWidth() * sScaleX; F32 ext_advance = (EXT_X_BEARING * sScaleX) + ext_width; if (!label.empty()) { ext_advance += (EXT_X_BEARING + getFontExtChar()->getWidthF32( label.c_str() )) * sScaleX; } if (start_x + scaled_max_pixels < cur_x + ext_advance) { // Not enough room for this character. break; } if (last_bound_texture != ext_image) { gGL.getTexUnit(0)->bind(ext_image); last_bound_texture = ext_image; } // snap origin to whole screen pixel const F32 ext_x = (F32)llround(cur_render_x + (EXT_X_BEARING * sScaleX)); const F32 ext_y = (F32)llround(cur_render_y + (EXT_Y_BEARING * sScaleY + mAscender - mLineHeight)); LLRectf uv_rect(0.f, 1.f, 1.f, 0.f); LLRectf screen_rect(ext_x, ext_y + ext_height, ext_x + ext_width, ext_y); drawGlyph(screen_rect, uv_rect, LLColor4::white, style, drop_shadow_strength); if (!label.empty()) { gGL.pushMatrix(); //glLoadIdentity(); //gGL.translatef(sCurOrigin.mX, sCurOrigin.mY, 0.0f); //glScalef(sScaleX, sScaleY, 1.f); getFontExtChar()->render(label, 0, /*llfloor*/((ext_x + (F32)ext_image->getWidth() + EXT_X_BEARING) / sScaleX), /*llfloor*/(cur_y / sScaleY), color, halign, BASELINE, NORMAL, S32_MAX, S32_MAX, NULL, TRUE ); gGL.popMatrix(); } gGL.color4fv(color.mV); chars_drawn++; cur_x += ext_advance; if (((i + 1) < length) && wstr[i+1]) { cur_x += EXT_KERNING * sScaleX; } cur_render_x = cur_x; } else { if (!hasGlyph(wch)) { addChar(wch); } const LLFontGlyphInfo* fgi= getGlyphInfo(wch); if (!fgi) { llerrs << "Missing Glyph Info" << llendl; break; } // Per-glyph bitmap texture. LLImageGL *image_gl = mFontBitmapCachep->getImageGL(fgi->mBitmapNum); if (last_bound_texture != image_gl) { gGL.getTexUnit(0)->bind(image_gl); last_bound_texture = image_gl; } if ((start_x + scaled_max_pixels) < (cur_x + fgi->mXBearing + fgi->mWidth)) { // Not enough room for this character. break; } // Draw the text at the appropriate location //Specify vertices and texture coordinates LLRectf uv_rect((fgi->mXBitmapOffset) * inv_width, (fgi->mYBitmapOffset + fgi->mHeight + PAD_UVY) * inv_height, (fgi->mXBitmapOffset + fgi->mWidth) * inv_width, (fgi->mYBitmapOffset - PAD_UVY) * inv_height); // snap glyph origin to whole screen pixel LLRectf screen_rect(llround(cur_render_x + (F32)fgi->mXBearing), llround(cur_render_y + (F32)fgi->mYBearing), llround(cur_render_x + (F32)fgi->mXBearing) + (F32)fgi->mWidth, llround(cur_render_y + (F32)fgi->mYBearing) - (F32)fgi->mHeight); drawGlyph(screen_rect, uv_rect, color, style, drop_shadow_strength); chars_drawn++; cur_x += fgi->mXAdvance; cur_y += fgi->mYAdvance; llwchar next_char = wstr[i+1]; if (next_char && (next_char < LAST_CHARACTER)) { // Kern this puppy. if (!hasGlyph(next_char)) { addChar(next_char); } cur_x += getXKerning(wch, next_char); } // Round after kerning. // Must do this to cur_x, not just to cur_render_x, otherwise you // will squish sub-pixel kerned characters too close together. // For example, "CCCCC" looks bad. cur_x = (F32)llfloor(cur_x + 0.5f); //cur_y = (F32)llfloor(cur_y + 0.5f); cur_render_x = cur_x; cur_render_y = cur_y; } } if (right_x) { *right_x = cur_x / sScaleX; } if (style & UNDERLINE) { gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); gGL.begin(LLRender::LINES); gGL.vertex2f(start_x, cur_y - (mDescender)); gGL.vertex2f(cur_x, cur_y - (mDescender)); gGL.end(); } // *FIX: get this working in all alignment cases, etc. if (draw_ellipses) { // recursively render ellipses at end of string // we've already reserved enough room gGL.pushMatrix(); //glLoadIdentity(); //gGL.translatef(sCurOrigin.mX, sCurOrigin.mY, 0.0f); //glScalef(sScaleX, sScaleY, 1.f); renderUTF8(std::string("..."), 0, cur_x / sScaleX, (F32)y, color, LEFT, valign, style, S32_MAX, max_pixels, right_x, FALSE); gGL.popMatrix(); } gGL.popMatrix(); return chars_drawn; }
void LLViewerCamera::setPerspective(BOOL for_selection, S32 x, S32 y_from_bot, S32 width, S32 height, BOOL limit_select_distance, F32 z_near, F32 z_far) { F32 fov_y, aspect; fov_y = RAD_TO_DEG * getView(); BOOL z_default_far = FALSE; if (z_far <= 0) { z_default_far = TRUE; z_far = getFar(); } if (z_near <= 0) { z_near = getNear(); } aspect = getAspect(); // Load camera view matrix gGL.matrixMode( LLRender::MM_PROJECTION ); gGL.loadIdentity(); LLMatrix4a proj_mat; proj_mat.setIdentity(); if (for_selection) { // make a tiny little viewport // anything drawn into this viewport will be "selected" const LLRect& rect = gViewerWindow->getWorldViewRectRaw(); const F32 scale_x = rect.getWidth() / F32(width); const F32 scale_y = rect.getHeight() / F32(height); const F32 trans_x = scale_x + (2.f * (rect.mLeft - x)) / F32(width) - 1.f; const F32 trans_y = scale_y + (2.f * (rect.mBottom - y_from_bot)) / F32(height) - 1.f; //Generate a pick matrix proj_mat.applyScale_affine(scale_x, scale_y, 1.f); proj_mat.setTranslate_affine(LLVector3(trans_x, trans_y, 0.f)); if (limit_select_distance) { // ...select distance from control // z_far = gSavedSettings.getF32("MaxSelectDistance"); // [RLVa:KB] - Checked: 2010-04-11 (RLVa-1.2.0e) | Added: RLVa-1.2.0e z_far = (!gRlvHandler.hasBehaviour(RLV_BHVR_FARTOUCH)) ? gSavedSettings.getF32("MaxSelectDistance") : 1.5; // [/RLVa:KB] } else { z_far = gAgentCamera.mDrawDistance; } } else { // Only override the far clip if it's not passed in explicitly. if (z_default_far) { z_far = MAX_FAR_CLIP; } glViewport(x, y_from_bot, width, height); gGLViewport[0] = x; gGLViewport[1] = y_from_bot; gGLViewport[2] = width; gGLViewport[3] = height; } if (mZoomFactor > 1.f) { float offset = mZoomFactor - 1.f; int pos_y = mZoomSubregion / llceil(mZoomFactor); int pos_x = mZoomSubregion - (pos_y*llceil(mZoomFactor)); proj_mat.applyTranslation_affine(offset - (F32)pos_x * 2.f, offset - (F32)pos_y * 2.f, 0.f); proj_mat.applyScale_affine(mZoomFactor,mZoomFactor,1.f); } calcProjection(z_far); // Update the projection matrix cache proj_mat.mul(gGL.genPersp(fov_y,aspect,z_near,z_far)); gGL.loadMatrix(proj_mat); gGLProjection = proj_mat; gGL.matrixMode(LLRender::MM_MODELVIEW ); LLMatrix4a ogl_matrix; getOpenGLTransform(ogl_matrix.getF32ptr()); LLMatrix4a modelview; modelview.setMul(OGL_TO_CFR_ROTATION, ogl_matrix); gGL.loadMatrix(modelview); if (for_selection && (width > 1 || height > 1)) { // NB: as of this writing, i believe the code below is broken (doesn't take into account the world view, assumes entire window) // however, it is also unused (the GL matricies are used for selection, (see LLCamera::sphereInFrustum())) and so i'm not // comfortable hacking on it. calculateFrustumPlanesFromWindow((F32)(x - width / 2) / (F32)gViewerWindow->getWindowWidthScaled() - 0.5f, (F32)(y_from_bot - height / 2) / (F32)gViewerWindow->getWindowHeightScaled() - 0.5f, (F32)(x + width / 2) / (F32)gViewerWindow->getWindowWidthScaled() - 0.5f, (F32)(y_from_bot + height / 2) / (F32)gViewerWindow->getWindowHeightScaled() - 0.5f); } // if not picking and not doing a snapshot, cache various GL matrices if (!for_selection && mZoomFactor == 1.f) { // Save GL matrices for access elsewhere in code, especially project_world_to_screen //glGetDoublev(GL_MODELVIEW_MATRIX, gGLModelView); glh_set_current_modelview(modelview); } updateFrustumPlanes(*this); /*if (gSavedSettings.getBOOL("CameraOffset")) { gGL.matrixMode(LLRender::MM_PROJECTION); gGL.translatef(0,0,-50); gGL.rotatef(20.0,1,0,0); gGL.matrixMode(LLRender::MM_MODELVIEW); }*/ }
//----------------------------------------------------------------------------- // updateMotion() //----------------------------------------------------------------------------- void LLMotionController::updateMotions(bool force_update) { BOOL use_quantum = (mTimeStep != 0.f); // Always update mPrevTimerElapsed F32 cur_time = mTimer.getElapsedTimeF32(); F32 delta_time = cur_time - mPrevTimerElapsed; mPrevTimerElapsed = cur_time; mLastTime = mAnimTime; // Always cap the number of loaded motions purgeExcessMotions(); // Update timing info for this time step. if (!mPaused) { F32 update_time = mAnimTime + delta_time * mTimeFactor; if (use_quantum) { F32 time_interval = fmodf(update_time, mTimeStep); //<singu> // This old code is nonsense. //S32 quantum_count = llmax(0, ll_round((update_time - time_interval) / mTimeStep)) + 1; // (update_time - time_interval) / mTimeStep is an integer! We need ll_round to get rid of floating point errors, not llfloor. // Moreover, just rounding off to the nearest integer with ll_round(update_time / mTimeStep) makes a lot more sense: // it is the best we can do to get as close to what we should draw as possible. // However, mAnimTime may only be incremented; therefore make sure of that with the llmax. S32 quantum_count = llmax(ll_round(update_time / mTimeStep), llceil(mAnimTime / mTimeStep)); //</singu> if (quantum_count == mTimeStepCount) { // we're still in same time quantum as before, so just interpolate and exit if (!mPaused) { F32 interp = time_interval / mTimeStep; mPoseBlender.interpolate(interp - mLastInterp); mLastInterp = interp; } updateLoadingMotions(); return; } // is calculating a new keyframe pose, make sure the last one gets applied mPoseBlender.interpolate(1.f); clearBlenders(); mTimeStepCount = quantum_count; // Singu note: mAnimTime may never be set back in time. // Despite the llmax/llceil above, (F32)quantum_count * mTimeStep can still // be a tiny bit smaller than mAnimTime due to floating point round off errors. mAnimTime = llmax(mAnimTime, (F32)quantum_count * mTimeStep); mLastInterp = 0.f; } else { // Singu note: mAnimTime may never be set back in time. mAnimTime = llmax(mAnimTime, update_time); } } updateLoadingMotions(); resetJointSignatures(); if (mPaused && !force_update) { updateIdleActiveMotions(); } else { // update additive motions updateAdditiveMotions(); resetJointSignatures(); // update all regular motions updateRegularMotions(); if (use_quantum) { mPoseBlender.blendAndCache(TRUE); } else { mPoseBlender.blendAndApply(); } } mHasRunOnce = TRUE; // llinfos << "Motion controller time " << motionTimer.getElapsedTimeF32() << llendl; }
void LLViewerCamera::setPerspective(BOOL for_selection, S32 x, S32 y_from_bot, S32 width, S32 height, BOOL limit_select_distance, F32 z_near, F32 z_far) { F32 fov_y, aspect; fov_y = RAD_TO_DEG * getView(); BOOL z_default_near, z_default_far = FALSE; if (z_far <= 0) { z_default_far = TRUE; z_far = getFar(); } if (z_near <= 0) { z_default_near = TRUE; z_near = getNear(); } aspect = getAspect(); // Load camera view matrix glMatrixMode( GL_PROJECTION ); glLoadIdentity(); glh::matrix4f proj_mat; if (for_selection) { // make a tiny little viewport // anything drawn into this viewport will be "selected" GLint viewport[4]; viewport[0] = gViewerWindow->getWorldViewRectRaw().mLeft; viewport[1] = gViewerWindow->getWorldViewRectRaw().mBottom; viewport[2] = gViewerWindow->getWorldViewRectRaw().getWidth(); viewport[3] = gViewerWindow->getWorldViewRectRaw().getHeight(); proj_mat = gl_pick_matrix(x+width/2.f, y_from_bot+height/2.f, (GLfloat) width, (GLfloat) height, viewport); if (limit_select_distance) { // ...select distance from control z_far = gSavedSettings.getF32("MaxSelectDistance"); } else { z_far = gAgent.mDrawDistance; } } else { // Only override the far clip if it's not passed in explicitly. if (z_default_far) { z_far = MAX_FAR_CLIP; } glViewport(x, y_from_bot, width, height); gGLViewport[0] = x; gGLViewport[1] = y_from_bot; gGLViewport[2] = width; gGLViewport[3] = height; } if (mZoomFactor > 1.f) { float offset = mZoomFactor - 1.f; int pos_y = mZoomSubregion / llceil(mZoomFactor); int pos_x = mZoomSubregion - (pos_y*llceil(mZoomFactor)); glh::matrix4f translate; translate.set_translate(glh::vec3f(offset - (F32)pos_x * 2.f, offset - (F32)pos_y * 2.f, 0.f)); glh::matrix4f scale; scale.set_scale(glh::vec3f(mZoomFactor, mZoomFactor, 1.f)); proj_mat = scale*proj_mat; proj_mat = translate*proj_mat; } calcProjection(z_far); // Update the projection matrix cache proj_mat *= gl_perspective(fov_y,aspect,z_near,z_far); glLoadMatrixf(proj_mat.m); for (U32 i = 0; i < 16; i++) { gGLProjection[i] = proj_mat.m[i]; } glMatrixMode( GL_MODELVIEW ); glh::matrix4f modelview((GLfloat*) OGL_TO_CFR_ROTATION); GLfloat ogl_matrix[16]; getOpenGLTransform(ogl_matrix); modelview *= glh::matrix4f(ogl_matrix); glLoadMatrixf(modelview.m); if (for_selection && (width > 1 || height > 1)) { // NB: as of this writing, i believe the code below is broken (doesn't take into account the world view, assumes entire window) // however, it is also unused (the GL matricies are used for selection, (see LLCamera::sphereInFrustum())) and so i'm not // comfortable hacking on it. calculateFrustumPlanesFromWindow((F32)(x - width / 2) / (F32)gViewerWindow->getWindowWidthScaled() - 0.5f, (F32)(y_from_bot - height / 2) / (F32)gViewerWindow->getWindowHeightScaled() - 0.5f, (F32)(x + width / 2) / (F32)gViewerWindow->getWindowWidthScaled() - 0.5f, (F32)(y_from_bot + height / 2) / (F32)gViewerWindow->getWindowHeightScaled() - 0.5f); } // if not picking and not doing a snapshot, cache various GL matrices if (!for_selection && mZoomFactor == 1.f) { // Save GL matrices for access elsewhere in code, especially project_world_to_screen //glGetDoublev(GL_MODELVIEW_MATRIX, gGLModelView); for (U32 i = 0; i < 16; i++) { gGLModelView[i] = modelview.m[i]; } } updateFrustumPlanes(*this); /*if (gSavedSettings.getBOOL("CameraOffset")) { glMatrixMode(GL_PROJECTION); glTranslatef(0,0,-50); glRotatef(20.0,1,0,0); glMatrixMode(GL_MODELVIEW); }*/ }
// There are three types of water objects: // Region water objects: the water in a region. // Hole water objects: water in the void but within current draw distance. // Edge water objects: the water outside the draw distance, up till the horizon. // // For example: // // -----------------------horizon------------------------- // | | | | // | Edge Water | | | // | | | | // | | | | // | | | | // | | | | // | | rwidth | | // | | <-----> | | // ------------------------------------------------------- // | |Hole |other| | | // | |Water|reg. | | | // | |-----------------| | // | |other|cur. |<--> | | // | |reg. | reg.| \__|_ draw distance | // | |-----------------| | // | | | |<--->| | // | | | | \__|_ range | // ------------------------------------------------------- // | |<----width------>|<--horizon ext.->| // | | | | // | | | | // | | | | // | | | | // | | | | // | | | | // | | | | // ------------------------------------------------------- // void LLWorld::updateWaterObjects() { if (!gAgent.getRegion()) { return; } if (mRegionList.empty()) { llwarns << "No regions!" << llendl; return; } LLViewerRegion const* regionp = gAgent.getRegion(); // Region width in meters. S32 const rwidth = (S32)regionp->getWidth(); // The distance we might see into the void // when standing on the edge of a region, in meters. S32 const draw_distance = llceil(mLandFarClip); // We can only have "holes" in the water (where there no region) if we // can have existing regions around it. Taking into account that this // code is only executed when we enter a region, and not when we walk // around in it, we (only) need to take into account regions that fall // within the draw_distance. // // Set 'range' to draw_distance, rounded up to the nearest multiple of rwidth. S32 const nsims = (draw_distance + rwidth - 1) / rwidth; S32 const range = nsims * rwidth; // Get South-West corner of current region. U32 region_x, region_y; from_region_handle(regionp->getHandle(), ®ion_x, ®ion_y); // The min. and max. coordinates of the South-West corners of the Hole water objects. S32 const min_x = (S32)region_x - range; S32 const min_y = (S32)region_y - range; S32 const max_x = (S32)region_x + rwidth-256 + range; S32 const max_y = (S32)region_y + rwidth-256 + range; // Attempt to determine a sensible water height for all the // Hole Water objects. // // It make little sense to try to guess what the best water // height should be when that isn't completely obvious: if it's // impossible to satisfy every region's water height without // getting a jump in the water height. // // In order to keep the reasoning simple, we assume something // logical as a group of connected regions, where the coastline // is at the outer edge. Anything more complex that would "break" // under such an assumption would probably break anyway (would // depend on terrain editing and existing mega prims, say, if // anything would make sense at all). // // So, what we do is find all connected regions within the // draw distance that border void, and then pick the lowest // water height of those (coast) regions. S32 const n = 2 * nsims + 1; S32 const origin = nsims + nsims * n; std::vector<F32> water_heights(n * n); std::vector<U8> checked(n * n, 0); // index = nx + ny * n + origin; U8 const region_bit = 1; U8 const hole_bit = 2; U8 const bordering_hole_bit = 4; U8 const bordering_edge_bit = 8; // Use the legacy waterheight for the Edge water in the case // that we don't find any Hole water at all. F32 water_height = DEFAULT_WATER_HEIGHT; int max_count = 0; LL_DEBUGS("WaterHeight") << "Current region: " << regionp->getName() << "; water height: " << regionp->getWaterHeight() << " m." << LL_ENDL; std::map<S32, int> water_height_counts; typedef std::queue<std::pair<S32, S32>, std::deque<std::pair<S32, S32> > > nxny_pairs_type; nxny_pairs_type nxny_pairs; nxny_pairs.push(nxny_pairs_type::value_type(0, 0)); water_heights[origin] = regionp->getWaterHeight(); checked[origin] = region_bit; // For debugging purposes. int number_of_connected_regions = 1; int uninitialized_regions = 0; int bordering_hole = 0; int bordering_edge = 0; while(!nxny_pairs.empty()) { S32 const nx = nxny_pairs.front().first; S32 const ny = nxny_pairs.front().second; LL_DEBUGS("WaterHeight") << "nx,ny = " << nx << "," << ny << LL_ENDL; S32 const index = nx + ny * n + origin; nxny_pairs.pop(); for (S32 dir = 0; dir < 4; ++dir) { S32 const cnx = nx + gDirAxes[dir][0]; S32 const cny = ny + gDirAxes[dir][1]; LL_DEBUGS("WaterHeight") << "dir = " << dir << "; cnx,cny = " << cnx << "," << cny << LL_ENDL; S32 const cindex = cnx + cny * n + origin; bool is_hole = false; bool is_edge = false; LLViewerRegion* new_region_found = NULL; if (cnx < -nsims || cnx > nsims || cny < -nsims || cny > nsims) { LL_DEBUGS("WaterHeight") << " Edge Water!" << LL_ENDL; // Bumped into Edge water object. is_edge = true; } else if (checked[cindex]) { LL_DEBUGS("WaterHeight") << " Already checked before!" << LL_ENDL; // Already checked. is_hole = (checked[cindex] & hole_bit); } else { S32 x = (S32)region_x + cnx * rwidth; S32 y = (S32)region_y + cny * rwidth; U64 region_handle = to_region_handle(x, y); new_region_found = getRegionFromHandle(region_handle); is_hole = !new_region_found; checked[cindex] = is_hole ? hole_bit : region_bit; } if (is_hole) { // This was a region that borders at least one 'hole'. // Count the found coastline. F32 new_water_height = water_heights[index]; LL_DEBUGS("WaterHeight") << " This is void; counting coastline with water height of " << new_water_height << LL_ENDL; S32 new_water_height_cm = llround(new_water_height * 100); int count = (water_height_counts[new_water_height_cm] += 1); // Just use the lowest water height: this is mainly about the horizon water, // and whatever we do, we don't want it to be possible to look under the water // when looking in the distance: it is better to make a step downwards in water // height when going away from the avie than a step upwards. However, since // everyone is used to DEFAULT_WATER_HEIGHT, don't allow a single region // to drag the water level below DEFAULT_WATER_HEIGHT on it's own. if (bordering_hole == 0 || // First time we get here. (new_water_height >= DEFAULT_WATER_HEIGHT && new_water_height < water_height) || (new_water_height < DEFAULT_WATER_HEIGHT && count > max_count) ) { water_height = new_water_height; } if (count > max_count) { max_count = count; } if (!(checked[index] & bordering_hole_bit)) { checked[index] |= bordering_hole_bit; ++bordering_hole; } } else if (is_edge && !(checked[index] & bordering_edge_bit)) { checked[index] |= bordering_edge_bit; ++bordering_edge; } if (!new_region_found) { // Dead end, there is no region here. continue; } // Found a new connected region. ++number_of_connected_regions; if (new_region_found->getName().empty()) { // Uninitialized LLViewerRegion, don't use it's water height. LL_DEBUGS("WaterHeight") << " Uninitialized region." << LL_ENDL; ++uninitialized_regions; continue; } nxny_pairs.push(nxny_pairs_type::value_type(cnx, cny)); water_heights[cindex] = new_region_found->getWaterHeight(); LL_DEBUGS("WaterHeight") << " Found a new region (name: " << new_region_found->getName() << "; water height: " << water_heights[cindex] << " m)!" << LL_ENDL; } } llinfos << "Number of connected regions: " << number_of_connected_regions << " (" << uninitialized_regions << " uninitialized); number of regions bordering Hole water: " << bordering_hole << "; number of regions bordering Edge water: " << bordering_edge << llendl; llinfos << "Coastline count (height, count): "; bool first = true; for (std::map<S32, int>::iterator iter = water_height_counts.begin(); iter != water_height_counts.end(); ++iter) { if (!first) llcont << ", "; llcont << "(" << (iter->first / 100.f) << ", " << iter->second << ")"; first = false; } llcont << llendl; llinfos << "Water height used for Hole and Edge water objects: " << water_height << llendl; // Update all Region water objects. for (region_list_t::iterator iter = mRegionList.begin(); iter != mRegionList.end(); ++iter) { LLViewerRegion* regionp = *iter; LLVOWater* waterp = regionp->getLand().getWaterObj(); if (waterp) { gObjectList.updateActive(waterp); } } // Clean up all existing Hole water objects. for (std::list<LLVOWater*>::iterator iter = mHoleWaterObjects.begin(); iter != mHoleWaterObjects.end(); ++iter) { LLVOWater* waterp = *iter; gObjectList.killObject(waterp); } mHoleWaterObjects.clear(); // Let the Edge and Hole water boxes be 1024 meter high so that they // are never too small to be drawn (A LL_VO_*_WATER box has water // rendered on it's bottom surface only), and put their bottom at // the current regions water height. F32 const box_height = 1024; F32 const water_center_z = water_height + box_height / 2; const S32 step = 256; // Create new Hole water objects within 'range' where there is no region. for (S32 x = min_x; x <= max_x; x += step) { for (S32 y = min_y; y <= max_y; y += step) { U64 region_handle = to_region_handle(x, y); if (!getRegionFromHandle(region_handle)) { LLVOWater* waterp = (LLVOWater*)gObjectList.createObjectViewer(LLViewerObject::LL_VO_VOID_WATER, gAgent.getRegion()); waterp->setUseTexture(FALSE); waterp->setPositionGlobal(LLVector3d(x + step / 2, y + step / 2, water_center_z)); waterp->setScale(LLVector3((F32)step, (F32)step, box_height)); gPipeline.createObject(waterp); mHoleWaterObjects.push_back(waterp); } } } // Center of the region. S32 const center_x = region_x + step / 2; S32 const center_y = region_y + step / 2; // Width of the area with Hole water objects. S32 const width = step + 2 * range; S32 const horizon_extend = 2048 + 512 - range; // Legacy value. // The overlap is needed to get rid of sky pixels being visible between the // Edge and Hole water object at greater distances (due to floating point // round off errors). S32 const edge_hole_overlap = 1; // Twice the actual overlap. for (S32 dir = 0; dir < 8; ++dir) { // Size of the Edge water objects. S32 const dim_x = (gDirAxes[dir][0] == 0) ? width : (horizon_extend + edge_hole_overlap); S32 const dim_y = (gDirAxes[dir][1] == 0) ? width : (horizon_extend + edge_hole_overlap); // And their position. S32 const water_center_x = center_x + (width + horizon_extend) / 2 * gDirAxes[dir][0]; S32 const water_center_y = center_y + (width + horizon_extend) / 2 * gDirAxes[dir][1]; LLVOWater* waterp = mEdgeWaterObjects[dir]; if (!waterp || waterp->isDead()) { // The edge water objects can be dead because they're attached to the region that the // agent was in when they were originally created. mEdgeWaterObjects[dir] = (LLVOWater *)gObjectList.createObjectViewer(LLViewerObject::LL_VO_VOID_WATER, gAgent.getRegion()); waterp = mEdgeWaterObjects[dir]; waterp->setUseTexture(FALSE); waterp->setIsEdgePatch(TRUE); // Mark that this is edge water and not hole water. gPipeline.createObject(waterp); } waterp->setRegion(gAgent.getRegion()); LLVector3d water_pos(water_center_x, water_center_y, water_center_z); LLVector3 water_scale((F32) dim_x, (F32) dim_y, box_height); waterp->setPositionGlobal(water_pos); waterp->setScale(water_scale); gObjectList.updateActive(waterp); } }
S32 LLFontGL::render(const LLWString &wstr, S32 begin_offset, F32 x, F32 y, const LLColor4 &color, HAlign halign, VAlign valign, U8 style, ShadowType shadow, S32 max_chars, S32 max_pixels, F32* right_x, BOOL use_ellipses) const { LLFastTimer _(FTM_RENDER_FONTS); if(!sDisplayFont) //do not display texts { return wstr.length() ; } if (wstr.empty()) { return 0; } gGL.getTexUnit(0)->enable(LLTexUnit::TT_TEXTURE); S32 scaled_max_pixels = max_pixels == S32_MAX ? S32_MAX : llceil((F32)max_pixels * sScaleX); // determine which style flags need to be added programmatically by stripping off the // style bits that are drawn by the underlying Freetype font U8 style_to_add = (style | mFontDescriptor.getStyle()) & ~mFontFreetype->getStyle(); F32 drop_shadow_strength = 0.f; if (shadow != NO_SHADOW) { F32 luminance; color.calcHSL(NULL, NULL, &luminance); drop_shadow_strength = clamp_rescale(luminance, 0.35f, 0.6f, 0.f, 1.f); if (luminance < 0.35f) { shadow = NO_SHADOW; } } gGL.pushUIMatrix(); gGL.loadUIIdentity(); //gGL.translateUI(floorf(sCurOrigin.mX*sScaleX), floorf(sCurOrigin.mY*sScaleY), sCurOrigin.mZ); // this code snaps the text origin to a pixel grid to start with //F32 pixel_offset_x = llround((F32)sCurOrigin.mX) - (sCurOrigin.mX); //F32 pixel_offset_y = llround((F32)sCurOrigin.mY) - (sCurOrigin.mY); //gGL.translateUI(-pixel_offset_x, -pixel_offset_y, 0.f); LLVector2 origin(floorf(sCurOrigin.mX*sScaleX), floorf(sCurOrigin.mY*sScaleY)); // snap the text origin to a pixel grid to start with origin.mV[VX] -= llround((F32)sCurOrigin.mX) - (sCurOrigin.mX); origin.mV[VY] -= llround((F32)sCurOrigin.mY) - (sCurOrigin.mY); S32 chars_drawn = 0; S32 i; S32 length; if (-1 == max_chars) { length = (S32)wstr.length() - begin_offset; } else { length = llmin((S32)wstr.length() - begin_offset, max_chars ); } F32 cur_x, cur_y, cur_render_x, cur_render_y; // Not guaranteed to be set correctly gGL.setSceneBlendType(LLRender::BT_ALPHA); cur_x = ((F32)x * sScaleX) + origin.mV[VX]; cur_y = ((F32)y * sScaleY) + origin.mV[VY]; // Offset y by vertical alignment. switch (valign) { case TOP: cur_y -= mFontFreetype->getAscenderHeight(); break; case BOTTOM: cur_y += mFontFreetype->getDescenderHeight(); break; case VCENTER: cur_y -= (mFontFreetype->getAscenderHeight() - mFontFreetype->getDescenderHeight()) / 2.f; break; case BASELINE: // Baseline, do nothing. break; default: break; } switch (halign) { case LEFT: break; case RIGHT: cur_x -= llmin(scaled_max_pixels, llround(getWidthF32(wstr.c_str(), begin_offset, length) * sScaleX)); break; case HCENTER: cur_x -= llmin(scaled_max_pixels, llround(getWidthF32(wstr.c_str(), begin_offset, length) * sScaleX)) / 2; break; default: break; } cur_render_y = cur_y; cur_render_x = cur_x; F32 start_x = llround(cur_x); const LLFontBitmapCache* font_bitmap_cache = mFontFreetype->getFontBitmapCache(); F32 inv_width = 1.f / font_bitmap_cache->getBitmapWidth(); F32 inv_height = 1.f / font_bitmap_cache->getBitmapHeight(); const S32 LAST_CHARACTER = LLFontFreetype::LAST_CHAR_FULL; BOOL draw_ellipses = FALSE; if (use_ellipses) { // check for too long of a string S32 string_width = llround(getWidthF32(wstr.c_str(), begin_offset, max_chars) * sScaleX); if (string_width > scaled_max_pixels) { // use four dots for ellipsis width to generate padding const LLWString dots(utf8str_to_wstring(std::string("...."))); scaled_max_pixels = llmax(0, scaled_max_pixels - llround(getWidthF32(dots.c_str()))); draw_ellipses = TRUE; } } const LLFontGlyphInfo* next_glyph = NULL; const S32 GLYPH_BATCH_SIZE = 30; LLVector3 vertices[GLYPH_BATCH_SIZE * 4]; LLVector2 uvs[GLYPH_BATCH_SIZE * 4]; LLColor4U colors[GLYPH_BATCH_SIZE * 4]; LLColor4U text_color(color); S32 bitmap_num = -1; S32 glyph_count = 0; for (i = begin_offset; i < begin_offset + length; i++) { llwchar wch = wstr[i]; const LLFontGlyphInfo* fgi = next_glyph; next_glyph = NULL; if(!fgi) { fgi = mFontFreetype->getGlyphInfo(wch); } if (!fgi) { llerrs << "Missing Glyph Info" << llendl; break; } // Per-glyph bitmap texture. S32 next_bitmap_num = fgi->mBitmapNum; if (next_bitmap_num != bitmap_num) { bitmap_num = next_bitmap_num; LLImageGL *font_image = font_bitmap_cache->getImageGL(bitmap_num); gGL.getTexUnit(0)->bind(font_image); } if ((start_x + scaled_max_pixels) < (cur_x + fgi->mXBearing + fgi->mWidth)) { // Not enough room for this character. break; } // Draw the text at the appropriate location //Specify vertices and texture coordinates LLRectf uv_rect((fgi->mXBitmapOffset) * inv_width, (fgi->mYBitmapOffset + fgi->mHeight + PAD_UVY) * inv_height, (fgi->mXBitmapOffset + fgi->mWidth) * inv_width, (fgi->mYBitmapOffset - PAD_UVY) * inv_height); // snap glyph origin to whole screen pixel LLRectf screen_rect(llround(cur_render_x + (F32)fgi->mXBearing), llround(cur_render_y + (F32)fgi->mYBearing), llround(cur_render_x + (F32)fgi->mXBearing) + (F32)fgi->mWidth, llround(cur_render_y + (F32)fgi->mYBearing) - (F32)fgi->mHeight); if (glyph_count >= GLYPH_BATCH_SIZE) { gGL.begin(LLRender::QUADS); { gGL.vertexBatchPreTransformed(vertices, uvs, colors, glyph_count * 4); } gGL.end(); glyph_count = 0; } drawGlyph(glyph_count, vertices, uvs, colors, screen_rect, uv_rect, text_color, style_to_add, shadow, drop_shadow_strength); chars_drawn++; cur_x += fgi->mXAdvance; cur_y += fgi->mYAdvance; llwchar next_char = wstr[i+1]; if (next_char && (next_char < LAST_CHARACTER)) { // Kern this puppy. next_glyph = mFontFreetype->getGlyphInfo(next_char); cur_x += mFontFreetype->getXKerning(fgi, next_glyph); } // Round after kerning. // Must do this to cur_x, not just to cur_render_x, otherwise you // will squish sub-pixel kerned characters too close together. // For example, "CCCCC" looks bad. cur_x = (F32)llround(cur_x); //cur_y = (F32)llround(cur_y); cur_render_x = cur_x; cur_render_y = cur_y; } gGL.begin(LLRender::QUADS); { gGL.vertexBatchPreTransformed(vertices, uvs, colors, glyph_count * 4); } gGL.end(); if (right_x) { *right_x = (cur_x - origin.mV[VX]) / sScaleX; } //FIXME: add underline as glyph? if (style_to_add & UNDERLINE) { F32 descender = mFontFreetype->getDescenderHeight(); gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); gGL.begin(LLRender::LINES); gGL.vertex2f(start_x, cur_y - (descender)); gGL.vertex2f(cur_x, cur_y - (descender)); gGL.end(); } if (draw_ellipses) { // recursively render ellipses at end of string // we've already reserved enough room gGL.pushUIMatrix(); renderUTF8(std::string("..."), 0, (cur_x - origin.mV[VX]) / sScaleX, (F32)y, color, LEFT, valign, style_to_add, shadow, S32_MAX, max_pixels, right_x, FALSE); gGL.popUIMatrix(); } gGL.popUIMatrix(); return chars_drawn; }
void LLFloaterBuyLandUI::refreshUI() { // section zero: title area { LLTextureCtrl* snapshot = getChild<LLTextureCtrl>("info_image"); if (snapshot) { snapshot->setImageAssetID( mParcelValid ? mParcelSnapshot : LLUUID::null); } if (mParcelValid) { getChild<LLUICtrl>("info_parcel")->setValue(mParcelLocation); LLStringUtil::format_map_t string_args; string_args["[AMOUNT]"] = llformat("%d", mParcelActualArea); string_args["[AMOUNT2]"] = llformat("%d", mParcelSupportedObjects); getChild<LLUICtrl>("info_size")->setValue(getString("meters_supports_object", string_args)); F32 cost_per_sqm = 0.0f; if (mParcelActualArea > 0) { cost_per_sqm = (F32)mParcelPrice / (F32)mParcelActualArea; } LLStringUtil::format_map_t info_price_args; info_price_args["[PRICE]"] = llformat("%d", mParcelPrice); info_price_args["[PRICE_PER_SQM]"] = llformat("%.1f", cost_per_sqm); if (mParcelSoldWithObjects) { info_price_args["[SOLD_WITH_OBJECTS]"] = getString("sold_with_objects"); } else { info_price_args["[SOLD_WITH_OBJECTS]"] = getString("sold_without_objects"); } getChild<LLUICtrl>("info_price")->setValue(getString("info_price_string", info_price_args)); getChildView("info_price")->setVisible( mParcelIsForSale); } else { getChild<LLUICtrl>("info_parcel")->setValue(getString("no_parcel_selected")); getChild<LLUICtrl>("info_size")->setValue(LLStringUtil::null); getChild<LLUICtrl>("info_price")->setValue(LLStringUtil::null); } getChild<LLUICtrl>("info_action")->setValue( mCanBuy ? mIsForGroup ? getString("buying_for_group")//"Buying land for group:" : getString("buying_will")//"Buying this land will:" : mCannotBuyIsError ? getString("cannot_buy_now")//"Cannot buy now:" : getString("not_for_sale")//"Not for sale:" ); } bool showingError = !mCanBuy || !mSiteValid; // error section if (showingError) { mChildren.setBadge(std::string("step_error"), mCannotBuyIsError ? LLViewChildren::BADGE_ERROR : LLViewChildren::BADGE_WARN); LLTextBox* message = getChild<LLTextBox>("error_message"); if (message) { message->setVisible(true); message->setValue(LLSD(!mCanBuy ? mCannotBuyReason : "(waiting for data)")); } getChildView("error_web")->setVisible(mCannotBuyIsError && !mCannotBuyURI.empty()); } else { getChildView("step_error")->setVisible(FALSE); getChildView("error_message")->setVisible(FALSE); getChildView("error_web")->setVisible(FALSE); } // section one: account if (!showingError) { mChildren.setBadge(std::string("step_1"), mSiteMembershipUpgrade ? LLViewChildren::BADGE_NOTE : LLViewChildren::BADGE_OK); getChild<LLUICtrl>("account_action")->setValue(mSiteMembershipAction); getChild<LLUICtrl>("account_reason")->setValue( mSiteMembershipUpgrade ? getString("must_upgrade") : getString("cant_own_land") ); LLComboBox* levels = getChild<LLComboBox>( "account_level"); if (levels) { levels->setVisible(mSiteMembershipUpgrade); levels->removeall(); for(std::vector<std::string>::const_iterator i = mSiteMembershipPlanNames.begin(); i != mSiteMembershipPlanNames.end(); ++i) { levels->add(*i); } levels->setCurrentByIndex(mUserPlanChoice); } getChildView("step_1")->setVisible(TRUE); getChildView("account_action")->setVisible(TRUE); getChildView("account_reason")->setVisible(TRUE); } else { getChildView("step_1")->setVisible(FALSE); getChildView("account_action")->setVisible(FALSE); getChildView("account_reason")->setVisible(FALSE); getChildView("account_level")->setVisible(FALSE); } // section two: land use fees if (!showingError) { mChildren.setBadge(std::string("step_2"), mSiteLandUseUpgrade ? LLViewChildren::BADGE_NOTE : LLViewChildren::BADGE_OK); getChild<LLUICtrl>("land_use_action")->setValue(mSiteLandUseAction); std::string message; if (mIsForGroup) { LLStringUtil::format_map_t string_args; string_args["[GROUP]"] = std::string(gAgent.getGroupName()); message += getString("insufficient_land_credits", string_args); } else { LLStringUtil::format_map_t string_args; string_args["[BUYER]"] = llformat("%d", mAgentCommittedTier); message += getString("land_holdings", string_args); } if (!mParcelValid) { message += LLTrans::getString("sentences_separator") + getString("no_parcel_selected"); } else if (mParcelBillableArea == mParcelActualArea) { LLStringUtil::format_map_t string_args; string_args["[AMOUNT]"] = llformat("%d ", mParcelActualArea); message += LLTrans::getString("sentences_separator") + getString("parcel_meters", string_args); } else { if (mParcelBillableArea > mParcelActualArea) { LLStringUtil::format_map_t string_args; string_args["[AMOUNT]"] = llformat("%d ", mParcelBillableArea); message += LLTrans::getString("sentences_separator") + getString("premium_land", string_args); } else { LLStringUtil::format_map_t string_args; string_args["[AMOUNT]"] = llformat("%d ", mParcelBillableArea); message += LLTrans::getString("sentences_separator") + getString("discounted_land", string_args); } } getChild<LLUICtrl>("land_use_reason")->setValue(message); getChildView("step_2")->setVisible(TRUE); getChildView("land_use_action")->setVisible(TRUE); getChildView("land_use_reason")->setVisible(TRUE); } else { getChildView("step_2")->setVisible(FALSE); getChildView("land_use_action")->setVisible(FALSE); getChildView("land_use_reason")->setVisible(FALSE); } // section three: purchase & currency S32 finalBalance = mAgentCashBalance + mCurrency.getAmount() - mParcelPrice; bool willHaveEnough = finalBalance >= 0; bool haveEnough = mAgentCashBalance >= mParcelPrice; S32 minContribution = llceil((F32)mParcelBillableArea / GROUP_LAND_BONUS_FACTOR); bool groupContributionEnough = mParcelGroupContribution >= minContribution; mCurrency.updateUI(!showingError && !haveEnough); if (!showingError) { mChildren.setBadge(std::string("step_3"), !willHaveEnough ? LLViewChildren::BADGE_WARN : mCurrency.getAmount() > 0 ? LLViewChildren::BADGE_NOTE : LLViewChildren::BADGE_OK); LLStringUtil::format_map_t string_args; string_args["[AMOUNT]"] = llformat("%d", mParcelPrice); string_args["[SELLER]"] = mParcelSellerName; getChild<LLUICtrl>("purchase_action")->setValue(getString("pay_to_for_land", string_args)); getChildView("purchase_action")->setVisible( mParcelValid); std::string reasonString; if (haveEnough) { LLStringUtil::format_map_t string_args; string_args["[AMOUNT]"] = llformat("%d", mAgentCashBalance); getChild<LLUICtrl>("currency_reason")->setValue(getString("have_enough_lindens", string_args)); } else { LLStringUtil::format_map_t string_args; string_args["[AMOUNT]"] = llformat("%d", mAgentCashBalance); string_args["[AMOUNT2]"] = llformat("%d", mParcelPrice - mAgentCashBalance); getChild<LLUICtrl>("currency_reason")->setValue(getString("not_enough_lindens", string_args)); getChild<LLUICtrl>("currency_est")->setTextArg("[LOCAL_AMOUNT]", mCurrency.getLocalEstimate()); } if (willHaveEnough) { LLStringUtil::format_map_t string_args; string_args["[AMOUNT]"] = llformat("%d", finalBalance); getChild<LLUICtrl>("currency_balance")->setValue(getString("balance_left", string_args)); } else { LLStringUtil::format_map_t string_args; string_args["[AMOUNT]"] = llformat("%d", mParcelPrice - mAgentCashBalance); getChild<LLUICtrl>("currency_balance")->setValue(getString("balance_needed", string_args)); } getChild<LLUICtrl>("remove_contribution")->setValue(LLSD(groupContributionEnough)); getChildView("remove_contribution")->setEnabled(groupContributionEnough); bool showRemoveContribution = mParcelIsGroupLand && (mParcelGroupContribution > 0); getChildView("remove_contribution")->setLabelArg("[AMOUNT]", llformat("%d", minContribution)); getChildView("remove_contribution")->setVisible( showRemoveContribution); getChildView("step_3")->setVisible(TRUE); getChildView("purchase_action")->setVisible(TRUE); getChildView("currency_reason")->setVisible(TRUE); getChildView("currency_balance")->setVisible(TRUE); } else { getChildView("step_3")->setVisible(FALSE); getChildView("purchase_action")->setVisible(FALSE); getChildView("currency_reason")->setVisible(FALSE); getChildView("currency_balance")->setVisible(FALSE); getChildView("remove_group_donation")->setVisible(FALSE); } bool agrees_to_covenant = false; LLCheckBoxCtrl* check = getChild<LLCheckBoxCtrl>("agree_covenant"); if (check) { agrees_to_covenant = check->get(); } getChildView("buy_btn")->setEnabled(mCanBuy && mSiteValid && willHaveEnough && !mTransaction && agrees_to_covenant); }
BOOL setup_hud_matrices(BOOL for_select) { LLVOAvatar* my_avatarp = gAgent.getAvatarObject(); if (my_avatarp && my_avatarp->hasHUDAttachment()) { if (!for_select) { // clamp target zoom level to reasonable values //MK if (RRenabled && gAgent.mRRInterface.mHasLockedHuds) { my_avatarp->mHUDTargetZoom = llclamp(my_avatarp->mHUDTargetZoom, 0.85f, 1.f); } else //mk my_avatarp->mHUDTargetZoom = llclamp(my_avatarp->mHUDTargetZoom, 0.1f, 1.f); // smoothly interpolate current zoom level my_avatarp->mHUDCurZoom = lerp(my_avatarp->mHUDCurZoom, my_avatarp->mHUDTargetZoom, LLCriticalDamp::getInterpolant(0.03f)); } F32 zoom_level = my_avatarp->mHUDCurZoom; // clear z buffer and set up transform for hud if (!for_select) { glClear(GL_DEPTH_BUFFER_BIT); } LLBBox hud_bbox = my_avatarp->getHUDBBox(); // set up transform to encompass bounding box of HUD glMatrixMode(GL_PROJECTION); glLoadIdentity(); F32 hud_depth = llmax(1.f, hud_bbox.getExtentLocal().mV[VX] * 1.1f); if (for_select) { //RN: reset viewport to window extents so ortho screen is calculated with proper reference frame gViewerWindow->setupViewport(); } glOrtho(-0.5f * gCamera->getAspect(), 0.5f * gCamera->getAspect(), -0.5f, 0.5f, 0.f, hud_depth); // apply camera zoom transform (for high res screenshots) F32 zoom_factor = gCamera->getZoomFactor(); S16 sub_region = gCamera->getZoomSubRegion(); if (zoom_factor > 1.f) { float offset = zoom_factor - 1.f; int pos_y = sub_region / llceil(zoom_factor); int pos_x = sub_region - (pos_y*llceil(zoom_factor)); glTranslatef(gCamera->getAspect() * 0.5f * (offset - (F32)pos_x * 2.f), 0.5f * (offset - (F32)pos_y * 2.f), 0.f); glScalef(zoom_factor, zoom_factor, 1.f); } glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glLoadMatrixf(OGL_TO_CFR_ROTATION); // Load Cory's favorite reference frame glTranslatef(-hud_bbox.getCenterLocal().mV[VX] + (hud_depth * 0.5f), 0.f, 0.f); glScalef(zoom_level, zoom_level, zoom_level); return TRUE; } else { return FALSE; } }
LLToastNotifyPanel::LLToastNotifyPanel(LLNotificationPtr& notification, const LLRect& rect) : LLToastPanel(notification), mTextBox(NULL), mInfoPanel(NULL), mControlPanel(NULL), mNumOptions(0), mNumButtons(0), mAddedDefaultBtn(false), mCloseNotificationOnDestroy(true) { LLUICtrlFactory::getInstance()->buildPanel(this, "panel_notification.xml"); if(rect != LLRect::null) { this->setShape(rect); } mInfoPanel = getChild<LLPanel>("info_panel"); mControlPanel = getChild<LLPanel>("control_panel"); BUTTON_WIDTH = gSavedSettings.getS32("ToastButtonWidth"); // customize panel's attributes // is it intended for displaying a tip mIsTip = notification->getType() == "notifytip"; // is it a script dialog mIsScriptDialog = (notification->getName() == "ScriptDialog" || notification->getName() == "ScriptDialogGroup"); // is it a caution // // caution flag can be set explicitly by specifying it in the notification payload, or it can be set implicitly if the // notify xml template specifies that it is a caution // tip-style notification handle 'caution' differently -they display the tip in a different color mIsCaution = notification->getPriority() >= NOTIFICATION_PRIORITY_HIGH; // setup parameters // get a notification message mMessage = notification->getMessage(); // init font variables if (!sFont) { sFont = LLFontGL::getFontSansSerif(); sFontSmall = LLFontGL::getFontSansSerifSmall(); } // clicking on a button does not steal current focus setIsChrome(TRUE); // initialize setFocusRoot(!mIsTip); // get a form for the notification LLNotificationFormPtr form(notification->getForm()); // get number of elements mNumOptions = form->getNumElements(); // customize panel's outfit // preliminary adjust panel's layout //move to the end //mIsTip ? adjustPanelForTipNotice() : adjustPanelForScriptNotice(form); // adjust text options according to the notification type // add a caution textbox at the top of a caution notification if (mIsCaution && !mIsTip) { mTextBox = getChild<LLTextBox>("caution_text_box"); } else { mTextBox = getChild<LLTextEditor>("text_editor_box"); } // *TODO: magic numbers(???) - copied from llnotify.cpp(250) const S32 MAX_LENGTH = 512 + 20 + DB_FIRST_NAME_BUF_SIZE + DB_LAST_NAME_BUF_SIZE + DB_INV_ITEM_NAME_BUF_SIZE; mTextBox->setMaxTextLength(MAX_LENGTH); mTextBox->setVisible(TRUE); mTextBox->setValue(notification->getMessage()); // add buttons for a script notification if (mIsTip) { adjustPanelForTipNotice(); } else { std::vector<index_button_pair_t> buttons; buttons.reserve(mNumOptions); S32 buttons_width = 0; // create all buttons and accumulate they total width to reshape mControlPanel for (S32 i = 0; i < mNumOptions; i++) { LLSD form_element = form->getElement(i); if (form_element["type"].asString() != "button") { continue; } LLButton* new_button = createButton(form_element, TRUE); buttons_width += new_button->getRect().getWidth(); S32 index = form_element["index"].asInteger(); buttons.push_back(index_button_pair_t(index,new_button)); } if (buttons.empty()) { addDefaultButton(); } else { const S32 button_panel_width = mControlPanel->getRect().getWidth();// do not change width of the panel S32 button_panel_height = mControlPanel->getRect().getHeight(); //try get an average h_pad to spread out buttons S32 h_pad = (button_panel_width - buttons_width) / (S32(buttons.size())); if(h_pad < 2*HPAD) { /* * Probably it is a scriptdialog toast * for a scriptdialog toast h_pad can be < 2*HPAD if we have a lot of buttons. * In last case set default h_pad to avoid heaping of buttons */ S32 button_per_row = button_panel_width / BUTTON_WIDTH; h_pad = (button_panel_width % BUTTON_WIDTH) / (button_per_row - 1);// -1 because we do not need space after last button in a row if(h_pad < 2*HPAD) // still not enough space between buttons ? { h_pad = 2*HPAD; } } if (mIsScriptDialog) { // we are using default width for script buttons so we can determinate button_rows //to get a number of rows we divide the required width of the buttons to button_panel_width S32 button_rows = llceil(F32(buttons.size() - 1) * (BUTTON_WIDTH + h_pad) / button_panel_width); //S32 button_rows = (buttons.size() - 1) * (BUTTON_WIDTH + h_pad) / button_panel_width; //reserve one row for the ignore_btn button_rows++; //calculate required panel height for scripdialog notification. button_panel_height = button_rows * (BTN_HEIGHT + VPAD) + IGNORE_BTN_TOP_DELTA + BOTTOM_PAD; } else { // in common case buttons can have different widths so we need to calculate button_rows according to buttons_width //S32 button_rows = llceil(F32(buttons.size()) * (buttons_width + h_pad) / button_panel_width); S32 button_rows = llceil(F32((buttons.size() - 1) * h_pad + buttons_width) / button_panel_width); //calculate required panel height button_panel_height = button_rows * (BTN_HEIGHT + VPAD) + BOTTOM_PAD; } // we need to keep min width and max height to make visible all buttons, because width of the toast can not be changed adjustPanelForScriptNotice(button_panel_width, button_panel_height); updateButtonsLayout(buttons, h_pad); // save buttons for later use in disableButtons() mButtons.assign(buttons.begin(), buttons.end()); } } // adjust panel's height to the text size mInfoPanel->setFollowsAll(); snapToMessageHeight(mTextBox, MAX_LENGTH); if(notification->isReusable()) { mButtonClickConnection = sButtonClickSignal.connect( boost::bind(&LLToastNotifyPanel::onToastPanelButtonClicked, this, _1, _2)); if(notification->isRespondedTo()) { // User selected an option in toast, now disable required buttons in IM window disableRespondedOptions(notification); } } }
S32 LLFontGL::render(const LLWString &wstr, S32 begin_offset, F32 x, F32 y, const LLColor4 &color, HAlign halign, VAlign valign, U8 style, ShadowType shadow, S32 max_chars, S32 max_pixels, F32* right_x, BOOL use_embedded, BOOL use_ellipses) const { LLFastTimer _(FTM_RENDER_FONTS); if(!sDisplayFont) //do not display texts { return wstr.length() ; } if (wstr.empty() || !max_pixels) { return 0; } if (max_chars == -1) max_chars = S32_MAX; const S32 max_index = llmin(llmax(max_chars, begin_offset + max_chars), S32(wstr.length())); if (max_index <= 0 || begin_offset >= max_index || max_pixels <= 0) return 0; gGL.getTexUnit(0)->enable(LLTexUnit::TT_TEXTURE); S32 scaled_max_pixels = max_pixels == S32_MAX ? S32_MAX : llceil((F32)max_pixels * sScaleX); // Strip off any style bits that are already accounted for by the font. style = style & (~getFontDesc().getStyle()); F32 drop_shadow_strength = 0.f; if (shadow != NO_SHADOW) { F32 luminance; color.calcHSL(NULL, NULL, &luminance); drop_shadow_strength = clamp_rescale(luminance, 0.35f, 0.6f, 0.f, 1.f); if (luminance < 0.35f) { shadow = NO_SHADOW; } } gGL.pushUIMatrix(); gGL.loadUIIdentity(); LLVector2 origin(floorf(sCurOrigin.mX*sScaleX), floorf(sCurOrigin.mY*sScaleY)); // Depth translation, so that floating text appears 'in-world' // and is correctly occluded. gGL.translatef(0.f,0.f,sCurDepth); S32 chars_drawn = 0; S32 i; S32 length = max_index - begin_offset; F32 cur_x, cur_y, cur_render_x, cur_render_y; // Not guaranteed to be set correctly gGL.setSceneBlendType(LLRender::BT_ALPHA); cur_x = ((F32)x * sScaleX) + origin.mV[VX]; cur_y = ((F32)y * sScaleY) + origin.mV[VY]; // Offset y by vertical alignment. // use unscaled font metrics here switch (valign) { case TOP: cur_y -= llceil(mFontFreetype->getAscenderHeight()); break; case BOTTOM: cur_y += llceil(mFontFreetype->getDescenderHeight()); break; case VCENTER: cur_y -= llceil((llceil(mFontFreetype->getAscenderHeight()) - llceil(mFontFreetype->getDescenderHeight())) / 2.f); break; case BASELINE: // Baseline, do nothing. break; default: break; } switch (halign) { case LEFT: break; case RIGHT: cur_x -= llmin(scaled_max_pixels, ll_round(getWidthF32(wstr.c_str(), begin_offset, length) * sScaleX)); break; case HCENTER: cur_x -= llmin(scaled_max_pixels, ll_round(getWidthF32(wstr.c_str(), begin_offset, length) * sScaleX)) / 2; break; default: break; } cur_render_y = cur_y; cur_render_x = cur_x; F32 start_x = (F32)ll_round(cur_x); const LLFontBitmapCache* font_bitmap_cache = mFontFreetype->getFontBitmapCache(); F32 inv_width = 1.f / font_bitmap_cache->getBitmapWidth(); F32 inv_height = 1.f / font_bitmap_cache->getBitmapHeight(); const S32 LAST_CHARACTER = LLFontFreetype::LAST_CHAR_FULL; BOOL draw_ellipses = FALSE; if (use_ellipses && halign == LEFT) { // check for too long of a string S32 string_width = ll_round(getWidthF32(wstr, begin_offset, max_chars) * sScaleX); if (string_width > scaled_max_pixels) { // use four dots for ellipsis width to generate padding const LLWString dots(utf8str_to_wstring(std::string("...."))); scaled_max_pixels = llmax(0, scaled_max_pixels - ll_round(getWidthF32(dots.c_str()))); draw_ellipses = TRUE; } } const LLFontGlyphInfo* next_glyph = NULL; const S32 GLYPH_BATCH_SIZE = 30; static LL_ALIGN_16(LLVector4a vertices[GLYPH_BATCH_SIZE * 4]); static LLVector2 uvs[GLYPH_BATCH_SIZE * 4]; static LLColor4U colors[GLYPH_BATCH_SIZE * 4]; LLColor4U text_color(color); S32 bitmap_num = -1; S32 glyph_count = 0; for (i = begin_offset; i < begin_offset + length; i++) { llwchar wch = wstr[i]; // Handle embedded characters first, if they're enabled. // Embedded characters are a hack for notecards const embedded_data_t* ext_data = use_embedded ? getEmbeddedCharData(wch) : NULL; if (ext_data) { LLImageGL* ext_image = ext_data->mImage; const LLWString& label = ext_data->mLabel; F32 ext_height = (F32)ext_image->getHeight() * sScaleY; F32 ext_width = (F32)ext_image->getWidth() * sScaleX; F32 ext_advance = (EXT_X_BEARING * sScaleX) + ext_width; if (!label.empty()) { ext_advance += (EXT_X_BEARING + getFontExtChar()->getWidthF32( label.c_str() )) * sScaleX; } if (start_x + scaled_max_pixels < cur_x + ext_advance) { // Not enough room for this character. break; } gGL.getTexUnit(0)->bind(ext_image); // snap origin to whole screen pixel const F32 ext_x = (F32)ll_round(cur_render_x + (EXT_X_BEARING * sScaleX)); const F32 ext_y = (F32)ll_round(cur_render_y + (EXT_Y_BEARING * sScaleY + mFontFreetype->getAscenderHeight() - mFontFreetype->getLineHeight())); LLRectf uv_rect(0.f, 1.f, 1.f, 0.f); LLRectf screen_rect(ext_x, ext_y + ext_height, ext_x + ext_width, ext_y); if (glyph_count > 0) { gGL.begin(LLRender::QUADS); { gGL.vertexBatchPreTransformed(vertices, uvs, colors, glyph_count * 4); } gGL.end(); glyph_count = 0; } renderQuad(vertices, uvs, colors, screen_rect, uv_rect, LLColor4U::white, 0); //No batching here. It will never happen. gGL.begin(LLRender::QUADS); { gGL.vertexBatchPreTransformed(vertices, uvs, colors, 4); } gGL.end(); if (!label.empty()) { gGL.pushMatrix(); getFontExtChar()->render(label, 0, /*llfloor*/(ext_x / sScaleX) + ext_image->getWidth() + EXT_X_BEARING - sCurOrigin.mX, /*llfloor*/(cur_render_y / sScaleY) - sCurOrigin.mY, color, halign, BASELINE, UNDERLINE, NO_SHADOW, S32_MAX, S32_MAX, NULL, TRUE ); gGL.popMatrix(); } chars_drawn++; cur_x += ext_advance; if (((i + 1) < length) && wstr[i+1]) { cur_x += EXT_KERNING * sScaleX; } cur_render_x = cur_x; } else { const LLFontGlyphInfo* fgi = next_glyph; next_glyph = NULL; if(!fgi) { fgi = mFontFreetype->getGlyphInfo(wch); } if (!fgi) { LL_ERRS() << "Missing Glyph Info" << LL_ENDL; break; } // Per-glyph bitmap texture. S32 next_bitmap_num = fgi->mBitmapNum; if (next_bitmap_num != bitmap_num) { // Actually draw the queued glyphs before switching their texture; // otherwise the queued glyphs will be taken from wrong textures. if (glyph_count > 0) { gGL.begin(LLRender::QUADS); { gGL.vertexBatchPreTransformed(vertices, uvs, colors, glyph_count * 4); } gGL.end(); glyph_count = 0; } bitmap_num = next_bitmap_num; LLImageGL *font_image = font_bitmap_cache->getImageGL(bitmap_num); gGL.getTexUnit(0)->bind(font_image); } if ((start_x + scaled_max_pixels) < (cur_x + fgi->mXBearing + fgi->mWidth)) { // Not enough room for this character. break; } // Draw the text at the appropriate location //Specify vertices and texture coordinates LLRectf uv_rect((fgi->mXBitmapOffset) * inv_width, (fgi->mYBitmapOffset + fgi->mHeight + PAD_UVY) * inv_height, (fgi->mXBitmapOffset + fgi->mWidth) * inv_width, (fgi->mYBitmapOffset - PAD_UVY) * inv_height); // snap glyph origin to whole screen pixel LLRectf screen_rect((F32)ll_round(cur_render_x + (F32)fgi->mXBearing), (F32)ll_round(cur_render_y + (F32)fgi->mYBearing), (F32)ll_round(cur_render_x + (F32)fgi->mXBearing) + (F32)fgi->mWidth, (F32)ll_round(cur_render_y + (F32)fgi->mYBearing) - (F32)fgi->mHeight); if (glyph_count >= GLYPH_BATCH_SIZE) { gGL.begin(LLRender::QUADS); { gGL.vertexBatchPreTransformed(vertices, uvs, colors, glyph_count * 4); } gGL.end(); glyph_count = 0; } drawGlyph(glyph_count, vertices, uvs, colors, screen_rect, uv_rect, text_color, style, shadow, drop_shadow_strength); chars_drawn++; cur_x += fgi->mXAdvance; cur_y += fgi->mYAdvance; llwchar next_char = wstr[i+1]; if (next_char && (next_char < LAST_CHARACTER)) { // Kern this puppy. next_glyph = mFontFreetype->getGlyphInfo(next_char); cur_x += mFontFreetype->getXKerning(fgi, next_glyph); } // Round after kerning. // Must do this to cur_x, not just to cur_render_x, otherwise you // will squish sub-pixel kerned characters too close together. // For example, "CCCCC" looks bad. cur_x = (F32)ll_round(cur_x); //cur_y = (F32)ll_round(cur_y); cur_render_x = cur_x; cur_render_y = cur_y; } } if(glyph_count) { gGL.begin(LLRender::QUADS); { gGL.vertexBatchPreTransformed(vertices, uvs, colors, glyph_count * 4); } gGL.end(); } if (right_x) { *right_x = (cur_x - origin.mV[VX]) / sScaleX; } if (style & UNDERLINE) { F32 descender = (F32)llfloor(mFontFreetype->getDescenderHeight()); gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); gGL.begin(LLRender::LINES); gGL.vertex2f(start_x, cur_y - descender); gGL.vertex2f(cur_x, cur_y - descender); gGL.end(); } if (draw_ellipses) { // recursively render ellipses at end of string // we've already reserved enough room gGL.pushUIMatrix(); renderUTF8(std::string("..."), 0, (cur_x - origin.mV[VX]) / sScaleX, (F32)y, color, LEFT, valign, style, shadow, S32_MAX, max_pixels, right_x, FALSE); gGL.popUIMatrix(); } gGL.popUIMatrix(); return chars_drawn; }