From a453ec1925eb68780bee8465607f2740ca6623fe Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 13 Nov 2015 01:44:03 +0100 Subject: [PATCH] Overhaul text generation; now 75x as fast as before. --- dtool/src/dtoolutil/unicodeLatinMap.cxx | 6 +- panda/src/express/pointerToBase.I | 2 +- panda/src/express/pointerToBase.h | 2 +- panda/src/express/pointerToVoid.I | 2 +- panda/src/express/pointerToVoid.h | 2 +- panda/src/text/dynamicTextFont.I | 18 +- panda/src/text/dynamicTextFont.cxx | 93 ++- panda/src/text/dynamicTextFont.h | 17 +- panda/src/text/dynamicTextGlyph.I | 55 +- panda/src/text/dynamicTextGlyph.cxx | 112 ---- panda/src/text/dynamicTextGlyph.h | 16 +- panda/src/text/dynamicTextPage.I | 15 +- panda/src/text/dynamicTextPage.cxx | 33 +- panda/src/text/dynamicTextPage.h | 7 +- panda/src/text/geomTextGlyph.cxx | 98 ++-- panda/src/text/geomTextGlyph.h | 24 +- panda/src/text/staticTextFont.cxx | 2 +- panda/src/text/staticTextFont.h | 2 +- panda/src/text/textAssembler.I | 18 - panda/src/text/textAssembler.cxx | 732 ++++++++++++++---------- panda/src/text/textAssembler.h | 67 ++- panda/src/text/textFont.I | 4 +- panda/src/text/textFont.h | 4 +- panda/src/text/textGlyph.I | 77 ++- panda/src/text/textGlyph.cxx | 275 ++++++++- panda/src/text/textGlyph.h | 28 +- 26 files changed, 1050 insertions(+), 661 deletions(-) diff --git a/dtool/src/dtoolutil/unicodeLatinMap.cxx b/dtool/src/dtoolutil/unicodeLatinMap.cxx index b7348ff4e9..f68fd13f3c 100644 --- a/dtool/src/dtoolutil/unicodeLatinMap.cxx +++ b/dtool/src/dtoolutil/unicodeLatinMap.cxx @@ -369,6 +369,8 @@ static const UnicodeLatinMap::Entry latin_map[] = { UnicodeLatinMap::AT_none, UnicodeLatinMap::AF_reversed }, { 0x0258, UnicodeLatinMap::CT_lower, 'e', 0, 0x0258, 0x018e, UnicodeLatinMap::AT_none, UnicodeLatinMap::AF_reversed }, + { 0x0259, UnicodeLatinMap::CT_lower, 'e', 0, 0x0259, 0x018f, + UnicodeLatinMap::AT_none, UnicodeLatinMap::AF_turned }, { 0x0066, UnicodeLatinMap::CT_lower, 'f', 0, 0x0066, 0x0046, UnicodeLatinMap::AT_none, 0 }, { 0x0046, UnicodeLatinMap::CT_upper, 'F', 0, 0x0066, 0x0046, @@ -465,7 +467,9 @@ static const UnicodeLatinMap::Entry latin_map[] = { UnicodeLatinMap::AT_line_below, 0 }, { 0x029c, UnicodeLatinMap::CT_upper, 'H', 0, 0x0068, 0x029c, UnicodeLatinMap::AT_none, UnicodeLatinMap::AF_smallcap }, - { 0x0195, UnicodeLatinMap::CT_lower, 'h', 'v', 0x0195, 0x195, + { 0x0195, UnicodeLatinMap::CT_lower, 'h', 'v', 0x0195, 0x01f6, + UnicodeLatinMap::AT_none, UnicodeLatinMap::AF_ligature }, + { 0x01f6, UnicodeLatinMap::CT_upper, 'H', 'v', 0x0195, 0x01f6, UnicodeLatinMap::AT_none, UnicodeLatinMap::AF_ligature }, { 0x0127, UnicodeLatinMap::CT_lower, 'h', 0, 0x0127, 0x0126, UnicodeLatinMap::AT_stroke, 0 }, diff --git a/panda/src/express/pointerToBase.I b/panda/src/express/pointerToBase.I index ce319a5f50..09b5f3c75b 100644 --- a/panda/src/express/pointerToBase.I +++ b/panda/src/express/pointerToBase.I @@ -162,7 +162,7 @@ update_type(To *ptr) { // (Assignment to a NULL pointer also works, of course.) //////////////////////////////////////////////////////////////////// template -INLINE void PointerToBase:: +ALWAYS_INLINE void PointerToBase:: clear() { reassign((To *)NULL); } diff --git a/panda/src/express/pointerToBase.h b/panda/src/express/pointerToBase.h index 0828e300f8..7ed792d7d2 100644 --- a/panda/src/express/pointerToBase.h +++ b/panda/src/express/pointerToBase.h @@ -55,7 +55,7 @@ protected: // vs. non-const later. PUBLISHED: - INLINE void clear(); + ALWAYS_INLINE void clear(); void output(ostream &out) const; }; diff --git a/panda/src/express/pointerToVoid.I b/panda/src/express/pointerToVoid.I index 8080801837..c462cb8266 100644 --- a/panda/src/express/pointerToVoid.I +++ b/panda/src/express/pointerToVoid.I @@ -50,7 +50,7 @@ PointerToVoid(const PointerToVoid &) { // false otherwise. (Direct comparison to a NULL // pointer also works.) //////////////////////////////////////////////////////////////////// -INLINE bool PointerToVoid:: +ALWAYS_INLINE bool PointerToVoid:: is_null() const { return (_void_ptr == (void *)NULL); } diff --git a/panda/src/express/pointerToVoid.h b/panda/src/express/pointerToVoid.h index d3a71d7c8b..204cf7b7b6 100644 --- a/panda/src/express/pointerToVoid.h +++ b/panda/src/express/pointerToVoid.h @@ -42,7 +42,7 @@ private: INLINE PointerToVoid(const PointerToVoid ©); PUBLISHED: - INLINE bool is_null() const; + ALWAYS_INLINE bool is_null() const; INLINE size_t get_hash() const; public: diff --git a/panda/src/text/dynamicTextFont.I b/panda/src/text/dynamicTextFont.I index 30efec424b..d8af7dc340 100644 --- a/panda/src/text/dynamicTextFont.I +++ b/panda/src/text/dynamicTextFont.I @@ -260,8 +260,18 @@ get_poly_margin() const { //////////////////////////////////////////////////////////////////// INLINE void DynamicTextFont:: set_page_size(int x_size, int y_size) { - _page_x_size = x_size; - _page_y_size = y_size; + _page_size.set(x_size, y_size); +} + +//////////////////////////////////////////////////////////////////// +// Function: DynamicTextFont::get_page_size +// Access: Published +// Description: Returns the size of the textures that are created +// for the DynamicTextFont. See set_page_size(). +//////////////////////////////////////////////////////////////////// +INLINE const LVecBase2i &DynamicTextFont:: +get_page_size() const { + return _page_size; } //////////////////////////////////////////////////////////////////// @@ -272,7 +282,7 @@ set_page_size(int x_size, int y_size) { //////////////////////////////////////////////////////////////////// INLINE int DynamicTextFont:: get_page_x_size() const { - return _page_x_size; + return _page_size.get_x(); } //////////////////////////////////////////////////////////////////// @@ -283,7 +293,7 @@ get_page_x_size() const { //////////////////////////////////////////////////////////////////// INLINE int DynamicTextFont:: get_page_y_size() const { - return _page_y_size; + return _page_size.get_y(); } //////////////////////////////////////////////////////////////////// diff --git a/panda/src/text/dynamicTextFont.cxx b/panda/src/text/dynamicTextFont.cxx index c089926002..514b4660a7 100644 --- a/panda/src/text/dynamicTextFont.cxx +++ b/panda/src/text/dynamicTextFont.cxx @@ -44,6 +44,8 @@ #include "nurbsCurveResult.h" //#include "renderModeAttrib.h" //#include "antialiasAttrib.h" +#include "colorAttrib.h" +#include "textureAttrib.h" TypeHandle DynamicTextFont::_type_handle; @@ -110,8 +112,7 @@ DynamicTextFont(const DynamicTextFont ©) : FreetypeFont(copy), _texture_margin(copy._texture_margin), _poly_margin(copy._poly_margin), - _page_x_size(copy._page_x_size), - _page_y_size(copy._page_y_size), + _page_size(copy._page_size), _minfilter(copy._minfilter), _magfilter(copy._magfilter), _anisotropic_degree(copy._anisotropic_degree), @@ -194,8 +195,8 @@ garbage_collect() { Cache new_cache; Cache::iterator ci; for (ci = _cache.begin(); ci != _cache.end(); ++ci) { - DynamicTextGlyph *glyph = (*ci).second; - if (glyph == (DynamicTextGlyph *)NULL || glyph->_geom_count != 0) { + TextGlyph *glyph = (*ci).second; + if (glyph == (TextGlyph *)NULL || glyph->get_ref_count() > 1) { // Keep this one. new_cache.insert(new_cache.end(), (*ci)); } else { @@ -252,7 +253,7 @@ write(ostream &out, int indent_level) const { Cache::const_iterator ci; for (ci = _cache.begin(); ci != _cache.end(); ++ci) { int glyph_index = (*ci).first; - DynamicTextGlyph *glyph = (*ci).second; + TextGlyph *glyph = (*ci).second; indent(out, indent_level + 2) << glyph_index; @@ -269,7 +270,7 @@ write(ostream &out, int indent_level) const { } release_face(face); - out << ", count = " << glyph->_geom_count << "\n"; + out << '\n'; } } @@ -285,7 +286,7 @@ write(ostream &out, int indent_level) const { // printable glyph. //////////////////////////////////////////////////////////////////// bool DynamicTextFont:: -get_glyph(int character, const TextGlyph *&glyph) { +get_glyph(int character, CPT(TextGlyph) &glyph) { if (!_is_valid) { glyph = (TextGlyph *)NULL; return false; @@ -302,12 +303,12 @@ get_glyph(int character, const TextGlyph *&glyph) { if (ci != _cache.end()) { glyph = (*ci).second; } else { - DynamicTextGlyph *dynamic_glyph = make_glyph(character, face, glyph_index); + TextGlyph *dynamic_glyph = make_glyph(character, face, glyph_index); _cache.insert(Cache::value_type(glyph_index, dynamic_glyph)); glyph = dynamic_glyph; } - if (glyph == (DynamicTextGlyph *)NULL) { + if (glyph == (TextGlyph *)NULL) { glyph = get_invalid_glyph(); glyph_index = 0; } @@ -327,8 +328,7 @@ void DynamicTextFont:: initialize() { _texture_margin = text_texture_margin; _poly_margin = text_poly_margin; - _page_x_size = text_page_size[0]; - _page_y_size = text_page_size[1]; + _page_size.set(text_page_size[0], text_page_size[1]); // We don't necessarily want to use mipmaps, since we don't want to // regenerate those every time the texture changes, but we probably @@ -442,10 +442,10 @@ determine_tex_format() { // newly-created TextGlyph object, or NULL if the // glyph cannot be created for some reason. //////////////////////////////////////////////////////////////////// -DynamicTextGlyph *DynamicTextFont:: +TextGlyph *DynamicTextFont:: make_glyph(int character, FT_Face face, int glyph_index) { if (!load_glyph(face, glyph_index, false)) { - return (DynamicTextGlyph *)NULL; + return (TextGlyph *)NULL; } FT_GlyphSlot slot = face->glyph; @@ -461,6 +461,7 @@ make_glyph(int character, FT_Face face, int glyph_index) { } PN_stdfloat advance = slot->advance.x / 64.0; + advance /= _font_pixels_per_unit; if (_render_mode != RM_texture && slot->format == ft_glyph_format_outline) { @@ -520,8 +521,8 @@ make_glyph(int character, FT_Face face, int glyph_index) { _contours.clear(); FT_Outline_Decompose(&slot->outline, &funcs, (void *)this); - PT(DynamicTextGlyph) glyph = - new DynamicTextGlyph(character, advance / _font_pixels_per_unit); + PT(TextGlyph) glyph = + new TextGlyph(character, advance); switch (_render_mode) { case RM_wireframe: render_wireframe_contours(glyph); @@ -553,8 +554,8 @@ make_glyph(int character, FT_Face face, int glyph_index) { if (bitmap.width == 0 || bitmap.rows == 0) { // If we got an empty bitmap, it's a special case. - PT(DynamicTextGlyph) glyph = - new DynamicTextGlyph(character, advance / _font_pixels_per_unit); + PT(TextGlyph) glyph = + new TextGlyph(character, advance); _empty_glyphs.push_back(glyph); return glyph; @@ -571,7 +572,7 @@ make_glyph(int character, FT_Face face, int glyph_index) { // If the bitmap produced from the font doesn't require scaling // or any other processing before it goes to the texture, we can // just copy it directly into the texture. - glyph = slot_glyph(character, bitmap.width, bitmap.rows); + glyph = slot_glyph(character, bitmap.width, bitmap.rows, advance); copy_bitmap_to_texture(bitmap, glyph); } else { @@ -599,7 +600,7 @@ make_glyph(int character, FT_Face face, int glyph_index) { int_y_size += outline * 2; tex_x_size += outline * 2; tex_y_size += outline * 2; - glyph = slot_glyph(character, int_x_size, int_y_size); + glyph = slot_glyph(character, int_x_size, int_y_size, advance); if (outline != 0) { // Pad the glyph image to make room for the outline. @@ -612,11 +613,43 @@ make_glyph(int character, FT_Face face, int glyph_index) { } } - glyph->make_geom((int)floor(slot->bitmap_top + outline * _scale_factor + 0.5f), - (int)floor(slot->bitmap_left - outline * _scale_factor + 0.5f), - advance, _poly_margin, - tex_x_size, tex_y_size, - _font_pixels_per_unit, _tex_pixels_per_unit); + DynamicTextPage *page = glyph->get_page(); + if (page != NULL) { + int bitmap_top = (int)floor(slot->bitmap_top + outline * _scale_factor + 0.5f); + int bitmap_left = (int)floor(slot->bitmap_left - outline * _scale_factor + 0.5f); + + tex_x_size += glyph->_margin * 2; + tex_y_size += glyph->_margin * 2; + + // Determine the corners of the rectangle in geometric units. + PN_stdfloat tex_poly_margin = _poly_margin / _tex_pixels_per_unit; + PN_stdfloat origin_y = bitmap_top / _font_pixels_per_unit; + PN_stdfloat origin_x = bitmap_left / _font_pixels_per_unit; + + LVecBase4 dimensions( + origin_x - tex_poly_margin, + origin_y - tex_y_size / _tex_pixels_per_unit - tex_poly_margin, + origin_x + tex_x_size / _tex_pixels_per_unit + tex_poly_margin, + origin_y + tex_poly_margin); + + // And the corresponding corners in UV units. We add 0.5f to center + // the UV in the middle of its texel, to minimize roundoff errors + // when we are close to 1-to-1 pixel size. + LVecBase2i page_size = page->get_size(); + LVecBase4 texcoords( + ((PN_stdfloat)(glyph->_x - _poly_margin) + 0.5f) / page_size[0], + 1.0f - ((PN_stdfloat)(glyph->_y + _poly_margin + tex_y_size) + 0.5f) / page_size[1], + ((PN_stdfloat)(glyph->_x + _poly_margin + tex_x_size) + 0.5f) / page_size[0], + 1.0f - ((PN_stdfloat)(glyph->_y - _poly_margin) + 0.5f) / page_size[1]); + + CPT(RenderState) state; + state = RenderState::make(TextureAttrib::make(page), + TransparencyAttrib::make(TransparencyAttrib::M_alpha)); + state = state->add_attrib(ColorAttrib::make_flat(LColor(1.0f, 1.0f, 1.0f, 1.0f)), -1); + + glyph->set_quad(dimensions, texcoords, state); + } + return glyph; } } @@ -835,7 +868,7 @@ blend_pnmimage_to_texture(const PNMImage &image, DynamicTextGlyph *glyph, // filled in yet except with its size. //////////////////////////////////////////////////////////////////// DynamicTextGlyph *DynamicTextFont:: -slot_glyph(int character, int x_size, int y_size) { +slot_glyph(int character, int x_size, int y_size, PN_stdfloat advance) { // Increase the indicated size by the current margin. x_size += _texture_margin * 2; y_size += _texture_margin * 2; @@ -850,7 +883,7 @@ slot_glyph(int character, int x_size, int y_size) { do { DynamicTextPage *page = _pages[pi]; - DynamicTextGlyph *glyph = page->slot_glyph(character, x_size, y_size, _texture_margin); + DynamicTextGlyph *glyph = page->slot_glyph(character, x_size, y_size, _texture_margin, advance); if (glyph != (DynamicTextGlyph *)NULL) { // Once we found a page to hold the glyph, that becomes our // new preferred page. @@ -874,7 +907,7 @@ slot_glyph(int character, int x_size, int y_size) { // glyphs? if (garbage_collect() != 0) { // Yes, we just freed up some space. Try once more, recursively. - return slot_glyph(character, x_size, y_size); + return slot_glyph(character, x_size, y_size, advance); } else { // No good; all recorded glyphs are actually in use. We need to @@ -882,7 +915,7 @@ slot_glyph(int character, int x_size, int y_size) { _preferred_page = _pages.size(); PT(DynamicTextPage) page = new DynamicTextPage(this, _preferred_page); _pages.push_back(page); - return page->slot_glyph(character, x_size, y_size, _texture_margin); + return page->slot_glyph(character, x_size, y_size, _texture_margin, advance); } } @@ -893,7 +926,7 @@ slot_glyph(int character, int x_size, int y_size) { // geometry, as a wireframe render. //////////////////////////////////////////////////////////////////// void DynamicTextFont:: -render_wireframe_contours(DynamicTextGlyph *glyph) { +render_wireframe_contours(TextGlyph *glyph) { PT(GeomVertexData) vdata = new GeomVertexData (string(), GeomVertexFormat::get_v3(), Geom::UH_static); @@ -926,7 +959,7 @@ render_wireframe_contours(DynamicTextGlyph *glyph) { // geometry, as a polygon render. //////////////////////////////////////////////////////////////////// void DynamicTextFont:: -render_polygon_contours(DynamicTextGlyph *glyph, bool face, bool extrude) { +render_polygon_contours(TextGlyph *glyph, bool face, bool extrude) { PT(GeomVertexData) vdata = new GeomVertexData (string(), GeomVertexFormat::get_v3n3(), Geom::UH_static); diff --git a/panda/src/text/dynamicTextFont.h b/panda/src/text/dynamicTextFont.h index 8c0e84d4e6..355f15ee43 100644 --- a/panda/src/text/dynamicTextFont.h +++ b/panda/src/text/dynamicTextFont.h @@ -74,6 +74,7 @@ PUBLISHED: INLINE PN_stdfloat get_poly_margin() const; INLINE void set_page_size(int x_size, int y_size); + INLINE const LVecBase2i &get_page_size() const; INLINE int get_page_x_size() const; INLINE int get_page_y_size() const; @@ -110,21 +111,21 @@ PUBLISHED: virtual void write(ostream &out, int indent_level) const; public: - virtual bool get_glyph(int character, const TextGlyph *&glyph); + virtual bool get_glyph(int character, CPT(TextGlyph) &glyph); private: void initialize(); void update_filters(); void determine_tex_format(); - DynamicTextGlyph *make_glyph(int character, FT_Face face, int glyph_index); + TextGlyph *make_glyph(int character, FT_Face face, int glyph_index); void copy_bitmap_to_texture(const FT_Bitmap &bitmap, DynamicTextGlyph *glyph); void copy_pnmimage_to_texture(const PNMImage &image, DynamicTextGlyph *glyph); void blend_pnmimage_to_texture(const PNMImage &image, DynamicTextGlyph *glyph, const LColor &fg); - DynamicTextGlyph *slot_glyph(int character, int x_size, int y_size); + DynamicTextGlyph *slot_glyph(int character, int x_size, int y_size, PN_stdfloat advance); - void render_wireframe_contours(DynamicTextGlyph *glyph); - void render_polygon_contours(DynamicTextGlyph *glyph, bool face, bool extrude); + void render_wireframe_contours(TextGlyph *glyph); + void render_polygon_contours(TextGlyph *glyph, bool face, bool extrude); static int outline_move_to(const FT_Vector *to, void *user); static int outline_line_to(const FT_Vector *to, void *user); @@ -137,7 +138,7 @@ private: int _texture_margin; PN_stdfloat _poly_margin; - int _page_x_size, _page_y_size; + LVecBase2i _page_size; SamplerState::FilterType _minfilter; SamplerState::FilterType _magfilter; @@ -159,14 +160,14 @@ private: // This doesn't need to be a reference-counting pointer, because the // reference to each glyph is kept by the DynamicTextPage object. - typedef pmap Cache; + typedef pmap Cache; Cache _cache; // This is a list of the glyphs that do not have any printable // properties (e.g. space), but still have an advance measure. We // store them here to keep their reference counts; they also appear // in the above table. - typedef pvector< PT(DynamicTextGlyph) > EmptyGlyphs; + typedef pvector< PT(TextGlyph) > EmptyGlyphs; EmptyGlyphs _empty_glyphs; class ContourPoint { diff --git a/panda/src/text/dynamicTextGlyph.I b/panda/src/text/dynamicTextGlyph.I index 4f6e589d03..0839f56492 100644 --- a/panda/src/text/dynamicTextGlyph.I +++ b/panda/src/text/dynamicTextGlyph.I @@ -21,16 +21,13 @@ //////////////////////////////////////////////////////////////////// INLINE DynamicTextGlyph:: DynamicTextGlyph(int character, DynamicTextPage *page, int x, int y, - int x_size, int y_size, int margin) : - TextGlyph(character), + int x_size, int y_size, int margin, PN_stdfloat advance) : + TextGlyph(character, advance), _page(page), _x(x), _y(y), _x_size(x_size), _y_size(y_size), - _margin(margin), - _top(0), _left(0), _bottom(0), _right(0), - _uv_top(0), _uv_left(0), _uv_bottom(0), _uv_right(0) + _margin(margin) { - _geom_count = 0; } //////////////////////////////////////////////////////////////////// @@ -42,14 +39,12 @@ DynamicTextGlyph(int character, DynamicTextPage *page, int x, int y, //////////////////////////////////////////////////////////////////// INLINE DynamicTextGlyph:: DynamicTextGlyph(int character, PN_stdfloat advance) : - TextGlyph(character), + TextGlyph(character, advance), _page((DynamicTextPage *)NULL), _x(0), _y(0), _x_size(0), _y_size(0), _margin(0) { - _advance = advance; - _geom_count = 1; } //////////////////////////////////////////////////////////////////// @@ -105,17 +100,6 @@ intersects(int x, int y, int x_size, int y_size) const { y >= mbot || hbot <= _y); } -//////////////////////////////////////////////////////////////////// -// Function: DynamicTextGlyph::get_top -// Access: Published -// Description: Returns the vertex coordinates that can be used when -// creating a custom text renderer. -//////////////////////////////////////////////////////////////////// -INLINE PN_stdfloat DynamicTextGlyph:: -get_top() const { - return _top; -} - //////////////////////////////////////////////////////////////////// // Function: DynamicTextGlyph::get_left // Access: Published @@ -124,7 +108,7 @@ get_top() const { //////////////////////////////////////////////////////////////////// INLINE PN_stdfloat DynamicTextGlyph:: get_left() const { - return _left; + return _quad_dimensions[0]; } //////////////////////////////////////////////////////////////////// @@ -135,7 +119,7 @@ get_left() const { //////////////////////////////////////////////////////////////////// INLINE PN_stdfloat DynamicTextGlyph:: get_bottom() const { - return _bottom; + return _quad_dimensions[1]; } //////////////////////////////////////////////////////////////////// @@ -146,18 +130,18 @@ get_bottom() const { //////////////////////////////////////////////////////////////////// INLINE PN_stdfloat DynamicTextGlyph:: get_right() const { - return _right; + return _quad_dimensions[2]; } //////////////////////////////////////////////////////////////////// -// Function: DynamicTextGlyph::get_uv_top +// Function: DynamicTextGlyph::get_top // Access: Published -// Description: Returns the UV coordinates that can be used when +// Description: Returns the vertex coordinates that can be used when // creating a custom text renderer. //////////////////////////////////////////////////////////////////// INLINE PN_stdfloat DynamicTextGlyph:: -get_uv_top() const { - return _uv_top; +get_top() const { + return _quad_dimensions[3]; } //////////////////////////////////////////////////////////////////// @@ -168,7 +152,7 @@ get_uv_top() const { //////////////////////////////////////////////////////////////////// INLINE PN_stdfloat DynamicTextGlyph:: get_uv_left() const { - return _uv_left; + return _quad_texcoords[0]; } //////////////////////////////////////////////////////////////////// @@ -179,7 +163,7 @@ get_uv_left() const { //////////////////////////////////////////////////////////////////// INLINE PN_stdfloat DynamicTextGlyph:: get_uv_bottom() const { - return _uv_bottom; + return _quad_texcoords[1]; } //////////////////////////////////////////////////////////////////// @@ -190,5 +174,16 @@ get_uv_bottom() const { //////////////////////////////////////////////////////////////////// INLINE PN_stdfloat DynamicTextGlyph:: get_uv_right() const { - return _uv_right; + return _quad_texcoords[2]; +} + +//////////////////////////////////////////////////////////////////// +// Function: DynamicTextGlyph::get_uv_top +// Access: Published +// Description: Returns the UV coordinates that can be used when +// creating a custom text renderer. +//////////////////////////////////////////////////////////////////// +INLINE PN_stdfloat DynamicTextGlyph:: +get_uv_top() const { + return _quad_texcoords[3]; } diff --git a/panda/src/text/dynamicTextGlyph.cxx b/panda/src/text/dynamicTextGlyph.cxx index 3702d0e866..ac1c3a40f1 100644 --- a/panda/src/text/dynamicTextGlyph.cxx +++ b/panda/src/text/dynamicTextGlyph.cxx @@ -87,118 +87,6 @@ erase(DynamicTextFont *font) { font->get_bg()); } -//////////////////////////////////////////////////////////////////// -// Function: DynamicTextGlyph::make_geom -// Access: Public -// Description: Creates the actual geometry for the glyph. The -// parameters bitmap_top and bitmap_left are from -// FreeType, and indicate the position of the top left -// corner of the bitmap relative to the glyph's origin. -// The advance number represents the number of pixels -// the pen should be advanced after drawing this glyph. -//////////////////////////////////////////////////////////////////// -void DynamicTextGlyph:: -make_geom(int bitmap_top, int bitmap_left, PN_stdfloat advance, PN_stdfloat poly_margin, - PN_stdfloat tex_x_size, PN_stdfloat tex_y_size, - PN_stdfloat font_pixels_per_unit, PN_stdfloat tex_pixels_per_unit) { - nassertv(_page != (DynamicTextPage *)NULL); - - // This function should not be called twice. - nassertv(_geom_count == 0); - - tex_x_size += _margin * 2; - tex_y_size += _margin * 2; - - // Determine the corners of the rectangle in geometric units. - PN_stdfloat tex_poly_margin = poly_margin / tex_pixels_per_unit; - PN_stdfloat origin_y = bitmap_top / font_pixels_per_unit; - PN_stdfloat origin_x = bitmap_left / font_pixels_per_unit; - _top = origin_y + tex_poly_margin; - _left = origin_x - tex_poly_margin; - _bottom = origin_y - tex_y_size / tex_pixels_per_unit - tex_poly_margin; - _right = origin_x + tex_x_size / tex_pixels_per_unit + tex_poly_margin; - - // And the corresponding corners in UV units. We add 0.5f to center - // the UV in the middle of its texel, to minimize roundoff errors - // when we are close to 1-to-1 pixel size. - _uv_top = 1.0f - ((PN_stdfloat)(_y - poly_margin) + 0.5f) / _page->get_y_size(); - _uv_left = ((PN_stdfloat)(_x - poly_margin) + 0.5f) / _page->get_x_size(); - _uv_bottom = 1.0f - ((PN_stdfloat)(_y + poly_margin + tex_y_size) + 0.5f) / _page->get_y_size(); - _uv_right = ((PN_stdfloat)(_x + poly_margin + tex_x_size) + 0.5f) / _page->get_x_size(); - - // Create a corresponding triangle pair. We use a pair of indexed - // triangles rather than a single triangle strip, to avoid the bad - // vertex duplication behavior with lots of two-triangle strips. - PT(GeomVertexData) vdata = new GeomVertexData - (string(), GeomVertexFormat::get_v3t2(), - Geom::UH_static); - vdata->unclean_set_num_rows(4); - - GeomVertexWriter vertex(vdata, InternalName::get_vertex()); - GeomVertexWriter texcoord(vdata, InternalName::get_texcoord()); - - vertex.add_data3(_left, 0, _top); - vertex.add_data3(_left, 0, _bottom); - vertex.add_data3(_right, 0, _top); - vertex.add_data3(_right, 0, _bottom); - - texcoord.add_data2(_uv_left, _uv_top); - texcoord.add_data2(_uv_left, _uv_bottom); - texcoord.add_data2(_uv_right, _uv_top); - texcoord.add_data2(_uv_right, _uv_bottom); - - PT(GeomTriangles) tris = new GeomTriangles(Geom::UH_static); - tris->reserve_num_vertices(6); - tris->add_vertex(0); - tris->add_vertex(1); - tris->add_vertex(2); - tris->close_primitive(); - tris->add_vertex(2); - tris->add_vertex(1); - tris->add_vertex(3); - tris->close_primitive(); - - PT(Geom) geom = new GeomTextGlyph(this, vdata); - geom->add_primitive(tris); - _geom = geom; - - // The above will increment our _geom_count to 1. Reset it back - // down to 0, since our own internal Geom doesn't count. - nassertv(_geom_count == 1); - _geom_count--; - - _state = RenderState::make(TextureAttrib::make(_page), - TransparencyAttrib::make(TransparencyAttrib::M_alpha)); - _state = _state->add_attrib(ColorAttrib::make_flat(LColor(1.0f, 1.0f, 1.0f, 1.0f)), -1); - - _advance = advance / font_pixels_per_unit; -} - -//////////////////////////////////////////////////////////////////// -// Function: DynamicTextGlyph::set_geom -// Access: Public -// Description: Sets the geom from a pre-built object. -//////////////////////////////////////////////////////////////////// -void DynamicTextGlyph:: -set_geom(GeomVertexData *vdata, GeomPrimitive *prim, - const RenderState *state) { - // This function is called when _geom_count = 1, because it was - // constructed via the empty Glyph constructor. - nassertv(_geom_count == 1); - _geom_count--; - - PT(Geom) geom = new GeomTextGlyph(this, vdata); - geom->add_primitive(prim); - _geom = geom; - - // The above will increment our _geom_count to 1. Reset it back - // down to 0, since our own internal Geom doesn't count. - nassertv(_geom_count == 1); - _geom_count--; - - _state = state; -} - //////////////////////////////////////////////////////////////////// // Function: DynamicTextGlyph::is_whitespace // Access: Public, Virtual diff --git a/panda/src/text/dynamicTextGlyph.h b/panda/src/text/dynamicTextGlyph.h index 08dc4094c4..39fdec65c2 100644 --- a/panda/src/text/dynamicTextGlyph.h +++ b/panda/src/text/dynamicTextGlyph.h @@ -35,7 +35,7 @@ class EXPCL_PANDA_TEXT DynamicTextGlyph : public TextGlyph { public: INLINE DynamicTextGlyph(int character, DynamicTextPage *page, int x, int y, int x_size, int y_size, - int margin); + int margin, PN_stdfloat advance); INLINE DynamicTextGlyph(int character, PN_stdfloat advance); private: INLINE DynamicTextGlyph(const DynamicTextGlyph ©); @@ -48,34 +48,28 @@ PUBLISHED: INLINE bool intersects(int x, int y, int x_size, int y_size) const; - INLINE PN_stdfloat get_top() const; INLINE PN_stdfloat get_left() const; INLINE PN_stdfloat get_bottom() const; INLINE PN_stdfloat get_right() const; + INLINE PN_stdfloat get_top() const; - INLINE PN_stdfloat get_uv_top() const; INLINE PN_stdfloat get_uv_left() const; INLINE PN_stdfloat get_uv_bottom() const; INLINE PN_stdfloat get_uv_right() const; + INLINE PN_stdfloat get_uv_top() const; public: unsigned char *get_row(int y); void erase(DynamicTextFont *font); - void make_geom(int top, int left, PN_stdfloat advance, PN_stdfloat poly_margin, - PN_stdfloat tex_x_size, PN_stdfloat tex_y_size, - PN_stdfloat font_pixels_per_unit, PN_stdfloat tex_pixels_per_unit); - void set_geom(GeomVertexData *vdata, GeomPrimitive *prim, - const RenderState *state); virtual bool is_whitespace() const; DynamicTextPage *_page; - int _geom_count; int _x, _y; int _x_size, _y_size; int _margin; - PN_stdfloat _top, _left, _bottom, _right; - PN_stdfloat _uv_top, _uv_left, _uv_bottom, _uv_right; + + friend class DynamicTextFont; public: static TypeHandle get_class_type() { diff --git a/panda/src/text/dynamicTextPage.I b/panda/src/text/dynamicTextPage.I index 64edd02a80..394e34436b 100644 --- a/panda/src/text/dynamicTextPage.I +++ b/panda/src/text/dynamicTextPage.I @@ -13,6 +13,16 @@ //////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////// +// Function: DynamicTextPage::get_size +// Access: Published +// Description: Returns the size of the page (texture), in pixels. +//////////////////////////////////////////////////////////////////// +INLINE const LVecBase2i &DynamicTextPage:: +get_size() const { + return _size; +} + //////////////////////////////////////////////////////////////////// // Function: DynamicTextPage::get_x_size // Access: Published @@ -20,7 +30,7 @@ //////////////////////////////////////////////////////////////////// INLINE int DynamicTextPage:: get_x_size() const { - return _x_size; + return _size.get_x(); } //////////////////////////////////////////////////////////////////// @@ -30,10 +40,9 @@ get_x_size() const { //////////////////////////////////////////////////////////////////// INLINE int DynamicTextPage:: get_y_size() const { - return _y_size; + return _size.get_y(); } - //////////////////////////////////////////////////////////////////// // Function: DynamicTextPage::is_empty // Access: Published diff --git a/panda/src/text/dynamicTextPage.cxx b/panda/src/text/dynamicTextPage.cxx index f42063c0fd..d145751481 100644 --- a/panda/src/text/dynamicTextPage.cxx +++ b/panda/src/text/dynamicTextPage.cxx @@ -38,10 +38,9 @@ DynamicTextPage(DynamicTextFont *font, int page_number) : // doesn't usually have a high fill factor. set_quality_level(text_quality_level); - _x_size = _font->get_page_x_size(); - _y_size = _font->get_page_y_size(); + _size = _font->get_page_size(); - setup_2d_texture(_x_size, _y_size, T_unsigned_byte, font->get_tex_format()); + setup_2d_texture(_size[0], _size[1], T_unsigned_byte, font->get_tex_format()); // Assign a name to the Texture. ostringstream strm; @@ -63,7 +62,7 @@ DynamicTextPage(DynamicTextFont *font, int page_number) : set_border_color(font->get_bg()); // Fill the page with the font's background color. - fill_region(0, 0, _x_size, _y_size, font->get_bg()); + fill_region(0, 0, _size[0], _size[1], font->get_bg()); } //////////////////////////////////////////////////////////////////// @@ -74,7 +73,8 @@ DynamicTextPage(DynamicTextFont *font, int page_number) : // glyph object and returns it; otherwise, returns NULL. //////////////////////////////////////////////////////////////////// DynamicTextGlyph *DynamicTextPage:: -slot_glyph(int character, int x_size, int y_size, int margin) { +slot_glyph(int character, int x_size, int y_size, int margin, + PN_stdfloat advance) { int x, y; if (!find_hole(x, y, x_size, y_size)) { // No room for the glyph. @@ -84,7 +84,7 @@ slot_glyph(int character, int x_size, int y_size, int margin) { // The glyph can be fit at (x, y). Slot it. PT(DynamicTextGlyph) glyph = new DynamicTextGlyph(character, this, - x, y, x_size, y_size, margin); + x, y, x_size, y_size, margin, advance); _glyphs.push_back(glyph); return glyph; } @@ -97,7 +97,7 @@ slot_glyph(int character, int x_size, int y_size, int margin) { //////////////////////////////////////////////////////////////////// void DynamicTextPage:: fill_region(int x, int y, int x_size, int y_size, const LColor &color) { - nassertv(x >= 0 && x + x_size <= _x_size && y >= 0 && y + y_size <= _y_size); + nassertv(x >= 0 && x + x_size <= _size[0] && y >= 0 && y + y_size <= _size[1]); int num_components = get_num_components(); if (num_components == 1) { // Luminance or alpha. @@ -110,7 +110,7 @@ fill_region(int x, int y, int x_size, int y_size, const LColor &color) { unsigned char *image = modify_ram_image(); for (int yi = y; yi < y + y_size; yi++) { - unsigned char *row = image + yi * _x_size; + unsigned char *row = image + yi * _size[0]; memset(row + x, v, x_size); } @@ -127,7 +127,7 @@ fill_region(int x, int y, int x_size, int y_size, const LColor &color) { PN_uint16 *image = (PN_uint16 *)modify_ram_image().p(); for (int yi = y; yi < y + y_size; yi++) { - PN_uint16 *row = image + yi * _x_size ; + PN_uint16 *row = image + yi * _size[0] ; for (int xi = x; xi < x + x_size; xi++) { row[xi] = v.v; } @@ -142,7 +142,7 @@ fill_region(int x, int y, int x_size, int y_size, const LColor &color) { unsigned char *image = modify_ram_image(); for (int yi = y; yi < y + y_size; yi++) { - unsigned char *row = image + yi * _x_size * 3; + unsigned char *row = image + yi * _size[0] * 3; for (int xi = x; xi < x + x_size; xi++) { row[xi * 3] = p0; row[xi * 3 + 1] = p1; @@ -164,12 +164,12 @@ fill_region(int x, int y, int x_size, int y_size, const LColor &color) { PN_uint32 *image = (PN_uint32 *)modify_ram_image().p(); for (int yi = y; yi < y + y_size; yi++) { - PN_uint32 *row = image + yi * _x_size; + PN_uint32 *row = image + yi * _size[0]; for (int xi = x; xi < x + x_size; xi++) { row[xi] = v.v; } } - } + } } //////////////////////////////////////////////////////////////////// @@ -189,7 +189,8 @@ garbage_collect(DynamicTextFont *font) { Glyphs::iterator gi; for (gi = _glyphs.begin(); gi != _glyphs.end(); ++gi) { DynamicTextGlyph *glyph = (*gi); - if (glyph->_geom_count != 0) { + cerr << glyph->get_character() << " - " << glyph->get_ref_count() << "\n"; + if (glyph->get_ref_count() > 1) { // Keep this one. new_glyphs.insert(new_glyphs.end(), (*gi)); } else { @@ -214,11 +215,11 @@ garbage_collect(DynamicTextFont *font) { bool DynamicTextPage:: find_hole(int &x, int &y, int x_size, int y_size) const { y = 0; - while (y + y_size <= _y_size) { - int next_y = _y_size; + while (y + y_size <= _size[1]) { + int next_y = _size[1]; // Scan along the row at 'y'. x = 0; - while (x + x_size <= _x_size) { + while (x + x_size <= _size[0]) { int next_x = x; // Consider the spot at x, y. diff --git a/panda/src/text/dynamicTextPage.h b/panda/src/text/dynamicTextPage.h index d0266df9f0..e32187f7f4 100644 --- a/panda/src/text/dynamicTextPage.h +++ b/panda/src/text/dynamicTextPage.h @@ -37,10 +37,11 @@ class EXPCL_PANDA_TEXT DynamicTextPage : public Texture { public: DynamicTextPage(DynamicTextFont *font, int page_number); - DynamicTextGlyph *slot_glyph(int character, - int x_size, int y_size, int margin); + DynamicTextGlyph *slot_glyph(int character, int x_size, int y_size, + int margin, PN_stdfloat advance); PUBLISHED: + INLINE const LVecBase2i &get_size() const; INLINE int get_x_size() const; INLINE int get_y_size() const; @@ -58,7 +59,7 @@ private: typedef pvector< PT(DynamicTextGlyph) > Glyphs; Glyphs _glyphs; - int _x_size, _y_size; + LVecBase2i _size; DynamicTextFont *_font; diff --git a/panda/src/text/geomTextGlyph.cxx b/panda/src/text/geomTextGlyph.cxx index 446f67ebe3..607c24a4fc 100644 --- a/panda/src/text/geomTextGlyph.cxx +++ b/panda/src/text/geomTextGlyph.cxx @@ -13,9 +13,6 @@ //////////////////////////////////////////////////////////////////// #include "geomTextGlyph.h" - -#ifdef HAVE_FREETYPE - #include "datagramIterator.h" #include "bamReader.h" #include "indent.h" @@ -26,26 +23,25 @@ TypeHandle GeomTextGlyph::_type_handle; //////////////////////////////////////////////////////////////////// // Function: GeomTextGlyph::Constructor // Access: Public -// Description: +// Description: //////////////////////////////////////////////////////////////////// GeomTextGlyph:: -GeomTextGlyph(DynamicTextGlyph *glyph, const GeomVertexData *data) : +GeomTextGlyph(const TextGlyph *glyph, const GeomVertexData *data) : Geom(data) { // Initially, there is only one glyph in the Geom. There might be // additional Glyphs later when we flatten the graph and call // Geom::unify(). - if (glyph != (DynamicTextGlyph *)NULL) { + if (glyph != (const TextGlyph *)NULL) { _glyphs.reserve(1); _glyphs.push_back(glyph); - glyph->_geom_count++; } } //////////////////////////////////////////////////////////////////// // Function: GeomTextGlyph::Constructor // Access: Public -// Description: +// Description: //////////////////////////////////////////////////////////////////// GeomTextGlyph:: GeomTextGlyph(const GeomVertexData *data) : @@ -57,59 +53,48 @@ GeomTextGlyph(const GeomVertexData *data) : //////////////////////////////////////////////////////////////////// // Function: GeomTextGlyph::Copy Constructor // Access: Public -// Description: +// Description: //////////////////////////////////////////////////////////////////// GeomTextGlyph:: GeomTextGlyph(const GeomTextGlyph ©) : Geom(copy), _glyphs(copy._glyphs) { - Glyphs::iterator gi; - for (gi = _glyphs.begin(); gi != _glyphs.end(); ++gi) { - DynamicTextGlyph *glyph = (*gi); - nassertv(glyph != (DynamicTextGlyph *)NULL); - glyph->_geom_count++; +} + +//////////////////////////////////////////////////////////////////// +// Function: GeomTextGlyph::Copy Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +GeomTextGlyph:: +GeomTextGlyph(const Geom ©, const TextGlyph *glyph) : + Geom(copy) +{ + if (glyph != (const TextGlyph *)NULL) { + _glyphs.reserve(1); + _glyphs.push_back(glyph); } } //////////////////////////////////////////////////////////////////// // Function: GeomTextGlyph::Copy Assignment Operator // Access: Public -// Description: +// Description: //////////////////////////////////////////////////////////////////// void GeomTextGlyph:: operator = (const GeomTextGlyph ©) { Geom::operator = (copy); - - Glyphs::iterator gi; - for (gi = _glyphs.begin(); gi != _glyphs.end(); ++gi) { - DynamicTextGlyph *glyph = (*gi); - nassertv(glyph != (DynamicTextGlyph *)NULL); - glyph->_geom_count--; - nassertv((*gi)->_geom_count >= 0); - } _glyphs = copy._glyphs; - for (gi = _glyphs.begin(); gi != _glyphs.end(); ++gi) { - DynamicTextGlyph *glyph = (*gi); - nassertv(glyph != (DynamicTextGlyph *)NULL); - glyph->_geom_count++; - } } //////////////////////////////////////////////////////////////////// // Function: GeomTextGlyph::Destructor // Access: Public, Virtual -// Description: +// Description: //////////////////////////////////////////////////////////////////// GeomTextGlyph:: ~GeomTextGlyph() { - Glyphs::iterator gi; - for (gi = _glyphs.begin(); gi != _glyphs.end(); ++gi) { - DynamicTextGlyph *glyph = (*gi); - nassertv(glyph != (DynamicTextGlyph *)NULL); - glyph->_geom_count--; - nassertv(glyph->_geom_count >= 0); - } } //////////////////////////////////////////////////////////////////// @@ -149,11 +134,8 @@ copy_primitives_from(const Geom *other) { DCAST_INTO_R(tother, other, false); // Also copy the glyph pointers. - Glyphs::const_iterator gi; - for (gi = tother->_glyphs.begin(); gi != tother->_glyphs.end(); ++gi) { - _glyphs.push_back(*gi); - (*gi)->_geom_count++; - } + _glyphs.reserve(_glyphs.size() + tother->_glyphs.size()); + _glyphs.insert(_glyphs.end(), tother->_glyphs.begin(), tother->_glyphs.end()); return true; } @@ -176,18 +158,15 @@ count_geom(const Geom *other) { const GeomTextGlyph *tother; DCAST_INTO_V(tother, other); - Glyphs::const_iterator gi; - for (gi = tother->_glyphs.begin(); gi != tother->_glyphs.end(); ++gi) { - _glyphs.push_back(*gi); - (*gi)->_geom_count++; - } + _glyphs.reserve(_glyphs.size() + tother->_glyphs.size()); + _glyphs.insert(_glyphs.end(), tother->_glyphs.begin(), tother->_glyphs.end()); } } //////////////////////////////////////////////////////////////////// // Function: GeomTextGlyph::output // Access: Public, Virtual -// Description: +// Description: //////////////////////////////////////////////////////////////////// void GeomTextGlyph:: output(ostream &out) const { @@ -195,8 +174,8 @@ output(ostream &out) const { out << ", glyphs: ["; Glyphs::const_iterator gi; for (gi = _glyphs.begin(); gi != _glyphs.end(); ++gi) { - DynamicTextGlyph *glyph = (*gi); - nassertv(glyph != (DynamicTextGlyph *)NULL); + const TextGlyph *glyph = (*gi); + nassertv(glyph != (const TextGlyph *)NULL); out << " " << glyph->get_character(); } out << " ]"; @@ -205,7 +184,7 @@ output(ostream &out) const { //////////////////////////////////////////////////////////////////// // Function: GeomTextGlyph::write // Access: Public, Virtual -// Description: +// Description: //////////////////////////////////////////////////////////////////// void GeomTextGlyph:: write(ostream &out, int indent_level) const { @@ -214,13 +193,24 @@ write(ostream &out, int indent_level) const { << "Glyphs: ["; Glyphs::const_iterator gi; for (gi = _glyphs.begin(); gi != _glyphs.end(); ++gi) { - DynamicTextGlyph *glyph = (*gi); - nassertv(glyph != (DynamicTextGlyph *)NULL); + const TextGlyph *glyph = (*gi); + nassertv(glyph != (const TextGlyph *)NULL); out << " " << glyph->get_character(); } out << " ]\n"; } +//////////////////////////////////////////////////////////////////// +// Function: GeomTextGlyph::add_glyph +// Access: Public +// Description: Adds a glyph to the list of glyphs referenced by +// this Geom. +//////////////////////////////////////////////////////////////////// +void GeomTextGlyph:: +add_glyph(const TextGlyph *glyph) { + _glyphs.push_back(glyph); +} + //////////////////////////////////////////////////////////////////// // Function: GeomTextGlyph::register_with_factory // Access: Public, Static @@ -238,7 +228,7 @@ register_with_read_factory() { //////////////////////////////////////////////////////////////////// TypedWritable* GeomTextGlyph:: make_GeomTextGlyph(const FactoryParams ¶ms) { - GeomTextGlyph *me = new GeomTextGlyph((DynamicTextGlyph *)NULL, + GeomTextGlyph *me = new GeomTextGlyph((const TextGlyph *)NULL, (GeomVertexData *)NULL); DatagramIterator scan; BamReader *manager; @@ -247,5 +237,3 @@ make_GeomTextGlyph(const FactoryParams ¶ms) { me->fillin(scan, manager); return me; } - -#endif // HAVE_FREETYPE diff --git a/panda/src/text/geomTextGlyph.h b/panda/src/text/geomTextGlyph.h index a49ae582a7..944e07c25a 100644 --- a/panda/src/text/geomTextGlyph.h +++ b/panda/src/text/geomTextGlyph.h @@ -17,15 +17,12 @@ #include "pandabase.h" #include "geom.h" - -#ifdef HAVE_FREETYPE - -#include "dynamicTextGlyph.h" +#include "textGlyph.h" //////////////////////////////////////////////////////////////////// // Class : GeomTextGlyph // Description : This is a specialization on Geom for containing a -// primitive intended to represent a DynamicTextGlyph. +// primitive intended to represent a TextGlyph. // Its sole purpose is to maintain the geom count on the // glyph, so we can determine the actual usage count on // a dynamic glyph (and thus know when it is safe to @@ -33,10 +30,10 @@ //////////////////////////////////////////////////////////////////// class EXPCL_PANDA_TEXT GeomTextGlyph : public Geom { public: - GeomTextGlyph(DynamicTextGlyph *glyph, - const GeomVertexData *data); + GeomTextGlyph(const TextGlyph *glyph, const GeomVertexData *data); GeomTextGlyph(const GeomVertexData *data); GeomTextGlyph(const GeomTextGlyph ©); + GeomTextGlyph(const Geom ©, const TextGlyph *glyph); void operator = (const GeomTextGlyph ©); virtual ~GeomTextGlyph(); ALLOC_DELETED_CHAIN(GeomTextGlyph); @@ -48,8 +45,12 @@ public: virtual void output(ostream &out) const; virtual void write(ostream &out, int indent_level = 0) const; + void add_glyph(const TextGlyph *glyph); + + friend class TextAssembler; + private: - typedef pvector< PT(DynamicTextGlyph) > Glyphs; + typedef pvector< CPT(TextGlyph) > Glyphs; Glyphs _glyphs; public: @@ -77,11 +78,4 @@ private: #include "geomTextGlyph.I" -#else // HAVE_FREETYPE - -// Without Freetype, a GeomTextGlyph is really just an ordinary Geom. -typedef Geom GeomTextGlyph; - -#endif // HAVE_FREETYPE - #endif // GEOMTEXTGLYPH_H diff --git a/panda/src/text/staticTextFont.cxx b/panda/src/text/staticTextFont.cxx index c174ec2fa5..28b7e94c61 100644 --- a/panda/src/text/staticTextFont.cxx +++ b/panda/src/text/staticTextFont.cxx @@ -234,7 +234,7 @@ write(ostream &out, int indent_level) const { // printable glyph. //////////////////////////////////////////////////////////////////// bool StaticTextFont:: -get_glyph(int character, const TextGlyph *&glyph) { +get_glyph(int character, CPT(TextGlyph) &glyph) { Glyphs::const_iterator gi = _glyphs.find(character); if (gi == _glyphs.end()) { // No definition for this character. diff --git a/panda/src/text/staticTextFont.h b/panda/src/text/staticTextFont.h index 92c626c0d1..c97796f6ef 100644 --- a/panda/src/text/staticTextFont.h +++ b/panda/src/text/staticTextFont.h @@ -47,7 +47,7 @@ PUBLISHED: virtual void write(ostream &out, int indent_level) const; public: - virtual bool get_glyph(int character, const TextGlyph *&glyph); + virtual bool get_glyph(int character, CPT(TextGlyph) &glyph); private: void find_character_gsets(PandaNode *root, CPT(Geom) &ch, CPT(Geom) &dot, diff --git a/panda/src/text/textAssembler.I b/panda/src/text/textAssembler.I index f6e40e26e6..0de7f7d06d 100644 --- a/panda/src/text/textAssembler.I +++ b/panda/src/text/textAssembler.I @@ -522,24 +522,6 @@ ComputedProperties(ComputedProperties *based_on, const wstring &wname, << "Unknown TextProperties: " << name << "\n"; } } - - -//////////////////////////////////////////////////////////////////// -// Function: TextAssembler::GlyphPlacement::add_piece -// Access: Public -// Description: Adds a piece of the glyph, consisting of a single -// Geom and an associated RenderState. Typically, a -// glyph will have exactly one piece; there will only be -// multiple pieces in the case of cheesy accent marks or -// ligatures. -//////////////////////////////////////////////////////////////////// -INLINE void TextAssembler::GlyphPlacement:: -add_piece(Geom *geom, const RenderState *state) { - Piece piece; - piece._geom = geom; - piece._state = state; - _pieces.push_back(piece); -} //////////////////////////////////////////////////////////////////// // Function: TextAssembler::GeomCollectorKey::Constructor diff --git a/panda/src/text/textAssembler.cxx b/panda/src/text/textAssembler.cxx index 99db6b28b4..bb81f91d6d 100644 --- a/panda/src/text/textAssembler.cxx +++ b/panda/src/text/textAssembler.cxx @@ -573,58 +573,62 @@ assemble_text() { const TextProperties *properties = NULL; CPT(RenderState) text_state; CPT(RenderState) shadow_state; - LMatrix4 shadow_xform; + LVector2 shadow(0); bool any_shadow = false; GeomCollectorMap geom_collector_map; GeomCollectorMap geom_shadow_collector_map; + QuadMap quad_map; + QuadMap quad_shadow_map; PlacedGlyphs::const_iterator pgi; for (pgi = placed_glyphs.begin(); pgi != placed_glyphs.end(); ++pgi) { - const GlyphPlacement *placement = (*pgi); + const GlyphPlacement &placement = (*pgi); - if (placement->_properties != properties) { + if (placement._properties != properties) { // Get a new set of properties for future glyphs. - properties = placement->_properties; + properties = placement._properties; text_state = properties->get_text_state(); - shadow_xform = LMatrix4::ident_mat(); if (properties->has_shadow()) { + shadow = properties->get_shadow(); shadow_state = properties->get_shadow_state(); - - LVector2 offset = properties->get_shadow(); - shadow_xform = LMatrix4::translate_mat(offset[0], 0.0f, -offset[1]); } else { + shadow.set(0, 0); shadow_state.clear(); } } - // We have to place the shadow first, because it copies as it - // goes, while the place-text function just stomps on the - // vertices. if (properties->has_shadow()) { if (_dynamic_merge) { - placement->assign_append_to(geom_shadow_collector_map, shadow_state, shadow_xform); + if (placement._glyph->has_quad()) { + placement.assign_quad_to(quad_shadow_map, shadow_state, shadow); + } else { + placement.assign_append_to(geom_shadow_collector_map, shadow_state, shadow); + } } else { - placement->assign_copy_to(shadow_geom_node, shadow_state, shadow_xform); + placement.assign_to(shadow_geom_node, shadow_state, shadow); } // Don't shadow the graphics. That can result in duplication of // button objects, plus it looks weird. If you want a shadowed // graphic, you can shadow it yourself before you add it. - //placement->copy_graphic_to(shadow_node, shadow_state, shadow_xform); + //placement.copy_graphic_to(shadow_node, shadow_state, shadow); any_shadow = true; } if (_dynamic_merge) { - placement->assign_append_to(geom_collector_map, text_state, LMatrix4::ident_mat()); + if (placement._glyph->has_quad()) { + placement.assign_quad_to(quad_map, text_state); + } else { + placement.assign_append_to(geom_collector_map, text_state); + } } else { - placement->assign_to(text_geom_node, text_state); + placement.assign_to(text_geom_node, text_state); } - placement->copy_graphic_to(text_node, text_state, LMatrix4::ident_mat()); - delete placement; - } + placement.copy_graphic_to(text_node, text_state); + } placed_glyphs.clear(); if (any_shadow) { @@ -638,14 +642,18 @@ assemble_text() { (*gc).second.append_geom(text_geom_node, (*gc).first._state); } + generate_quads(text_geom_node, quad_map); + if (any_shadow) { for (gc = geom_shadow_collector_map.begin(); gc != geom_shadow_collector_map.end(); ++gc) { (*gc).second.append_geom(shadow_geom_node, (*gc).first._state); } + + generate_quads(shadow_geom_node, quad_shadow_map); } - + parent_node->add_child(text_node); return parent_node; @@ -669,8 +677,8 @@ calc_width(wchar_t character, const TextProperties &properties) { } bool got_glyph; - const TextGlyph *first_glyph = NULL; - const TextGlyph *second_glyph = NULL; + CPT(TextGlyph) first_glyph; + CPT(TextGlyph) second_glyph; UnicodeLatinMap::AccentType accent_type; int additional_flags; PN_stdfloat glyph_scale; @@ -733,7 +741,7 @@ has_exact_character(wchar_t character, const TextProperties &properties) { TextFont *font = properties.get_font(); nassertr(font != (TextFont *)NULL, false); - const TextGlyph *glyph = NULL; + CPT(TextGlyph) glyph; return font->get_glyph(character, glyph); } @@ -759,8 +767,8 @@ has_character(wchar_t character, const TextProperties &properties) { } bool got_glyph; - const TextGlyph *first_glyph = NULL; - const TextGlyph *second_glyph = NULL; + CPT(TextGlyph) first_glyph; + CPT(TextGlyph) second_glyph; UnicodeLatinMap::AccentType accent_type; int additional_flags; PN_stdfloat glyph_scale; @@ -803,7 +811,7 @@ is_whitespace(wchar_t character, const TextProperties &properties) { TextFont *font = properties.get_font(); nassertr(font != (TextFont *)NULL, false); - const TextGlyph *glyph = NULL; + CPT(TextGlyph) glyph; if (!font->get_glyph(character, glyph)) { return false; } @@ -811,7 +819,6 @@ is_whitespace(wchar_t character, const TextProperties &properties) { return glyph->is_whitespace(); } -#ifndef CPPPARSER // interrogate has a bit of trouble with wstring. //////////////////////////////////////////////////////////////////// // Function: TextAssembler::scan_wtext // Access: Private @@ -897,7 +904,7 @@ scan_wtext(TextAssembler::TextString &output_string, TextPropertiesManager *manager = TextPropertiesManager::get_global_ptr(); - + // Get the graphic image. const TextGraphic *named_graphic = manager->get_graphic_ptr(graphic_name); if (named_graphic != (TextGraphic *)NULL) { @@ -915,7 +922,6 @@ scan_wtext(TextAssembler::TextString &output_string, } } } -#endif // CPPPARSER //////////////////////////////////////////////////////////////////// // Function: TextAssembler::wordwrap_text @@ -1196,6 +1202,181 @@ calc_hyphen_width(const TextCharacter &tch) { return hyphen_width; } +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::generate_quads +// Access: Private +// Description: Generates Geoms for the given quads and adds them +// to the GeomNode. +//////////////////////////////////////////////////////////////////// +void TextAssembler:: +generate_quads(GeomNode *geom_node, const QuadMap &quad_map) { + QuadMap::const_iterator qmi; + for (qmi = quad_map.begin(); qmi != quad_map.end(); ++qmi) { + const QuadDefs &quads = qmi->second; + GeomTextGlyph::Glyphs glyphs; + glyphs.reserve(quads.size()); + + static CPT(GeomVertexFormat) format; + if (format.is_null()) { + // The optimized code below assumes 32-bit floats, so let's + // make sure we got the right format by creating it ourselves. + format = GeomVertexFormat::register_format(new GeomVertexArrayFormat( + InternalName::get_vertex(), 3, GeomEnums::NT_float32, GeomEnums::C_point, + InternalName::get_texcoord(), 2, GeomEnums::NT_float32, GeomEnums::C_texcoord)); + } + + PT(GeomVertexData) vdata = new GeomVertexData("text", format, Geom::UH_static); + + PT(GeomTriangles) tris = new GeomTriangles(Geom::UH_static); + if (quads.size() > 10922) { + tris->set_index_type(GeomEnums::NT_uint32); + } else { + tris->set_index_type(GeomEnums::NT_uint16); + } + PT(GeomVertexArrayData) indices = tris->modify_vertices(); + + int i = 0; + + // This is quite a critical loop and GeomVertexWriter quickly becomes + // the bottleneck. So, I've written this out the hard way instead. + // Two versions of the loop: one for 32-bit indices, one for 16-bit. + { + PT(GeomVertexArrayDataHandle) vtx_handle = vdata->modify_array(0)->modify_handle(); + vtx_handle->unclean_set_num_rows(quads.size() * 4); + + unsigned char *write_ptr = vtx_handle->get_write_pointer(); + size_t stride = format->get_array(0)->get_stride() / sizeof(PN_float32); + + PN_float32 *vtx_ptr = (PN_float32 *) + (write_ptr + format->get_column(InternalName::get_vertex())->get_start()); + PN_float32 *tex_ptr = (PN_float32 *) + (write_ptr + format->get_column(InternalName::get_texcoord())->get_start()); + + if (tris->get_index_type() == GeomEnums::NT_uint32) { + // 32-bit index case. + PT(GeomVertexArrayDataHandle) idx_handle = indices->modify_handle(); + idx_handle->unclean_set_num_rows(quads.size() * 6); + PN_uint32 *idx_ptr = (PN_uint32 *)idx_handle->get_write_pointer(); + + QuadDefs::const_iterator qi; + for (qi = quads.begin(); qi != quads.end(); ++qi) { + const QuadDef &quad = (*qi); + + vtx_ptr[0] = quad._dimensions[0] + quad._slanth; + vtx_ptr[1] = 0; + vtx_ptr[2] = quad._dimensions[3]; + vtx_ptr += stride; + + tex_ptr[0] = quad._uvs[0]; + tex_ptr[1] = quad._uvs[3]; + tex_ptr += stride; + + vtx_ptr[0] = quad._dimensions[0] + quad._slantl; + vtx_ptr[1] = 0; + vtx_ptr[2] = quad._dimensions[1]; + vtx_ptr += stride; + + tex_ptr[0] = quad._uvs[0]; + tex_ptr[1] = quad._uvs[1]; + tex_ptr += stride; + + vtx_ptr[0] = quad._dimensions[2] + quad._slanth; + vtx_ptr[1] = 0; + vtx_ptr[2] = quad._dimensions[3]; + vtx_ptr += stride; + + tex_ptr[0] = quad._uvs[2]; + tex_ptr[1] = quad._uvs[3]; + tex_ptr += stride; + + vtx_ptr[0] = quad._dimensions[2] + quad._slantl; + vtx_ptr[1] = 0; + vtx_ptr[2] = quad._dimensions[1]; + vtx_ptr += stride; + + tex_ptr[0] = quad._uvs[2]; + tex_ptr[1] = quad._uvs[1]; + tex_ptr += stride; + + *(idx_ptr++) = i + 0; + *(idx_ptr++) = i + 1; + *(idx_ptr++) = i + 2; + *(idx_ptr++) = i + 2; + *(idx_ptr++) = i + 1; + *(idx_ptr++) = i + 3; + i += 4; + + glyphs.push_back(MOVE(quad._glyph)); + } + } else { + // 16-bit index case. + PT(GeomVertexArrayDataHandle) idx_handle = indices->modify_handle(); + idx_handle->unclean_set_num_rows(quads.size() * 6); + PN_uint16 *idx_ptr = (PN_uint16 *)idx_handle->get_write_pointer(); + + QuadDefs::const_iterator qi; + for (qi = quads.begin(); qi != quads.end(); ++qi) { + const QuadDef &quad = (*qi); + + vtx_ptr[0] = quad._dimensions[0] + quad._slanth; + vtx_ptr[1] = 0; + vtx_ptr[2] = quad._dimensions[3]; + vtx_ptr += stride; + + tex_ptr[0] = quad._uvs[0]; + tex_ptr[1] = quad._uvs[3]; + tex_ptr += stride; + + vtx_ptr[0] = quad._dimensions[0] + quad._slantl; + vtx_ptr[1] = 0; + vtx_ptr[2] = quad._dimensions[1]; + vtx_ptr += stride; + + tex_ptr[0] = quad._uvs[0]; + tex_ptr[1] = quad._uvs[1]; + tex_ptr += stride; + + vtx_ptr[0] = quad._dimensions[2] + quad._slanth; + vtx_ptr[1] = 0; + vtx_ptr[2] = quad._dimensions[3]; + vtx_ptr += stride; + + tex_ptr[0] = quad._uvs[2]; + tex_ptr[1] = quad._uvs[3]; + tex_ptr += stride; + + vtx_ptr[0] = quad._dimensions[2] + quad._slantl; + vtx_ptr[1] = 0; + vtx_ptr[2] = quad._dimensions[1]; + vtx_ptr += stride; + + tex_ptr[0] = quad._uvs[2]; + tex_ptr[1] = quad._uvs[1]; + tex_ptr += stride; + + *(idx_ptr++) = i + 0; + *(idx_ptr++) = i + 1; + *(idx_ptr++) = i + 2; + *(idx_ptr++) = i + 2; + *(idx_ptr++) = i + 1; + *(idx_ptr++) = i + 3; + i += 4; + + glyphs.push_back(MOVE(quad._glyph)); + } + } + } + + // We can compute this value much faster than GeomPrimitive can. + tris->set_minmax(0, i - 1, NULL, NULL); + + PT(GeomTextGlyph) geom = new GeomTextGlyph(vdata); + geom->_glyphs.swap(glyphs); + geom->add_primitive(tris); + geom_node->add_geom(geom, qmi->first); + } +} + //////////////////////////////////////////////////////////////////// // Function: TextAssembler::assemble_paragraph // Access: Private @@ -1215,16 +1396,17 @@ assemble_paragraph(TextAssembler::PlacedGlyphs &placed_glyphs) { for (bi = _text_block.begin(); bi != _text_block.end(); ++bi) { TextRow &row = (*bi); + // Store the index of the first glyph we're going to place. + size_t first_glyph = placed_glyphs.size(); + // First, assemble all the glyphs of this row. - PlacedGlyphs row_placed_glyphs; PN_stdfloat row_width, line_height, wordwrap; TextProperties::Alignment align; - assemble_row(row, row_placed_glyphs, + assemble_row(row, placed_glyphs, row_width, line_height, align, wordwrap); + // Now move the row to its appropriate position. This might // involve a horizontal as well as a vertical translation. - LMatrix4 mat = LMatrix4::ident_mat(); - if (num_rows == 0) { // If this is the first row, account for its space. _ul[1] = 0.8f * line_height; @@ -1275,15 +1457,13 @@ assemble_paragraph(TextAssembler::PlacedGlyphs &placed_glyphs) { break; } - mat.set_row(3, LVector3(xpos, 0.0f, ypos)); row._xpos = xpos; row._ypos = ypos; - // Now store the geoms we assembled. - PlacedGlyphs::iterator pi; - for (pi = row_placed_glyphs.begin(); pi != row_placed_glyphs.end(); ++pi) { - (*pi)->_xform *= mat; - placed_glyphs.push_back(*pi); + // Now adjust the geoms we've assembled. + for (size_t i = first_glyph; i < placed_glyphs.size(); ++i) { + placed_glyphs[i]._xpos += xpos; + placed_glyphs[i]._ypos += ypos; } // Advance to the next line. @@ -1307,7 +1487,7 @@ assemble_paragraph(TextAssembler::PlacedGlyphs &placed_glyphs) { //////////////////////////////////////////////////////////////////// void TextAssembler:: assemble_row(TextAssembler::TextRow &row, - TextAssembler::PlacedGlyphs &row_placed_glyphs, + TextAssembler::PlacedGlyphs &placed_glyphs, PN_stdfloat &row_width, PN_stdfloat &line_height, TextProperties::Alignment &align, PN_stdfloat &wordwrap) { Thread *current_thread = Thread::get_current_thread(); @@ -1332,7 +1512,7 @@ assemble_row(TextAssembler::TextRow &row, properties->get_underscore_height() != underscore_properties->get_underscore_height()))) { // Change the underscore status. if (underscore && underscore_start != xpos) { - draw_underscore(row_placed_glyphs, underscore_start, xpos, + draw_underscore(placed_glyphs, underscore_start, xpos, underscore_properties); } underscore = properties->get_underscore(); @@ -1349,6 +1529,11 @@ assemble_row(TextAssembler::TextRow &row, align = properties->get_align(); } + //[fabius] a good place to take wordwrap size + if (properties->get_wordwrap() > 0.0f) { + wordwrap = properties->get_wordwrap(); + } + // And the height of the row is the maximum of all the fonts used // within the row. if (graphic != (TextGraphic *)NULL) { @@ -1373,8 +1558,7 @@ assemble_row(TextAssembler::TextRow &row, } else if (graphic != (TextGraphic *)NULL) { // A special embedded graphic. - GlyphPlacement *placement = new GlyphPlacement; - row_placed_glyphs.push_back(placement); + GlyphPlacement placement; PT(PandaNode) model = graphic->get_model().node(); if (graphic->get_instance_flag()) { @@ -1383,12 +1567,12 @@ assemble_row(TextAssembler::TextRow &row, PT(ModelNode) model_node = new ModelNode(""); model_node->set_preserve_transform(ModelNode::PT_no_touch); model_node->add_child(model); - placement->_graphic_model = model_node.p(); + placement._graphic_model = model_node.p(); } else { // Copy the model in. This the preferred way; it's a little // cheaper to render than instancing (because flattening is // more effective). - placement->_graphic_model = model->copy_subgraph(); + placement._graphic_model = model->copy_subgraph(); } LVecBase4 frame = graphic->get_frame(); @@ -1398,29 +1582,20 @@ assemble_row(TextAssembler::TextRow &row, // Now compute the matrix that will transform the glyph (or // glyphs) into position. - LMatrix4 glyph_xform = LMatrix4::scale_mat(glyph_scale); + placement._scale = properties->get_glyph_scale(); + placement._xpos = (xpos - frame[0]); + placement._ypos = (properties->get_glyph_shift() - frame[2]); + placement._properties = properties; - glyph_xform(3, 0) += (xpos - frame[0]); - glyph_xform(3, 2) += (properties->get_glyph_shift() - frame[2]); - - if (properties->has_slant()) { - LMatrix4 shear(1.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 1.0f, 0.0f, 0.0f, - properties->get_slant(), 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f); - glyph_xform = shear * glyph_xform; - } - - placement->_xform = glyph_xform; - placement->_properties = properties; + placed_glyphs.push_back(placement); xpos += advance * glyph_scale; } else { // A printable character. bool got_glyph; - const TextGlyph *first_glyph; - const TextGlyph *second_glyph; + CPT(TextGlyph) first_glyph; + CPT(TextGlyph) second_glyph; UnicodeLatinMap::AccentType accent_type; int additional_flags; PN_stdfloat glyph_scale; @@ -1447,35 +1622,17 @@ assemble_row(TextAssembler::TextRow &row, // into this character. Normally, there is only one Geom per // character, but it may involve multiple Geoms if we need to // add cheesy accents or ligatures. - GlyphPlacement *placement = new GlyphPlacement; - row_placed_glyphs.push_back(placement); - - PN_stdfloat advance = 0.0f; - - if (first_glyph != (TextGlyph *)NULL) { - PT(Geom) first_char_geom = first_glyph->get_geom(_usage_hint); - if (first_char_geom != (Geom *)NULL) { - placement->add_piece(first_char_geom, first_glyph->get_state()); - } - advance = first_glyph->get_advance() * advance_scale; - } - if (second_glyph != (TextGlyph *)NULL) { - PT(Geom) second_char_geom = second_glyph->get_geom(_usage_hint); - if (second_char_geom != (Geom *)NULL) { - second_char_geom->transform_vertices(LMatrix4::translate_mat(advance, 0.0f, 0.0f)); - placement->add_piece(second_char_geom, second_glyph->get_state()); - } - advance += second_glyph->get_advance(); - } + GlyphPlacement placement; glyph_scale *= properties->get_glyph_scale() * properties->get_text_scale(); - //[fabius] a good place to take wordwrap size - if (properties->get_wordwrap() > 0.0f) { - wordwrap = properties->get_wordwrap(); - } - // Now compute the matrix that will transform the glyph (or - // glyphs) into position. - LMatrix4 glyph_xform = LMatrix4::scale_mat(glyph_scale); + placement._glyph = NULL; + placement._scale = glyph_scale; + placement._xpos = xpos; + placement._ypos = properties->get_glyph_shift(); + placement._slant = properties->get_slant(); + placement._properties = properties; + + PN_stdfloat advance = 0.0f; if (accent_type != UnicodeLatinMap::AT_none || additional_flags != 0) { // If we have some special handling to perform, do so now. @@ -1483,14 +1640,18 @@ assemble_row(TextAssembler::TextRow &row, // glyph, so go get that. LPoint3 min_vert, max_vert; bool found_any = false; - placement->calc_tight_bounds(min_vert, max_vert, found_any, - current_thread); + if (first_glyph != NULL) { + first_glyph->calc_tight_bounds(min_vert, max_vert, found_any, + current_thread); + } + if (second_glyph != NULL) { + second_glyph->calc_tight_bounds(min_vert, max_vert, found_any, + current_thread); + } if (found_any) { LPoint3 centroid = (min_vert + max_vert) / 2.0f; - tack_on_accent(accent_type, min_vert, max_vert, centroid, - properties, placement); - + if ((additional_flags & UnicodeLatinMap::AF_turned) != 0) { // Invert the character. Should we also invert the accent // mark, so that an accent that would have been above the @@ -1503,28 +1664,35 @@ assemble_row(TextAssembler::TextRow &row, // We rotate the character around its centroid, which may // not always be the right point, but it's the best we've // got and it's probably pretty close. - LMatrix4 rotate = - LMatrix4::translate_mat(-centroid) * - LMatrix4::rotate_mat_normaxis(180.0f, LVecBase3(0.0f, -1.0f, 0.0f)) * - LMatrix4::translate_mat(centroid); - glyph_xform *= rotate; + placement._scale *= -1; + placement._xpos += centroid[0] * 2; + placement._ypos += centroid[2] * 2; + } + + if (accent_type != UnicodeLatinMap::AT_none) { + GlyphPlacement accent_placement(placement); + tack_on_accent(accent_type, min_vert, max_vert, centroid, + properties, accent_placement); + placed_glyphs.push_back(accent_placement); } } } - glyph_xform(3, 0) += xpos; - glyph_xform(3, 2) += properties->get_glyph_shift(); - - if (properties->has_slant()) { - LMatrix4 shear(1.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 1.0f, 0.0f, 0.0f, - properties->get_slant(), 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f); - glyph_xform = shear * glyph_xform; + if (first_glyph != (TextGlyph *)NULL) { + assert(!first_glyph->is_whitespace()); + advance = first_glyph->get_advance() * advance_scale; + swap(placement._glyph, first_glyph); + placed_glyphs.push_back(placement); + } + + // Check if there is a second glyph to create a hacky ligature or + // some such nonsense. + if (second_glyph != (TextGlyph *)NULL) { + placement._xpos += advance * glyph_scale; + advance += second_glyph->get_advance(); + swap(placement._glyph, second_glyph); + placed_glyphs.push_back(placement); } - - placement->_xform = glyph_xform; - placement->_properties = properties; xpos += advance * glyph_scale; line_height = max(line_height, font->get_line_height() * glyph_scale); @@ -1532,7 +1700,7 @@ assemble_row(TextAssembler::TextRow &row, } if (underscore && underscore_start != xpos) { - draw_underscore(row_placed_glyphs, underscore_start, xpos, + draw_underscore(placed_glyphs, underscore_start, xpos, underscore_properties); } @@ -1553,7 +1721,7 @@ assemble_row(TextAssembler::TextRow &row, } } } - + //////////////////////////////////////////////////////////////////// // Function: TextAssembler::draw_underscore // Access: Private, Static @@ -1561,35 +1729,44 @@ assemble_row(TextAssembler::TextRow &row, // for the indicated range of glyphs in this row. //////////////////////////////////////////////////////////////////// void TextAssembler:: -draw_underscore(TextAssembler::PlacedGlyphs &row_placed_glyphs, - PN_stdfloat underscore_start, PN_stdfloat underscore_end, +draw_underscore(TextAssembler::PlacedGlyphs &placed_glyphs, + PN_stdfloat underscore_start, PN_stdfloat underscore_end, const TextProperties *underscore_properties) { + CPT(GeomVertexFormat) format = GeomVertexFormat::get_v3cp(); PT(GeomVertexData) vdata = - new GeomVertexData("text", format, Geom::UH_static); - vdata->reserve_num_rows(2); + new GeomVertexData("underscore", format, Geom::UH_static); + vdata->unclean_set_num_rows(2); GeomVertexWriter vertex(vdata, InternalName::get_vertex()); GeomVertexWriter color(vdata, InternalName::get_color()); PN_stdfloat y = underscore_properties->get_underscore_height(); - vertex.add_data3(underscore_start, 0.0f, y); - color.add_data4(underscore_properties->get_text_color()); - vertex.add_data3(underscore_end, 0.0f, y); - color.add_data4(underscore_properties->get_text_color()); + vertex.set_data3(underscore_start, 0.0f, y); + color.set_data4(underscore_properties->get_text_color()); + vertex.set_data3(underscore_end, 0.0f, y); + color.set_data4(underscore_properties->get_text_color()); PT(GeomLines) lines = new GeomLines(Geom::UH_static); - lines->add_vertices(0, 1); + lines->add_next_vertices(2); lines->close_primitive(); PT(Geom) geom = new Geom(vdata); geom->add_primitive(lines); - GlyphPlacement *placement = new GlyphPlacement; - placement->add_piece(geom, RenderState::make_empty()); - placement->_xform = LMatrix4::ident_mat(); - placement->_properties = underscore_properties; + PT(TextGlyph) glyph = new TextGlyph(0, geom, RenderState::make_empty(), 0); - row_placed_glyphs.push_back(placement); + // Eventually we should probably replace this with the set_quad + // approach, or better, for improved performance. + //glyph->set_quad(LVecBase4(underscore_start, y, underscore_end, y+0.1), LVecBase4(0), RenderState::make_empty()); + + GlyphPlacement placement; + placement._glyph = MOVE(glyph); + placement._xpos = 0; + placement._ypos = 0; + placement._scale = 1; + placement._slant = 0; + placement._properties = underscore_properties; + placed_glyphs.push_back(placement); } //////////////////////////////////////////////////////////////////// @@ -1613,8 +1790,8 @@ draw_underscore(TextAssembler::PlacedGlyphs &row_placed_glyphs, //////////////////////////////////////////////////////////////////// void TextAssembler:: get_character_glyphs(int character, const TextProperties *properties, - bool &got_glyph, const TextGlyph *&glyph, - const TextGlyph *&second_glyph, + bool &got_glyph, CPT(TextGlyph) &glyph, + CPT(TextGlyph) &second_glyph, UnicodeLatinMap::AccentType &accent_type, int &additional_flags, PN_stdfloat &glyph_scale, PN_stdfloat &advance_scale) { @@ -1684,8 +1861,7 @@ get_character_glyphs(int character, const TextProperties *properties, } } } - - + //////////////////////////////////////////////////////////////////// // Function: TextAssembler::tack_on_accent // Access: Private @@ -1698,7 +1874,7 @@ tack_on_accent(UnicodeLatinMap::AccentType accent_type, const LPoint3 &min_vert, const LPoint3 &max_vert, const LPoint3 ¢roid, const TextProperties *properties, - TextAssembler::GlyphPlacement *placement) const { + TextAssembler::GlyphPlacement &placement) const { switch (accent_type) { case UnicodeLatinMap::AT_grave: // We use the slash as the grave and acute accents. ASCII does @@ -1826,7 +2002,7 @@ tack_on_accent(UnicodeLatinMap::AccentType accent_type, default: // There are lots of other crazy kinds of accents. Forget 'em. break; - } + } } //////////////////////////////////////////////////////////////////// @@ -1843,48 +2019,42 @@ tack_on_accent(char accent_mark, TextAssembler::CheesyPosition position, const LPoint3 &min_vert, const LPoint3 &max_vert, const LPoint3 ¢roid, const TextProperties *properties, - TextAssembler::GlyphPlacement *placement) const { + TextAssembler::GlyphPlacement &placement) const { TextFont *font = properties->get_font(); nassertr(font != (TextFont *)NULL, false); Thread *current_thread = Thread::get_current_thread(); - const TextGlyph *accent_glyph; + CPT(TextGlyph) accent_glyph; if (font->get_glyph(accent_mark, accent_glyph) || font->get_glyph(toupper(accent_mark), accent_glyph)) { - PT(Geom) accent_geom = accent_glyph->get_geom(_usage_hint); - if (accent_geom != (Geom *)NULL) { + if (!accent_glyph->is_whitespace()) { LPoint3 min_accent, max_accent; bool found_any = false; - accent_geom->calc_tight_bounds(min_accent, max_accent, found_any, - current_thread); + accent_glyph->calc_tight_bounds(min_accent, max_accent, found_any, + current_thread); if (found_any) { PN_stdfloat t, u; LMatrix4 accent_mat; - - // This gets set to true if the glyph gets mirrored and needs - // to have backface culling disabled. - bool mirrored = false; + bool has_mat = true; switch (transform) { case CT_none: - accent_mat = LMatrix4::ident_mat(); + has_mat = false; break; case CT_mirror_x: - accent_mat = LMatrix4::scale_mat(-1.0f, 1.0f, 1.0f); + accent_mat = LMatrix4::scale_mat(-1.0f, -1.0f, 1.0f); t = min_accent[0]; min_accent[0] = -max_accent[0]; max_accent[0] = -t; - mirrored = true; break; case CT_mirror_y: - accent_mat = LMatrix4::scale_mat(1.0f, 1.0f, -1.0f); + accent_mat = LMatrix4::scale_mat(1.0f, -1.0f, -1.0f); t = min_accent[2]; min_accent[2] = -max_accent[2]; max_accent[2] = -t; - mirrored = true; break; case CT_rotate_90: @@ -1899,8 +2069,8 @@ tack_on_accent(char accent_mark, TextAssembler::CheesyPosition position, break; case CT_rotate_180: - accent_mat = LMatrix4::scale_mat(-1.0f, -1.0f, 1.0f); - + has_mat = false; + placement._scale *= -1; t = min_accent[0]; min_accent[0] = -max_accent[0]; max_accent[0] = -t; @@ -1929,20 +2099,19 @@ tack_on_accent(char accent_mark, TextAssembler::CheesyPosition position, break; case CT_squash_mirror_y: - accent_mat = LMatrix4::scale_mat(squash_accent_scale_x, 1.0f, -squash_accent_scale_y); + accent_mat = LMatrix4::scale_mat(squash_accent_scale_x, -1.0f, -squash_accent_scale_y); min_accent[0] *= squash_accent_scale_x; max_accent[0] *= squash_accent_scale_x; t = min_accent[2]; min_accent[2] = -max_accent[2] * squash_accent_scale_y; max_accent[2] = -t * squash_accent_scale_y; - mirrored = true; break; case CT_squash_mirror_diag: accent_mat = LMatrix4::rotate_mat_normaxis(270.0f, LVecBase3(0.0f, -1.0f, 0.0f)) * - LMatrix4::scale_mat(-squash_accent_scale_x, 1.0f, squash_accent_scale_y); - + LMatrix4::scale_mat(-squash_accent_scale_x, -1.0f, squash_accent_scale_y); + // rotate min, max t = min_accent[0]; u = max_accent[0]; @@ -1950,7 +2119,6 @@ tack_on_accent(char accent_mark, TextAssembler::CheesyPosition position, max_accent[0] = max_accent[2] * -squash_accent_scale_x; min_accent[2] = -u * squash_accent_scale_y; max_accent[2] = -t * squash_accent_scale_y; - mirrored = true; break; case CT_small_squash: @@ -1962,20 +2130,19 @@ tack_on_accent(char accent_mark, TextAssembler::CheesyPosition position, break; case CT_small_squash_mirror_y: - accent_mat = LMatrix4::scale_mat(small_squash_accent_scale_x, 1.0f, -small_squash_accent_scale_y); + accent_mat = LMatrix4::scale_mat(small_squash_accent_scale_x, -1.0f, -small_squash_accent_scale_y); min_accent[0] *= small_squash_accent_scale_x; max_accent[0] *= small_squash_accent_scale_x; t = min_accent[2]; min_accent[2] = -max_accent[2] * small_squash_accent_scale_y; max_accent[2] = -t * small_squash_accent_scale_y; - mirrored = true; break; case CT_small_squash_mirror_diag: accent_mat = LMatrix4::rotate_mat_normaxis(270.0f, LVecBase3(0.0f, -1.0f, 0.0f)) * - LMatrix4::scale_mat(-small_squash_accent_scale_x, 1.0f, small_squash_accent_scale_y); - + LMatrix4::scale_mat(-small_squash_accent_scale_x, -1.0f, small_squash_accent_scale_y); + // rotate min, max t = min_accent[0]; u = max_accent[0]; @@ -1983,11 +2150,11 @@ tack_on_accent(char accent_mark, TextAssembler::CheesyPosition position, max_accent[0] = max_accent[2] * -small_squash_accent_scale_x; min_accent[2] = -u * small_squash_accent_scale_y; max_accent[2] = -t * small_squash_accent_scale_y; - mirrored = true; break; case CT_small: - accent_mat = LMatrix4::scale_mat(small_accent_scale); + has_mat = false; + placement._scale *= small_accent_scale; min_accent *= small_accent_scale; max_accent *= small_accent_scale; break; @@ -2007,20 +2174,20 @@ tack_on_accent(char accent_mark, TextAssembler::CheesyPosition position, break; case CT_tiny: - accent_mat = LMatrix4::scale_mat(tiny_accent_scale); + has_mat = false; + placement._scale *= tiny_accent_scale; min_accent *= tiny_accent_scale; max_accent *= tiny_accent_scale; break; case CT_tiny_mirror_x: - accent_mat = LMatrix4::scale_mat(-tiny_accent_scale, 1.0f, tiny_accent_scale); - + accent_mat = LMatrix4::scale_mat(-tiny_accent_scale, -1.0f, tiny_accent_scale); + t = min_accent[0]; min_accent[0] = -max_accent[0] * tiny_accent_scale; max_accent[0] = -t * tiny_accent_scale; min_accent[2] *= tiny_accent_scale; max_accent[2] *= tiny_accent_scale; - mirrored = true; break; case CT_tiny_rotate_270: @@ -2036,60 +2203,52 @@ tack_on_accent(char accent_mark, TextAssembler::CheesyPosition position, min_accent[2] = -u * tiny_accent_scale; max_accent[2] = -t * tiny_accent_scale; break; + + default: + has_mat = false; } LPoint3 accent_centroid = (min_accent + max_accent) / 2.0f; PN_stdfloat accent_height = max_accent[2] - min_accent[2]; - LVector3 trans; + PN_stdfloat accent_y = 0; switch (position) { case CP_above: // A little above the character. - trans.set(centroid[0] - accent_centroid[0], 0.0f, - max_vert[2] - accent_centroid[2] + accent_height * 0.5); + accent_y = max_vert[2] - accent_centroid[2] + accent_height * 0.5f; break; case CP_below: // A little below the character. - trans.set(centroid[0] - accent_centroid[0], 0.0f, - min_vert[2] - accent_centroid[2] - accent_height * 0.5); + accent_y = min_vert[2] - accent_centroid[2] - accent_height * 0.5f; break; case CP_top: // Touching the top of the character. - trans.set(centroid[0] - accent_centroid[0], 0.0f, - max_vert[2] - accent_centroid[2]); + accent_y = max_vert[2] - accent_centroid[2]; break; case CP_bottom: // Touching the bottom of the character. - trans.set(centroid[0] - accent_centroid[0], 0.0f, - min_vert[2] - accent_centroid[2]); + accent_y = min_vert[2] - accent_centroid[2]; break; case CP_within: // Centered within the character. - trans.set(centroid[0] - accent_centroid[0], 0.0f, - centroid[2] - accent_centroid[2]); + accent_y = centroid[2] - accent_centroid[2]; break; } - accent_mat.set_row(3, trans); - accent_geom->transform_vertices(accent_mat); + placement._xpos += placement._scale * (centroid[0] - accent_centroid[0] + placement._slant * accent_y); + placement._ypos += placement._scale * accent_y; - if (mirrored) { - // Once someone asks for this pointer, we hold its reference - // count and never free it. - static CPT(RenderState) disable_backface; - if (disable_backface == (const RenderState *)NULL) { - disable_backface = RenderState::make - (CullFaceAttrib::make(CullFaceAttrib::M_cull_none)); - } - - CPT(RenderState) state = - accent_glyph->get_state()->compose(disable_backface); - placement->add_piece(accent_geom, state); + if (has_mat) { + // Some non-trivial transformation. Apply it to the Geom. + PT(Geom) accent_geom = accent_glyph->get_geom(_usage_hint); + accent_geom->transform_vertices(accent_mat); + placement._glyph = new TextGlyph(0, accent_geom, accent_glyph->get_state(), 0); } else { - placement->add_piece(accent_geom, accent_glyph->get_state()); + // A trivial transformation. + placement._glyph = accent_glyph; } return true; @@ -2138,25 +2297,6 @@ append_delta(wstring &wtext, TextAssembler::ComputedProperties *other) { } } -//////////////////////////////////////////////////////////////////// -// Function: TextAssembler::GlyphPlacement::calc_tight_bounds -// Access: Private -// Description: Expands min_point and max_point to include all of the -// vertices in the glyph(s), if any. found_any is set -// true if any points are found. It is the caller's -// responsibility to initialize min_point, max_point, -// and found_any before calling this function. -//////////////////////////////////////////////////////////////////// -void TextAssembler::GlyphPlacement:: -calc_tight_bounds(LPoint3 &min_point, LPoint3 &max_point, - bool &found_any, Thread *current_thread) const { - Pieces::const_iterator pi; - for (pi = _pieces.begin(); pi != _pieces.end(); ++pi) { - (*pi)._geom->calc_tight_bounds(min_point, max_point, found_any, - current_thread); - } -} - //////////////////////////////////////////////////////////////////// // Function: TextAssembler::GlyphPlacement::assign_to // Access: Private @@ -2165,33 +2305,17 @@ calc_tight_bounds(LPoint3 &min_point, LPoint3 &max_point, // modified by this operation. //////////////////////////////////////////////////////////////////// void TextAssembler::GlyphPlacement:: -assign_to(GeomNode *geom_node, const RenderState *state) const { - Pieces::const_iterator pi; - for (pi = _pieces.begin(); pi != _pieces.end(); ++pi) { - (*pi)._geom->transform_vertices(_xform); - geom_node->add_geom((*pi)._geom, state->compose((*pi)._state)); - } -} +assign_to(GeomNode *geom_node, const RenderState *state, + const LVector2 &offset) const { -//////////////////////////////////////////////////////////////////// -// Function: TextAssembler::GlyphPlacement::assign_copy_to -// Access: Private -// Description: Puts the pieces of the GlyphPlacement in the -// indicated GeomNode. This flavor will make a copy of -// the Geoms first, and then apply the additional -// transform to the vertices. -//////////////////////////////////////////////////////////////////// -void TextAssembler::GlyphPlacement:: -assign_copy_to(GeomNode *geom_node, const RenderState *state, - const LMatrix4 &extra_xform) const { - LMatrix4 new_xform = _xform * extra_xform; - Pieces::const_iterator pi; - for (pi = _pieces.begin(); pi != _pieces.end(); ++pi) { - const Geom *geom = (*pi)._geom; - PT(Geom) new_geom = geom->make_copy(); - new_geom->transform_vertices(new_xform); - geom_node->add_geom(new_geom, state->compose((*pi)._state)); - } + LMatrix4 xform(_scale, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + _slant * _scale, 0.0f, _scale, 0.0f, + _xpos + offset[0], 0.0f, _ypos - offset[1], 1.0f); + + PT(Geom) geom = _glyph->get_geom(GeomEnums::UH_static); + geom->transform_vertices(xform); + geom_node->add_geom(geom, state->compose(_glyph->get_state())); } //////////////////////////////////////////////////////////////////// @@ -2203,72 +2327,98 @@ assign_copy_to(GeomNode *geom_node, const RenderState *state, // vertices. //////////////////////////////////////////////////////////////////// void TextAssembler::GlyphPlacement:: -assign_append_to(GeomCollectorMap &geom_collector_map, +assign_append_to(GeomCollectorMap &geom_collector_map, const RenderState *state, - const LMatrix4 &extra_xform) const { - LMatrix4 new_xform = _xform * extra_xform; - Pieces::const_iterator pi; + const LVector2 &offset) const { + + LMatrix4 xform(_scale, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + _slant * _scale, 0.0f, _scale, 0.0f, + _xpos + offset[0], 0.0f, _ypos - offset[1], 1.0f); + + PT(Geom) geom = _glyph->get_geom(GeomEnums::UH_static); int p, sp, s, e, i; - for (pi = _pieces.begin(); pi != _pieces.end(); ++pi) { - const Geom *geom = (*pi)._geom; - const GeomVertexData *vdata = geom->get_vertex_data(); - CPT(RenderState) rs = (*pi)._state->compose(state); - GeomCollectorKey key(rs, vdata->get_format()); - GeomCollectorMap::iterator mi = geom_collector_map.find(key); - if (mi == geom_collector_map.end()) { - mi = geom_collector_map.insert(GeomCollectorMap::value_type(key, GeomCollector(vdata->get_format()))).first; - } - GeomCollector &geom_collector = (*mi).second; - geom_collector.count_geom(geom); + const GeomVertexData *vdata = geom->get_vertex_data(); + CPT(RenderState) rs = _glyph->get_state()->compose(state); + GeomCollectorKey key(rs, vdata->get_format()); - // We use this map to keep track of vertex indices we have already - // added, so that we don't needlessly duplicate vertices into our - // output vertex data. - VertexIndexMap vimap; + GeomCollectorMap::iterator mi = geom_collector_map.find(key); + if (mi == geom_collector_map.end()) { + mi = geom_collector_map.insert(GeomCollectorMap::value_type(key, GeomCollector(vdata->get_format()))).first; + } + GeomCollector &geom_collector = (*mi).second; + geom_collector.count_geom(geom); - for (p = 0; p < geom->get_num_primitives(); p++) { - CPT(GeomPrimitive) primitive = geom->get_primitive(p)->decompose(); + // We use this map to keep track of vertex indices we have already + // added, so that we don't needlessly duplicate vertices into our + // output vertex data. + VertexIndexMap vimap; - // Get a new GeomPrimitive of the corresponding type. - GeomPrimitive *new_prim = geom_collector.get_primitive(primitive->get_type()); + for (p = 0; p < geom->get_num_primitives(); p++) { + CPT(GeomPrimitive) primitive = geom->get_primitive(p)->decompose(); - // Walk through all of the components (e.g. triangles) of the - // primitive. - for (sp = 0; sp < primitive->get_num_primitives(); sp++) { - s = primitive->get_primitive_start(sp); - e = primitive->get_primitive_end(sp); + // Get a new GeomPrimitive of the corresponding type. + GeomPrimitive *new_prim = geom_collector.get_primitive(primitive->get_type()); - // Walk through all of the vertices in the component. - for (i = s; i < e; i++) { - int vi = primitive->get_vertex(i); + // Walk through all of the components (e.g. triangles) of the + // primitive. + for (sp = 0; sp < primitive->get_num_primitives(); sp++) { + s = primitive->get_primitive_start(sp); + e = primitive->get_primitive_end(sp); - // Attempt to insert number "vi" into the map. - pair added = vimap.insert(VertexIndexMap::value_type(vi, 0)); - int new_vertex; - if (added.second) { - // The insert succeeded. That means this is the first - // time we have encountered this vertex. - new_vertex = geom_collector.append_vertex(vdata, vi, new_xform); - // Update the map with the newly-created target vertex index. - (*(added.first)).second = new_vertex; + // Walk through all of the vertices in the component. + for (i = s; i < e; i++) { + int vi = primitive->get_vertex(i); - } else { - // The insert failed. This means we have previously - // encountered this vertex, and we have already entered - // its target vertex index into the vimap. Extract that - // vertex index, so we can reuse it. - new_vertex = (*(added.first)).second; - } - new_prim->add_vertex(new_vertex); + // Attempt to insert number "vi" into the map. + pair added = vimap.insert(VertexIndexMap::value_type(vi, 0)); + int new_vertex; + if (added.second) { + // The insert succeeded. That means this is the first + // time we have encountered this vertex. + new_vertex = geom_collector.append_vertex(vdata, vi, xform); + // Update the map with the newly-created target vertex index. + (*(added.first)).second = new_vertex; + + } else { + // The insert failed. This means we have previously + // encountered this vertex, and we have already entered + // its target vertex index into the vimap. Extract that + // vertex index, so we can reuse it. + new_vertex = (*(added.first)).second; } - new_prim->close_primitive(); + new_prim->add_vertex(new_vertex); } + new_prim->close_primitive(); } } } +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::GlyphPlacement::assign_quad_to +// Access: Private +// Description: If this glyph is representable as a single quad, +// assigns it to the appropriate position in the map. +//////////////////////////////////////////////////////////////////// +void TextAssembler::GlyphPlacement:: +assign_quad_to(QuadMap &quad_map, const RenderState *state, + const LVector2 &offset) const { + + QuadDef quad; + if (_glyph->get_quad(quad._dimensions, quad._uvs)) { + quad._dimensions *= _scale; + quad._slantl = quad._dimensions[1] * _slant; + quad._slanth = quad._dimensions[3] * _slant; + quad._dimensions += LVecBase4(_xpos, _ypos, _xpos, _ypos); + quad._dimensions += LVecBase4(offset[0], -offset[1], offset[0], -offset[1]); + quad._glyph = _glyph; + + quad_map[state->compose(_glyph->get_state())].push_back(MOVE(quad)); + } +} + //////////////////////////////////////////////////////////////////// // Function: TextAssembler::GlyphPlacement::copy_graphic_to // Access: Private @@ -2276,16 +2426,20 @@ assign_append_to(GeomCollectorMap &geom_collector_map, // copies it to the indicated node. //////////////////////////////////////////////////////////////////// void TextAssembler::GlyphPlacement:: -copy_graphic_to(PandaNode *node, const RenderState *state, - const LMatrix4 &extra_xform) const { +copy_graphic_to(PandaNode *node, const RenderState *state) const { if (_graphic_model != (PandaNode *)NULL) { - LMatrix4 new_xform = _xform * extra_xform; - // We need an intermediate node to hold the transform and state. PT(PandaNode) intermediate_node = new PandaNode(""); node->add_child(intermediate_node); - intermediate_node->set_transform(TransformState::make_mat(new_xform)); + intermediate_node->set_transform( + TransformState::make_pos_hpr_scale_shear( + LVecBase3(_xpos, 0, _ypos), + LVecBase3::zero(), + LVecBase3(_scale, 1, _scale), + LVecBase3(0, _slant, 0) + ) + ); intermediate_node->set_state(state); intermediate_node->add_child(_graphic_model); } diff --git a/panda/src/text/textAssembler.h b/panda/src/text/textAssembler.h index 07bb80b0b9..24db6244c6 100644 --- a/panda/src/text/textAssembler.h +++ b/panda/src/text/textAssembler.h @@ -164,12 +164,10 @@ private: // And here it is, wordwrapped. TextBlock _text_block; -#ifndef CPPPARSER // interrogate has a bit of trouble with wstring iterators. void scan_wtext(TextString &output_string, wstring::const_iterator &si, const wstring::const_iterator &send, ComputedProperties *current_cprops); -#endif // CPPPARSER bool wordwrap_text(); @@ -179,13 +177,6 @@ private: // These structures are built up by assemble_paragraph() and // assemble_row(). They represent the actual Geoms as laid out in a // paragraph. - - class Piece { - public: - PT(Geom) _geom; - CPT(RenderState) _state; - }; - typedef pvector Pieces; class GeomCollectorKey { public: @@ -218,26 +209,50 @@ private: }; typedef pmap GeomCollectorMap; - class GlyphPlacement : public MemoryBase { + struct QuadDef { + // Copying this class is a performance hotspot, hence we define the + // move constructor. + ALWAYS_INLINE QuadDef() {} + ALWAYS_INLINE QuadDef(const QuadDef ©) : + _dimensions(copy._dimensions), _uvs(copy._uvs), + _slantl(copy._slantl), _slanth(copy._slanth), + _glyph(copy._glyph) {} + +#ifdef USE_MOVE_SEMANTICS + ALWAYS_INLINE QuadDef(QuadDef &&from) NOEXCEPT : + _dimensions(from._dimensions), _uvs(from._uvs), + _slantl(from._slantl), _slanth(from._slanth), + _glyph(move(from._glyph)) {} +#endif + + LVecBase4 _dimensions; + LVecBase4 _uvs; + PN_stdfloat _slantl, _slanth; + CPT(TextGlyph) _glyph; + }; + typedef pvector QuadDefs; + typedef pmap QuadMap; + + void generate_quads(GeomNode *geom_node, const QuadMap &quad_map); + + class GlyphPlacement { public: - INLINE void add_piece(Geom *geom, const RenderState *state); - void calc_tight_bounds(LPoint3 &min_point, LPoint3 &max_point, - bool &found_any, Thread *current_thread) const; - void assign_to(GeomNode *geom_node, const RenderState *state) const; - void assign_copy_to(GeomNode *geom_node, const RenderState *state, - const LMatrix4 &extra_xform) const; + void assign_to(GeomNode *geom_node, const RenderState *state, + const LVector2 &offset = LVector2::zero()) const; void assign_append_to(GeomCollectorMap &geom_collector_map, const RenderState *state, - const LMatrix4 &extra_xform) const; - void copy_graphic_to(PandaNode *node, const RenderState *state, - const LMatrix4 &extra_xform) const; + const LVector2 &offset = LVector2::zero()) const; + void assign_quad_to(QuadMap &quad_map, const RenderState *state, + const LVector2 &offset = LVector2::zero()) const; + void copy_graphic_to(PandaNode *node, const RenderState *state) const; - Pieces _pieces; + CPT(TextGlyph) _glyph; PT(PandaNode) _graphic_model; - LMatrix4 _xform; + PN_stdfloat _xpos, _ypos; + PN_stdfloat _scale, _slant; const TextProperties *_properties; }; - typedef pvector PlacedGlyphs; + typedef pvector PlacedGlyphs; void assemble_paragraph(PlacedGlyphs &placed_glyphs); void assemble_row(TextRow &row, @@ -281,8 +296,8 @@ private: static void get_character_glyphs(int character, const TextProperties *properties, - bool &got_glyph, const TextGlyph *&glyph, - const TextGlyph *&second_glyph, + bool &got_glyph, CPT(TextGlyph) &glyph, + CPT(TextGlyph) &second_glyph, UnicodeLatinMap::AccentType &accent_type, int &additional_flags, PN_stdfloat &glyph_scale, PN_stdfloat &advance_scale); @@ -291,13 +306,13 @@ private: tack_on_accent(UnicodeLatinMap::AccentType accent_type, const LPoint3 &min_vert, const LPoint3 &max_vert, const LPoint3 ¢roid, - const TextProperties *properties, GlyphPlacement *placement) const; + const TextProperties *properties, GlyphPlacement &placement) const; bool tack_on_accent(char accent_mark, CheesyPosition position, CheesyTransform transform, const LPoint3 &min_vert, const LPoint3 &max_vert, const LPoint3 ¢roid, - const TextProperties *properties, GlyphPlacement *placement) const; + const TextProperties *properties, GlyphPlacement &placement) const; // These are filled in by assemble_paragraph(). LVector2 _ul; diff --git a/panda/src/text/textFont.I b/panda/src/text/textFont.I index 030f96d28d..278d8ebaf7 100644 --- a/panda/src/text/textFont.I +++ b/panda/src/text/textFont.I @@ -87,9 +87,9 @@ set_space_advance(PN_stdfloat space_advance) { // it may still return a printable glyph, or it may // return NULL. //////////////////////////////////////////////////////////////////// -INLINE const TextGlyph *TextFont:: +INLINE CPT(TextGlyph) TextFont:: get_glyph(int character) { - const TextGlyph *glyph = NULL; + CPT(TextGlyph) glyph; get_glyph(character, glyph); return glyph; } diff --git a/panda/src/text/textFont.h b/panda/src/text/textFont.h index 193616c298..74de7f1d3e 100644 --- a/panda/src/text/textFont.h +++ b/panda/src/text/textFont.h @@ -78,12 +78,12 @@ PUBLISHED: INLINE PN_stdfloat get_space_advance() const; INLINE void set_space_advance(PN_stdfloat space_advance); - INLINE const TextGlyph *get_glyph(int character); + INLINE CPT(TextGlyph) get_glyph(int character); virtual void write(ostream &out, int indent_level) const; public: - virtual bool get_glyph(int character, const TextGlyph *&glyph)=0; + virtual bool get_glyph(int character, CPT(TextGlyph) &glyph)=0; TextGlyph *get_invalid_glyph(); static RenderMode string_render_mode(const string &string); diff --git a/panda/src/text/textGlyph.I b/panda/src/text/textGlyph.I index 89852d38ea..b728f1e456 100644 --- a/panda/src/text/textGlyph.I +++ b/panda/src/text/textGlyph.I @@ -16,14 +16,15 @@ //////////////////////////////////////////////////////////////////// // Function: TextGlyph::Default constructor // Access: Public -// Description: This constructor makes an invalid glyph. +// Description: This constructor makes an empty glyph. //////////////////////////////////////////////////////////////////// INLINE TextGlyph:: -TextGlyph(int character) : - _character(character) +TextGlyph(int character, PN_stdfloat advance) : + _character(character), + _geom((Geom *)NULL), + _advance(advance), + _has_quad(false) { - _geom = (Geom *)NULL; - _advance = 0.0f; } //////////////////////////////////////////////////////////////////// @@ -37,8 +38,12 @@ TextGlyph(int character, const Geom *geom, _character(character), _geom(geom), _state(state), - _advance(advance) -{ + _advance(advance), + _has_quad(false) +{ + if (geom != NULL) { + check_quad_geom(); + } if (_state == (RenderState *)NULL) { _state = RenderState::make_empty(); } @@ -54,8 +59,11 @@ TextGlyph(const TextGlyph ©) : _character(copy._character), _geom(copy._geom), _state(copy._state), - _advance(copy._advance) -{ + _advance(copy._advance), + _has_quad(copy._has_quad), + _quad_dimensions(copy._quad_dimensions), + _quad_texcoords(copy._quad_texcoords) +{ } //////////////////////////////////////////////////////////////////// @@ -69,6 +77,9 @@ operator = (const TextGlyph ©) { _geom = copy._geom; _state = copy._state; _advance = copy._advance; + _has_quad = copy._has_quad; + _quad_dimensions = copy._quad_dimensions; + _quad_texcoords = copy._quad_texcoords; } //////////////////////////////////////////////////////////////////// @@ -83,28 +94,40 @@ get_character() const { } //////////////////////////////////////////////////////////////////// -// Function: TextGlyph::get_geom +// Function: TextGlyph::has_quad // Access: Public -// Description: Returns a Geom that renders the particular glyph. +// Description: Returns true if this glyph contains the definition +// for a simple quad, rather than a more complex piece +// of geometry. +// +// You may still call get_geom() even if this returns +// true, which will synthesize a Geom for this quad. //////////////////////////////////////////////////////////////////// -INLINE PT(Geom) TextGlyph:: -get_geom(Geom::UsageHint usage_hint) const { - if (_geom == (Geom *)NULL) { - return NULL; +INLINE bool TextGlyph:: +has_quad() const { + return _has_quad; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextGlyph::get_quad +// Access: Public +// Description: Assuming that this glyph is representable as a +// textured quad, returns its dimensions and UV range. +// Returns false if it is not representable as a quad, +// or if it is whitespace. +// +// The order of the components is left, bottom, +// right, top. +//////////////////////////////////////////////////////////////////// +INLINE bool TextGlyph:: +get_quad(LVecBase4 &dimensions, LVecBase4 &texcoords) const { + if (!_has_quad) { + return false; } - // We always return a copy of the geom. That will allow the caller - // to modify its vertices without fear of stomping on other copies; - // it is also critical for the DynamicTextGlyph, which depends on - // this behavior to properly count references to this glyph. - PT(Geom) new_geom = _geom->make_copy(); - new_geom->set_usage_hint(usage_hint); - const GeomVertexData *vdata = new_geom->get_vertex_data(); - nassertr(vdata != NULL, new_geom); - if (vdata->get_usage_hint() != usage_hint) { - new_geom->modify_vertex_data()->set_usage_hint(usage_hint); - } - return new_geom; + dimensions = _quad_dimensions; + texcoords = _quad_texcoords; + return true; } //////////////////////////////////////////////////////////////////// diff --git a/panda/src/text/textGlyph.cxx b/panda/src/text/textGlyph.cxx index 7c1c6ff4a6..c3bad08c39 100644 --- a/panda/src/text/textGlyph.cxx +++ b/panda/src/text/textGlyph.cxx @@ -27,7 +27,7 @@ TextGlyph:: //////////////////////////////////////////////////////////////////// // Function: TextGlyph::is_whitespace -// Access: Public, Virtual +// Access: Published, Virtual // Description: Returns true if this glyph represents invisible // whitespace, or false if it corresponds to some // visible character. @@ -38,3 +38,276 @@ is_whitespace() const { // all glyphs are non-whitespace. return false; } + +//////////////////////////////////////////////////////////////////// +// Function: TextGlyph::get_geom +// Access: Published +// Description: Returns a Geom that renders the particular glyph. +// It will be generated if necessary. +// +// This method will always return a copy of the Geom, +// so the caller is free to modify it. +//////////////////////////////////////////////////////////////////// +PT(Geom) TextGlyph:: +get_geom(Geom::UsageHint usage_hint) const { + if (_geom.is_null()) { + // Maybe we have yet to generate a suitable Geom. + if (_has_quad) { + ((TextGlyph *)this)->make_quad_geom(); + if (_geom.is_null()) { + return (Geom *)NULL; + } + } else { + // Nope. + return (Geom *)NULL; + } + } + + // We always return a copy of the geom. That will allow the caller + // to modify its vertices without fear of stomping on other copies. + // It is also important that we store a reference to this glyph on + // the Geom, since the DynamicTextFont relies on counting references + // to determine whether a glyph is no longer used. + PT(Geom) new_geom = new GeomTextGlyph(*_geom, this); + new_geom->set_usage_hint(usage_hint); + const GeomVertexData *vdata = new_geom->get_vertex_data(); + nassertr(vdata != NULL, new_geom); + if (vdata->get_usage_hint() != usage_hint) { + new_geom->modify_vertex_data()->set_usage_hint(usage_hint); + } + return new_geom; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextGlyph::calc_tight_bounds +// Access: Public +// Description: Expands min_point and max_point to include all of the +// vertices in the glyph, if any. found_any is set +// true if any points are found. It is the caller's +// responsibility to initialize min_point, max_point, +// and found_any before calling this function. +//////////////////////////////////////////////////////////////////// +void TextGlyph:: +calc_tight_bounds(LPoint3 &min_point, LPoint3 &max_point, + bool &found_any, Thread *current_thread) const { + if (_has_quad) { + found_any = true; + min_point.set(_quad_dimensions[0], 0, _quad_dimensions[1]); + max_point.set(_quad_dimensions[2], 0, _quad_dimensions[3]); + + } else if (!_geom.is_null()) { + _geom->calc_tight_bounds(min_point, max_point, found_any, current_thread); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: TextGlyph::set_quad +// Access: Public +// Description: Sets the glyph using the given quad parameters. +// Any Geom assigned will be cleared. +// The order of the components is left, bottom, +// right, top. +//////////////////////////////////////////////////////////////////// +void TextGlyph:: +set_quad(const LVecBase4 &dimensions, const LVecBase4 &texcoords, + const RenderState *state) { + _geom.clear(); + + _quad_dimensions = dimensions; + _quad_texcoords = texcoords; + _has_quad = true; + _state = state; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextGlyph::set_geom +// Access: Public +// Description: Sets the geom from a pre-built Geom object. +// Any quad parameters assigned will be cleared. +//////////////////////////////////////////////////////////////////// +void TextGlyph:: +set_geom(GeomVertexData *vdata, GeomPrimitive *prim, + const RenderState *state) { + PT(Geom) geom = new Geom(vdata); + geom->add_primitive(prim); + _geom = geom; + + _has_quad = false; + _state = state; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextGlyph::check_quad_geom +// Access: Private +// Description: Checks if the geom that was passed in is actually +// a quad, and if so, sets the appropriate quad +// parameters. +// +// This is useful when loading static text fonts, so +// that they can still benefit from the fast text +// assembly that is done for quads. +//////////////////////////////////////////////////////////////////// +void TextGlyph:: +check_quad_geom() { + // Currently it looks for rather specific signs that this glyph + // has been generated using egg-mkfont. For now, this is fine. + CPT(GeomVertexData) vdata = _geom->get_vertex_data(); + if (vdata->get_num_rows() != 4) { + return; + } + + // Does it appear to have a single primitive with two triangles? + if (_geom->get_num_primitives() != 1) { + return; + } + CPT(GeomPrimitive) prim = _geom->get_primitive(0); + if (!prim->is_indexed() || + prim->get_primitive_type() != GeomPrimitive::PT_polygons) { + return; + } + if (!prim->is_of_type(GeomTriangles::get_class_type())) { + prim = prim->decompose(); + } + if (prim->get_num_vertices() != 6) { + return; + } + + // Check that it has a vertex column and optionally a texcoord column. + CPT(GeomVertexFormat) format = vdata->get_format(); + if (format->get_num_columns() > 2 || + !format->has_column(InternalName::get_vertex())) { + return; + } + if (format->has_column(InternalName::get_texcoord()) != (format->get_num_columns() == 2)) { + // The second column, if there is one, is not a texcoord. + return; + } + + // Check that the vertices are arranged in a square. + GeomVertexReader vertex(vdata, InternalName::get_vertex()); + LVecBase3 v = vertex.get_data3(); + if (!IS_NEARLY_ZERO(v[1])) { + return; + } + PN_stdfloat minx = v[0]; + PN_stdfloat maxx = v[0]; + PN_stdfloat miny = v[2]; + PN_stdfloat maxy = v[2]; + + for (int i = 0; i < 3; ++i) { + v = vertex.get_data3(); + if (!IS_NEARLY_ZERO(v[1])) { + return; + } + if (!IS_NEARLY_EQUAL(v[0], minx) && !IS_NEARLY_EQUAL(v[0], maxx)) { + if (!IS_NEARLY_EQUAL(minx, maxx)) { + return; + } + minx = min(v[0], minx); + maxx = max(v[0], maxx); + } + if (!IS_NEARLY_EQUAL(v[2], miny) && !IS_NEARLY_EQUAL(v[2], maxy)) { + if (!IS_NEARLY_EQUAL(miny, maxy)) { + return; + } + miny = min(v[2], miny); + maxy = max(v[2], maxy); + } + } + + PN_stdfloat minu = 0; + PN_stdfloat maxu = 0; + PN_stdfloat minv = 0; + PN_stdfloat maxv = 0; + + // Same for the texcoord data. + if (format->has_column(InternalName::get_texcoord())) { + GeomVertexReader texcoord(vdata, InternalName::get_texcoord()); + LVecBase2 tc = texcoord.get_data2(); + minu = tc[0]; + maxu = tc[0]; + minv = tc[1]; + maxv = tc[1]; + + for (int i = 0; i < 3; ++i) { + tc = texcoord.get_data2(); + if (!IS_NEARLY_EQUAL(tc[0], minu) && !IS_NEARLY_EQUAL(tc[0], maxu)) { + if (!IS_NEARLY_EQUAL(minu, maxu)) { + return; + } + minu = min(tc[0], minu); + maxu = max(tc[0], maxu); + } + if (!IS_NEARLY_EQUAL(tc[1], minv) && !IS_NEARLY_EQUAL(tc[1], maxv)) { + if (!IS_NEARLY_EQUAL(minv, maxv)) { + return; + } + minv = min(tc[1], minv); + maxv = max(tc[1], maxv); + } + } + } + + _quad_dimensions.set(minx, miny, maxx, maxy); + _quad_texcoords.set(minu, minv, maxu, maxv); + _has_quad = true; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextGlyph::make_quad_geom +// Access: Private +// Description: Generates a Geom representing this text glyph, if +// at all possible. +//////////////////////////////////////////////////////////////////// +void TextGlyph:: +make_quad_geom() { + // The default implementation is to generate a Geom based on the + // get_quad() implementation, if any. + LVecBase4 dimensions, uvs; + if (!get_quad(dimensions, uvs)) { + return; + } + + // Create a corresponding triangle pair. We use a pair of indexed + // triangles rather than a single triangle strip, to avoid the bad + // vertex duplication behavior with lots of two-triangle strips. + PT(GeomVertexData) vdata = new GeomVertexData + (string(), GeomVertexFormat::get_v3t2(), Geom::UH_static); + vdata->unclean_set_num_rows(4); + + PT(GeomTriangles) tris = new GeomTriangles(Geom::UH_static); + + { + GeomVertexWriter vertex(vdata, InternalName::get_vertex()); + GeomVertexWriter texcoord(vdata, InternalName::get_texcoord()); + + PT(GeomVertexArrayData) indices = tris->modify_vertices(); + indices->unclean_set_num_rows(6); + + GeomVertexWriter index(indices, 0); + + vertex.set_data3(dimensions[0], 0, dimensions[3]); + vertex.set_data3(dimensions[0], 0, dimensions[1]); + vertex.set_data3(dimensions[2], 0, dimensions[3]); + vertex.set_data3(dimensions[2], 0, dimensions[1]); + + texcoord.set_data2(uvs[0], uvs[3]); + texcoord.set_data2(uvs[0], uvs[1]); + texcoord.set_data2(uvs[2], uvs[3]); + texcoord.set_data2(uvs[2], uvs[1]); + + index.set_data1i(0); + index.set_data1i(1); + index.set_data1i(2); + index.set_data1i(2); + index.set_data1i(1); + index.set_data1i(3); + } + + // We create a regular Geom here, not a GeomTextGlyph, since doing so + // would create a circular reference. When the get_geom method makes + // a copy, it will add in a pointer to this text glyph. + PT(Geom) geom = new Geom(vdata); + geom->add_primitive(tris); + _geom = geom; +} diff --git a/panda/src/text/textGlyph.h b/panda/src/text/textGlyph.h index fc321fbaf9..85f10b8509 100644 --- a/panda/src/text/textGlyph.h +++ b/panda/src/text/textGlyph.h @@ -30,7 +30,7 @@ //////////////////////////////////////////////////////////////////// class EXPCL_PANDA_TEXT TextGlyph : public TypedReferenceCount { public: - INLINE TextGlyph(int character); + INLINE TextGlyph(int character, PN_stdfloat advance=0); INLINE TextGlyph(int character, const Geom *geom, const RenderState *state, PN_stdfloat advance); INLINE TextGlyph(const TextGlyph ©); @@ -39,18 +39,42 @@ public: PUBLISHED: INLINE int get_character() const; - INLINE PT(Geom) get_geom(Geom::UsageHint usage_hint) const; + INLINE bool has_quad() const; + INLINE bool get_quad(LVecBase4 &dimensions, LVecBase4 &texcoords) const; INLINE const RenderState *get_state() const; INLINE PN_stdfloat get_advance() const; virtual bool is_whitespace() const; + PT(Geom) get_geom(Geom::UsageHint usage_hint) const; + +public: + void calc_tight_bounds(LPoint3 &min_point, LPoint3 &max_point, + bool &found_any, Thread *current_thread) const; + + void set_quad(const LVecBase4 &dimensions, const LVecBase4 &texcoords, + const RenderState *state); + + void set_geom(GeomVertexData *vdata, GeomPrimitive *prim, + const RenderState *state); + +private: + void check_quad_geom(); + void make_quad_geom(); + protected: int _character; CPT(Geom) _geom; CPT(RenderState) _state; PN_stdfloat _advance; + bool _has_quad; + // top, left, bottom, right + LVecBase4 _quad_dimensions; + LVecBase4 _quad_texcoords; + + friend class GeomTextGlyph; + public: static TypeHandle get_class_type() { return _type_handle;