2 simple-spacer.cc -- implement Simple_spacer
4 source file of the GNU LilyPond music typesetter
6 (c) 1999--2004 Han-Wen Nienhuys <hanwen@cs.uu.nl>
9 - add support for different stretch/shrink constants?
14 #include <libc-extension.hh> // isinf
16 #include "simple-spacer.hh"
17 #include "paper-column.hh"
21 #include "column-x-positions.hh"
22 #include "spaceable-grob.hh"
23 #include "dimensions.hh"
27 A simple spacing constraint solver. The approach:
29 Stretch the line uniformly until none of the constraints (rods)
30 block. It then is very wide.
32 Compress until the next constraint blocks,
34 Mark the springs over the constrained part to be non-active.
36 Repeat with the smaller set of non-active constraints, until all
37 constraints blocked, or until the line is as short as desired.
39 This is much simpler, and much much faster than full scale
40 Constrained QP. On the other hand, a situation like this will not
41 be typeset as dense as possible, because
44 veryveryverylongsyllable2 veryveryverylongsyllable2
45 " "4 veryveryverylongsyllable2 syllable4
48 can be further compressed to
52 veryveryverylongsyllable2 veryveryverylongsyllable2
53 " "4 veryveryverylongsyllable2 syllable4
56 Perhaps this is not a bad thing, because the 1st looks better anyway. */
59 Simple_spacer::Simple_spacer ()
62 Give an extra penalty for compression. Needed to avoid compressing
68 default_space_ = 20 PT;
72 Simple_spacer::add_rod (int l, int r, Real dist)
74 if (isinf (dist) || isnan (dist))
76 programming_error ("Weird minimum distance. Ignoring");
80 Real c = range_stiffness (l,r);
84 If a spring is fixed, we have to do something here:
85 we let the rod override the spring.
88 for (int i = l ; i < r; i++)
89 total_dist += springs_[i].ideal_;
91 if (total_dist < dist)
92 for (int i = l ; i < r; i++)
93 springs_[i].ideal_ *= dist/total_dist;
98 Real d = range_ideal_len (l,r);
99 Real block_stretch = dist - d;
101 Real block_force = c * block_stretch;
102 force_ = force_ >? block_force;
104 for (int i=l; i < r; i++)
105 springs_[i].block_force_ = block_force >?
106 springs_[i].block_force_ ;
110 Simple_spacer::range_ideal_len (int l, int r) const
113 for (int i=l; i < r; i++)
114 d += springs_[i].ideal_;
119 Simple_spacer::range_stiffness (int l, int r) const
122 for (int i=l; i < r; i++)
124 if (springs_[i].is_active_)
125 den += 1 / springs_[i].hooke_;
132 Simple_spacer::active_blocking_force () const
134 Real bf = - infinity_f;
135 for (int i=0; i < springs_.size (); i++)
136 if (springs_[i].is_active_)
138 bf = bf >? springs_[i].block_force_;
144 Simple_spacer::active_springs_stiffness () const
146 return range_stiffness (0, springs_.size ());
150 Simple_spacer::set_active_states ()
152 /* float comparison is safe, since force is only copied. */
153 for (int i=0 ; i <springs_.size (); i++)
154 if (springs_[i].is_active_
155 && springs_[i].block_force_ >= force_)
157 springs_[i].is_active_ = false;
163 Simple_spacer::configuration_length () const
166 for (int i=0; i < springs_.size (); i++)
167 l += springs_[i].length (force_);
173 Simple_spacer::is_active () const
175 return active_count_;
179 Simple_spacer::my_solve_linelen ()
183 force_ = active_blocking_force ();
184 Real conf = configuration_length ();
186 if (conf < line_len_)
188 force_ += (line_len_ - conf) * active_springs_stiffness ();
192 set_active_states ();
198 Simple_spacer::my_solve_natural_len ()
202 force_ = active_blocking_force () >? 0.0;
204 if (force_ < 1e-8) // ugh.,
207 set_active_states ();
211 LY_DEFINE(ly_solve_spring_rod_problem, "ly:solve-spring-rod-problem",
212 4, 1, 0, (SCM springs, SCM rods, SCM length, SCM ragged),
213 "Solve a spring and rod problem for @var{count} objects, that "
214 "are connected by @var{count-1} springs, and an arbitrary number of rods "
215 "Springs have the format (ideal, hooke) and rods (idx1, idx2, distance) "
216 "@var{length} is a number, @var{ragged} a boolean "
217 "Return: a list containing the force (#f for non-satisfied constraints)"
218 "followed by positions of the objects."
221 SCM_ASSERT_TYPE (scm_ilength (springs) >= 0, springs, SCM_ARG1, __FUNCTION__, "list of springs");
222 SCM_ASSERT_TYPE (scm_ilength (rods) >= 0, rods, SCM_ARG2, __FUNCTION__, "list of rods");
223 SCM_ASSERT_TYPE (scm_is_number (length) || length == SCM_BOOL_F,
224 length, SCM_ARG3, __FUNCTION__, "number or #f");
226 bool is_ragged = ragged == SCM_BOOL_T;
227 Simple_spacer spacer;
228 for (SCM s = springs; ly_c_pair_p (s); s = ly_cdr (s))
230 Real ideal = scm_to_double (ly_caar (s));
231 Real hooke = scm_to_double (ly_cadar (s));
233 spacer.add_spring (ideal, hooke);
236 for (SCM s = rods; ly_c_pair_p (s); s = ly_cdr (s))
238 SCM entry = ly_car (s);
239 int l = scm_to_int (ly_car (s));
240 int r = scm_to_int (ly_cadr (s));
243 Real distance = scm_to_double (ly_car (s));
244 spacer.add_rod (l, r, distance);
248 spacer.my_solve_natural_len ();
251 spacer.line_len_ = scm_to_double (length);
253 spacer.my_solve_linelen ();
258 for (int i = 0; i < spacer.springs_.size(); i++)
260 Real l = spacer.springs_[i].length ((is_ragged) ? 0.0 : spacer.force_);
261 posns.push (posns.top() + l);
264 SCM force_return = scm_from_double (spacer.force_);
267 Real len = posns.top ();
268 if (spacer.line_len_ - len >= 0)
269 force_return = scm_from_double ((spacer.line_len_ - len)
270 * spacer.active_springs_stiffness ());
272 force_return = SCM_BOOL_F;
276 for (int i = posns.size(); i--;)
278 retval = scm_cons (scm_from_double (posns[i]), retval);
281 retval = scm_cons (force_return, retval);
286 /****************************************************************/
288 Spring_description::Spring_description ()
298 Spring_description::is_sane () const
300 return (hooke_ > 0) && !isinf (ideal_) && !isnan (ideal_);
304 Spring_description::length (Real f) const
308 return ideal_ + f / hooke_ ;
310 /****************************************************************/
315 TODO: should a add penalty for widely varying spring forces (caused
324 The ## forces the notes apart; we shouldn't allow the O's to touch
329 Simple_spacer_wrapper::solve (Column_x_positions *positions, bool ragged)
332 spacer_->my_solve_natural_len ();
334 spacer_->my_solve_linelen ();
336 positions->force_ = spacer_->force_;
339 We used to have a penalty for compression, no matter what, but that
340 fucked up wtk1-fugue2 (taking 3 full pages.)
342 positions->config_.push (spacer_->indent_);
343 for (int i=0; i < spacer_->springs_.size (); i++)
345 Real l = spacer_->springs_[i].length ((ragged) ? 0.0 : spacer_->force_);
346 positions->config_.push (positions->config_.top () + l);
348 we have l>= 0 here, up to rounding errors
353 For raggedright, we must have a measure of music density: this is
354 to prevent lots of short lines (which all have force = 0).
358 Real len = positions->config_.top ();
359 if (spacer_->line_len_ - len >= 0)
360 positions->force_ = ((spacer_->line_len_ - len)
361 * spacer_->active_springs_stiffness ());
364 positions->force_ = 0.0;
366 Don't go past end-of-line in ragged right.
368 positions->satisfies_constraints_ = false;
373 positions->cols_ = spaced_cols_;
374 positions->loose_cols_ = loose_cols_;
375 positions->satisfies_constraints_ =
376 positions->satisfies_constraints_ && spacer_->is_active ();
379 Check if breaking constraints are met.
381 bool break_satisfy = true;
382 int sz = positions->cols_.size ();
383 for (int i = sz; i--; )
385 SCM p = positions->cols_[i]->get_property ( "penalty");
386 if (ly_c_number_p (p))
388 if (ly_scm2double (p) < -9999)
389 break_satisfy = break_satisfy && (i == 0 || i == sz -1);
390 if (ly_scm2double (p) > 9999)
391 break_satisfy = break_satisfy && !(i == 0 || i == sz -1);
396 positions->satisfies_constraints_ =
397 positions->satisfies_constraints_ && break_satisfy;
401 Simple_spacer::add_spring (Real ideal, Real hooke)
403 Spring_description desc;
407 if (!desc.is_sane ())
409 programming_error ("Insane spring found. Setting to unit spring.");
417 desc.is_active_ = false;
424 desc.block_force_ = - desc.hooke_ * desc.ideal_; // block at distance 0
428 springs_.push (desc);
432 Simple_spacer_wrapper::add_columns (Link_array<Grob> const &icols)
434 Link_array<Grob> cols (icols);
436 for (int i = cols.size (); i--;)
437 if (ly_c_pair_p (cols[i]->get_property ("between-cols")))
439 loose_cols_.push (cols[i]);
444 for (int i=0; i < cols.size () - 1; i++)
446 Spring_smob *spring = 0;
448 for (SCM s = cols[i]->get_property ("ideal-distances");
449 !spring && ly_c_pair_p (s);
452 Spring_smob *sp = unsmob_spring (ly_car (s));
455 if (sp->other_ == cols[i+1])
460 programming_error (_f ("No spring between column %d and next one",
461 Paper_column::get_rank (cols[i])
464 Real ideal = (spring) ? spring->distance_ : spacer_->default_space_;
465 Real hooke = (spring) ? spring->strength_ : 1.0;
467 spacer_->add_spring (ideal, hooke);
470 for (int i=0; i < cols.size () - 1; i++)
472 for (SCM s = Spaceable_grob::get_minimum_distances (cols[i]);
473 ly_c_pair_p (s); s = ly_cdr (s))
475 Grob * other = unsmob_grob (ly_caar (s));
476 int oi = cols.find_index (other);
479 spacer_->add_rod (i, oi, ly_scm2double (ly_cdar (s)));
485 Simple_spacer_wrapper::Simple_spacer_wrapper ()
487 spacer_ = new Simple_spacer ();
490 Simple_spacer_wrapper::~Simple_spacer_wrapper ()
496 Simple_spacer_wrapper::Simple_spacer_wrapper (Simple_spacer_wrapper const&)