Line data Source code
1 : // Copyright (c) 2021 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/ftmesh
4 : // contact@m2osw.com
5 : //
6 : // This program is free software; you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation; either version 2 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License along
17 : // with this program; if not, write to the Free Software Foundation, Inc.,
18 : // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 :
20 : /** \file
21 : * \brief Implementation of the ftpolygon class.
22 : *
23 : * The FreeType library gives us a list of tagged vertices which we need
24 : * to transform in a simple polygon. Then we can transform that polygon
25 : * to a mesh using the glut.
26 : *
27 : * \private
28 : */
29 :
30 : // self
31 : //
32 : #include <ftmesh/polygon.h>
33 :
34 :
35 : // C++ lib
36 : //
37 : #include <algorithm>
38 : #include <iostream>
39 :
40 :
41 : // last include
42 : //
43 : #include <snapdev/poison.h>
44 :
45 :
46 :
47 :
48 :
49 : namespace ftmesh
50 : {
51 :
52 :
53 : constexpr unsigned int const BEZIER_STEPS = 5;
54 :
55 :
56 :
57 :
58 :
59 11 : polygon::polygon(
60 : FT_Vector * contour
61 : , char * tags
62 11 : , unsigned int n)
63 : {
64 11 : if(n < 3)
65 : {
66 0 : throw std::logic_error("the polygon constructor expects at least 3 vectors in the contour");
67 : }
68 :
69 11 : point prev;
70 11 : point cur(contour[n - 1].x, contour[n - 1].y); // we know n >= 3
71 11 : point next(contour[0].x, contour[0].y);
72 :
73 11 : double olddir(0.0);
74 11 : double dir((next - cur).angle());
75 11 : double angle(0.0);
76 :
77 : // see http://freetype.sourceforge.net/freetype2/docs/glyphs/glyphs-6.html
78 : // for a full description of FreeType tags
79 : //
80 170 : for(unsigned int i(0); i < n; ++i)
81 : {
82 159 : prev = cur;
83 159 : cur = next;
84 159 : int const next_pos(i + 1 == n ? 0 : i + 1);
85 159 : next = point(contour[next_pos].x, contour[next_pos].y);
86 159 : olddir = dir;
87 159 : dir = (next - cur).angle();
88 :
89 : // compute our path new direction.
90 : //
91 159 : double t(dir - olddir);
92 159 : if(t < -M_PI)
93 : {
94 9 : t += 2 * M_PI;
95 : }
96 159 : if(t > M_PI)
97 : {
98 18 : t -= 2 * M_PI;
99 : }
100 159 : angle += t;
101 :
102 : // only process point tags we know.
103 : //
104 159 : if(n < 2
105 159 : || FT_CURVE_TAG(tags[i]) == FT_CURVE_TAG_ON)
106 : {
107 101 : add_point(cur);
108 : }
109 58 : else if(FT_CURVE_TAG(tags[i]) == FT_CURVE_TAG_CONIC)
110 : {
111 58 : point prev2(prev);
112 58 : point next2(next);
113 :
114 : // previous point is either the real previous point (an "on"
115 : // point), or the midpoint between the current one and the
116 : // previous "conic off" point.
117 : //
118 58 : if(FT_CURVE_TAG(tags[(i - 1 + n) % n]) == FT_CURVE_TAG_CONIC)
119 : {
120 29 : prev2 = (cur + prev) * 0.5;
121 29 : add_point(prev2);
122 : }
123 :
124 : // next point is either the real next point or the midpoint.
125 : //
126 58 : if(FT_CURVE_TAG(tags[(i + 1) % n]) == FT_CURVE_TAG_CONIC)
127 : {
128 29 : next2 = (cur + next) * 0.5;
129 : }
130 :
131 58 : evaluate_quadratic_curve(
132 : prev2
133 : , cur
134 : , next2);
135 : }
136 0 : else if(FT_CURVE_TAG(tags[i ]) == FT_CURVE_TAG_CUBIC
137 0 : && FT_CURVE_TAG(tags[(i + 1) % n]) == FT_CURVE_TAG_CUBIC)
138 : {
139 0 : int const after_next_pos((i + 2) % n);
140 0 : point const after_next(
141 0 : contour[after_next_pos].x
142 0 : , contour[after_next_pos].y);
143 0 : evaluate_cubic_curve(
144 : prev
145 : , cur
146 : , next
147 : , after_next);
148 : }
149 : }
150 :
151 : // if final angle is positive (+2PI), it's an anti-clockwise polygon,
152 : // otherwise (-2PI) it's clockwise.
153 : //
154 11 : f_clockwise = angle < 0.0;
155 11 : }
156 :
157 :
158 1475 : std::size_t polygon::size() const
159 : {
160 1475 : return f_points.size();
161 : }
162 :
163 :
164 968 : point const & polygon::at(int idx) const
165 : {
166 : // idx is often used with +1, +2 or -1
167 : //
168 968 : if(idx >= static_cast<int>(size()))
169 : {
170 6 : idx -= size();
171 : }
172 962 : else if(idx < 0)
173 : {
174 0 : idx += size();
175 : }
176 968 : return f_points.at(idx);
177 : }
178 :
179 :
180 58 : void polygon::evaluate_quadratic_curve(
181 : point const & a
182 : , point const & b
183 : , point const & c)
184 : {
185 290 : for(unsigned int i(1); i < BEZIER_STEPS; i++)
186 : {
187 232 : double const t(static_cast<double>(i) / static_cast<double>(BEZIER_STEPS));
188 232 : double const nt(1.0 - t);
189 232 : point const u(a * nt + b * t);
190 232 : point const v(b * nt + c * t);
191 232 : add_point(u * nt + v * t);
192 : }
193 58 : }
194 :
195 :
196 0 : void polygon::evaluate_cubic_curve(
197 : point const & a
198 : , point const & b
199 : , point const & c
200 : , point const & d)
201 : {
202 0 : for(unsigned int i = 0; i < BEZIER_STEPS; i++)
203 : {
204 0 : double const t(static_cast<double>(i) / static_cast<double>(BEZIER_STEPS));
205 0 : double const nt(1.0 - t);
206 0 : point const u(a * nt + b * t);
207 0 : point const v(b * nt + c * t);
208 0 : point const w(c * nt + d * t);
209 0 : point const m(u * nt + v * t);
210 0 : point const n(v * nt + w * t);
211 0 : add_point(m * nt + n * t);
212 : }
213 0 : }
214 :
215 :
216 362 : void polygon::add_point(point const & p)
217 : {
218 : // try to avoid duplicates as it doesn't play well with glut tessellation
219 : //
220 : // HOWEVER, the current algorithm prevents points from going through
221 : // the starting point again, which I think is a mistake, that could
222 : // happen in the middle of the contour (but that's probably very rare)
223 : //
224 724 : if(f_points.empty()
225 362 : || (p != f_points.front() && p != f_points.back()))
226 : {
227 362 : f_points.push_back(p);
228 362 : if(p.is_left_of(f_leftmost))
229 : {
230 115 : f_leftmost = p;
231 : }
232 : }
233 362 : }
234 :
235 :
236 11 : point const & polygon::leftmost() const
237 : {
238 11 : return f_leftmost;
239 : }
240 :
241 :
242 11 : void polygon::apply_parity(int parity)
243 : {
244 : // contour orientation reversed?
245 : //
246 : // TBD: this would be important if we were to use the outset,
247 : // I don't think it is for calculating the mesh
248 : //
249 11 : if((parity & 1) == 0 ^ f_clockwise)
250 : {
251 0 : std::reverse(f_points.begin(), f_points.end());
252 0 : f_clockwise = !f_clockwise;
253 : }
254 11 : }
255 :
256 :
257 :
258 6 : } // namespace ftmesh
259 : // vim: ts=4 sw=4 et
|