Files
OpenSceneGraph/src/osgText/Glyph.cpp

590 lines
20 KiB
C++

/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield
*
* This library is open source and may be redistributed and/or modified under
* the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or
* (at your option) any later version. The full license is in LICENSE file
* included with this distribution, and on the openscenegraph.org website.
*
* 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
* OpenSceneGraph Public License for more details.
*/
#include <osgText/Font>
#include <osgText/Text>
#include <osg/State>
#include <osg/Notify>
#include <osg/GLU>
#include <osgUtil/SmoothingVisitor>
#include <string.h>
#include <stdlib.h>
#include "GlyphGeometry.h"
using namespace osgText;
using namespace std;
#if 0
#define TEXTURE_IMAGE_NUM_CHANNELS 1
#define TEXTURE_IMAGE_FORMAT OSGTEXT_GLYPH_FORMAT
#else
#define TEXTURE_IMAGE_NUM_CHANNELS 4
#define TEXTURE_IMAGE_FORMAT GL_RGBA
#endif
GlyphTexture::GlyphTexture():
_margin(1),
_marginRatio(0.02f),
_interval(1),
_usedY(0),
_partUsedX(0),
_partUsedY(0)
{
setWrap(WRAP_S, CLAMP_TO_EDGE);
setWrap(WRAP_T, CLAMP_TO_EDGE);
}
GlyphTexture::~GlyphTexture()
{
}
// return -1 if *this < *rhs, 0 if *this==*rhs, 1 if *this>*rhs.
int GlyphTexture::compare(const osg::StateAttribute& rhs) const
{
if (this<&rhs) return -1;
else if (this>&rhs) return 1;
return 0;
}
bool GlyphTexture::getSpaceForGlyph(Glyph* glyph, int& posX, int& posY)
{
int maxAxis = osg::maximum(glyph->s(), glyph->t());
int margin_from_ratio = (int)((float)maxAxis * _marginRatio);
int search_distance = glyph->getFontResolution().second/8;
int margin = _margin + osg::maximum(margin_from_ratio, search_distance);
int width = glyph->s()+2*margin;
int height = glyph->t()+2*margin;
int partUsedX = ((_partUsedX % _interval) == 0) ? _partUsedX : (((_partUsedX/_interval)+1)*_interval);
int partUsedY = ((_partUsedY % _interval) == 0) ? _partUsedY : (((_partUsedY/_interval)+1)*_interval);
int usedY = ((_usedY % _interval) == 0) ? _usedY : (((_usedY/_interval)+1)*_interval);
// first check box (partUsedX, usedY) to (width,height)
if (width <= (getTextureWidth()-partUsedX) &&
height <= (getTextureHeight()-usedY))
{
// can fit in existing row.
// record the position in which the texture will be stored.
posX = partUsedX+margin;
posY = usedY+margin;
// move used markers on.
_partUsedX = posX+width;
if (_usedY+height>_partUsedY) _partUsedY = _usedY+height;
return true;
}
// start an new row.
if (width <= getTextureWidth() &&
height <= (getTextureHeight()-_partUsedY))
{
// can fit next row.
_partUsedX = 0;
_usedY = partUsedY;
posX = _partUsedX+margin;
posY = _usedY+margin;
// move used markers on.
_partUsedX = posX+width;
_partUsedY = _usedY+height;
return true;
}
// doesn't fit into glyph.
return false;
}
void GlyphTexture::addGlyph(Glyph* glyph, int posX, int posY)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_mutex);
if (!_image.valid()) createImage();
_glyphs.push_back(glyph);
// set up the details of where to place glyph's image in the texture.
glyph->setTexture(this);
glyph->setTexturePosition(posX,posY);
glyph->setMinTexCoord( osg::Vec2( static_cast<float>(posX)/static_cast<float>(getTextureWidth()),
static_cast<float>(posY)/static_cast<float>(getTextureHeight()) ) );
glyph->setMaxTexCoord( osg::Vec2( static_cast<float>(posX+glyph->s())/static_cast<float>(getTextureWidth()),
static_cast<float>(posY+glyph->t())/static_cast<float>(getTextureHeight()) ) );
copyGlyphImage(glyph);
}
void GlyphTexture::copyGlyphImage(Glyph* glyph)
{
if (_glyphTextureFeatures==GREYSCALE)
{
// OSG_NOTICE<<"GlyphTexture::copyGlyphImage() greyscale copy"<<std::endl;
_image->copySubImage(glyph->getTexturePositionX(), glyph->getTexturePositionY(), 0, glyph);
_image->dirty();
return;
}
// OSG_NOTICE<<"GlyphTexture::copyGlyphImage() generating signed distance field."<<std::endl;
int src_columns = glyph->s();
int src_rows = glyph->t();
unsigned char* src_data = glyph->data();
int dest_columns = _image->s();
int dest_rows = _image->t();
unsigned char* dest_data = _image->data(glyph->getTexturePositionX(),glyph->getTexturePositionY());
int search_distance = glyph->getFontResolution().second/8;
int left = -search_distance;
int right = glyph->s()+search_distance;
int lower = -search_distance;
int upper = glyph->t()+search_distance;
float multiplier = 1.0/255.0f;
float max_distance = sqrtf(float(search_distance*search_distance)*2.0f);
int num_channels = TEXTURE_IMAGE_NUM_CHANNELS;
if ((left+glyph->getTexturePositionX())<0) left = -glyph->getTexturePositionX();
if ((right+glyph->getTexturePositionX())>=dest_columns) right = dest_columns-glyph->getTexturePositionX()-1;
if ((lower+glyph->getTexturePositionY())<0) lower = -glyph->getTexturePositionY();
if ((upper+glyph->getTexturePositionY())>=dest_rows) upper = dest_rows-glyph->getTexturePositionY()-1;
for(int dr=lower; dr<=upper; ++dr)
{
for(int dc=left; dc<=right; ++dc)
{
unsigned char value = 0;
unsigned char center_value = 0;
if (dr>=0 && dr<src_rows && dc>=0 && dc<src_columns) center_value = *(src_data + dr*src_columns + dc);
float center_value_f = center_value*multiplier;
float min_distance = FLT_MAX;
if (center_value>0 && center_value<255)
{
if (center_value_f>=0.5f)
{
min_distance = center_value_f-0.5f;
value = 128+(min_distance/max_distance)*127;
}
else
{
min_distance = 0.5f-center_value_f;
value = 127-(min_distance/max_distance)*127;
}
}
else
{
for(int radius=1; radius<search_distance; ++radius)
{
for(int span=-radius; span<radius; ++span)
{
{
// left
int dx = -radius;
int dy = span;
int c = dc+dx;
int r = dr+dy;
unsigned char local_value = 0;
if (r>=0 && r<src_rows && c>=0 && c<src_columns) local_value = *(src_data + r*src_columns + c);
if (local_value!=center_value)
{
float local_value_f = float(local_value)*multiplier;
float D = sqrtf(float(dx*dx) + float(dy*dy));
float local_multiplier = (abs(dx)>abs(dy)) ? D/float(abs(dx)) : D/float(abs(dy));
float local_distance = sqrtf(float(radius*radius)+float(span*span));
if (center_value==0) local_distance += (0.5f-local_value_f)*local_multiplier;
else local_distance += (local_value_f - 0.5f)*local_multiplier;
}
}
{
// top
int dx = radius;
int dy = span;
int c = dc+dx;
int r = dr+dy;
unsigned char local_value = 0;
if (r>=0 && r<src_rows && c>=0 && c<src_columns) local_value = *(src_data + r*src_columns + c);
if (local_value!=center_value)
{
float local_value_f = float(local_value)*multiplier;
float D = sqrtf(float(dx*dx) + float(dy*dy));
float local_multiplier = (abs(dx)>abs(dy)) ? D/float(abs(dx)) : D/float(abs(dy));
float local_distance = sqrtf(float(radius*radius)+float(span*span));
if (center_value==0) local_distance += (0.5f-local_value_f)*local_multiplier;
else local_distance += (local_value_f - 0.5f)*local_multiplier;
}
}
{
// right
int dx = radius;
int dy = -span;
int c = dc+dx;
int r = dr+dy;
unsigned char local_value = 0;
if (r>=0 && r<src_rows && c>=0 && c<src_columns) local_value = *(src_data + r*src_columns + c);
if (local_value!=center_value)
{
float local_value_f = float(local_value)*multiplier;
float D = sqrtf(float(dx*dx) + float(dy*dy));
float local_multiplier = (abs(dx)>abs(dy)) ? D/float(abs(dx)) : D/float(abs(dy));
float local_distance = sqrtf(float(radius*radius)+float(span*span));
if (center_value==0) local_distance += (0.5f-local_value_f)*local_multiplier;
else local_distance += (local_value_f - 0.5f)*local_multiplier;
if (local_distance<min_distance) min_distance = local_distance;
}
}
{
// bottom
int dx = -radius;
int dy = -span;
int c = dc+dx;
int r = dr+dy;
unsigned char local_value = 0;
if (r>=0 && r<src_rows && c>=0 && c<src_columns) local_value = *(src_data + r*src_columns + c);
if (local_value!=center_value)
{
float local_value_f = float(local_value)*multiplier;
float D = sqrtf(float(dx*dx) + float(dy*dy));
float local_multiplier = (abs(dx)>abs(dy)) ? D/float(abs(dx)) : D/float(abs(dy));
float local_distance = sqrtf(float(radius*radius)+float(span*span));
if (center_value==0) local_distance += (0.5f-local_value_f)*local_multiplier;
else local_distance += (local_value_f - 0.5f)*local_multiplier;
if (local_distance<min_distance) min_distance = local_distance;
}
}
}
}
if (center_value_f>=0.5)
{
value = 128+(min_distance/max_distance)*127;
}
else
{
value = 127-(min_distance/max_distance)*127;
}
}
unsigned char* dest_ptr = dest_data + (dr*dest_columns + dc)*num_channels;
if (num_channels==4)
{
// signed distance field value
*(dest_ptr++) = value;
float outline_distance = max_distance/4.0f;
// compute the alpha value of outline, one texel thick
unsigned char outline = center_value;
if (center_value<255)
{
if (min_distance<outline_distance-1.0) outline = 255;
else if (min_distance<outline_distance) outline = 255*(outline_distance-min_distance);
else outline = 0;
}
*(dest_ptr++) = outline;
outline_distance *= 2.0f;
// compute the alpha vlaue of outline two texel thick
outline = center_value;
if (center_value<255)
{
if (min_distance<outline_distance-1.0) outline = 255;
else if (min_distance<outline_distance) outline = 255*(outline_distance-min_distance);
else outline = 0;
}
*(dest_ptr++) = outline;
// original alpha value from glyph image
*(dest_ptr) = center_value;
}
else
{
*(dest_ptr) = value;
}
}
}
}
void GlyphTexture::setThreadSafeRefUnref(bool threadSafe)
{
osg::Texture2D::setThreadSafeRefUnref(threadSafe);
}
void GlyphTexture::resizeGLObjectBuffers(unsigned int maxSize)
{
osg::Texture2D::resizeGLObjectBuffers(maxSize);
unsigned int initialSize = _glyphsToSubload.size();
_glyphsToSubload.resize(maxSize);
for(unsigned i=initialSize; i<_glyphsToSubload.size(); ++i)
{
for(GlyphRefList::iterator itr = _glyphs.begin();
itr != _glyphs.end();
++itr)
{
_glyphsToSubload[i].push_back(itr->get());
}
}
}
osg::Image* GlyphTexture::createImage()
{
if (!_image)
{
OSG_NOTICE<<"GlyphTexture::createImage() : Creating image 0x"<<std::hex<<TEXTURE_IMAGE_FORMAT<<std::dec<<std::endl;
_image = new osg::Image;
#if defined(OSG_GL3_AVAILABLE) && !defined(OSG_GL2_AVAILABLE) && !defined(OSG_GL1_AVAILABLE)
GLenum imageFormat = (_glyphTextureFeatures==GREYSCALE) ? GL_RED : GL_RGBA;
#else
GLenum imageFormat = (_glyphTextureFeatures==GREYSCALE) ? GL_ALPHA : GL_RGBA;
#endif
_image->allocateImage(getTextureWidth(), getTextureHeight(), 1, imageFormat, GL_UNSIGNED_BYTE);
memset(_image->data(), 0, _image->getTotalSizeInBytes());
for(GlyphRefList::iterator itr = _glyphs.begin();
itr != _glyphs.end();
++itr)
{
Glyph* glyph = itr->get();
copyGlyphImage(glyph);
}
}
return _image.get();
}
// all the methods in Font::Glyph have been made non inline because VisualStudio6.0 is STUPID, STUPID, STUPID PILE OF JUNK.
Glyph::Glyph(Font* font, unsigned int glyphCode):
_font(font),
_glyphCode(glyphCode),
_width(1.0f),
_height(1.0f),
_horizontalBearing(0.0f,0.f),
_horizontalAdvance(0.f),
_verticalBearing(0.0f,0.f),
_verticalAdvance(0.f),
_texture(0),
_texturePosX(0),
_texturePosY(0),
_minTexCoord(0.0f,0.0f),
_maxTexCoord(0.0f,0.0f)
{
setThreadSafeRefUnref(true);
}
Glyph::~Glyph()
{
}
void Glyph::setHorizontalBearing(const osg::Vec2& bearing) { _horizontalBearing=bearing; }
const osg::Vec2& Glyph::getHorizontalBearing() const { return _horizontalBearing; }
void Glyph::setHorizontalAdvance(float advance) { _horizontalAdvance=advance; }
float Glyph::getHorizontalAdvance() const { return _horizontalAdvance; }
void Glyph::setVerticalBearing(const osg::Vec2& bearing) { _verticalBearing=bearing; }
const osg::Vec2& Glyph::getVerticalBearing() const { return _verticalBearing; }
void Glyph::setVerticalAdvance(float advance) { _verticalAdvance=advance; }
float Glyph::getVerticalAdvance() const { return _verticalAdvance; }
void Glyph::setTexture(GlyphTexture* texture) { _texture = texture; }
GlyphTexture* Glyph::getTexture() { return _texture; }
const GlyphTexture* Glyph::getTexture() const { return _texture; }
void Glyph::setTexturePosition(int posX,int posY) { _texturePosX = posX; _texturePosY = posY; }
int Glyph::getTexturePositionX() const { return _texturePosX; }
int Glyph::getTexturePositionY() const { return _texturePosY; }
void Glyph::setMinTexCoord(const osg::Vec2& coord) { _minTexCoord=coord; }
const osg::Vec2& Glyph::getMinTexCoord() const { return _minTexCoord; }
void Glyph::setMaxTexCoord(const osg::Vec2& coord) { _maxTexCoord=coord; }
const osg::Vec2& Glyph::getMaxTexCoord() const { return _maxTexCoord; }
Glyph3D::Glyph3D(Font* font, unsigned int glyphCode):
osg::Referenced(true),
_font(font),
_glyphCode(glyphCode),
_width(1.0f),
_height(1.0f),
_horizontalBearing(0,0),
_horizontalAdvance(0),
_verticalBearing(0,0),
_verticalAdvance(0)
{}
void Glyph3D::setThreadSafeRefUnref(bool threadSafe)
{
for(GlyphGeometries::iterator itr = _glyphGeometries.begin();
itr != _glyphGeometries.end();
++itr)
{
(*itr)->setThreadSafeRefUnref(threadSafe);
}
}
GlyphGeometry* Glyph3D::getGlyphGeometry(const Style* style)
{
for(GlyphGeometries::iterator itr = _glyphGeometries.begin();
itr != _glyphGeometries.end();
++itr)
{
GlyphGeometry* glyphGeometry = itr->get();
if (glyphGeometry->match(style))
{
OSG_INFO<<"Glyph3D::getGlyphGeometry(Style* style) found matching GlyphGeometry."<<std::endl;
return glyphGeometry;
}
}
OSG_INFO<<"Glyph3D::getGlyphGeometry(Style* style) could not find matching GlyphGeometry, creating a new one."<<std::endl;
osg::ref_ptr<GlyphGeometry> glyphGeometry = new GlyphGeometry();
glyphGeometry->setup(this, style);
_glyphGeometries.push_back(glyphGeometry);
return glyphGeometry.get();
}
GlyphGeometry::GlyphGeometry()
{
}
void GlyphGeometry::setThreadSafeRefUnref(bool threadSafe)
{
if (_geode.valid()) _geode->setThreadSafeRefUnref(threadSafe);
}
void GlyphGeometry::setup(const Glyph3D* glyph, const Style* style)
{
float creaseAngle = 30.0f;
bool smooth = true;
osg::ref_ptr<osg::Geometry> shellGeometry;
if (!style)
{
OSG_INFO<<"GlyphGeometry::setup(const Glyph* glyph, NULL) creating default glyph geometry."<<std::endl;
float width = 0.1f;
_geometry = osgText::computeTextGeometry(glyph, width);
}
else
{
OSG_INFO<<"GlyphGeometry::setup(const Glyph* glyph, NULL) create glyph geometry with custom Style."<<std::endl;
// record the style
_style = osg::clone(style, osg::CopyOp::DEEP_COPY_ALL);
const Bevel* bevel = style->getBevel();
bool outline = style->getOutlineRatio()>0.0f;
float width = style->getThicknessRatio();
if (bevel)
{
osg::ref_ptr<osg::Geometry> glyphGeometry = osgText::computeGlyphGeometry(glyph, *bevel, width);
_geometry = osgText::computeTextGeometry(glyphGeometry.get(), *bevel, width);
shellGeometry = outline ? osgText::computeShellGeometry(glyphGeometry.get(), *bevel, width) : 0;
}
else
{
_geometry = osgText::computeTextGeometry(glyph, width);
}
}
if (!_geometry)
{
OSG_INFO<<"Warning: GlyphGeometry::setup(const Glyph* glyph, const Style* style) failed."<<std::endl;
return;
}
_geode = new osg::Geode;
_geode->addDrawable(_geometry.get());
if (shellGeometry.valid()) _geode->addDrawable(shellGeometry.get());
// create the normals
if (smooth)
{
osgUtil::SmoothingVisitor::smooth(*_geometry, osg::DegreesToRadians(creaseAngle));
}
_vertices = dynamic_cast<osg::Vec3Array*>(_geometry->getVertexArray());
_normals = dynamic_cast<osg::Vec3Array*>(_geometry->getNormalArray());
for(osg::Geometry::PrimitiveSetList::iterator itr = _geometry->getPrimitiveSetList().begin();
itr != _geometry->getPrimitiveSetList().end();
++itr)
{
osg::PrimitiveSet* prim = itr->get();
if (prim->getName()=="front") _frontPrimitiveSetList.push_back(prim);
else if (prim->getName()=="back") _backPrimitiveSetList.push_back(prim);
else if (prim->getName()=="wall") _wallPrimitiveSetList.push_back(prim);
}
}
bool GlyphGeometry::match(const Style* style) const
{
if (_style == style) return true;
if (!_style || !style) return false;
return (*_style==*style);
}