/**************************************************************************\
* Copyright (c) Kongsberg Oil & Gas Technologies AS
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\**************************************************************************/
/*!
\class SoText3 SoText3.h Inventor/nodes/SoText3.h
\brief The SoText3 class renders extruded 3D text.
\ingroup nodes
Render text as 3D geometry.
The size of the textual geometry representation is decided from the
SoFont::size field of a preceding SoFont-node in the scene graph,
which specifies the size in unit coordinates. This value sets the
approximate vertical size of the letters. The default value if no
SoFont-nodes are used, is 10.
This node will create 3D geometry from a specified font defined by a
preceding SoFont node. The complexity of the glyphs is controlled by
a preceding SoComplexity node with \e Type set to OBJECT_SPACE.
Please note that the default builtin 3D font will not be affected by
the SoComplexity node.
This is a simple example of an extruded SoText3 string:
\verbatim
#Inventor V2.1 ascii
Separator {
renderCaching ON
Font {
name "Arial"
size 2
}
ProfileCoordinate2 {
point [ 0 0,
0.05 0.05,
0.25 0.05,
0.3 0 ]
}
LinearProfile {
index [ 0, 1, 2, 3 ]
}
Complexity {
type OBJECT_SPACE
value 1
}
ShapeHints {
creaseAngle 1.5
shapeType SOLID
vertexOrdering COUNTERCLOCKWISE
}
Material {
diffuseColor 0.6 0.6 0.8
specularColor 1 1 1
}
Text3 {
string ["Coin3D"]
parts ALL
}
}
\endverbatim
\image html text3.png "Rendering of Example File"
if SoText3::Part is set to SIDES or ALL and no profile is provided, a
flat, one unit long profile will be created.
Separate colors can be assigned to the front, sides and back of the
glyphs by adding a preceding SoMaterialBinding node. Set the \e value
field to PER_PART (default is OVERALL). The front, side and back of
the glyphs will then be colored according to diffuse color 0, 1 and 2
found on the stack.
Beware that using a lot of SoText3 text characters in a scene will
usually have severe impact on the rendering performance, as each and
every character of the text increases the polygon-count a lot. This
makes SoText3 nodes most suitable in situations where you just need
a few characters to be placed in your scene, rather than to
visualize complete sentences.
FILE FORMAT/DEFAULTS:
\code
Text3 {
string ""
spacing 1
justification LEFT
parts FRONT
}
\endcode
\sa SoText2, SoAsciiText, SoProfile
*/
// *************************************************************************
#include
#include
#include // FLT_MAX, FLT_MIN
#ifdef HAVE_CONFIG_H
#include
#endif // HAVE_CONFIG_H
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef COIN_THREADSAFE
#include
#endif // COIN_THREADSAFE
#if COIN_DEBUG
#include
#endif // COIN_DEBUG
#include "coindefs.h" // COIN_OBSOLETED()
#include "nodes/SoSubNodeP.h"
#include "fonts/glyph3d.h"
#include "caches/SoGlyphCache.h"
// *************************************************************************
/*!
\enum SoText3::Part
Used to specify which parts should be rendered/generated.
*/
/*!
\var SoText3::Part SoText3::FRONT
Front of characters.
*/
/*!
\var SoText3::Part SoText3::SIDES
Sides of characters.
*/
/*!
\var SoText3::Part SoText3::BACK
Back of characters.
*/
/*!
\var SoText3::Part SoText3::ALL
All parts.
*/
/*!
\enum SoText3::Justification
Used to specify horizontal string alignment.
*/
/*!
\var SoText3::Justification SoText3::LEFT
Left edges of strings are aligned.
*/
/*!
\var SoText3::Justification SoText3::RIGHT
Right edges of strings are aligned.
*/
/*!
\var SoText3::Justification SoText3::CENTER
Strings are centered.
*/
/*!
\var SoMFString SoText3::string
The strings to render.
Array defaults to contain a single empty string.
*/
/*!
\var SoSFFloat SoText3::spacing
Vertical spacing. 1.0 is the default spacing.
*/
/*!
\var SoSFEnum SoText3::justification
Horizontal justification. Default is alignment at the left border.
*/
/*!
\var SoSFBitMask SoText3::parts
Character parts. Default is to show only the front-facing part.
*/
// FIXME: missing features, pederb 20000224
// - Texture coordinates are not generated yet.
// - Normals for SIDES should be smoothed.
// *************************************************************************
class SoText3P {
public:
SoText3P(SoText3 * master) : master(master) { }
void render(SoState * state, const cc_font_specification * fontspec, unsigned int part);
void generate(SoAction * action, const cc_font_specification * fontspec, unsigned int part);
SbList widths;
void setUpGlyphs(SoState * state, SoText3 * textnode);
SbBox3f maxglyphbbox;
SoNormalGenerator * normalgenerator;
SoGlyphCache * cache;
void lock(void) {
#ifdef COIN_THREADSAFE
this->mutex.lock();
#endif // COIN_THREADSAFE
}
void unlock(void) {
#ifdef COIN_THREADSAFE
this->mutex.unlock();
#endif // COIN_THREADSAFE
}
private:
#ifdef COIN_THREADSAFE
// FIXME: a mutex for every instance seems a bit excessive,
// especially since MSWindows might have rather strict limits on the
// total amount of mutex resources a process (or even a user) can
// allocate. so consider making this a class-wide instance instead.
// -mortene.
SbMutex mutex;
#endif // COIN_THREADSAFE
SoText3 * master;
};
#define PRIVATE(p) ((p)->pimpl)
#define PUBLIC(p) ((p)->master)
// *************************************************************************
SO_NODE_SOURCE(SoText3);
// *************************************************************************
SoText3::SoText3(void)
{
SO_NODE_INTERNAL_CONSTRUCTOR(SoText3);
SO_NODE_ADD_FIELD(string, (""));
SO_NODE_ADD_FIELD(spacing, (1.0f));
SO_NODE_ADD_FIELD(justification, (SoText3::LEFT));
SO_NODE_ADD_FIELD(parts, (SoText3::FRONT));
SO_NODE_DEFINE_ENUM_VALUE(Justification, LEFT);
SO_NODE_DEFINE_ENUM_VALUE(Justification, RIGHT);
SO_NODE_DEFINE_ENUM_VALUE(Justification, CENTER);
SO_NODE_SET_SF_ENUM_TYPE(justification, Justification);
SO_NODE_DEFINE_ENUM_VALUE(Part, FRONT);
SO_NODE_DEFINE_ENUM_VALUE(Part, SIDES);
SO_NODE_DEFINE_ENUM_VALUE(Part, BACK);
SO_NODE_DEFINE_ENUM_VALUE(Part, ALL);
SO_NODE_SET_SF_ENUM_TYPE(parts, Part);
PRIVATE(this) = new SoText3P(this);
PRIVATE(this)->normalgenerator = new SoNormalGenerator(FALSE, 0xff);
PRIVATE(this)->cache = NULL;
}
SoText3::~SoText3()
{
if (PRIVATE(this)->cache) PRIVATE(this)->cache->unref();
delete PRIVATE(this)->normalgenerator;
delete PRIVATE(this);
}
// doc in parent
void
SoText3::initClass(void)
{
SO_NODE_INTERNAL_INIT_CLASS(SoText3, SO_FROM_INVENTOR_2_1);
}
// doc in parent
void
SoText3::computeBBox(SoAction * action, SbBox3f & box, SbVec3f & center)
{
SoState * state = action->getState();
PRIVATE(this)->lock();
PRIVATE(this)->setUpGlyphs(state, this);
SoCacheElement::addCacheDependency(state, PRIVATE(this)->cache);
const cc_font_specification * fontspec = PRIVATE(this)->cache->getCachedFontspec();
int i, n = PRIVATE(this)->widths.getLength();
if (n == 0) {
PRIVATE(this)->unlock();
return; // empty bbox
}
float maxw = FLT_MIN;
for (i = 0; i < n; i++) {
maxw = SbMax(maxw, PRIVATE(this)->widths[i]);
}
if (maxw == FLT_MIN) { // There is no text to bound. Returning.
PRIVATE(this)->unlock();
return;
}
SbBox2f maxbox;
const float maxy = 0;
float miny = -this->spacing.getValue() * fontspec->size * (n-1);
float minx, maxx;
switch (this->justification.getValue()) {
case SoText3::LEFT:
minx = 0.0f;
maxx = maxw;
break;
case SoText3::RIGHT:
minx = -maxw;
maxx = 0.0f;
break;
case SoText3::CENTER:
maxx = maxw * 0.5f;
minx = -maxx;
break;
default:
assert(0 && "unknown justification");
minx = maxx = 0.0f;
break;
}
// check profiles and extend bounding box if necessary
float profsize = 0;
float minz = -1.0f, maxz = 0.0f;
const SoNodeList profilenodes = SoProfileElement::get(state);
int numprofiles = profilenodes.getLength();
if ( numprofiles > 0) {
assert(profilenodes[0]->getTypeId().isDerivedFrom(SoProfile::getClassTypeId()));
for (int i = numprofiles-1; i >= 0; i--) {
SoProfile *pn = (SoProfile *)profilenodes[i];
if (pn->isOfType(SoNurbsProfile::getClassTypeId())) {
// Don't use SoProfile::getVertices() for SoNurbsProfile
// nodes as this would cause a call to the GLU library, which
// requires a valid GL context. Instead we approximate using
// SoNurbsProfile::getTrimCurve(), and use the control points
// to calculate the bounding box. This is an approximation,
// but the same technique is used in So[Indexed]NurbsSurface
// and So[Indexed]NurbsCurve. To avoid this approximation we
// would need our own NURBS library. pederb, 20000926
SoNurbsProfile * np = (SoNurbsProfile*) pn;
float * knots;
int32_t numknots;
int dim;
int32_t numpts;
float * points;
np->getTrimCurve(state, numpts, points, dim,
numknots, knots);
for (int j = 0; j < numpts; j++) {
if (-points[j*dim] > maxz) maxz = -points[j*dim];
if (-points[j*dim] < minz) minz = -points[j*dim];
if (points[j*dim+1] > profsize) profsize = points[j*dim+1];
}
}
else {
int32_t num;
SbVec2f *coords;
pn->getVertices(state, num, coords);
for (int j = 0; j < num; j++) {
if (-coords[j][0] > maxz) maxz = -coords[j][0];
if (-coords[j][0] < minz) minz = -coords[j][0];
if (coords[j][1] > profsize) profsize = coords[j][1];
}
}
}
}
else {
// extrude
if (this->parts.getValue() == SoText3::BACK) {
maxz = -1.0f;
}
else if (this->parts.getValue() == SoText3::FRONT) {
minz = 0.0f;
}
}
box.setBounds(SbVec3f(minx, miny, minz), SbVec3f(maxx, maxy, maxz));
// Expanding bbox so that glyphs like 'j's and 'q's are completely inside.
box.extendBy(SbVec3f(0,PRIVATE(this)->maxglyphbbox.getMin()[1] - (n-1) * fontspec->size, 0));
box.extendBy(PRIVATE(this)->maxglyphbbox);
box.extendBy(SbVec3f(box.getMax()[0] + profsize, box.getMax()[1] + profsize, 0));
box.extendBy(SbVec3f(box.getMin()[0] - profsize, box.getMin()[1] - profsize, 0));
center = box.getCenter();
PRIVATE(this)->unlock();
}
/*!
Not implemented. Should probably have been private in OIV. Let us
know if you need this method for anything, and we'll implement it.
*/
SbBox3f
SoText3::getCharacterBounds(SoState * COIN_UNUSED_ARG(state), int COIN_UNUSED_ARG(stringindex), int COIN_UNUSED_ARG(charindex))
{
COIN_OBSOLETED();
return SbBox3f();
}
// doc in parent
void
SoText3::GLRender(SoGLRenderAction * action)
{
if (!this->shouldGLRender(action))
return;
PRIVATE(this)->lock();
SoState * state = action->getState();
// FIXME: implement this feature. 20040820 mortene.
static SbBool warned = FALSE;
if (!warned) {
const int stackidx = SoTextOutlineEnabledElement::getClassStackIndex();
const SbBool outlinepresence = state->isElementEnabled(stackidx);
if (outlinepresence && SoTextOutlineEnabledElement::get(state)) {
#if COIN_DEBUG
SoDebugError::postWarning("SoText3::GLRender",
"Support for rendering SoText3 nodes in outline "
"(i.e. heeding the SoTextOutlineEnabledElement) "
"not yet implemented.");
#endif // COIN_DEBUG
warned = TRUE;
}
}
PRIVATE(this)->setUpGlyphs(state, this);
SoCacheElement::addCacheDependency(state, PRIVATE(this)->cache);
const cc_font_specification * fontspec = PRIVATE(this)->cache->getCachedFontspec();
SoMaterialBindingElement::Binding binding = SoMaterialBindingElement::get(state);
SoMaterialBundle mb(action);
mb.sendFirst();
const unsigned int prts = this->parts.getValue();
SoLazyElement * lazyelement = SoLazyElement::getInstance(state);
const int numdiffuse = lazyelement->getNumDiffuse();
SbBool matperpart = (binding != SoMaterialBindingElement::OVERALL);
if (prts & SoText3::FRONT) {
PRIVATE(this)->render(state, fontspec, SoText3::FRONT);
}
if (prts & SoText3::SIDES) {
if (matperpart && (numdiffuse > 1))
mb.send(1, FALSE);
PRIVATE(this)->render(state, fontspec, SoText3::SIDES);
}
if (prts & SoText3::BACK) {
if (matperpart && (numdiffuse > 2))
mb.send(2, FALSE);
PRIVATE(this)->render(state, fontspec, SoText3::BACK);
}
if (SoComplexityTypeElement::get(state) == SoComplexityTypeElement::OBJECT_SPACE) {
SoGLCacheContextElement::shouldAutoCache(state, SoGLCacheContextElement::DO_AUTO_CACHE);
SoGLCacheContextElement::incNumShapes(state);
}
PRIVATE(this)->unlock();
}
// doc in parent
void
SoText3::getPrimitiveCount(SoGetPrimitiveCountAction * action)
{
if (action->is3DTextCountedAsTriangles()) {
// will cause a call to generatePrimitives()
// slow, but we can't be bothered to implement a new loop to count triangles
inherited::getPrimitiveCount(action);
}
else {
action->addNumText(this->string.getNum());
}
}
// doc in parent
void
SoText3::generatePrimitives(SoAction * action)
{
SoState * state = action->getState();
PRIVATE(this)->lock();
PRIVATE(this)->setUpGlyphs(state, this);
if (PRIVATE(this)->cache) {
const cc_font_specification * fontspec = PRIVATE(this)->cache->getCachedFontspec();
unsigned int prts = this->parts.getValue();
if (prts & SoText3::FRONT) {
PRIVATE(this)->generate(action, fontspec, SoText3::FRONT);
}
if (prts & SoText3::SIDES) {
PRIVATE(this)->generate(action, fontspec, SoText3::SIDES);
}
if (prts & SoText3::BACK) {
PRIVATE(this)->generate(action, fontspec, SoText3::BACK);
}
}
PRIVATE(this)->unlock();
}
// doc in parent
SoDetail *
SoText3::createTriangleDetail(SoRayPickAction * COIN_UNUSED_ARG(action),
const SoPrimitiveVertex * v1,
const SoPrimitiveVertex * COIN_UNUSED_ARG(v2),
const SoPrimitiveVertex * COIN_UNUSED_ARG(v3),
SoPickedPoint * COIN_UNUSED_ARG(pp))
{
// generatePrimitives() places text details inside each primitive vertex
assert(v1->getDetail());
return v1->getDetail()->copy();
}
void
SoText3P::render(SoState * state, const cc_font_specification * fontspec,
unsigned int part)
{
int i, n = this->widths.getLength();
int firstprofile = -1;
int32_t profnum;
SbVec2f *profcoords;
float nearz = FLT_MAX;
float farz = -FLT_MAX;
float creaseangle = SoCreaseAngleElement::get(state);
SbBool do2Dtextures = FALSE;
SbBool do3Dtextures = FALSE;
if (SoGLMultiTextureEnabledElement::get(state, 0)) {
do2Dtextures = TRUE;
if (SoGLMultiTextureEnabledElement::getMode(state, 0) ==
SoMultiTextureEnabledElement::TEXTURE3D) {
do3Dtextures = TRUE;
}
}
// FIXME: implement proper support for 3D-texturing, and get rid of
// this. (20031010 handegar)
if (do3Dtextures) {
static SbBool first = TRUE;
if (first) {
first = FALSE;
#if COIN_DEBUG
SoDebugError::postWarning("SoText3::GLRender",
"3D-textures not supported for this node type yet.");
#endif // COIN_DEBUG
}
}
const SoNodeList & profilenodes = SoProfileElement::get(state);
int numprofiles = profilenodes.getLength();
// Indicates if a valid profile has been specified. If it has not,
// text will be rendered extruded.
SbBool validprofile = FALSE;
if (numprofiles > 0) {
assert(profilenodes[0]->getTypeId().isDerivedFrom(SoProfile::getClassTypeId()));
// Find near/far z (for modifying position of front/back)
for (int l=numprofiles-1; l>=0; l--) {
SoProfile * pn = (SoProfile *) profilenodes[l];
pn->getVertices(state, profnum, profcoords);
if (profnum > 0) {
if (profcoords[profnum-1][0] > farz) farz = profcoords[profnum-1][0];
if (profcoords[0][0] < nearz) nearz = profcoords[0][0];
if (pn->linkage.getValue() == SoProfile::START_FIRST) {
if (firstprofile == -1) {
firstprofile = l;
validprofile = TRUE;
}
break;
}
}
}
nearz = -nearz;
farz = -farz;
}
// If no profiles have been specified, or if no valid coordinates have
// been given, set near and far / front and back values to default.
if (!validprofile) {
nearz = 0.0;
farz = -1.0;
}
float ypos = 0.0f;
for (i = 0; i < n; i++) {
float xpos = 0.0f;
switch (PUBLIC(this)->justification.getValue()) {
case SoText3::RIGHT:
xpos = -this->widths[i];
break;
case SoText3::CENTER:
xpos = - this->widths[i] * 0.5f;
break;
}
SbString str = PUBLIC(this)->string[i];
cc_glyph3d * prevglyph = NULL;
const char * p = str.getString();
size_t length = cc_string_utf8_validate_length(p);
// No assertion as zero length is handled correctly (results in a new line)
for (unsigned int strcharidx = 0; strcharidx < length; strcharidx++) {
uint32_t glyphidx = 0;
glyphidx = cc_string_utf8_get_char(p);
p = cc_string_utf8_next_char(p);
cc_glyph3d * glyph = cc_glyph3d_ref(glyphidx, fontspec);
const SbVec2f * coords = (SbVec2f *) cc_glyph3d_getcoords(glyph);
// Get kerning
if (strcharidx > 0) {
float kerningx, kerningy;
cc_glyph3d_getkerning(prevglyph, glyph, &kerningx, &kerningy);
xpos += kerningx * fontspec->size;
}
if (prevglyph) {
cc_glyph3d_unref(prevglyph);
}
prevglyph = glyph;
if (part != SoText3::SIDES) { // FRONT & BACK
const int * ptr = cc_glyph3d_getfaceindices(glyph);
glBegin(GL_TRIANGLES);
while (*ptr >= 0) {
SbVec2f v0, v1, v2;
float zval;
if (part == SoText3::FRONT) {
glNormal3f(0.0f, 0.0f, 1.0f);
v2 = coords[*ptr++];
v1 = coords[*ptr++];
v0 = coords[*ptr++];
zval = nearz;
}
else { // BACK
glNormal3f(0.0f, 0.0f, -1.0f);
v0 = coords[*ptr++];
v1 = coords[*ptr++];
v2 = coords[*ptr++];
zval = farz;
}
if(do2Dtextures)
glTexCoord2f(v0[0] + xpos/fontspec->size,
v0[1] + ypos/fontspec->size);
glVertex3f(v0[0] * fontspec->size + xpos, v0[1] * fontspec->size + ypos, zval);
if(do2Dtextures)
glTexCoord2f(v1[0] + xpos/fontspec->size,
v1[1] + ypos/fontspec->size);
glVertex3f(v1[0] * fontspec->size + xpos, v1[1] * fontspec->size + ypos, zval);
if(do2Dtextures)
glTexCoord2f(v2[0] + xpos/fontspec->size,
v2[1] + ypos/fontspec->size);
glVertex3f(v2[0] * fontspec->size + xpos, v2[1] * fontspec->size + ypos, zval);
}
glEnd();
}
else { // SIDES
if (!validprofile) { // no profile - extrude
const int * ptr = cc_glyph3d_getedgeindices(glyph);
SbVec2f v0, v1;
int counter = 0;
glBegin(GL_QUADS);
while (*ptr >= 0) {
v1 = coords[*ptr++];
v0 = coords[*ptr++];
const int * ccw = (int *) cc_glyph3d_getnextccwedge(glyph, counter);
const int * cw = (int *) cc_glyph3d_getnextcwedge(glyph, counter);
SbVec3f vleft(coords[*(ccw+1)][0], coords[*(ccw+1)][1], 0);
SbVec3f vright(coords[*cw][0], coords[*cw][1], 0);
counter++;
// create two 'normal' vectors pointing out from the edges
SbVec3f normala(vright[0] - v0[0], vright[1] - v0[1], 0.0f);
normala = normala.cross(SbVec3f(0.0f, 0.0f, 1.0f));
if (normala.length() > 0)
normala.normalize();
SbVec3f normalb(v1[0] - vleft[0], v1[1] - vleft[1], 0.0f);
normalb = normalb.cross(SbVec3f(0.0f, 0.0f, 1.0f));
if (normalb.length() > 0)
normalb.normalize();
SbBool flatshading = FALSE;
float dot = normala.dot(normalb);
if(acos(dot) > creaseangle) {
normala = SbVec3f(v1[0] - v0[0], v1[1] - v0[1], 0.0f);
normala = normala.cross(SbVec3f(0.0f, 0.0f, 1.0f));
if (normala.length() > 0)
normala.normalize();
flatshading = TRUE;
}
if (!flatshading) {
if(do2Dtextures)
glTexCoord2f(v1[0] + xpos/fontspec->size,
v1[1] + ypos/fontspec->size);
glNormal3fv(normala.getValue());
glVertex3f(v1[0]*fontspec->size + xpos, v1[1]*fontspec->size + ypos, 0.0f);
if(do2Dtextures)
glTexCoord2f(v0[0] + xpos/fontspec->size,
v0[1] + ypos/fontspec->size);
glNormal3fv(normalb.getValue());
glVertex3f(v0[0]*fontspec->size + xpos, v0[1]*fontspec->size + ypos, 0.0f);
if(do2Dtextures)
glTexCoord2f(v0[0] + xpos/fontspec->size,
v0[1] + ypos/fontspec->size);
glNormal3fv(normalb.getValue());
glVertex3f(v0[0]*fontspec->size + xpos, v0[1]*fontspec->size + ypos, -1.0f);
if(do2Dtextures)
glTexCoord2f(v1[0] + xpos/fontspec->size,
v1[1] + ypos/fontspec->size);
glNormal3fv(normala.getValue());
glVertex3f(v1[0]*fontspec->size + xpos, v1[1]*fontspec->size + ypos, -1.0f);
}
else {
glNormal3fv(normala.getValue());
if(do2Dtextures)
glTexCoord2f(v1[0] + xpos/fontspec->size,
v1[1] + ypos/fontspec->size);
glVertex3f(v1[0]*fontspec->size + xpos, v1[1]*fontspec->size + ypos, 0.0f);
if(do2Dtextures)
glTexCoord2f(v0[0] + xpos/fontspec->size,
v0[1] + ypos/fontspec->size);
glVertex3f(v0[0]*fontspec->size + xpos, v0[1]*fontspec->size + ypos, 0.0f);
if(do2Dtextures)
glTexCoord2f(v0[0] + xpos/fontspec->size,
v0[1] + ypos/fontspec->size);
glVertex3f(v0[0]*fontspec->size + xpos, v0[1]*fontspec->size + ypos, -1.0f);
if(do2Dtextures)
glTexCoord2f(v1[0] + xpos/fontspec->size,
v1[1] + ypos/fontspec->size);
glVertex3f(v1[0]*fontspec->size + xpos, v1[1]*fontspec->size + ypos, -1.0f);
}
}
glEnd();
}
else { // profile
assert(validprofile && firstprofile >= 0);
const int * indices = cc_glyph3d_getedgeindices(glyph);
int ind = 0;
SbVec3f normala, normalb;
SbList vertexlist;
this->normalgenerator->reset(FALSE);
while (*indices >= 0) {
int i0 = *indices++;
int i1 = *indices++;
SbVec3f va(coords[i0][0], coords[i0][1], nearz);
SbVec3f vb(coords[i1][0], coords[i1][1], nearz);
const int * ccw = (int *) cc_glyph3d_getnextccwedge(glyph, ind);
const int * cw = (int *) cc_glyph3d_getnextcwedge(glyph, ind);
SbVec3f vleft(coords[*(ccw+1)][0], coords[*(ccw+1)][1], nearz);
SbVec3f vright(coords[*cw][0], coords[*cw][1], nearz);
ind++;
va[0] = va[0] * fontspec->size;
va[1] = va[1] * fontspec->size;
vb[0] = vb[0] * fontspec->size;
vb[1] = vb[1] * fontspec->size;
vleft[0] = vleft[0] * fontspec->size;
vleft[1] = vleft[1] * fontspec->size;
vright[0] = vright[0] * fontspec->size;
vright[1] = vright[1] * fontspec->size;
// create two 'normal' vectors pointing out from the edges
SbVec3f normala(vleft[0] - va[0], vleft[1] - va[1], 0.0f);
normala = normala.cross(SbVec3f(0.0f, 0.0f, -1.0f));
if (normala.length() > 0)
normala.normalize();
SbVec3f normalb(vb[0] - vright[0], vb[1] - vright[1], 0.0f);
normalb = normalb.cross(SbVec3f(0.0f, 0.0f, -1.0f));
if (normalb.length() > 0)
normalb.normalize();
SoProfile * pn = (SoProfile *) profilenodes[firstprofile];
pn->getVertices(state, profnum, profcoords);
SbVec3f vc,vd;
SbVec2f starta(va[0], va[1]);
SbVec2f startb(vb[0], vb[1]);
for (int j=firstprofile; jgetVertices(state, profnum, profcoords);
for (int k=1; ktriangle(va,vd,vb);
}
if ((vb != vd) && (vb != vc) && (vd != vc)) {
vertexlist.append(vb);
vertexlist.append(vd);
vertexlist.append(vc);
normalgenerator->triangle(vb,vd,vc);
}
va = vd;
vb = vc;
}
}
}
normalgenerator->generate(creaseangle);
const SbVec3f * normals = normalgenerator->getNormals();
const int size = vertexlist.getLength();
// NOTE: We add the xpos and ypos to each vertex at this
// point because Linux systems seems to accumulate an error
// when calculating the normals (ie. two 'o's in a row
// doesn't get the same normals due to the xpos
// difference). This doesn't happen on Windows so it is
// probably a floating point precision issue linked to the
// compilator. (Tested on MSVC 6 and GCC 2.95.4) (20031010
// handegar).
glBegin(GL_TRIANGLES);
for (int z = 0;z < size;z += 3) {
// FIXME: Add proper texturing for profile
// coords. (20031010 handegar)
glNormal3fv(normals[z+2].getValue());
glVertex3fv(SbVec3f(vertexlist[z+2][0] + xpos,
vertexlist[z+2][1] + ypos,
vertexlist[z+2][2]).getValue());
glNormal3fv(normals[z+1].getValue());
glVertex3fv(SbVec3f(vertexlist[z+1][0] + xpos,
vertexlist[z+1][1] + ypos,
vertexlist[z+1][2]).getValue());
glNormal3fv(normals[z].getValue());
glVertex3fv(SbVec3f(vertexlist[z][0] + xpos,
vertexlist[z][1] + ypos,
vertexlist[z][2]).getValue());
}
glEnd();
vertexlist.truncate(0);
}
}
float advancex, advancey;
cc_glyph3d_getadvance(glyph, &advancex, &advancey);
xpos += advancex * fontspec->size;
}
if (prevglyph) {
cc_glyph3d_unref(prevglyph);
prevglyph = NULL;
}
ypos -= fontspec->size * PUBLIC(this)->spacing.getValue();
}
}
// render text geometry
void
SoText3::render(SoState * COIN_UNUSED_ARG(state), unsigned int COIN_UNUSED_ARG(part))
{
assert(FALSE && "obsoleted");
}
void
SoText3::generate(SoAction * COIN_UNUSED_ARG(action), unsigned int COIN_UNUSED_ARG(part))
{
assert(FALSE && "obsoleted");
}
// generate text geometry
void
SoText3P::generate(SoAction * action, const cc_font_specification * fontspec,
unsigned int part)
{
SoState * state = action->getState();
// SoCreaseAngleElement is not enabled for SoGetPrimitiveCountAction.
float creaseangle = 0.5f;
if (state->isElementEnabled(SoCreaseAngleElement::getClassStackIndex())) {
creaseangle = SoCreaseAngleElement::get(state);
}
SoPrimitiveVertex vertex;
SoTextDetail detail;
detail.setPart(part);
vertex.setDetail(&detail);
// we might get here from getPrimitiveCount(). We therefore need to
// check if lazy element is enabled
if (SoMaterialBindingElement::get(state) !=
SoMaterialBindingElement::OVERALL &&
state->isElementEnabled(SoLazyElement::getClassStackIndex())) {
SoLazyElement * lazyelement = SoLazyElement::getInstance(state);
const int numdiffuse = lazyelement->getNumDiffuse();
if (part == SoText3::SIDES && (numdiffuse > 1))
vertex.setMaterialIndex(1);
else if (part == SoText3::BACK && (numdiffuse > 2))
vertex.setMaterialIndex(2);
}
SbBool do2Dtextures = FALSE;
SbBool do3Dtextures = FALSE;
// not all actions have these elements enabled
// (for instance SoGetPrimitiveCountAction)
if (state->isElementEnabled(SoMultiTextureEnabledElement::getClassStackIndex())) {
if (SoMultiTextureEnabledElement::get(state)) do2Dtextures = TRUE;
}
// FIXME: implement proper support for 3D-texturing, and get rid of
// this. (20031010 handegar)
if (do3Dtextures) {
static SbBool first = TRUE;
if (first) {
first = FALSE;
#if COIN_DEBUG
SoDebugError::postWarning("SoText3::GLRender",
"3D-textures not supported for this node type yet.");
#endif // COIN_DEBUG
}
}
int i, n = this->widths.getLength();
int firstprofile = -1;
int32_t profnum;
SbVec2f *profcoords;
float nearz = FLT_MAX;
float farz = -FLT_MAX;
const SoNodeList & profilenodes = SoProfileElement::get(state);
int numprofiles = profilenodes.getLength();
if (numprofiles > 0) {
assert(profilenodes[0]->getTypeId().isDerivedFrom(SoProfile::getClassTypeId()));
// Find near/far z (for modifying position of front/back)
for (int l = numprofiles-1; l >= 0; l--) {
SoProfile * pn = (SoProfile *)profilenodes[l];
pn->getVertices(state, profnum, profcoords);
if (profnum > 0) {
if (profcoords[profnum-1][0] > farz) farz = profcoords[profnum-1][0];
if (profcoords[0][0] < nearz) nearz = profcoords[0][0];
if (pn->linkage.getValue() == SoProfile::START_FIRST) {
if (firstprofile == -1) firstprofile = l;
break;
}
}
}
nearz = -nearz;
farz = -farz;
}
else {
nearz = 0.0;
farz = -1.0;
}
float ypos = 0.0f;
for (i = 0; i < n; i++) {
detail.setStringIndex(i);
float xpos = 0.0f;
switch (PUBLIC(this)->justification.getValue()) {
case SoText3::RIGHT:
xpos = -this->widths[i];
break;
case SoText3::CENTER:
xpos = - this->widths[i] * 0.5f;
break;
}
SbString str = PUBLIC(this)->string[i];
cc_glyph3d * prevglyph = NULL;
const char * p = str.getString();
size_t length = cc_string_utf8_validate_length(p);
// No assertion as zero length is handled correctly (results in a new line)
for (unsigned int strcharidx = 0; strcharidx < length; strcharidx++) {
uint32_t glyphidx = 0;
glyphidx = cc_string_utf8_get_char(p);
p = cc_string_utf8_next_char(p);
cc_glyph3d * glyph = cc_glyph3d_ref(glyphidx, fontspec);
const SbVec2f * coords = (SbVec2f *) cc_glyph3d_getcoords(glyph);
detail.setCharacterIndex(strcharidx);
// Get kerning
if (strcharidx > 0) {
float kerningx, kerningy;
cc_glyph3d_getkerning(prevglyph, glyph, &kerningx, &kerningy);
xpos += kerningx * fontspec->size;
}
if (prevglyph) {
cc_glyph3d_unref(prevglyph);
}
prevglyph = glyph;
if (part != SoText3::SIDES) { // FRONT & BACK
const int * ptr = cc_glyph3d_getfaceindices(glyph);
PUBLIC(this)->beginShape(action, SoShape::TRIANGLES, NULL);
while (*ptr >= 0) {
SbVec2f v0, v1, v2;
float zval;
if (part == SoText3::FRONT) {
vertex.setNormal(SbVec3f(0.0f, 0.0f, 1.0f));
v2 = coords[*ptr++];
v1 = coords[*ptr++];
v0 = coords[*ptr++];
zval = nearz;
}
else { // BACK
vertex.setNormal(SbVec3f(0.0f, 0.0f, -1.0f));
v0 = coords[*ptr++];
v1 = coords[*ptr++];
v2 = coords[*ptr++];
zval = farz;
}
if(do2Dtextures) {
vertex.setTextureCoords(SbVec2f(v0[0] + xpos/fontspec->size, v0[1] + ypos/fontspec->size));
}
vertex.setPoint(SbVec3f(v0[0] * fontspec->size + xpos, v0[1] * fontspec->size + ypos, zval));
PUBLIC(this)->shapeVertex(&vertex);
if(do2Dtextures) {
vertex.setTextureCoords(SbVec2f(v1[0] + xpos/fontspec->size, v1[1] + ypos/fontspec->size));
}
vertex.setPoint(SbVec3f(v1[0] * fontspec->size + xpos, v1[1] * fontspec->size + ypos, zval));
PUBLIC(this)->shapeVertex(&vertex);
if(do2Dtextures) {
vertex.setTextureCoords(SbVec2f(v2[0] + xpos/fontspec->size, v2[1] + ypos/fontspec->size));
}
vertex.setPoint(SbVec3f(v2[0] * fontspec->size + xpos, v2[1] * fontspec->size + ypos, zval));
PUBLIC(this)->shapeVertex(&vertex);
}
PUBLIC(this)->endShape();
}
else { // SIDES
if (profilenodes.getLength() == 0) { // no profile - extrude
const int * ptr = cc_glyph3d_getedgeindices(glyph);
SbVec2f v0, v1;
int counter = 0;
PUBLIC(this)->beginShape(action, SoShape::QUADS, NULL);
while (*ptr >= 0) {
v1 = coords[*ptr++];
v0 = coords[*ptr++];
const int * ccw = (int *) cc_glyph3d_getnextccwedge(glyph, counter);
const int * cw = (int *) cc_glyph3d_getnextcwedge(glyph, counter);
SbVec3f vleft(coords[*(ccw+1)][0], coords[*(ccw+1)][1], 0);
SbVec3f vright(coords[*cw][0], coords[*cw][1], 0);
counter++;
v0[0] = v0[0] * fontspec->size;
v0[1] = v0[1] * fontspec->size;
v1[0] = v1[0] * fontspec->size;
v1[1] = v1[1] * fontspec->size;
vleft[0] = vleft[0] * fontspec->size;
vleft[1] = vleft[1] * fontspec->size;
vright[0] = vright[0] * fontspec->size;
vright[1] = vright[1] * fontspec->size;
// create two 'normal' vectors pointing out from the edges
SbVec3f normala(vright[0] - v0[0], vright[1] - v0[1], 0.0f);
normala = normala.cross(SbVec3f(0.0f, 0.0f, 1.0f));
if (normala.length() > 0)
normala.normalize();
SbVec3f normalb(v1[0] - vleft[0], v1[1] - vleft[1], 0.0f);
normalb = normalb.cross(SbVec3f(0.0f, 0.0f, 1.0f));
if (normalb.length() > 0)
normalb.normalize();
SbBool flatshading = FALSE;
float dot = normala.dot(normalb);
if(acos(dot) > creaseangle) {
normala = SbVec3f(v1[0] - v0[0], v1[1] - v0[1], 0.0f);
normala = normala.cross(SbVec3f(0.0f, 0.0f, 1.0f));
if (normala.length() > 0)
normala.normalize();
flatshading = TRUE;
}
if (!flatshading) {
if (do2Dtextures) {
vertex.setTextureCoords(SbVec2f(v1[0] + xpos/fontspec->size,
v1[1] + ypos/fontspec->size));
}
vertex.setNormal(normala);
vertex.setPoint(SbVec3f(v1[0]*fontspec->size + xpos, v1[1]*fontspec->size + ypos, 0.0f));
PUBLIC(this)->shapeVertex(&vertex);
if (do2Dtextures) {
vertex.setTextureCoords(SbVec2f(v0[0] + xpos/fontspec->size,
v0[1] + ypos/fontspec->size));
}
vertex.setNormal(normalb);
vertex.setPoint(SbVec3f(v0[0]*fontspec->size + xpos, v0[1]*fontspec->size + ypos, 0.0f));
PUBLIC(this)->shapeVertex(&vertex);
if (do2Dtextures) {
vertex.setTextureCoords(SbVec2f(v0[0] + xpos/fontspec->size,
v0[1] + ypos/fontspec->size));
}
vertex.setNormal(normalb);
vertex.setPoint(SbVec3f(v0[0]*fontspec->size + xpos, v0[1]*fontspec->size + ypos, -1.0f));
PUBLIC(this)->shapeVertex(&vertex);
if (do2Dtextures) {
vertex.setTextureCoords(SbVec2f(v1[0] + xpos/fontspec->size,
v1[1] + ypos/fontspec->size));
}
vertex.setNormal(normala);
vertex.setPoint(SbVec3f(v1[0]*fontspec->size + xpos, v1[1]*fontspec->size + ypos, -1.0f));
PUBLIC(this)->shapeVertex(&vertex);
}
else {
vertex.setNormal(normala);
if (do2Dtextures) {
vertex.setTextureCoords(SbVec2f(v1[0] + xpos/fontspec->size,
v1[1] + ypos/fontspec->size));
}
vertex.setPoint(SbVec3f(v1[0]*fontspec->size + xpos, v1[1]*fontspec->size + ypos, 0.0f));
PUBLIC(this)->shapeVertex(&vertex);
if (do2Dtextures) {
vertex.setTextureCoords(SbVec2f(v0[0] + xpos/fontspec->size,
v0[1] + ypos/fontspec->size));
}
vertex.setPoint(SbVec3f(v0[0]*fontspec->size + xpos, v0[1]*fontspec->size + ypos, 0.0f));
PUBLIC(this)->shapeVertex(&vertex);
if (do2Dtextures) {
vertex.setTextureCoords(SbVec2f(v0[0] + xpos/fontspec->size,
v0[1] + ypos/fontspec->size));
}
vertex.setPoint(SbVec3f(v0[0]*fontspec->size + xpos, v0[1]*fontspec->size + ypos, -1.0f));
PUBLIC(this)->shapeVertex(&vertex);
if (do2Dtextures) {
vertex.setTextureCoords(SbVec2f(v1[0] + xpos/fontspec->size,
v1[1] + ypos/fontspec->size));
}
vertex.setPoint(SbVec3f(v1[0]*fontspec->size + xpos, v1[1]*fontspec->size + ypos, -1.0f));
PUBLIC(this)->shapeVertex(&vertex);
}
}
PUBLIC(this)->endShape();
}
else { // profile
const int *indices = cc_glyph3d_getedgeindices(glyph);
int ind = 0;
SbVec3f normala, normalb;
SbList vertexlist;
this->normalgenerator->reset(FALSE);
while (*indices >= 0) {
int i0 = *indices++;
int i1 = *indices++;
SbVec3f va(coords[i0][0], coords[i0][1], nearz);
SbVec3f vb(coords[i1][0], coords[i1][1], nearz);
const int *ccw = (int *) cc_glyph3d_getnextccwedge(glyph, ind);
const int *cw = (int *) cc_glyph3d_getnextcwedge(glyph, ind);
SbVec3f vleft(coords[*(ccw+1)][0], coords[*(ccw+1)][1], nearz);
SbVec3f vright(coords[*cw][0], coords[*cw][1], nearz);
ind++;
va[0] = va[0] * fontspec->size;
va[1] = va[1] * fontspec->size;
vb[0] = vb[0] * fontspec->size;
vb[1] = vb[1] * fontspec->size;
vleft[0] = vleft[0] * fontspec->size;
vleft[1] = vleft[1] * fontspec->size;
vright[0] = vright[0] * fontspec->size;
vright[1] = vright[1] * fontspec->size;
// create two 'normal' vectors pointing out from the edges
SbVec3f normala(vleft[0] - va[0], vleft[1] - va[1], 0.0f);
normala = normala.cross(SbVec3f(0.0f, 0.0f, -1.0f));
if (normala.length() > 0)
normala.normalize();
SbVec3f normalb(vb[0] - vright[0], vb[1] - vright[1], 0.0f);
normalb = normalb.cross(SbVec3f(0.0f, 0.0f, -1.0f));
if (normalb.length() > 0)
normalb.normalize();
SoProfile *pn = (SoProfile *)profilenodes[firstprofile];
pn->getVertices(state, profnum, profcoords);
SbVec3f vc,vd;
SbVec2f starta(va[0], va[1]);
SbVec2f startb(vb[0], vb[1]);
for (int j=firstprofile; jgetVertices(state, profnum, profcoords);
for (int k=1; ktriangle(va,vd,vb);
}
if ((vb != vd) && (vb != vc) && (vd != vc)) {
vertexlist.append(vb);
vertexlist.append(vd);
vertexlist.append(vc);
normalgenerator->triangle(vb,vd,vc);
}
va = vd;
vb = vc;
}
}
}
normalgenerator->generate(creaseangle);
const SbVec3f * normals = normalgenerator->getNormals();
const int size = vertexlist.getLength();
PUBLIC(this)->beginShape(action, SoShape::TRIANGLES, NULL);
for (int z = 0;z < size;z += 3) {
vertex.setNormal(normals[z+2].getValue());
vertex.setPoint(SbVec3f(vertexlist[z+2][0] + xpos,
vertexlist[z+2][1] + ypos,
vertexlist[z+2][2]).getValue());
PUBLIC(this)->shapeVertex(&vertex);
vertex.setNormal(normals[z+1].getValue());
vertex.setPoint(SbVec3f(vertexlist[z+1][0] + xpos,
vertexlist[z+1][1] + ypos,
vertexlist[z+1][2]).getValue());
PUBLIC(this)->shapeVertex(&vertex);
vertex.setNormal(normals[z].getValue());
vertex.setPoint(SbVec3f(vertexlist[z][0] + xpos,
vertexlist[z][1] + ypos,
vertexlist[z][2]).getValue());
PUBLIC(this)->shapeVertex(&vertex);
}
PUBLIC(this)->endShape();
vertexlist.truncate(0);
}
}
float advancex, advancey;
cc_glyph3d_getadvance(glyph, &advancex, &advancey);
xpos += advancex * fontspec->size;
}
if (prevglyph) {
cc_glyph3d_unref(prevglyph);
prevglyph = NULL;
}
ypos -= fontspec->size * PUBLIC(this)->spacing.getValue();
}
}
// Documented in superclass.
void
SoText3::notify(SoNotList * list)
{
PRIVATE(this)->lock();
if (PRIVATE(this)->cache) {
SoField * f = list->getLastField();
if (f == &this->string) PRIVATE(this)->cache->invalidate();
}
PRIVATE(this)->unlock();
inherited::notify(list);
}
// recalculate glyphs
void
SoText3P::setUpGlyphs(SoState * state, SoText3 * textnode)
{
// not all actions have SoCacheElement enabled
if (!state->isElementEnabled(SoCacheElement::getClassStackIndex())) return;
if (this->cache && this->cache->isValid(state)) return;
SoGlyphCache * oldcache = this->cache;
state->push();
SbBool storedinvalid = SoCacheElement::setInvalid(FALSE);
this->cache = new SoGlyphCache(state);
this->cache->ref();
SoCacheElement::set(state, this->cache);
this->cache->readFontspec(state);
const cc_font_specification * fontspec = this->cache->getCachedFontspec();
this->widths.truncate(0);
for (int i = 0; i < textnode->string.getNum(); i++) {
float stringwidth = 0.0f;
float kerningx = 0;
float kerningy = 0;
float advancex = 0;
float advancey = 0;
cc_glyph3d * prevglyph = NULL;
const float * maxbbox;
this->maxglyphbbox.makeEmpty();
SbString str = textnode->string[i];
const char * p = str.getString();
size_t length = cc_string_utf8_validate_length(p);
// No assertion as zero length is handled correctly (results in a new line)
for (unsigned int strcharidx = 0; strcharidx < length; strcharidx++) {
uint32_t glyphidx = 0;
glyphidx = cc_string_utf8_get_char(p);
p = cc_string_utf8_next_char(p);
cc_glyph3d * glyph = cc_glyph3d_ref(glyphidx, fontspec);
this->cache->addGlyph(glyph);
assert(glyph);
maxbbox = cc_glyph3d_getboundingbox(glyph); // Get max height
this->maxglyphbbox.extendBy(SbVec3f(0, maxbbox[1] * fontspec->size, 0));
this->maxglyphbbox.extendBy(SbVec3f(0, maxbbox[3] * fontspec->size, 0));
if (strcharidx > 0)
cc_glyph3d_getkerning(prevglyph, glyph, &kerningx, &kerningy);
cc_glyph3d_getadvance(glyph, &advancex, &advancey);
stringwidth += (advancex + kerningx) * fontspec->size;
prevglyph = glyph;
}
if (prevglyph != NULL) {
// Italic font might cause last letter to be outside bbox. Add width if needed.
if (advancex < cc_glyph3d_getwidth(prevglyph))
stringwidth += (cc_glyph3d_getwidth(prevglyph) - advancex) * fontspec->size;
}
this->widths.append(stringwidth);
}
state->pop();
SoCacheElement::setInvalid(storedinvalid);
// unref old cache after creating the new one to avoid recreating glyphs
if (oldcache) oldcache->unref();
}
#undef PRIVATE
#undef PUBLIC