From 629b215aeeaf0fae5c54088637e911aebcec2d1a Mon Sep 17 00:00:00 2001 From: David Rose Date: Wed, 24 Jan 2007 00:27:39 +0000 Subject: [PATCH] DynamicTextFont::RenderMode --- panda/src/mathutil/triangulator.I | 12 + panda/src/mathutil/triangulator.cxx | 81 +++- panda/src/mathutil/triangulator.h | 4 +- panda/src/parametrics/nurbsCurveResult.I | 64 ++- panda/src/parametrics/nurbsCurveResult.cxx | 78 ++++ panda/src/parametrics/nurbsCurveResult.h | 21 +- panda/src/pnmtext/freetypeFont.cxx | 8 +- panda/src/pnmtext/freetypeFont.h | 4 +- panda/src/text/Sources.pp | 3 +- panda/src/text/config_text.cxx | 4 + panda/src/text/dynamicTextFont.I | 51 ++- panda/src/text/dynamicTextFont.cxx | 443 ++++++++++++++++++++- panda/src/text/dynamicTextFont.h | 67 ++++ panda/src/text/dynamicTextGlyph.cxx | 26 ++ panda/src/text/dynamicTextGlyph.h | 2 + panda/src/text/textProperties.cxx | 2 + 16 files changed, 841 insertions(+), 29 deletions(-) diff --git a/panda/src/mathutil/triangulator.I b/panda/src/mathutil/triangulator.I index ff7a591a22..f65f774fd4 100644 --- a/panda/src/mathutil/triangulator.I +++ b/panda/src/mathutil/triangulator.I @@ -53,6 +53,18 @@ get_vertex(int n) const { return _vertices[n]; } +//////////////////////////////////////////////////////////////////// +// Function: Triangulator::is_left_winding +// Access: Published +// Description: Returns true if the polygon vertices are listed in +// counterclockwise order, or false if they appear to be +// listed in clockwise order. +//////////////////////////////////////////////////////////////////// +INLINE bool Triangulator:: +is_left_winding() const { + return check_left_winding(_polygon); +} + //////////////////////////////////////////////////////////////////// // Function: Triangulator::Triangle::Constructor // Access: Private diff --git a/panda/src/mathutil/triangulator.cxx b/panda/src/mathutil/triangulator.cxx index 0a1cb33caa..18f235997d 100644 --- a/panda/src/mathutil/triangulator.cxx +++ b/panda/src/mathutil/triangulator.cxx @@ -72,9 +72,10 @@ clear_polygon() { // This vertex should index into the vertex pool // established by repeated calls to add_vertex(). // -// The vertices should be listed in counterclockwise -// order, and should not be repeated. In particular, do -// not repeat the first vertex at the end. +// The vertices may be listed in either clockwise or +// counterclockwise order. Vertices should not be +// repeated. In particular, do not repeat the first +// vertex at the end. //////////////////////////////////////////////////////////////////// void Triangulator:: add_polygon_vertex(int index) { @@ -99,8 +100,9 @@ begin_hole() { // This vertex should index into the vertex pool // established by repeated calls to add_vertex(). // -// The vertices should be listed in clockwise order, and -// should not be repeated. +// The vertices may be listed in either clockwise or +// counterclockwise order. Vertices should not be +// repeated. //////////////////////////////////////////////////////////////////// void Triangulator:: add_hole_vertex(int index) { @@ -123,11 +125,11 @@ triangulate() { // Set up the list of segments. seg.clear(); seg.push_back(segment_t()); // we don't use the first entry. - make_segment(_polygon); + make_segment(_polygon, true); Holes::const_iterator hi; for (hi = _holes.begin(); hi != _holes.end(); ++hi) { - make_segment(*hi); + make_segment(*hi, false); } // Shuffle the segment index. @@ -305,30 +307,75 @@ get_triangle_v2(int n) const { #define LENGTH(v0) (sqrt((v0).x * (v0).x + (v0).y * (v0).y)) +//////////////////////////////////////////////////////////////////// +// Function: Triangulator::check_left_winding +// Access: Private +// Description: Returns true if the list of vertices is +// counter-clockwise, false if it is clockwise. +//////////////////////////////////////////////////////////////////// +bool Triangulator:: +check_left_winding(const vector_int &range) const { + // We do this by computing the polygon's signed area. If it comes + // out negative, the polygon is right-winding. + + double area = 0.0; + size_t j = range.size() - 1; + for (size_t i = 0; i < range.size(); ++i) { + const LPoint2d &p0 = _vertices[range[j]]; + const LPoint2d &p1 = _vertices[range[i]]; + area += p0[0] * p1[1] - p0[1] * p1[0]; + j = i; + } + + return area >= 0.0; +} + //////////////////////////////////////////////////////////////////// // Function: Triangulator::make_segment // Access: Private // Description: Converts a linear list of integer vertices to a list -// of segment_t. +// of segment_t. If want_left_winding is true, the list +// is reversed if necessary to make it left-winding; +// otherwise, it is reversed to make it right-winding. //////////////////////////////////////////////////////////////////// void Triangulator:: -make_segment(const vector_int &range) { +make_segment(const vector_int &range, bool want_left_winding) { int num_points = (int)range.size(); nassertv(num_points >= 2); int first = (int)seg.size(); int last = first + num_points - 1; - seg.push_back(segment_t(this, range[0], range[1], - last, first + 1)); + if (want_left_winding == check_left_winding(range)) { + // Keep it in its natural order. + int first = (int)seg.size(); + int last = first + num_points - 1; + + seg.push_back(segment_t(this, range[0], range[1], + last, first + 1)); + + for (int i = 1; i < num_points - 1; ++i) { + seg.push_back(segment_t(this, range[i], range[i + 1], + first + i - 1, first + i + 1)); + } + + seg.push_back(segment_t(this, range[num_points - 1], range[0], + last - 1, first)); + + } else { + // Reverse it. + seg.push_back(segment_t(this, range[0], range[num_points - 1], + last, first + 1)); + + for (int i = 1; i < num_points - 1; ++i) { + seg.push_back(segment_t(this, range[num_points - i], range[num_points - i - 1], + first + i - 1, first + i + 1)); + } + + seg.push_back(segment_t(this, range[1], range[0], + last - 1, first)); - for (int i = 1; i < num_points - 1; ++i) { - seg.push_back(segment_t(this, range[i], range[i + 1], - first + i - 1, first + i + 1)); } - - seg.push_back(segment_t(this, range[num_points - 1], range[0], - last - 1, first)); } /* Return the next segment in the generated random ordering of all the */ diff --git a/panda/src/mathutil/triangulator.h b/panda/src/mathutil/triangulator.h index 3b4a87701a..7ced954a0c 100644 --- a/panda/src/mathutil/triangulator.h +++ b/panda/src/mathutil/triangulator.h @@ -52,6 +52,7 @@ PUBLISHED: void clear_polygon(); void add_polygon_vertex(int index); + INLINE bool is_left_winding() const; void begin_hole(); void add_hole_vertex(int index); @@ -170,7 +171,8 @@ private: vector_int visited; - void make_segment(const vector_int &range); + bool check_left_winding(const vector_int &range) const; + void make_segment(const vector_int &range, bool want_left_winding); int choose_segment(); static double math_log2(double v); diff --git a/panda/src/parametrics/nurbsCurveResult.I b/panda/src/parametrics/nurbsCurveResult.I index daa6b89c79..3999ad07e7 100644 --- a/panda/src/parametrics/nurbsCurveResult.I +++ b/panda/src/parametrics/nurbsCurveResult.I @@ -28,7 +28,7 @@ INLINE NurbsCurveResult:: //////////////////////////////////////////////////////////////////// // Function: NurbsCurveResult::get_start_t -// Access: Public +// Access: Published // Description: Returns the first legal value of t on the curve. // Usually this is 0.0. //////////////////////////////////////////////////////////////////// @@ -39,7 +39,7 @@ get_start_t() const { //////////////////////////////////////////////////////////////////// // Function: NurbsCurveResult::get_end_t -// Access: Public +// Access: Published // Description: Returns the last legal value of t on the curve. //////////////////////////////////////////////////////////////////// INLINE float NurbsCurveResult:: @@ -124,7 +124,7 @@ eval_extended_points(float t, int d, float result[], int num_values) { //////////////////////////////////////////////////////////////////// // Function: NurbsCurveResult::get_num_segments -// Access: Public +// Access: Published // Description: Returns the number of piecewise continuous segments // within the curve. This number is usually not // important unless you plan to call @@ -137,7 +137,7 @@ get_num_segments() const { //////////////////////////////////////////////////////////////////// // Function: NurbsCurveResult::get_segment_t -// Access: Public +// Access: Published // Description: Accepts a t value in the range [0, 1], and assumed to // be relative to the indicated segment (as in // eval_segment_point()), and returns the corresponding @@ -147,3 +147,59 @@ INLINE float NurbsCurveResult:: get_segment_t(int segment, float t) const { return t * (_basis.get_to(segment) - _basis.get_from(segment)) + _basis.get_from(segment); } + +//////////////////////////////////////////////////////////////////// +// Function: NurbsCurveResult::get_num_samples +// Access: Published +// Description: Returns the number of sample points generated by the +// previous call to adaptive_sample(). +//////////////////////////////////////////////////////////////////// +INLINE int NurbsCurveResult:: +get_num_samples() const { + return (int)_adaptive_result.size(); +} + +//////////////////////////////////////////////////////////////////// +// Function: NurbsCurveResult::get_sample_t +// Access: Published +// Description: Returns the t value of the nth sample point generated +// by the previous call to adaptive_sample(). +//////////////////////////////////////////////////////////////////// +INLINE float NurbsCurveResult:: +get_sample_t(int n) const { + nassertr(n >= 0 && n < (int)_adaptive_result.size(), 0.0f); + return _adaptive_result[n]._t; +} + +//////////////////////////////////////////////////////////////////// +// Function: NurbsCurveResult::get_sample_point +// Access: Published +// Description: Returns the point on the curve of the nth sample +// point generated by the previous call to +// adaptive_sample(). +// +// For tangents, or extended points, you should use +// get_sample_t() and pass it into eval_tangent() or +// eval_extended_point(). +//////////////////////////////////////////////////////////////////// +INLINE const LPoint3f &NurbsCurveResult:: +get_sample_point(int n) const { + nassertr(n >= 0 && n < (int)_adaptive_result.size(), LPoint3f::zero()); + return _adaptive_result[n]._point; +} + +//////////////////////////////////////////////////////////////////// +// Function: NurbsCurveResult::get_segment_t +// Access: Public +// Description: Accepts a t value in the range [0, 1], and assumed to +// be relative to the indicated segment (as in +// eval_segment_point()), and returns the corresponding +// t value in the entire curve (as in eval_point()). +//////////////////////////////////////////////////////////////////// +INLINE NurbsCurveResult::AdaptiveSample:: +AdaptiveSample(float t, const LPoint3f &point) : + _t(t), + _point(point) +{ +} + diff --git a/panda/src/parametrics/nurbsCurveResult.cxx b/panda/src/parametrics/nurbsCurveResult.cxx index 1300c593ff..634c17b5d2 100644 --- a/panda/src/parametrics/nurbsCurveResult.cxx +++ b/panda/src/parametrics/nurbsCurveResult.cxx @@ -199,6 +199,41 @@ eval_segment_extended_points(int segment, float t, int d, } } +//////////////////////////////////////////////////////////////////// +// Function: NurbsCurveResult::adaptive_sample +// Access: Published +// Description: Determines the set of subdivisions necessary to +// approximate the curve with a set of linear segments, +// no point of which is farther than tolerance units +// from the actual curve. +// +// After this call, you may walk through the resulting +// set of samples with get_num_samples(), +// get_sample_t(), and get_sample_point(). +//////////////////////////////////////////////////////////////////// +void NurbsCurveResult:: +adaptive_sample(float tolerance) { + float tolerance_2 = tolerance * tolerance; + _adaptive_result.clear(); + + LPoint3f p0, p1; + + int num_segments = _basis.get_num_segments(); + for (int segment = 0; segment < num_segments; ++segment) { + eval_segment_point(segment, 0.0f, p0); + if (segment == 0 || !p0.almost_equal(p1)) { + // We explicitly push the first point, and the boundary point + // anytime the segment boundary is discontinuous. + _adaptive_result.push_back(AdaptiveSample(_basis.get_from(segment), p0)); + } + + eval_segment_point(segment, 1.0f, p1); + + // Then we recusrively get the remaining points in the segment. + r_adaptive_sample(segment, 0.0f, p0, 1.0f, p1, tolerance_2); + } +} + //////////////////////////////////////////////////////////////////// // Function: NurbsCurveResult::find_segment // Access: Private @@ -264,3 +299,46 @@ r_find_segment(float t, int top, int bot) const { } } +//////////////////////////////////////////////////////////////////// +// Function: NurbsCurveResult::r_adaptive_sample +// Access: Private +// Description: Recursively subdivides a potential evaluation of the +// segment until it is found to be within tolerance. +// This will add everything up to and including t1, but +// excluding t0. +//////////////////////////////////////////////////////////////////// +void NurbsCurveResult:: +r_adaptive_sample(int segment, float t0, const LPoint3f &p0, + float t1, const LPoint3f &p1, float tolerance_2) { + float tmid = (t0 + t1) * 0.5f; + LPoint3f pmid; + eval_segment_point(segment, tmid, pmid); + + if (sqr_dist_to_line(pmid, p0, p1 - p0) > tolerance_2) { + // The line is still too curved--subdivide. + r_adaptive_sample(segment, t0, p0, tmid, pmid, tolerance_2); + r_adaptive_sample(segment, tmid, pmid, t1, p1, tolerance_2); + + } else { + // The line is sufficiently close. Stop here. + _adaptive_result.push_back(AdaptiveSample(_basis.scale_t(segment, t1), p1)); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: NurbsCurveResult::sqr_dist_to_line +// Access: Private, Static +// Description: A support function for r_adaptive_sample(), this +// computes the minimum distance from a point to a line, +// and returns the distance squared. +//////////////////////////////////////////////////////////////////// +float NurbsCurveResult:: +sqr_dist_to_line(const LPoint3f &point, const LPoint3f &origin, + const LVector3f &vec) { + LVector3f norm = vec; + norm.normalize(); + LVector3f d = point - origin; + float hyp_2 = d.length_squared(); + float leg = d.dot(norm); + return hyp_2 - leg * leg; +} diff --git a/panda/src/parametrics/nurbsCurveResult.h b/panda/src/parametrics/nurbsCurveResult.h index f59b789f8a..e3a741aa4a 100644 --- a/panda/src/parametrics/nurbsCurveResult.h +++ b/panda/src/parametrics/nurbsCurveResult.h @@ -22,6 +22,7 @@ #include "pandabase.h" #include "referenceCount.h" #include "nurbsBasisVector.h" +#include "vector_float.h" class NurbsVertex; @@ -63,11 +64,21 @@ PUBLISHED: void eval_segment_extended_points(int segment, float t, int d, float result[], int num_values) const; INLINE float get_segment_t(int segment, float t) const; + + void adaptive_sample(float tolerance); + INLINE int get_num_samples() const; + INLINE float get_sample_t(int n) const; + INLINE const LPoint3f &get_sample_point(int n) const; private: int find_segment(float t); int r_find_segment(float t, int top, int bot) const; + void r_adaptive_sample(int segment, float t0, const LPoint3f &p0, + float t1, const LPoint3f &p1, float tolerance_2); + static float sqr_dist_to_line(const LPoint3f &point, const LPoint3f &origin, + const LVector3f &vec); + NurbsBasisVector _basis; const NurbsVertex *_verts; @@ -77,10 +88,18 @@ private: typedef pvector ComposedGeom; ComposedGeom _composed; - int _last_segment; float _last_from; float _last_to; + + class AdaptiveSample { + public: + INLINE AdaptiveSample(float t, const LPoint3f &point); + float _t; + LPoint3f _point; + }; + typedef pvector AdaptiveResult; + AdaptiveResult _adaptive_result; }; #include "nurbsCurveResult.I" diff --git a/panda/src/pnmtext/freetypeFont.cxx b/panda/src/pnmtext/freetypeFont.cxx index ada808cd36..ad2325b63f 100644 --- a/panda/src/pnmtext/freetypeFont.cxx +++ b/panda/src/pnmtext/freetypeFont.cxx @@ -170,12 +170,18 @@ unload_font() { // false otherwise. //////////////////////////////////////////////////////////////////// bool FreetypeFont:: -load_glyph(int glyph_index) { +load_glyph(int glyph_index, bool prerender) { int flags = FT_LOAD_RENDER; if (!_native_antialias) { flags |= FT_LOAD_MONOCHROME; } + if (!prerender) { + // If we want to render as an outline font, don't pre-render it to + // a bitmap. + flags = 0; + } + int error = FT_Load_Glyph(_face, glyph_index, flags); if (error) { pnmtext_cat.error() diff --git a/panda/src/pnmtext/freetypeFont.h b/panda/src/pnmtext/freetypeFont.h index a5717b5b51..fd2a28f049 100644 --- a/panda/src/pnmtext/freetypeFont.h +++ b/panda/src/pnmtext/freetypeFont.h @@ -76,7 +76,7 @@ public: INLINE static float get_points_per_inch(); protected: - bool load_glyph(int glyph_index); + bool load_glyph(int glyph_index, bool prerender = true); void copy_bitmap_to_pnmimage(const FT_Bitmap &bitmap, PNMImage &image); private: @@ -99,7 +99,7 @@ protected: FT_Face _face; -private: +protected: bool _font_loaded; // This string is used to hold the data read from the font file in diff --git a/panda/src/text/Sources.pp b/panda/src/text/Sources.pp index 402904733f..6a12cd6367 100644 --- a/panda/src/text/Sources.pp +++ b/panda/src/text/Sources.pp @@ -7,7 +7,8 @@ #define TARGET text #define LOCAL_LIBS \ putil gobj pgraph linmath \ - pnmtext pnmimage gsgbase mathutil + pnmtext pnmimage gsgbase mathutil \ + parametrics #define COMBINED_SOURCES $[TARGET]_composite1.cxx $[TARGET]_composite2.cxx diff --git a/panda/src/text/config_text.cxx b/panda/src/text/config_text.cxx index b0a66b1bdc..ebf73d740b 100644 --- a/panda/src/text/config_text.cxx +++ b/panda/src/text/config_text.cxx @@ -182,6 +182,10 @@ ConfigVariableEnum text_wrap_mode ("text-wrap-mode", Texture::WM_border_color, PRC_DESC("The default wrap mode for dynamic text fonts")); +ConfigVariableEnum text_render_mode +("text-render-mode", DynamicTextFont::RM_texture, + PRC_DESC("The default render mode for dynamic text fonts")); + //////////////////////////////////////////////////////////////////// diff --git a/panda/src/text/dynamicTextFont.I b/panda/src/text/dynamicTextFont.I index b18e7a358b..f410cc3378 100644 --- a/panda/src/text/dynamicTextFont.I +++ b/panda/src/text/dynamicTextFont.I @@ -102,7 +102,8 @@ get_pixels_per_unit() const { // larger than 1.0 to improve the font's antialiasing // (since FreeType doesn't really do a swell job of // antialiasing by itself). There is some performance -// implication for setting this different than 1.0. +// implication for setting this different than 1.0, but +// it is probably small. // // This should only be called before any characters have // been requested out of the font, or immediately after @@ -360,6 +361,54 @@ get_anisotropic_degree() const { return _anisotropic_degree; } +//////////////////////////////////////////////////////////////////// +// Function: DynamicTextFont::set_render_mode +// Access: Published +// Description: Specifies the way the glyphs on this particular font +// are generated. The default is RM_texture, which is +// the only mode supported for bitmap fonts. Other modes +// are possible for most modern fonts. +//////////////////////////////////////////////////////////////////// +INLINE void DynamicTextFont:: +set_render_mode(DynamicTextFont::RenderMode render_mode) { + _render_mode = render_mode; +} + +//////////////////////////////////////////////////////////////////// +// Function: DynamicTextFont::get_render_mode +// Access: Published +// Description: Returns the way the glyphs on this particular font +// are generated. See set_render_mode(). +//////////////////////////////////////////////////////////////////// +INLINE DynamicTextFont::RenderMode DynamicTextFont:: +get_render_mode() const { + return _render_mode; +} + +//////////////////////////////////////////////////////////////////// +// Function: DynamicTextFont::set_winding_order +// Access: Published +// Description: Specifies an explicitly winding order on this +// particular font. This is only necessary if the +// render_mode is RM_polygon or RM_solid, and only if +// FreeType appears to guess wrong on this font. +// Normally, you should leave this at WO_default. +//////////////////////////////////////////////////////////////////// +INLINE void DynamicTextFont:: +set_winding_order(DynamicTextFont::WindingOrder winding_order) { + _winding_order = winding_order; +} + +//////////////////////////////////////////////////////////////////// +// Function: DynamicTextFont::get_winding_order +// Access: Published +// Description: Returns the winding order set via set_winding_order(). +//////////////////////////////////////////////////////////////////// +INLINE DynamicTextFont::WindingOrder DynamicTextFont:: +get_winding_order() const { + return _winding_order; +} + INLINE ostream & operator << (ostream &out, const DynamicTextFont &dtf) { return out << dtf.get_name(); diff --git a/panda/src/text/dynamicTextFont.cxx b/panda/src/text/dynamicTextFont.cxx index c6402689e4..05d61a006e 100644 --- a/panda/src/text/dynamicTextFont.cxx +++ b/panda/src/text/dynamicTextFont.cxx @@ -20,10 +20,29 @@ #ifdef HAVE_FREETYPE +#undef interface // I don't know where this symbol is defined, but it interferes with FreeType. +#include FT_OUTLINE_H +#include FT_STROKER_H +#include FT_BBOX_H +#ifdef FT_BITMAP_H +#include FT_BITMAP_H +#endif + #include "config_text.h" #include "config_util.h" #include "config_express.h" #include "virtualFileSystem.h" +#include "geomVertexData.h" +#include "geomVertexFormat.h" +#include "geomVertexWriter.h" +#include "geomLinestrips.h" +#include "geomTriangles.h" +#include "renderState.h" +#include "string_utils.h" +#include "triangulator.h" +#include "nurbsCurveEvaluator.h" +#include "nurbsCurveResult.h" +//#include "renderModeAttrib.h" TypeHandle DynamicTextFont::_type_handle; @@ -229,6 +248,51 @@ get_glyph(int character, const TextGlyph *&glyph) { return (glyph_index != 0 && glyph != (DynamicTextGlyph *)NULL); } + +//////////////////////////////////////////////////////////////////// +// Function: DynamicTextFont::string_render_mode +// Access: Public +// Description: Returns the RenderMode value associated with the given +// string representation, or RM_invalid if the string +// does not match any known RenderMode value. +//////////////////////////////////////////////////////////////////// +DynamicTextFont::RenderMode DynamicTextFont:: +string_render_mode(const string &string) { + if (cmp_nocase_uh(string, "texture") == 0) { + return RM_texture; + } else if (cmp_nocase_uh(string, "wireframe") == 0) { + return RM_wireframe; + } else if (cmp_nocase_uh(string, "polygon") == 0) { + return RM_polygon; + } else if (cmp_nocase_uh(string, "extruded") == 0) { + return RM_extruded; + } else if (cmp_nocase_uh(string, "solid") == 0) { + return RM_solid; + } else { + return RM_invalid; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: DynamicTextFont::string_winding_order +// Access: Public +// Description: Returns the WindingOrder value associated with the given +// string representation, or WO_invalid if the string +// does not match any known WindingOrder value. +//////////////////////////////////////////////////////////////////// +DynamicTextFont::WindingOrder DynamicTextFont:: +string_winding_order(const string &string) { + if (cmp_nocase_uh(string, "default") == 0) { + return WO_default; + } else if (cmp_nocase_uh(string, "left") == 0) { + return WO_left; + } else if (cmp_nocase_uh(string, "right") == 0) { + return WO_right; + } else { + return WO_invalid; + } +} + //////////////////////////////////////////////////////////////////// // Function: DynamicTextFont::initialize // Access: Private @@ -253,6 +317,9 @@ initialize() { // require generating mipmaps, but does require hardware support. _anisotropic_degree = text_anisotropic_degree; + _render_mode = text_render_mode; + _winding_order = WO_default; + _preferred_page = 0; } @@ -284,7 +351,7 @@ update_filters() { //////////////////////////////////////////////////////////////////// DynamicTextGlyph *DynamicTextFont:: make_glyph(int character, int glyph_index) { - if (!load_glyph(glyph_index)) { + if (!load_glyph(glyph_index, false)) { return (DynamicTextGlyph *)NULL; } @@ -293,6 +360,79 @@ make_glyph(int character, int glyph_index) { float advance = slot->advance.x / 64.0; + if (_render_mode != RM_texture && + slot->format == ft_glyph_format_outline) { + // Re-stroke the glyph to make it an outline glyph. + /* + FT_Stroker stroker; + FT_Stroker_New(_face->memory, &stroker); + FT_Stroker_Set(stroker, 16 * 16, FT_STROKER_LINECAP_BUTT, + FT_STROKER_LINEJOIN_ROUND, 0); + + FT_Stroker_ParseOutline(stroker, &slot->outline, 0); + + FT_UInt num_points, num_contours; + FT_Stroker_GetCounts(stroker, &num_points, &num_contours); + + FT_Outline border; + FT_Outline_New(_ft_library, num_points, num_contours, &border); + border.n_points = 0; + border.n_contours = 0; + FT_Stroker_Export(stroker, &border); + FT_Stroker_Done(stroker); + + FT_Outline_Done(_ft_library, &slot->outline); + memcpy(&slot->outline, &border, sizeof(border)); + */ + + // Ask FreeType to extract the contours out of the outline + // description. + FT_Outline_Funcs funcs; + memset(&funcs, 0, sizeof(funcs)); + funcs.move_to = (FT_Outline_MoveTo_Func)outline_move_to; + funcs.line_to = (FT_Outline_LineTo_Func)outline_line_to; + funcs.conic_to = (FT_Outline_ConicTo_Func)outline_conic_to; + funcs.cubic_to = (FT_Outline_CubicTo_Func)outline_cubic_to; + + WindingOrder wo = _winding_order; + if (wo == WO_default) { + // If we weren't told an explicit winding order, ask FreeType to + // figure it out. Sometimes it appears to guess wrong. + if (FT_Outline_Get_Orientation(&slot->outline) == FT_ORIENTATION_FILL_RIGHT) { + wo = WO_right; + } else { + wo = WO_left; + } + } + + if (wo != WO_left) { + FT_Outline_Reverse(&slot->outline); + } + + _contours.clear(); + FT_Outline_Decompose(&slot->outline, &funcs, (void *)this); + + PT(DynamicTextGlyph) glyph = + new DynamicTextGlyph(character, advance / _font_pixels_per_unit); + switch (_render_mode) { + case RM_wireframe: + render_wireframe_contours(glyph); + return glyph; + + case RM_polygon: + render_polygon_contours(glyph); + return glyph; + + default: + break; + } + } + + // Render the glyph if necessary. + if (slot->format != ft_glyph_format_bitmap) { + FT_Render_Glyph(slot, FT_RENDER_MODE_NORMAL); + } + if (bitmap.width == 0 || bitmap.rows == 0) { // If we got an empty bitmap, it's a special case. PT(DynamicTextGlyph) glyph = @@ -485,4 +625,305 @@ slot_glyph(int character, int x_size, int y_size) { } } +//////////////////////////////////////////////////////////////////// +// Function: DynamicTextFont::render_wireframe_contours +// Access: Private +// Description: Converts from the _contours list to an actual glyph +// geometry, as a wireframe render. +//////////////////////////////////////////////////////////////////// +void DynamicTextFont:: +render_wireframe_contours(DynamicTextGlyph *glyph) { + PT(GeomVertexData) vdata = new GeomVertexData + (string(), GeomVertexFormat::get_v3(), + Geom::UH_static); + GeomVertexWriter vertex(vdata, InternalName::get_vertex()); + + PT(GeomLinestrips) lines = new GeomLinestrips(Geom::UH_static); + + Contours::const_iterator ci; + for (ci = _contours.begin(); ci != _contours.end(); ++ci) { + const Contour &contour = (*ci); + Points::const_iterator pi; + + for (pi = contour._points.begin(); pi != contour._points.end(); ++pi) { + const LPoint2f &p = (*pi); + vertex.add_data3f(p[0], 0.0f, p[1]); + } + + lines->add_next_vertices(contour._points.size()); + lines->close_primitive(); + } + + glyph->set_geom(vdata, lines, RenderState::make_empty()); + _contours.clear(); +} + +//////////////////////////////////////////////////////////////////// +// Function: DynamicTextFont::render_polygon_contours +// Access: Private +// Description: Converts from the _contours list to an actual glyph +// geometry, as a polygon render. +//////////////////////////////////////////////////////////////////// +void DynamicTextFont:: +render_polygon_contours(DynamicTextGlyph *glyph) { + PT(GeomVertexData) vdata = new GeomVertexData + (string(), GeomVertexFormat::get_v3(), + Geom::UH_static); + GeomVertexWriter vertex(vdata, InternalName::get_vertex()); + + PT(GeomTriangles) tris = new GeomTriangles(Geom::UH_static); + Triangulator t; + + // First, build up the list of vertices, and determine which + // contours are solid and which are holes. + Contours::iterator ci; + for (ci = _contours.begin(); ci != _contours.end(); ++ci) { + Contour &contour = (*ci); + + t.clear_polygon(); + contour._start_vertex = t.get_num_vertices(); + for (size_t i = 0; i < contour._points.size() - 1; ++i) { + const LPoint2f &p = contour._points[i]; + vertex.add_data3f(p[0], 0.0f, p[1]); + int vi = t.add_vertex(p[0], p[1]); + t.add_polygon_vertex(vi); + } + + contour._is_solid = t.is_left_winding(); + } + + // Now go back and generate the actual triangles. + for (ci = _contours.begin(); ci != _contours.end(); ++ci) { + const Contour &contour = (*ci); + + if (contour._is_solid && !contour._points.empty()) { + t.clear_polygon(); + for (size_t i = 0; i < contour._points.size() - 1; ++i) { + t.add_polygon_vertex(contour._start_vertex + i); + } + + // Also add all the holes to each polygon. + Contours::iterator cj; + for (cj = _contours.begin(); cj != _contours.end(); ++cj) { + Contour &hole = (*cj); + if (!hole._is_solid && !hole._points.empty()) { + t.begin_hole(); + for (size_t j = 0; j < hole._points.size() - 1; ++j) { + t.add_hole_vertex(hole._start_vertex + j); + } + } + } + + t.triangulate(); + int num_triangles = t.get_num_triangles(); + for (int ti = 0; ti < num_triangles; ++ti) { + tris->add_vertex(t.get_triangle_v0(ti)); + tris->add_vertex(t.get_triangle_v1(ti)); + tris->add_vertex(t.get_triangle_v2(ti)); + tris->close_primitive(); + } + } + } + + glyph->set_geom(vdata, tris, RenderState::make_empty()); + // glyph->set_geom(vdata, tris, RenderState::make(RenderModeAttrib::make(RenderModeAttrib::M_wireframe))); + _contours.clear(); +} + +//////////////////////////////////////////////////////////////////// +// Function: DynamicTextFont::outline_move_to +// Access: Private, Static +// Description: A callback from FT_Outline_Decompose(). It marks the +// beginning of a new contour. +//////////////////////////////////////////////////////////////////// +int DynamicTextFont:: +outline_move_to(const FT_Vector *to, void *user) { + DynamicTextFont *self = (DynamicTextFont *)user; + + // Convert from 26.6 pixel units to Panda units. + float scale = 1.0f / (64.0f * self->_font_pixels_per_unit); + LPoint2f p = LPoint2f(to->x, to->y) * scale; + + self->_contours.push_back(Contour()); + self->_contours.back()._points.push_back(p); + return 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: DynamicTextFont::outline_line_to +// Access: Private, Static +// Description: A callback from FT_Outline_Decompose(). It marks a +// straight line in the contour. +//////////////////////////////////////////////////////////////////// +int DynamicTextFont:: +outline_line_to(const FT_Vector *to, void *user) { + DynamicTextFont *self = (DynamicTextFont *)user; + nassertr(!self->_contours.empty(), 1); + + // Convert from 26.6 pixel units to Panda units. + float scale = 1.0f / (64.0f * self->_font_pixels_per_unit); + LPoint2f p = LPoint2f(to->x, to->y) * scale; + + self->_contours.back()._points.push_back(p); + return 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: DynamicTextFont::outline_conic_to +// Access: Private, Static +// Description: A callback from FT_Outline_Decompose(). It marks a +// parabolic (3rd-order) Bezier curve in the contour. +//////////////////////////////////////////////////////////////////// +int DynamicTextFont:: +outline_conic_to(const FT_Vector *control, + const FT_Vector *to, void *user) { + DynamicTextFont *self = (DynamicTextFont *)user; + nassertr(!self->_contours.empty(), 1); + const LPoint2f &q = self->_contours.back()._points.back(); + + // Convert from 26.6 pixel units to Panda units. + float scale = 1.0f / (64.0f * self->_font_pixels_per_unit); + + LPoint2f c = LPoint2f(control->x, control->y) * scale; + LPoint2f p = LPoint2f(to->x, to->y) * scale; + + // The NurbsCurveEvaluator will evaluate the Bezier segment for us. + NurbsCurveEvaluator nce; + nce.local_object(); + nce.set_order(3); + nce.reset(3); + nce.set_vertex(0, LVecBase3f(q[0], q[1], 0.0f)); + nce.set_vertex(1, LVecBase3f(c[0], c[1], 0.0f)); + nce.set_vertex(2, LVecBase3f(p[0], p[1], 0.0f)); + + PT(NurbsCurveResult) ncr = nce.evaluate(); + + // Sample it down so that the lines approximate the curve to within + // a "pixel." + ncr->adaptive_sample(1.0f / self->_font_pixels_per_unit); + + int num_samples = ncr->get_num_samples(); + for (int i = 1; i < num_samples; ++i) { + const LPoint3f &p = ncr->get_sample_point(i); + self->_contours.back()._points.push_back(LPoint2f(p[0], p[1])); + } + return 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: DynamicTextFont::outline_cubic_to +// Access: Private, Static +// Description: A callback from FT_Outline_Decompose(). It marks a +// cubic (4th-order) Bezier curve in the contour. +//////////////////////////////////////////////////////////////////// +int DynamicTextFont:: +outline_cubic_to(const FT_Vector *control1, const FT_Vector *control2, + const FT_Vector *to, void *user) { + DynamicTextFont *self = (DynamicTextFont *)user; + nassertr(!self->_contours.empty(), 1); + const LPoint2f &q = self->_contours.back()._points.back(); + + // Convert from 26.6 pixel units to Panda units. + float scale = 1.0f / (64.0f * self->_font_pixels_per_unit); + + LPoint2f c1 = LPoint2f(control1->x, control1->y) * scale; + LPoint2f c2 = LPoint2f(control2->x, control2->y) * scale; + LPoint2f p = LPoint2f(to->x, to->y) * scale; + + // The NurbsCurveEvaluator will evaluate the Bezier segment for us. + NurbsCurveEvaluator nce; + nce.local_object(); + nce.set_order(4); + nce.reset(4); + nce.set_vertex(0, LVecBase3f(q[0], q[1], 0.0f)); + nce.set_vertex(1, LVecBase3f(c1[0], c1[1], 0.0f)); + nce.set_vertex(2, LVecBase3f(c2[0], c2[1], 0.0f)); + nce.set_vertex(3, LVecBase3f(p[0], p[1], 0.0f)); + + PT(NurbsCurveResult) ncr = nce.evaluate(); + + // Sample it down so that the lines approximate the curve to within + // a "pixel." + ncr->adaptive_sample(1.0f / self->_font_pixels_per_unit); + + int num_samples = ncr->get_num_samples(); + for (int i = 1; i < num_samples; ++i) { + const LPoint3f &p = ncr->get_sample_point(i); + self->_contours.back()._points.push_back(LPoint2f(p[0], p[1])); + } + return 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: DynamicTextFont::RenderMode output operator +// Description: +//////////////////////////////////////////////////////////////////// +ostream & +operator << (ostream &out, DynamicTextFont::RenderMode rm) { + switch (rm) { + case DynamicTextFont::RM_texture: + return out << "texture"; + case DynamicTextFont::RM_wireframe: + return out << "wireframe"; + case DynamicTextFont::RM_polygon: + return out << "polygon"; + case DynamicTextFont::RM_extruded: + return out << "extruded"; + case DynamicTextFont::RM_solid: + return out << "solid"; + + case DynamicTextFont::RM_invalid: + return out << "invalid"; + } + + return out << "(**invalid DynamicTextFont::RenderMode(" << (int)rm << ")**)"; +} + +//////////////////////////////////////////////////////////////////// +// Function: DynamicTextFont::RenderMode input operator +// Description: +//////////////////////////////////////////////////////////////////// +istream & +operator >> (istream &in, DynamicTextFont::RenderMode &rm) { + string word; + in >> word; + + rm = DynamicTextFont::string_render_mode(word); + return in; +} + +//////////////////////////////////////////////////////////////////// +// Function: DynamicTextFont::WindingOrder output operator +// Description: +//////////////////////////////////////////////////////////////////// +ostream & +operator << (ostream &out, DynamicTextFont::WindingOrder wo) { + switch (wo) { + case DynamicTextFont::WO_default: + return out << "default"; + case DynamicTextFont::WO_left: + return out << "left"; + case DynamicTextFont::WO_right: + return out << "right"; + + case DynamicTextFont::WO_invalid: + return out << "invalid"; + } + + return out << "(**invalid DynamicTextFont::WindingOrder(" << (int)wo << ")**)"; +} + +//////////////////////////////////////////////////////////////////// +// Function: DynamicTextFont::WindingOrder input operator +// Description: +//////////////////////////////////////////////////////////////////// +istream & +operator >> (istream &in, DynamicTextFont::WindingOrder &wo) { + string word; + in >> word; + + wo = DynamicTextFont::string_winding_order(word); + return in; +} + #endif // HAVE_FREETYPE diff --git a/panda/src/text/dynamicTextFont.h b/panda/src/text/dynamicTextFont.h index fd87be3893..bf11e8739c 100644 --- a/panda/src/text/dynamicTextFont.h +++ b/panda/src/text/dynamicTextFont.h @@ -45,6 +45,34 @@ //////////////////////////////////////////////////////////////////// class EXPCL_PANDA DynamicTextFont : public TextFont, public FreetypeFont { PUBLISHED: + enum RenderMode { + // Each glyph is a single textured rectangle + RM_texture, + + // Each glyph is a lot of line segments + RM_wireframe, + + // Each glyph is a lot of triangles + RM_polygon, + + // a 3-D outline, like a cookie cutter + RM_extruded, + + // combination of RM_extruded and RM_polygon + RM_solid, + + // Returned by string_render_mode() for an invalid match. + RM_invalid, + }; + + enum WindingOrder { + WO_default, + WO_left, + WO_right, + + WO_invalid, + }; + DynamicTextFont(const Filename &font_filename, int face_index = 0); DynamicTextFont(const char *font_data, int data_length, int face_index); virtual ~DynamicTextFont(); @@ -84,6 +112,11 @@ PUBLISHED: INLINE void set_anisotropic_degree(int anisotropic_degree); INLINE int get_anisotropic_degree() const; + INLINE void set_render_mode(RenderMode render_mode); + INLINE RenderMode get_render_mode() const; + INLINE void set_winding_order(WindingOrder winding_order); + INLINE WindingOrder get_winding_order() const; + int get_num_pages() const; DynamicTextPage *get_page(int n) const; @@ -95,6 +128,9 @@ PUBLISHED: public: virtual bool get_glyph(int character, const TextGlyph *&glyph); + static RenderMode string_render_mode(const string &string); + static WindingOrder string_winding_order(const string &string); + private: void initialize(); void update_filters(); @@ -103,6 +139,17 @@ private: void copy_pnmimage_to_texture(const PNMImage &image, DynamicTextGlyph *glyph); DynamicTextGlyph *slot_glyph(int character, int x_size, int y_size); + void render_wireframe_contours(DynamicTextGlyph *glyph); + void render_polygon_contours(DynamicTextGlyph *glyph); + + static int outline_move_to(const FT_Vector *to, void *user); + static int outline_line_to(const FT_Vector *to, void *user); + static int outline_conic_to(const FT_Vector *control, + const FT_Vector *to, void *user); + static int outline_cubic_to(const FT_Vector *control1, + const FT_Vector *control2, + const FT_Vector *to, void *user); + int _texture_margin; float _poly_margin; int _page_x_size, _page_y_size; @@ -111,6 +158,9 @@ private: Texture::FilterType _magfilter; int _anisotropic_degree; + RenderMode _render_mode; + WindingOrder _winding_order; + typedef pvector< PT(DynamicTextPage) > Pages; Pages _pages; int _preferred_page; @@ -127,6 +177,18 @@ private: typedef pvector< PT(DynamicTextGlyph) > EmptyGlyphs; EmptyGlyphs _empty_glyphs; + typedef pvector Points; + + class Contour { + public: + Points _points; + bool _is_solid; + int _start_vertex; + }; + + typedef pvector Contours; + Contours _contours; + public: static TypeHandle get_class_type() { return _type_handle; @@ -149,6 +211,11 @@ private: INLINE ostream &operator << (ostream &out, const DynamicTextFont &dtf); +EXPCL_PANDA ostream &operator << (ostream &out, DynamicTextFont::RenderMode rm); +EXPCL_PANDA istream &operator >> (istream &in, DynamicTextFont::RenderMode &rm); +EXPCL_PANDA ostream &operator << (ostream &out, DynamicTextFont::WindingOrder wo); +EXPCL_PANDA istream &operator >> (istream &in, DynamicTextFont::WindingOrder &wo); + #include "dynamicTextFont.I" #endif // HAVE_FREETYPE diff --git a/panda/src/text/dynamicTextGlyph.cxx b/panda/src/text/dynamicTextGlyph.cxx index 92dbd38c4b..a6f3028572 100644 --- a/panda/src/text/dynamicTextGlyph.cxx +++ b/panda/src/text/dynamicTextGlyph.cxx @@ -168,4 +168,30 @@ make_geom(int bitmap_top, int bitmap_left, float advance, float poly_margin, } +//////////////////////////////////////////////////////////////////// +// 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; +} + + #endif // HAVE_FREETYPE diff --git a/panda/src/text/dynamicTextGlyph.h b/panda/src/text/dynamicTextGlyph.h index b90d37d3ba..d2dc99c33f 100644 --- a/panda/src/text/dynamicTextGlyph.h +++ b/panda/src/text/dynamicTextGlyph.h @@ -53,6 +53,8 @@ public: void make_geom(int top, int left, float advance, float poly_margin, float tex_x_size, float tex_y_size, float font_pixels_per_unit, float tex_pixels_per_unit); + void set_geom(GeomVertexData *vdata, GeomPrimitive *prim, + const RenderState *state); DynamicTextPage *_page; int _geom_count; diff --git a/panda/src/text/textProperties.cxx b/panda/src/text/textProperties.cxx index e2957bc190..bb8662f22e 100644 --- a/panda/src/text/textProperties.cxx +++ b/panda/src/text/textProperties.cxx @@ -289,6 +289,8 @@ load_default_font() { // Loading the compiled-in FreeType font is relatively easy. _default_font = new DynamicTextFont((const char *)default_font_data, default_font_size, 0); + // The compiled-in font seems to confuse FreeType about its winding order. + ((DynamicTextFont *)_default_font.p())->set_winding_order(DynamicTextFont::WO_left); #else // The compiled-in Bam font requires creating a BamFile object to