//################################################################################################## // // Custom Visualization Core library // Copyright (C) 2011-2012 Ceetron AS // // This library is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // 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 General Public License at <> // for more details. // //################################################################################################## #include "cvfBase.h" #include "cvfTextDrawer.h" #include "cvfFont.h" #include "cvfGlyph.h" #include "cvfTextureImage.h" #include "cvfOpenGL.h" #include "cvfOpenGLResourceManager.h" #include "cvfShaderProgram.h" #include "cvfUniform.h" #include "cvfCamera.h" #include "cvfViewport.h" #include "cvfBoundingBox.h" #include "cvfShaderProgramGenerator.h" #include "cvfShaderSourceProvider.h" #include "cvfMatrixState.h" #include "cvfRenderStateDepth.h" #include "cvfRenderStateBlending.h" #include "cvfRenderStatePolygonOffset.h" #ifndef CVF_OPENGL_ES #include "cvfRenderState_FF.h" #endif namespace cvf { //================================================================================================== /// /// \class cvf::TextDrawer /// \ingroup Render /// /// Contains text which can be rendered with a given font //================================================================================================== //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- TextDrawer::TextDrawer(Font* font) : m_font(font), m_drawBackground(false), m_drawBorder(false), m_textColor(Color3::GRAY), m_backgroundColor(1.0f, 1.0f, 0.8f), m_borderColor(Color3::DARK_GRAY), m_useDepthBuffer(false), m_verticalAlignment(0) // BASELINE { CVF_ASSERT(font); CVF_ASSERT(!font->isEmpty()); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- TextDrawer::~TextDrawer() { } // -------------------------------------------------------------------------------------------------- /// Add text to be drawn // -------------------------------------------------------------------------------------------------- void TextDrawer::addText(const String& text, const Vec2f& pos) { CVF_ASSERT(!text.isEmpty()); CVF_ASSERT(!pos.isUndefined()); m_texts.push_back(text); m_positions.push_back(Vec3f(pos)); } //-------------------------------------------------------------------------------------------------- /// Add text to be drawn /// /// Note: The Z coordinate needs to correspond with the orthographic projection that is setup /// to render the text. So the range should be <1..-1> with 1 being closest to the near plane. //-------------------------------------------------------------------------------------------------- void TextDrawer::addText(const String& text, const Vec3f& pos) { CVF_ASSERT(!text.isEmpty()); CVF_ASSERT(!pos.isUndefined()); m_texts.push_back(text); m_positions.push_back(pos); } //-------------------------------------------------------------------------------------------------- /// Remove all texts in the drawer //-------------------------------------------------------------------------------------------------- void TextDrawer::removeAllTexts() { m_texts.clear(); m_positions.clear(); } //-------------------------------------------------------------------------------------------------- /// Set color of the text to be rendered //-------------------------------------------------------------------------------------------------- void TextDrawer::setTextColor(const Color3f& color) { m_textColor = color; } //-------------------------------------------------------------------------------------------------- /// Set color of the text to be rendered //-------------------------------------------------------------------------------------------------- void TextDrawer::setBackgroundColor(const Color3f& color) { m_backgroundColor = color; } //-------------------------------------------------------------------------------------------------- /// Set color of the text to be rendered //-------------------------------------------------------------------------------------------------- void TextDrawer::setBorderColor(const Color3f& color) { m_borderColor = color; } //-------------------------------------------------------------------------------------------------- /// Set vertical alignment for horizontal text //-------------------------------------------------------------------------------------------------- void TextDrawer::setVerticalAlignment(TextDrawer::Alignment alignment) { CVF_ASSERT(m_font.notNull()); CVF_ASSERT(!m_font->isEmpty()); switch (alignment) { case TextDrawer::TOP: { // Character assumed to reach all the way up ref glyph_top = m_font->getGlyph(L'`'); m_verticalAlignment = static_cast(-glyph_top->horizontalBearingY()); break; } case TextDrawer::CENTER: { // Center around A ref glyph_top = m_font->getGlyph(L'A'); m_verticalAlignment = static_cast(-((glyph_top->horizontalBearingY() + 1) >> 1)); break; } case TextDrawer::BASELINE: { m_verticalAlignment = 0; break; } case TextDrawer::BOTTOM: { // Character assumed to reach all the way down ref glyph_bottom = m_font->getGlyph(L'g'); m_verticalAlignment = static_cast(static_cast(glyph_bottom->height()) + glyph_bottom->horizontalBearingY()); break; } default: { CVF_FAIL_MSG("Unsupported alignment type"); break; } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void TextDrawer::setDrawBackground(bool drawBackground) { m_drawBackground = drawBackground; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void TextDrawer::setDrawBorder(bool drawBorder) { m_drawBorder = drawBorder; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void TextDrawer::setUseDepthBuffer(bool useDepthBuffer) { m_useDepthBuffer = useDepthBuffer; } //-------------------------------------------------------------------------------------------------- /// Returns the color used to draw the text //-------------------------------------------------------------------------------------------------- Color3f TextDrawer::textColor() const { return m_textColor; } //-------------------------------------------------------------------------------------------------- /// Returns the color of the background //-------------------------------------------------------------------------------------------------- Color3f TextDrawer::backgroundColor() const { return m_backgroundColor; } //-------------------------------------------------------------------------------------------------- /// Returns the color of the border. //-------------------------------------------------------------------------------------------------- Color3f TextDrawer::borderColor() const { return m_borderColor; } //-------------------------------------------------------------------------------------------------- /// Returns true if the background will be drawn //-------------------------------------------------------------------------------------------------- bool TextDrawer::drawBackground() const { return m_drawBackground; } //-------------------------------------------------------------------------------------------------- /// Returns true if the border will be drawn //-------------------------------------------------------------------------------------------------- bool TextDrawer::drawBorder() const { return m_drawBorder; } //-------------------------------------------------------------------------------------------------- /// Draw text based using OpenGL shader programs //-------------------------------------------------------------------------------------------------- void TextDrawer::render(OpenGLContext* oglContext, const MatrixState& matrixState) { doRender2d(oglContext, matrixState, false); } //-------------------------------------------------------------------------------------------------- /// Draw text based using OpenGL shader programs //-------------------------------------------------------------------------------------------------- void TextDrawer::renderSoftware(OpenGLContext* oglContext, const MatrixState& matrixState) { doRender2d(oglContext, matrixState, true); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void TextDrawer::doRender2d(OpenGLContext* oglContext, const MatrixState& matrixState, bool softwareRendering) { CVF_CALLSITE_OPENGL(oglContext); CVF_ASSERT(m_positions.size() == m_texts.size()); static const ushort connects[] = { 0, 1, 2, 0, 2, 3 }; static const ushort lineConnects[] = { 0, 1, 1, 2, 2, 3, 3, 0 }; float vertexArray[12]; float textureCoords[8]; // Will be updated for each glyph float* v1 = &vertexArray[0]; float* v2 = &vertexArray[3]; float* v3 = &vertexArray[6]; float* v4 = &vertexArray[9]; v1[2] = v2[2] = v3[2] = v4[2] = 0.0f; // Prepare 2D pixel exact projection to draw texts Camera projCam; projCam.setViewport(matrixState.viewportPosition().x(), matrixState.viewportPosition().y(), matrixState.viewportSize().x(), matrixState.viewportSize().y()); projCam.setProjectionAsPixelExact2D(); projCam.setViewMatrix(Mat4d::IDENTITY); MatrixState projMatrixState(projCam); // Turn off depth test RenderStateDepth depth(m_useDepthBuffer, RenderStateDepth::LESS, m_useDepthBuffer); depth.applyOpenGL(oglContext); // Setup viewport projCam.viewport()->applyOpenGL(oglContext, Viewport::DO_NOT_CLEAR); if (softwareRendering) { if (ShaderProgram::supportedOpenGL(oglContext)) { ShaderProgram::useNoProgram(oglContext); } #ifndef CVF_OPENGL_ES RenderStateMaterial_FF mat; mat.enableColorMaterial(true); mat.applyOpenGL(oglContext); RenderStateLighting_FF light(false); light.applyOpenGL(oglContext); #endif projCam.applyOpenGL(); } else { glBindBuffer(GL_ARRAY_BUFFER, 0); glEnableVertexAttribArray(ShaderProgram::VERTEX); glVertexAttribPointer(ShaderProgram::VERTEX, 3, GL_FLOAT, GL_FALSE, 0, vertexArray); } // Use a fixed line spacing float lineSpacing = m_font->lineSpacing(); Vec2f offset(0,0); // Render background and border // ------------------------------------------------------------------------- if (m_drawBackground || m_drawBorder) { if (m_useDepthBuffer) { RenderStatePolygonOffset polygonOffset; polygonOffset.configurePolygonPositiveOffset(); polygonOffset.applyOpenGL(oglContext); } ref backgroundShader; if (!softwareRendering) { backgroundShader = oglContext->resourceManager()->getLinkedUnlitColorShaderProgram(oglContext); if (backgroundShader->useProgram(oglContext)) { backgroundShader->clearUniformApplyTracking(); backgroundShader->applyFixedUniforms(oglContext, projMatrixState); } } // Figure out margin ref glyph = m_font->getGlyph(L'A'); float charHeight = static_cast(glyph->height()); float charWidth = static_cast(glyph->width()); offset.x() = cvf::Math::floor(charWidth/2.0f); offset.y() = cvf::Math::floor(charHeight/2.0f); size_t numTexts = m_texts.size(); for (size_t i = 0; i < numTexts; i++) { Vec3f pos = m_positions[i]; String text = m_texts[i]; Vec2ui textExtent = m_font->textExtent(text); Vec3f min = pos;//Vec3f(textBB.min()); Vec3f max = Vec3f(min.x() + static_cast(textExtent.x()) + offset.x()*2.0f, min.y() + static_cast(textExtent.y()) + offset.y()*2.0f, 0.0f); // Draw the background triangle v1[0] = min.x(); v1[1] = min.y(); v1[2] = pos.z(); v2[0] = max.x(); v2[1] = min.y(); v2[2] = pos.z(); v3[0] = max.x(); v3[1] = max.y(); v3[2] = pos.z(); v4[0] = min.x(); v4[1] = max.y(); v4[2] = pos.z(); if (m_drawBackground) { if (softwareRendering) { #ifndef CVF_OPENGL_ES glEnable(GL_COLOR_MATERIAL); glDisable(GL_TEXTURE_2D); glColor4fv(m_backgroundColor.ptr()); glBegin(GL_TRIANGLE_FAN); glVertex3fv(v1); glVertex3fv(v2); glVertex3fv(v3); glVertex3fv(v4); glEnd(); #endif } else { UniformFloat backgroundColor("u_color", Color4f(m_backgroundColor)); backgroundShader->applyUniform(oglContext, backgroundColor); // Draw background glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, connects); } } if (m_drawBorder) { if (softwareRendering) { #ifndef CVF_OPENGL_ES glColor3fv(m_borderColor.ptr()); glBegin(GL_LINE_LOOP); glVertex3fv(v1); glVertex3fv(v2); glVertex3fv(v3); glVertex3fv(v4); glEnd(); #endif } else { UniformFloat borderColor("u_color", Color4f(m_borderColor)); backgroundShader->applyUniform(oglContext, borderColor); // Draw border glDrawElements(GL_LINES, 8, GL_UNSIGNED_SHORT, lineConnects); } } } if (m_useDepthBuffer) { RenderStatePolygonOffset resetPolygonOffset; resetPolygonOffset.applyOpenGL(oglContext); } } // Render texts // ------------------------------------------------------------------------- if (softwareRendering) { #ifndef CVF_OPENGL_ES glEnable(GL_TEXTURE_2D); glColor3fv(m_textColor.ptr()); #endif } else { ref textShader = oglContext->resourceManager()->getLinkedTextShaderProgram(oglContext); if (textShader->useProgram(oglContext)) { textShader->clearUniformApplyTracking(); textShader->applyFixedUniforms(oglContext, projMatrixState); UniformFloat uniformColor("u_color", m_textColor); textShader->applyUniform(oglContext, uniformColor); UniformInt uniformTexture("u_texture2D", 0); textShader->applyUniform(oglContext, uniformTexture); } glEnableVertexAttribArray(ShaderProgram::TEX_COORD_2F_0); glVertexAttribPointer(ShaderProgram::TEX_COORD_2F_0, 2, GL_FLOAT, GL_FALSE, 0, textureCoords); // Setup texture, Note: Each glyph will do additional setup glActiveTexture(GL_TEXTURE0); } RenderStateBlending blending; blending.configureTransparencyBlending(); blending.applyOpenGL(oglContext); wchar_t character; size_t numCharacters; size_t numTexts = m_texts.size(); size_t i, j; for (i = 0; i < numTexts; i++) { Vec3f pos = m_positions[i]; String text = m_texts[i]; // Need to round off to integer positions to avoid buggy text drawing on iPad2 pos.x() = cvf::Math::floor(pos.x()); pos.y() = cvf::Math::floor(pos.y()); // Cursor incrementor Vec2f cursor(pos); cursor += offset; std::vector lines = text.split("\n"); for (size_t lineIdx = lines.size(); lineIdx-- > 0; ) { String line = lines[lineIdx]; numCharacters = line.size(); for (j = 0; j < numCharacters; j++) { character = line[j]; ref glyph = m_font->getGlyph(character); float textureWidth = static_cast(glyph->width()); float textureHeight = static_cast(glyph->height()); // Lower left corner v1[0] = cursor.x() + static_cast(glyph->horizontalBearingX()); v1[1] = cursor.y() + static_cast(glyph->horizontalBearingY()) - textureHeight + static_cast(m_verticalAlignment); v1[2] = pos.z(); // Lower right corner v2[0] = v1[0] + textureWidth; v2[1] = v1[1]; v2[2] = pos.z(); // Upper right corner v3[0] = v2[0]; v3[1] = v1[1] + textureHeight; v3[2] = pos.z(); // Upper left corner v4[0] = v1[0]; v4[1] = v3[1]; v4[2] = pos.z(); glyph->setupAndBindTexture(oglContext, softwareRendering); // Get texture coordinates const FloatArray* textureCoordinates = glyph->textureCoordinates(); CVF_ASSERT(textureCoordinates); CVF_ASSERT(textureCoordinates->size() == 8); const float* textureCoordinatesPtr = textureCoordinates->ptr(); CVF_ASSERT(textureCoordinatesPtr); int t; for (t = 0; t < 8; t++) { textureCoords[t] = textureCoordinatesPtr[t]; } if (softwareRendering) { #ifndef CVF_OPENGL_ES glBegin(GL_TRIANGLES); // First triangle in quad glTexCoord2fv(&textureCoordinatesPtr[0]); glVertex3fv(v1); glTexCoord2fv(&textureCoordinatesPtr[2]); glVertex3fv(v2); glTexCoord2fv(&textureCoordinatesPtr[4]); glVertex3fv(v3); // Second triangle in quad glTexCoord2fv(&textureCoordinatesPtr[0]); glVertex3fv(v1); glTexCoord2fv(&textureCoordinatesPtr[4]); glVertex3fv(v3); glTexCoord2fv(&textureCoordinatesPtr[6]); glVertex3fv(v4); glEnd(); #endif } else { glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, connects); } // Jump to the next character in the string, if any if (j < (numCharacters - 1)) { float advance = static_cast(m_font->advance(character, text[j + 1])); cursor.x() += advance; } } // CR cursor.x() = pos.x() + offset.x(); cursor.y() += lineSpacing; } } if (softwareRendering) { #ifndef CVF_OPENGL_ES // apply the projection matrix glMatrixMode(GL_PROJECTION); glLoadMatrixf(matrixState.projectionMatrix().ptr()); // apply the view matrix glMatrixMode(GL_MODELVIEW); glLoadMatrixf(matrixState.viewMatrix().ptr()); RenderStateMaterial_FF mat; mat.applyOpenGL(oglContext); RenderStateLighting_FF light; light.applyOpenGL(oglContext); #endif } else { glDisableVertexAttribArray(ShaderProgram::TEX_COORD_2F_0); glDisableVertexAttribArray(ShaderProgram::VERTEX); ShaderProgram::useNoProgram(oglContext); } // Reset render states RenderStateBlending resetBlending; resetBlending.applyOpenGL(oglContext); // Turn off depth test RenderStateDepth resetDepth; resetDepth.applyOpenGL(oglContext); CVF_CHECK_OGL(oglContext); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- bool TextDrawer::pickText(const Vec3f& pickCoord2d, const String& text, const Vec3f& pos, Font* font) { // Figure out margin ref glyph = font->getGlyph(L'A'); float charHeight = static_cast(glyph->height()); float charWidth = static_cast(glyph->width()); float offsetX = cvf::Math::floor(charWidth/2.0f); float offsetY = cvf::Math::floor(charHeight/2.0f); Vec2ui textExtent = font->textExtent(text); Vec3f min = pos; Vec3f max = Vec3f(min.x() + static_cast(textExtent.x()) + offsetX*2.0f, min.y() + static_cast(textExtent.y()) + offsetY*2.0f, 0.0f); if (pickCoord2d.x() > min.x() && pickCoord2d.x() < max.x() && pickCoord2d.y() > min.y() && pickCoord2d.y() < max.y()) { return true; } return false; } } // namespace cvf