2 collision.cc -- implement Collision
4 source file of the GNU LilyPond music typesetter
6 (c) 1997--2005 Han-Wen Nienhuys <hanwen@cs.uu.nl>
9 #include "note-collision.hh"
14 #include "note-column.hh"
15 #include "note-head.hh"
16 #include "rhythmic-head.hh"
17 #include "output-def.hh"
18 #include "axis-group-interface.hh"
20 #include "side-position-interface.hh"
21 #include "dot-column.hh"
23 MAKE_SCHEME_CALLBACK (Note_collision_interface
, force_shift_callback
, 2);
26 Note_collision_interface::force_shift_callback (SCM element_smob
, SCM axis
)
28 Grob
*me
= unsmob_grob (element_smob
);
29 Axis a
= (Axis
) scm_to_int (axis
);
32 me
= me
->get_parent (a
);
34 if (! to_boolean (me
->get_property ("positioning-done")))
36 me
->set_property ("positioning-done", SCM_BOOL_T
);
40 return scm_make_real (0.0);
44 check_meshing_chords (Grob
*me
,
45 Drul_array
<Array
<Real
> > *offsets
,
46 Drul_array
<Array
<Slice
> > const &extents
,
47 Drul_array
<Link_array
<Grob
> > const &clash_groups
)
50 if (!extents
[UP
].size () || ! extents
[DOWN
].size ())
53 Grob
*cu
= clash_groups
[UP
][0];
54 Grob
*cd
= clash_groups
[DOWN
][0];
56 /* Every note column should have a stem, but avoid a crash. */
57 if (!Note_column::get_stem (cu
) || !Note_column::get_stem (cd
))
60 Grob
*nu
= Note_column::first_head (cu
);
61 Grob
*nd
= Note_column::first_head (cd
);
63 Array
<int> ups
= Stem::note_head_positions (Note_column::get_stem (cu
));
64 Array
<int> dps
= Stem::note_head_positions (Note_column::get_stem (cd
));
66 /* Too far apart to collide. */
67 if (ups
[0] > dps
.top () + 1)
70 // FIXME: what's this?
71 bool merge_possible
= (ups
[0] >= dps
[0]) && (ups
.top () >= dps
.top ());
73 /* Do not merge notes typeset in different style. */
74 if (!ly_c_equal_p (nu
->get_property ("style"),
75 nd
->get_property ("style")))
76 merge_possible
= false;
78 int upball_type
= Note_head::get_balltype (nu
);
79 int dnball_type
= Note_head::get_balltype (nd
);
81 /* Do not merge whole notes (or longer, like breve, longa, maxima). */
82 if (merge_possible
&& (upball_type
<= 0 || dnball_type
<= 0))
83 merge_possible
= false;
86 && Rhythmic_head::dot_count (nu
) != Rhythmic_head::dot_count (nd
)
87 && !to_boolean (me
->get_property ("merge-differently-dotted")))
88 merge_possible
= false;
90 /* Can only merge different heads if merge-differently-headed is
93 && upball_type
!= dnball_type
94 && !to_boolean (me
->get_property ("merge-differently-headed")))
95 merge_possible
= false;
97 /* Should never merge quarter and half notes, as this would make
98 them indistinguishable. */
100 && ((Rhythmic_head::duration_log (nu
) == 1
101 && Rhythmic_head::duration_log (nd
) == 2)
102 || (Rhythmic_head::duration_log (nu
) == 2
103 && Rhythmic_head::duration_log (nd
) == 1)))
104 merge_possible
= false;
107 this case (distant half collide),
114 the noteheads may be closer than this case (close half collide)
125 /* TODO: filter out the 'o's in this configuration, since they're no
126 part in the collision.
135 bool close_half_collide
= false;
136 bool distant_half_collide
= false;
137 bool full_collide
= false;
140 while (i
< ups
.size () && j
< dps
.size ())
142 if (abs (ups
[i
] - dps
[j
]) == 1)
144 merge_possible
= false;
146 close_half_collide
= true;
148 distant_half_collide
= true;
150 else if (ups
[i
] == dps
[j
])
152 else if (ups
[i
] > dps
[0] && ups
[i
] < dps
.top ())
153 merge_possible
= false;
154 else if (dps
[j
] > ups
[0] && dps
[j
] < ups
.top ())
155 merge_possible
= false;
159 else if (ups
[i
] > dps
[j
])
168 full_collide
= full_collide
|| (close_half_collide
169 && distant_half_collide
);
171 Drul_array
<Real
> center_note_shifts
;
172 center_note_shifts
[LEFT
] = 0.0;
173 center_note_shifts
[RIGHT
] = 0.0;
175 Real shift_amount
= 1;
177 bool touch
= (ups
[0] >= dps
.top ());
181 /* For full collisions, the right hand head may obscure dots, so
182 make sure the dotted heads go to the right. */
183 bool stem_to_stem
= false;
185 if (Rhythmic_head::dot_count (nu
) > Rhythmic_head::dot_count (nd
))
187 else if (Rhythmic_head::dot_count (nu
) < Rhythmic_head::dot_count (nd
))
194 /* If possible, don't wipe any heads. Else, wipe shortest head,
195 or head with smallest amount of dots. Note: when merging
196 different heads, dots on the smaller one disappear. */
198 Grob
*dot_wipe_head
= nu
;
200 if (upball_type
== dnball_type
)
202 if (Rhythmic_head::dot_count (nd
) < Rhythmic_head::dot_count (nu
))
207 else if (Rhythmic_head::dot_count (nd
) > Rhythmic_head::dot_count (nu
))
217 else if (dnball_type
> upball_type
)
222 else if (dnball_type
< upball_type
)
230 if (Grob
*d
= unsmob_grob (dot_wipe_head
->get_property ("dot")))
234 if (wipe_ball
&& wipe_ball
->is_live ())
236 wipe_ball
->set_property ("transparent", SCM_BOOL_T
);
237 wipe_ball
->set_property ("stencil", SCM_EOL
);
240 /* TODO: these numbers are magic; should devise a set of grob props
241 to tune this behavior. */
242 else if (stem_to_stem
)
243 shift_amount
= -abs (shift_amount
) * 0.65;
244 else if (close_half_collide
&& !touch
)
245 shift_amount
*= 0.52;
246 else if (distant_half_collide
&& !touch
)
248 else if (distant_half_collide
|| close_half_collide
|| full_collide
)
252 else if (Rhythmic_head::dot_count (nu
) || Rhythmic_head::dot_count (nd
))
255 shift_amount
*= 0.17;
257 /* For full or close half collisions, the right hand head may
258 obscure dots. Move dots to the right. */
259 if (abs (shift_amount
) > 1e-6
260 && Rhythmic_head::dot_count (nd
) > Rhythmic_head::dot_count (nu
)
261 && (full_collide
|| close_half_collide
))
263 Grob
*d
= unsmob_grob (nd
->get_property ("dot"));
264 Grob
*parent
= d
->get_parent (X_AXIS
);
274 the . is put right of o which is erroneous o force-shifted
277 if (Dot_column::has_interface (parent
))
278 Side_position_interface::add_support (parent
, nu
);
284 for (int i
= 0; i
< clash_groups
[d
].size (); i
++)
285 (*offsets
)[d
][i
] += d
* shift_amount
;
287 while ((flip (&d
)) != UP
);
291 Note_collision_interface::do_shifts (Grob
*me
)
293 Drul_array
<Link_array
<Grob
> > cg
= get_clash_groups (me
);
295 SCM
autos (automatic_shift (me
, cg
));
296 SCM
hand (forced_shift (me
));
305 wid
= Note_column::first_head (h
)->extent (h
, X_AXIS
).length ();
308 while (flip (&d
) != UP
);
310 Link_array
<Grob
> done
;
311 Real left_most
= 1e6
;
314 for (; scm_is_pair (hand
); hand
= scm_cdr (hand
))
316 Grob
*s
= unsmob_grob (scm_caar (hand
));
317 Real amount
= scm_to_double (scm_cdar (hand
)) * wid
;
320 amounts
.push (amount
);
321 if (amount
< left_most
)
324 for (; scm_is_pair (autos
); autos
= scm_cdr (autos
))
326 Grob
*s
= unsmob_grob (scm_caar (autos
));
327 Real amount
= scm_to_double (scm_cdar (autos
)) * wid
;
332 amounts
.push (amount
);
333 if (amount
< left_most
)
338 for (int i
= 0; i
< amounts
.size (); i
++)
340 done
[i
]->translate_axis (amounts
[i
] - left_most
, X_AXIS
);
344 Drul_array
< Link_array
<Grob
>
345 > Note_collision_interface::get_clash_groups (Grob
*me
)
347 Drul_array
<Link_array
<Grob
> > clash_groups
;
349 SCM s
= me
->get_property ("elements");
350 for (; scm_is_pair (s
); s
= scm_cdr (s
))
352 SCM car
= scm_car (s
);
354 Grob
*se
= unsmob_grob (car
);
355 if (Note_column::has_interface (se
))
356 clash_groups
[Note_column::dir (se
)].push (se
);
362 Link_array
<Grob
> &clashes (clash_groups
[d
]);
363 clashes
.sort (Note_column::shift_compare
);
365 while ((flip (&d
)) != UP
);
370 /** This complicated routine moves note columns around horizontally to
371 ensure that notes don't clash.
373 This should be put into Scheme.
376 Note_collision_interface::automatic_shift (Grob
*me
,
377 Drul_array
< Link_array
<Grob
>
380 Drul_array
< Array
<int> > shifts
;
386 Array
<int> &shift (shifts
[d
]);
387 Link_array
<Grob
> &clashes (clash_groups
[d
]);
389 for (int i
= 0; i
< clashes
.size (); i
++)
392 = clashes
[i
]->get_property ("horizontal-shift");
394 if (scm_is_number (sh
))
395 shift
.push (scm_to_int (sh
));
400 for (int i
= 1; i
< shift
.size (); i
++)
402 if (shift
[i
- 1] == shift
[i
])
404 clashes
[0]->warning (_ ("ignoring too many clashing note columns"));
409 while ((flip (&d
)) != UP
);
411 Drul_array
<Array
<Slice
> > extents
;
412 Drul_array
<Array
<Real
> > offsets
;
416 for (int i
= 0; i
< clash_groups
[d
].size (); i
++)
418 Slice
s (Note_column::head_positions_interval (clash_groups
[d
][i
]));
422 offsets
[d
].push (d
* 0.5 * i
);
425 while ((flip (&d
)) != UP
);
428 do horizontal shifts of each direction
438 for (int i
= 1; i
< clash_groups
[d
].size (); i
++)
440 Slice prev
= extents
[d
][i
- 1];
441 prev
.intersect (extents
[d
][i
]);
442 if (prev
.length () > 0
443 || (extents
[-d
].size () && d
* (extents
[d
][i
][-d
] - extents
[-d
][0][d
]) < 0))
444 for (int j
= i
; j
< clash_groups
[d
].size (); j
++)
445 offsets
[d
][j
] += d
* 0.5;
448 while ((flip (&d
)) != UP
);
451 Check if chords are meshing
454 check_meshing_chords (me
, &offsets
, extents
, clash_groups
);
458 for (int i
= 0; i
< clash_groups
[d
].size (); i
++)
459 tups
= scm_cons (scm_cons (clash_groups
[d
][i
]->self_scm (),
460 scm_make_real (offsets
[d
][i
])),
463 while (flip (&d
) != UP
);
469 Note_collision_interface::forced_shift (Grob
*me
)
473 SCM s
= me
->get_property ("elements");
474 for (; scm_is_pair (s
); s
= scm_cdr (s
))
476 Grob
*se
= unsmob_grob (scm_car (s
));
478 SCM force
= se
->get_property ("force-hshift");
479 if (scm_is_number (force
))
481 tups
= scm_cons (scm_cons (se
->self_scm (), force
),
489 Note_collision_interface::add_column (Grob
*me
, Grob
*ncol
)
491 ncol
->add_offset_callback (Note_collision_interface::force_shift_callback_proc
, X_AXIS
);
492 Axis_group_interface::add_element (me
, ncol
);
493 me
->add_dependency (ncol
);
496 ADD_INTERFACE (Note_collision_interface
, "note-collision-interface",
497 "An object that handles collisions between notes with different stem "
498 "directions and horizontal shifts. Most of the interesting properties "
499 "are to be set in @ref{note-column-interface}: these are "
500 "@code{force-hshift} and @code{horizontal-shift}.",
502 "merge-differently-dotted merge-differently-headed positioning-done");