X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=lily%2Fbezier.cc;h=1eaf041b886e8c782efdd802301b0f2ebf78a60a;hb=5457e0162783d5bbcd549857a244d949e93b5ca1;hp=f261d37c28e87fa0d3f7680f539f7b685e8c8d76;hpb=472c212541034e95c30f5a1a6bc99d7f36f15b68;p=lilypond.git diff --git a/lily/bezier.cc b/lily/bezier.cc index f261d37c28..1eaf041b88 100644 --- a/lily/bezier.cc +++ b/lily/bezier.cc @@ -3,433 +3,265 @@ source file of the GNU LilyPond music typesetter - (c) 1998 Jan Nieuwenhuizen + (c) 1998--2007 Jan Nieuwenhuizen */ -#include #include "bezier.hh" +#include "warn.hh" +#include "libc-extension.hh" -#ifndef STANDALONE -#include "direction.hh" -#include "dimen.hh" -#include "paper-def.hh" -#include "debug.hh" -#include "main.hh" -#define SLUR_DOUT if (check_debug && !monitor->silent_b ("Slur")) cout -#else -#define SLUR_DOUT cerr -#endif +Real binomial_coefficient_3[] = { + 1, 3, 3, 1 +}; void -Curve::flipy () +scale (vector *array, Real x, Real y) { - for (int i = 0; i < size (); i++) - (*this)[i].mirror (Y_AXIS); -} - -int -Curve::largest_disturbing () -{ - Real alpha = 0; - int j = 0; - for (int i = 1; i < size (); i++) + for (vsize i = 0; i < array->size (); i++) { - if ((*this)[i].y () > 0) - { - Real phi = (*this)[i].y () / (*this)[i].x (); - if (phi > alpha) - { - alpha = phi; - j = i; - } - } + (*array)[i][X_AXIS] = x * (*array)[i][X_AXIS]; + (*array)[i][Y_AXIS] = y * (*array)[i][Y_AXIS]; } - return j; } void -Curve::rotate (Real phi) +rotate (vector *array, Real phi) { Offset rot (complex_exp (Offset (0, phi))); - for (int i = 1; i < size (); i++) - (*this)[i] = complex_multiply (rot, (*this)[i]); + for (vsize i = 0; i < array->size (); i++) + (*array)[i] = complex_multiply (rot, (*array)[i]); } void -Curve::translate (Offset o) +translate (vector *array, Offset o) { - for (int i = 1; i < size (); i++) - (*this)[i] += o; + for (vsize i = 0; i < array->size (); i++) + (*array)[i] += o; } -Bezier::Bezier () -{ - control_.set_size (4); -} +/* + Formula of the bezier 3-spline -void -Bezier::calc (int steps) -{ - steps = steps >? 10; - curve_.set_size (steps); - Real dt = 1.0 / curve_.size (); - Offset c = 3.0 * (control_[1] - control_[0]); - Offset b = 3.0 * (control_[2] - control_[1]) - c; - Offset a = control_[3] - (control_[0] + c + b); - Real t = 0.0; - for (int i = 0; i < curve_.size (); i++ ) - { - curve_[i] = ((a * t + b) * t + c) * t + control_[0]; - t += dt; - } -} + sum_{j = 0}^3 (3 over j) z_j (1-t)^ (3-j) t^j -void -Bezier::set (Array points) -{ - assert (points.size () == 4); - control_ = points; -} + + A is the axis of X coordinate. +*/ Real -Bezier::y (Real x) +Bezier::get_other_coordinate (Axis a, Real x) const { -// if (x <= curve_[0].x ()) -// return curve_[0].y (); - for (int i = 1; i < curve_.size (); i++ ) + Axis other = Axis ((a +1) % NO_AXES); + vector ts = solve_point (a, x); + + if (ts.size () == 0) { - if (x < curve_[i].x () || (i == curve_.size () - 1)) - { - Offset z1 = curve_[i-1]; - Offset z2 = curve_[i]; - Real multiplier = (x - z2.x ()) / (z1.x () - z2.x ()); - Real y = z1.y () * multiplier + (1.0 - multiplier) z2.y(); - - return y; - } + programming_error ("no solution found for Bezier intersection"); + return 0.0; } - assert (false); - // silly c++ - return 0; -} +#ifdef PARANOID + Offset c = curve_point (ts[0]); + if (fabs (c[a] - x) > 1e-8) + programming_error ("bezier intersection not correct?"); +#endif -Bezier_bow::Bezier_bow (Paper_def* paper_l) -{ - paper_l_ = paper_l; - return_.set_size (4); + return curve_coordinate (ts[0], other); } -void -Bezier_bow::blow_fit () +Real +Bezier::curve_coordinate (Real t, Axis a) const { - Real dy1 = check_fit_f (); - if (!dy1) - return; - - // be careful not to take too big step - Real f = 0.3; - Real h1 = dy1 * f; - control_[1].y () += h1; - control_[2].y () += h1; - return_[1].y () += h1; - return_[2].y () += h1; - - calc_bezier (); - Real dy2 = check_fit_f (); - if (!dy2) - return; - -#ifndef STANDALONE - Real epsilon = paper_l_->rule_thickness (); -#else - Real epsilon = 1.5 * 0.4 PT; -#endif - if (abs (dy2 - dy1) < epsilon) - return; - - /* - Assume - dy = B (h) - with - B (h) = a * h + b; - - Then we get for h : B (h) = 0 - - B(0) = dy1 = a * 0 + b => b = dy1 - B(h1) = dy2 = a * h1 + b => a * f * dy1 + b = dy2 - - => - - a * dy1 / 2 + dy1 = dy2 => a = (dy2 - dy1) / (f * dy1) - */ + Real tj = 1; + Real one_min_tj[4]; + one_min_tj[0] = 1; + for (int i = 1; i < 4; i++) + one_min_tj[i] = one_min_tj[i - 1] * (1 - t); + + Real r = 0.0; + for (int j = 0; j < 4; j++) + { + r += control_[j][a] * binomial_coefficient_3[j] + * tj * one_min_tj[3 - j]; - Real a = (dy2 - dy1) / (f * dy1); - Real b = dy1; - Real h = -b / a; + tj *= t; + } - control_[1].y () += -h1 +h; - control_[2].y () += -h1 +h; - return_[1].y () += -h1 +h; - return_[2].y () += -h1 +h; + return r; } -void -Bezier_bow::calc_bezier () +Offset +Bezier::curve_point (Real t) const { - Real s = sqrt (control_[3].x () * control_[3].x () - + control_[1].y () * control_[2].y ()); -#ifndef STANDALONE - Real internote = paper_l_->internote_f (); -#else - Real internote = STAFFHEIGHT / 8; -#endif - int steps = (int)rint (s / internote); - Bezier::calc (steps); -} - -Real -Bezier_bow::calc_f (Real height) -{ - transform (); - calc_default (height); + Real tj = 1; + Real one_min_tj[4]; + one_min_tj[0] = 1; + for (int i = 1; i < 4; i++) + one_min_tj[i] = one_min_tj[i - 1] * (1 - t); + + Offset o; + for (int j = 0; j < 4; j++) + { + o += control_[j] * binomial_coefficient_3[j] + * tj * one_min_tj[3 - j]; - calc_bezier (); + tj *= t; + } - Real dy = check_fit_f (); - calc_return (0, 0); +#ifdef PARANOID + assert (fabs (o[X_AXIS] - polynomial (X_AXIS).eval (t)) < 1e-8); + assert (fabs (o[Y_AXIS] - polynomial (Y_AXIS).eval (t)) < 1e-8); +#endif - transform_controls_back (); - return dy; + return o; } -void -Bezier_bow::calc () +/* + Cache binom (3, j) t^j (1-t)^{3-j} +*/ +struct Polynomial_cache { + Polynomial terms_[4]; + Polynomial_cache () + { + for (int j = 0; j <= 3; j++) + terms_[j] + = binomial_coefficient_3[j] + * Polynomial::power (j, Polynomial (0, 1)) + * Polynomial::power (3 - j, Polynomial (1, -1)); + } +}; + +static Polynomial_cache poly_cache; + +Polynomial +Bezier::polynomial (Axis a) const { - transform (); - calc_default (0); - calc_bezier (); - - if (check_fit_bo ()) - calc_return (0, 0); - else + Polynomial p (0.0); + Polynomial q; + for (int j = 0; j <= 3; j++) { - calc_controls (); - blow_fit (); + q = poly_cache.terms_[j]; + q *= control_[j][a]; + p += q; } - transform_controls_back (); + return p; } -void -Bezier_bow::calc_return (Real begin_alpha, Real end_alpha) +/** + Remove all numbers outside [0, 1] from SOL +*/ +vector +filter_solutions (vector sol) { -#ifndef STANDALONE - Real thick = 1.8 * paper_l_->rule_thickness (); -#else - Real thick = 1.8 * 0.4 PT; -#endif + for (vsize i = sol.size (); i--;) + if (sol[i] < 0 || sol[i] > 1) + sol.erase (sol.begin () + i); + return sol; +} + +/** + find t such that derivative is proportional to DERIV +*/ +vector +Bezier::solve_derivative (Offset deriv) const +{ + Polynomial xp = polynomial (X_AXIS); + Polynomial yp = polynomial (Y_AXIS); + xp.differentiate (); + yp.differentiate (); - return_[0] = control_[3]; - return_[3] = control_[0]; + Polynomial combine = xp * deriv[Y_AXIS] - yp * deriv [X_AXIS]; - return_[1] = control_[2] - thick * complex_exp (Offset (0, 90 + end_alpha)); - return_[2] = control_[1] - - thick * complex_exp (Offset (0, 90 - begin_alpha)); + return filter_solutions (combine.solve ()); } /* - [TODO] - Document algorithm in: - See Documentation/fonts.tex - */ -void -Bezier_bow::calc_controls () + Find t such that curve_point (t)[AX] == COORDINATE +*/ +vector +Bezier::solve_point (Axis ax, Real coordinate) const { - Offset ijk_p (control_[3].x () / 2, control_[1].y ()); - SLUR_DOUT << "ijk: " << ijk_p.x () << ", " << ijk_p.y () << endl; - - Real default_rc = ijk_p.y () / ijk_p.x (); + Polynomial p (polynomial (ax)); + p.coefs_[0] -= coordinate; - int begin_disturb = encompass_.largest_disturbing (); - Offset begin_p = begin_disturb ? Offset (encompass_[begin_disturb].x (), - encompass_[begin_disturb].y ()) : ijk_p; - Real begin_rc = begin_p.y () / begin_p.x (); - if (default_rc > begin_rc) - { - begin_p = ijk_p; - begin_rc = default_rc; - } - - Curve reversed; - reversed.set_size (encompass_.size ()); - Real b = control_[3].x (); - for (int i = 0; i < encompass_.size (); i++ ) - { - // b 1 0 - // r = - * c - // 0 0 -1 - reversed[i].x () = b - encompass_[encompass_.size () - i - 1].x (); - reversed[i].y () = encompass_[encompass_.size () - i - 1].y (); - } + vector sol (p.solve ()); + return filter_solutions (sol); +} - int end_disturb = reversed.largest_disturbing (); - end_disturb = end_disturb ? encompass_.size () - end_disturb - 1 : 0; - Offset end_p = end_disturb ? Offset (encompass_[end_disturb].x (), - encompass_[end_disturb].y ()) : ijk_p; - Real end_rc = end_p.y () / (control_[3].x () - end_p.x ()); - if (default_rc > end_rc) +/** + Compute the bounding box dimensions in direction of A. +*/ +Interval +Bezier::extent (Axis a) const +{ + int o = (a + 1)%NO_AXES; + Offset d; + d[Axis (o)] = 1.0; + Interval iv; + vector sols (solve_derivative (d)); + sols.push_back (1.0); + sols.push_back (0.0); + for (vsize i = sols.size (); i--;) { - end_p = ijk_p; - end_rc = default_rc; + Offset o (curve_point (sols[i])); + iv.unite (Interval (o[a], o[a])); } - SLUR_DOUT << "begin " << begin_p.x () << ", " << begin_p.y () << endl; - SLUR_DOUT << "end " << end_p.x () << ", " << end_p.y () << endl; - - Real height =control_[1].y (); - for (int i = 0; i < encompass_.size (); i++ ) - height = height >? encompass_[i].y (); - - // emperic computer science: - // * tangents somewhat steeper than minimal line - Real rc_correct = 2.4; - - begin_rc *= rc_correct; - end_rc *= rc_correct; - Real rc1 = begin_rc; - Real rc2 = -end_rc; - - Real begin_alpha = atan (begin_rc); - Real end_alpha = atan (-end_rc); - Real theta = (begin_alpha - end_alpha) / 2; - -#ifndef STANDALONE - Real internote = paper_l_->internote_f (); -#else - Real internote = STAFFHEIGHT / 8; -#endif - Real epsilon = internote / 5; - - // if we have two disturbing points, have height line through those... - if (!((abs (begin_p.x () - end_p.x ()) < epsilon) - && (abs (begin_p.y () - end_p.y ()) < epsilon))) - theta = atan (end_p.y () - begin_p.y ()) / (end_p.x () - begin_p.x ()); - - Real rc3 = tan (theta); - // ugh: be less steep - rc3 /= 2*rc_correct; - - Real c2 = -rc2 * control_[3].x (); - Real c3 = begin_p.y () > end_p.y () ? begin_p.y () - - rc3 * begin_p.x () : end_p.y () - rc3 * end_p.x (); - - SLUR_DOUT << "y1 = " << rc1 << " x + 0" << endl; - SLUR_DOUT << "y2 = " << rc2 << " x + " << c2 << endl; - SLUR_DOUT << "y3 = " << rc3 << " x + " << c3 << endl; - control_[1].x () = c3 / (rc1 - rc3); - control_[1].y () = rc1 * control_[1].x (); - control_[2].x () = (c3 - c2) / (rc2 - rc3); - SLUR_DOUT << "c2.x () = " << control_[2].x () << endl; - SLUR_DOUT << "(c3 - c2) = " << (c3 - c2) << endl; - SLUR_DOUT << "(rc2 - rc3) = " << (rc2 - rc3) << endl; - control_[2].y () = rc2 * control_[2].x () + c2; - SLUR_DOUT << "c2.y ()" << control_[2].y () << endl; - - calc_return (begin_alpha, end_alpha); + return iv; } -bool -Bezier_bow::check_fit_bo () +Interval +Bezier::control_point_extent (Axis a) const { - for (int i = 1; i < encompass_.size () - 1; i++) - if (encompass_[i].y () > y (encompass_[i].x ())) - return false; - return true; + Interval ext; + for (int i = CONTROL_COUNT; i--;) + ext.add_point (control_[i][a]); + + return ext; } -Real -Bezier_bow::check_fit_f () + +/** + Flip around axis A +*/ +void +Bezier::scale (Real x, Real y) { - Real dy = 0; - for (int i = 1; i < encompass_.size () - 1; i++) - dy = dy >? (encompass_[i].y () - y (encompass_[i].x ())); - return dy; + for (int i = CONTROL_COUNT; i--;) + { + control_[i][X_AXIS] = x * control_[i][X_AXIS]; + control_[i][Y_AXIS] = y * control_[i][Y_AXIS]; + } } void -Bezier_bow::set (Array points, int dir) +Bezier::rotate (Real phi) { - dir_ = dir; - encompass_ = points; + Offset rot (complex_exp (Offset (0, phi))); + for (int i = 0; i < CONTROL_COUNT; i++) + control_[i] = complex_multiply (rot, control_[i]); } void -Bezier_bow::transform () +Bezier::translate (Offset o) { - origin_ = encompass_[0]; - encompass_.translate (-origin_); - - Offset delta = encompass_[encompass_.size () - 1] - encompass_[0]; - - alpha_ = delta.arg (); - encompass_.rotate (-alpha_); - - if (dir_ == DOWN) - encompass_.flipy (); + for (int i = 0; i < CONTROL_COUNT; i++) + control_[i] += o; } void -Bezier_bow::transform_controls_back () +Bezier::assert_sanity () const { - // silly name; let's transform encompass back too - // to allow recalculation without re-set()ting encompass array - if (dir_ == DOWN) - { - control_.flipy (); - return_.flipy (); - encompass_.flipy (); - } - - control_.rotate (alpha_); - control_.translate (origin_); - - return_.rotate (alpha_); - return_.translate (origin_); - - encompass_.rotate (alpha_); - encompass_.translate (origin_); + for (int i = 0; i < CONTROL_COUNT; i++) + assert (!isnan (control_[i].length ()) + && !isinf (control_[i].length ())); } -/* - See Documentation/fonts.tex - */ void -Bezier_bow::calc_default (Real h) +Bezier::reverse () { - Real pi = M_PI; -#ifndef STANDALONE - Real staffsize_f = paper_l_->get_var ("barsize"); -#else - Real staffsize_f = STAFFHEIGHT; -#endif - - Real height_limit = staffsize_f; - Real ratio = 1.0/3.0; - - Real alpha = height_limit * 2.0 / pi; - Real beta = pi * ratio / (2.0 * height_limit); - - Offset delta (encompass_[encompass_.size () - 1].x () - - encompass_[0].x (), 0); - Real b = delta.length (); - Real indent = alpha * atan (beta * b); - Real height = indent + h; - - Array control; - control.push (Offset (0, 0)); - control.push (Offset (indent, height)); - control.push (Offset (b - indent, height)); - control.push (Offset (b, 0)); - Bezier::set (control); + Bezier b2; + for (int i = 0; i < CONTROL_COUNT; i++) + b2.control_[CONTROL_COUNT - i - 1] = control_[i]; + *this = b2; } -