]> git.donarmstrong.com Git - lilypond.git/blob - lily/bezier-bow.cc
patch::: 1.3.13.jcn1
[lilypond.git] / lily / bezier-bow.cc
1 /*
2   bezier.cc -- implement Bezier and Bezier_bow
3
4   source file of the GNU LilyPond music typesetter
5
6   (c) 1998--1999 Jan Nieuwenhuizen <janneke@gnu.org>
7 */
8
9 #include <math.h>
10 #include "bezier-bow.hh"
11 #include "misc.hh"
12 #include "bezier.hh"
13 #include "dimensions.hh"
14 #include "direction.hh"
15 #include "paper-def.hh"
16 #include "debug.hh"
17 #include "main.hh"
18
19 void
20 flipy (Array<Offset>  &c)
21 {
22   for (int i = c.size (); i--;)
23     c[i][Y_AXIS] = - c[i][Y_AXIS];
24 }
25
26 void
27 rotate (Array<Offset> &c, Real phi)
28 {
29   Offset rot (complex_exp (Offset (0, phi)));
30   for (int i = 0; i < c.size (); i++)
31     c[i] = complex_multiply (rot, c[i]);
32 }
33
34 void
35 translate (Array<Offset> &c, Offset o)
36 {
37   for (int i = 0; i < c.size (); i++)
38     c[i] += o;
39 }
40
41
42 Bezier_bow::Bezier_bow (Paper_def* paper_l)
43 {
44   paper_l_ = paper_l;
45 }
46
47 void
48 Bezier_bow::blow_fit ()
49 {
50   Real x1 = encompass_[0][X_AXIS];
51   Real x2 = encompass_.top ()[X_AXIS];
52
53   Real factor = 1.0;
54   for (int i=1; i < encompass_.size ()-1; i++)
55     {
56       if (encompass_[i][X_AXIS] > x1 && encompass_[i][X_AXIS] < x2)
57         {
58          Real y = curve_.get_other_coordinate (X_AXIS, encompass_[i][X_AXIS]);
59          if (y>0)
60            {
61              Real f = encompass_[i][Y_AXIS] / y;
62              factor = factor >? f;
63            }
64         }
65     }
66
67   curve_.control_[1][Y_AXIS] *= factor;
68   curve_.control_[2][Y_AXIS] *= factor;  
69   return_.control_[1][Y_AXIS] *= factor;
70   return_.control_[2][Y_AXIS] *= factor;
71
72   curve_.check_sanity ();
73 }
74
75 Real
76 Bezier_bow::calc_f (Real height)
77 {
78   transform ();
79   calc_default (height);
80
81   Real dy = check_fit_f ();
82   calc_return (0, 0);
83
84   transform_back ();
85   return dy;
86 }
87
88 void
89 Bezier_bow::calc ()
90 {
91   transform ();
92   calc_controls ();
93   transform_back ();
94 }
95
96
97
98 /*
99   [TODO]
100     * see if it works
101     * document in Documentation/fonts.tex
102  */
103
104 /*
105   Clipping
106
107   This function tries to address two issues:
108     * the tangents of the slur should always point inwards 
109       in the actual slur, i.e.  *after rotating back*.
110
111     * slurs shouldn't be too high 
112       let's try : h <= 1.2 b && h <= 3 staffheight?
113
114   We could calculate the tangent of the bezier curve from
115   both ends going inward, and clip the slur at the point
116   where the tangent (after rotation) points up (or inward
117   with a certain maximum angle).
118   
119   However, we assume that real clipping is not the best
120   answer.  We expect that moving the outer control point up 
121   if the slur becomes too high will result in a nicer slur 
122   after recalculation.
123
124   Knowing that the tangent is the line through the first
125   two control points, we'll clip (move the outer control
126   point upwards) too if the tangent points outwards.
127  */
128
129 bool
130 Bezier_bow::calc_clipping ()
131 {
132   Real clip_height = paper_l_->get_var ("slur_clip_height");
133   Real clip_ratio = paper_l_->get_var ("slur_clip_ratio");
134   Real clip_angle = paper_l_->get_var ("slur_clip_angle");
135
136   Real b = curve_.control_[3][X_AXIS] - curve_.control_[0][X_AXIS];
137   Real clip_h = clip_ratio * b <? clip_height;
138   Real begin_h = curve_.control_[1][Y_AXIS] - curve_.control_[0][Y_AXIS];
139   Real end_h = curve_.control_[2][Y_AXIS] - curve_.control_[3][Y_AXIS];
140   Real begin_dy = 0 >? begin_h - clip_h;
141   Real end_dy = 0 >? end_h - clip_h;
142   
143   Real pi = M_PI;
144   Real begin_alpha = (curve_.control_[1] - curve_.control_[0]).arg () + dir_ * alpha_;
145   Real end_alpha = pi -  (curve_.control_[2] - curve_.control_[3]).arg () - dir_  * alpha_;
146
147   Real max_alpha = clip_angle / 90 * pi / 2;
148   if ((begin_dy < 0) && (end_dy < 0)
149     && (begin_alpha < max_alpha) && (end_alpha < max_alpha))
150     return false;
151
152   transform_back ();
153
154   bool again = true;
155
156   if ((begin_dy > 0) || (end_dy > 0))
157     {
158       Real dy = (begin_dy + end_dy) / 4;
159       dy *= cos (alpha_);
160       encompass_[0][Y_AXIS] += dir_ * dy;
161       encompass_.top ()[Y_AXIS] += dir_ * dy;
162     }
163   else
164     {
165       //ugh
166       Real c = 0.4;
167       if (begin_alpha >= max_alpha)
168         begin_dy = 0 >? c * begin_alpha / max_alpha * begin_h;
169       if (end_alpha >= max_alpha)
170         end_dy = 0 >? c * end_alpha / max_alpha * end_h;
171
172       encompass_[0][Y_AXIS] += dir_ * begin_dy;
173       encompass_.top ()[Y_AXIS] += dir_ * end_dy;
174
175       Offset delta = encompass_.top () - encompass_[0];
176       alpha_ = delta.arg ();
177     }
178
179   transform ();
180
181   return again;
182 }
183
184 void
185 Bezier_bow::calc_controls ()
186 {
187   for (int i = 0; i < 3; i++)
188     {
189       
190       if (i && !calc_clipping ())
191         return;
192
193       /*
194         why do we always recalc from 0?
195         shouldn't calc_f () be used (too), rather than blow_fit () (only)?
196        */
197       calc_default (0);
198       curve_.check_sanity ();
199       if (check_fit_f () > 0)
200         {
201           calc_tangent_controls ();
202           blow_fit ();
203         }
204       else
205         {
206           calc_return (0, 0);
207           return;
208         }
209     }
210 }
211
212 void
213 Bezier_bow::calc_return (Real begin_alpha, Real end_alpha)
214 {
215   Real thick = paper_l_->get_var ("slur_thickness");
216
217   return_.control_[0] = curve_.control_[3];
218   return_.control_[3] = curve_.control_[0];
219
220   return_.control_[1] = curve_.control_[2] - thick * complex_exp (Offset (0, 90 + end_alpha));
221   return_.control_[2] = curve_.control_[1] - thick * complex_exp (Offset (0, 90 - begin_alpha));  
222 }
223
224 static Real const FUDGE = 1e-8;
225
226 /*
227   This function calculates 2 center control points,
228   based on lines through c_0 --> left disturbing
229   and c_3--> right disturbing encompass points.
230   
231   See Documentation/fonts.tex
232  */
233 void
234 Bezier_bow::calc_tangent_controls ()
235 {
236   Real b = curve_.control_[3][X_AXIS];
237   Real h = curve_.control_[1][Y_AXIS];
238   
239
240   Drul_array<Offset> disturb;
241   Drul_array<Real> maxtan;  
242   maxtan[LEFT]  = maxtan[RIGHT] = h/(b/2);
243   disturb[LEFT]  = disturb[RIGHT] =   Offset (b / 2, h);
244
245   for (int i = 1; i < encompass_.size () -1; i++)
246     {
247       Real y= encompass_[i][Y_AXIS];
248       if (y> 0)
249         {
250           Real x = encompass_[i][X_AXIS];
251           
252           Direction d = LEFT;
253           do
254             {
255               // 1 if d == LEFT
256               int k = (1 - d)/2;
257               Real tan = y /  ((1-k)* b - d * x);
258
259               if (tan > maxtan[d])
260                 {
261                   maxtan[d] = tan;
262                   disturb[d] = Offset (x,y);
263                 }
264             }
265           while (flip (&d)!=LEFT);
266         }
267     }
268
269   for (int i = 0; i < encompass_.size (); i++ )
270     h = h >? encompass_[i][Y_AXIS];
271
272   /*
273     The curve will always be under line between curve_.control_0 -> curve_.control_1, so
274     make it extra steep by slur_rc_factor
275   */
276   Real rc_correct = paper_l_->get_var ("slur_rc_factor");
277   
278   Drul_array<Real> angles;
279   Direction d = LEFT;
280   do
281     {
282       maxtan[d] *= -d * rc_correct;
283       angles[d] = atan (maxtan[d]);
284     }
285   while (flip(&d) != LEFT);
286
287   Real rc3 = 0.0;
288
289   /* 
290     if we have two disturbing points, have line through those...
291     in order to get a sane line, make sure points are reasonably far apart
292     X distance must be reasonably(!) big (division)
293    */
294   if (abs (disturb[LEFT][X_AXIS] - disturb[RIGHT][X_AXIS]) > FUDGE)
295     rc3 = (disturb[RIGHT][Y_AXIS] - disturb[LEFT][Y_AXIS]) / (disturb[RIGHT][X_AXIS] - disturb[LEFT][X_AXIS]);
296
297   else
298     rc3 = tan ((angles[LEFT] - angles[RIGHT]) / 2);
299
300
301   // ugh: be less steep
302   rc3 /= 2*rc_correct;
303   
304
305   Real c2 = -maxtan[RIGHT] * curve_.control_[3][X_AXIS];
306
307   // use highest because rc3 is damped.
308   Real maxy = disturb[LEFT][Y_AXIS] >? disturb[RIGHT][Y_AXIS];
309   Real c3 = disturb[LEFT][Y_AXIS] > disturb[RIGHT][Y_AXIS] ?
310     maxy - rc3 * disturb[LEFT][X_AXIS] :
311     maxy - rc3 * disturb[RIGHT][X_AXIS];
312
313   curve_.control_[1][X_AXIS] = c3 / (maxtan[LEFT] - rc3);
314   curve_.control_[1][Y_AXIS] = maxtan[LEFT] * curve_.control_[1][X_AXIS];
315   
316   curve_.control_[2][X_AXIS] = (c3 - c2) / (maxtan[RIGHT] - rc3);
317   curve_.control_[2][Y_AXIS] = maxtan[RIGHT] * curve_.control_[2][X_AXIS] + c2;
318
319
320   curve_.check_sanity();
321   
322   calc_return (angles[LEFT], angles[RIGHT]);
323 }
324
325 /*
326   The maximum amount that the encompass points stick out above the bezier curve.
327  */
328 Real
329 Bezier_bow::check_fit_f () const
330 {
331   Real dy = 0;
332   Real x1 = encompass_[0][X_AXIS];
333   Real x2 = encompass_.top ()[X_AXIS];
334   for (int i = 1; i < encompass_.size () - 1; i++)
335     {
336       Real x = encompass_[i][X_AXIS];
337       if (x1< x&& x < x2)
338         dy = dy >? (encompass_[i][Y_AXIS] - curve_.get_other_coordinate (X_AXIS, x));
339     }
340   return dy;
341 }
342
343
344 void
345 Bezier_bow::set (Array<Offset> points, Direction dir)
346 {
347   dir_ = dir;
348   encompass_ = points;
349 }
350
351 void
352 Bezier_bow::transform ()
353 {
354   origin_ = encompass_[0];
355   translate (encompass_,-origin_);
356
357   Offset delta = encompass_.top () - encompass_[0];
358   alpha_ = delta.arg ();
359
360   rotate (encompass_, -alpha_);
361
362   if (dir_ == DOWN)
363     flipy (encompass_);
364 }
365
366 void
367 Bezier_bow::transform_back ()
368 {
369   if (dir_ == DOWN)
370     {
371       curve_.flip (Y_AXIS);
372       return_.flip (Y_AXIS);
373       flipy (encompass_);
374     }
375
376   curve_.rotate (alpha_);
377   curve_.translate (origin_);
378  
379   return_.rotate (alpha_);
380   return_.translate (origin_);
381  
382   rotate (encompass_,alpha_);
383   translate (encompass_,origin_);
384 }
385
386 /*
387  See Documentation/fonts.tex
388  */
389 void
390 Bezier_bow::calc_default (Real h)
391 {
392   Real pi = M_PI;
393
394   Real height_limit = paper_l_->get_var ("slur_height_limit");
395   Real ratio = paper_l_->get_var ("slur_ratio");
396
397   Real alpha = height_limit * 2.0 / pi;
398   Real beta = pi * ratio / (2.0 * height_limit);
399
400   Offset delta (encompass_.top ()[X_AXIS] 
401     - encompass_[0][X_AXIS], 0);
402
403   Real b = delta.length ();
404   Real indent = alpha * atan (beta * b);
405   Real height = indent + h;
406  
407   curve_.control_ [0] = Offset (0, 0);
408   curve_.control_ [1] = Offset (indent, height);
409   curve_.control_ [2] = Offset (b - indent, height);
410   curve_.control_ [3] = Offset (b, 0);
411 }
412
413