2 collision.cc -- implement Collision
4 source file of the GNU LilyPond music typesetter
6 (c) 1997--2008 Han-Wen Nienhuys <hanwen@xs4all.nl>
9 #include "note-collision.hh"
11 #include "axis-group-interface.hh"
12 #include "dot-column.hh"
13 #include "international.hh"
14 #include "note-column.hh"
15 #include "note-head.hh"
16 #include "output-def.hh"
17 #include "pointer-group-interface.hh"
19 #include "rhythmic-head.hh"
20 #include "staff-symbol-referencer.hh"
21 #include "side-position-interface.hh"
27 check_meshing_chords (Grob
*me
,
28 Drul_array
<vector
<Real
> > *offsets
,
29 Drul_array
<vector
<Slice
> > const &extents
,
30 Drul_array
<vector
<Grob
*> > const &clash_groups
)
33 if (!extents
[UP
].size () || ! extents
[DOWN
].size ())
36 Grob
*clash_up
= clash_groups
[UP
][0];
37 Grob
*clash_down
= clash_groups
[DOWN
][0];
39 /* Every note column should have a stem, but avoid a crash. */
40 if (!Note_column::get_stem (clash_up
) || !Note_column::get_stem (clash_down
))
43 Drul_array
<Grob
*> stems (Note_column::get_stem (clash_down
),
44 Note_column::get_stem (clash_up
));
46 Grob
*head_up
= Note_column::first_head (clash_up
);
47 Grob
*head_down
= Note_column::first_head (clash_down
);
49 vector
<int> ups
= Stem::note_head_positions (Note_column::get_stem (clash_up
));
50 vector
<int> dps
= Stem::note_head_positions (Note_column::get_stem (clash_down
));
52 /* Too far apart to collide. */
53 if (ups
[0] > dps
.back () + 1)
56 /* Merge heads if the notes lie the same line, or if the "stem-up-note" is
57 above the "stem-down-note". */
58 bool merge_possible
= (ups
[0] >= dps
[0]) && (ups
.back () >= dps
.back ());
60 /* Do not merge notes typeset in different style. */
61 if (!ly_is_equal (head_up
->get_property ("style"),
62 head_down
->get_property ("style")))
63 merge_possible
= false;
65 int up_ball_type
= Rhythmic_head::duration_log (head_up
);
66 int down_ball_type
= Rhythmic_head::duration_log (head_down
);
68 /* Do not merge whole notes (or longer, like breve, longa, maxima). */
69 if (merge_possible
&& (up_ball_type
<= 0 || down_ball_type
<= 0))
70 merge_possible
= false;
73 && Rhythmic_head::dot_count (head_up
) != Rhythmic_head::dot_count (head_down
)
74 && !to_boolean (me
->get_property ("merge-differently-dotted")))
75 merge_possible
= false;
77 /* Can only merge different heads if merge-differently-headed is
80 && up_ball_type
!= down_ball_type
81 && !to_boolean (me
->get_property ("merge-differently-headed")))
82 merge_possible
= false;
84 /* Should never merge quarter and half notes, as this would make
85 them indistinguishable. */
87 && ((Stem::duration_log (stems
[UP
]) == 1
88 && Stem::duration_log (stems
[DOWN
]) == 2)
89 || (Stem::duration_log (stems
[UP
]) == 2
90 && Stem::duration_log (stems
[DOWN
]) == 1)))
91 merge_possible
= false;
94 this case (distant half collide),
101 the noteheads may be closer than this case (close half collide)
112 /* TODO: filter out the 'o's in this configuration, since they're no
113 part in the collision.
122 bool close_half_collide
= false;
123 bool distant_half_collide
= false;
124 bool full_collide
= false;
126 for (vsize i
= 0, j
= 0; i
< ups
.size () && j
< dps
.size (); )
128 if (abs (ups
[i
] - dps
[j
]) == 1)
130 merge_possible
= false;
132 close_half_collide
= true;
134 distant_half_collide
= true;
136 else if (ups
[i
] == dps
[j
])
138 else if (ups
[i
] > dps
[0] && ups
[i
] < dps
.back ())
139 merge_possible
= false;
140 else if (dps
[j
] > ups
[0] && dps
[j
] < ups
.back ())
141 merge_possible
= false;
145 else if (ups
[i
] > dps
[j
])
154 full_collide
= full_collide
|| (close_half_collide
155 && distant_half_collide
);
157 Drul_array
<Real
> center_note_shifts
;
158 center_note_shifts
[LEFT
] = 0.0;
159 center_note_shifts
[RIGHT
] = 0.0;
161 Real shift_amount
= 1;
163 bool touch
= (ups
[0] >= dps
.back ());
164 /* As a special case, if the topmost part of the downstem chord is a second,
165 the top note of which is the same pitch as the lowest upstem note, they
166 shouldn't count as touching.
168 if (dps
.back () == ups
[0] && dps
.size () > 1 && dps
[dps
.size() - 2] == ups
[0] - 1)
174 /* For full collisions, the right hand head may obscure dots, so
175 make sure the dotted heads go to the right. */
176 bool stem_to_stem
= false;
179 if (Rhythmic_head::dot_count (head_up
) > Rhythmic_head::dot_count (head_down
))
181 else if (Rhythmic_head::dot_count (head_up
) < Rhythmic_head::dot_count (head_down
))
185 /* The solfa is a triangle, which is inverted depending on stem
186 direction. In case of a collision, one of them should be removed,
187 so the resulting note does not look like a block.
190 && head_up
->get_property ("style") == ly_symbol2scm ("fa")
191 && head_down
->get_property ("style") == ly_symbol2scm ("fa"))
193 Interval uphead_size
= head_up
->extent (head_up
, Y_AXIS
);
194 Offset att
= Offset (0.0, -1.0);
195 head_up
->set_property ("stem-attachment", ly_offset2scm (att
));
196 head_up
->set_property ("transparent", SCM_BOOL_T
);
203 /* If possible, don't wipe any heads. Else, wipe shortest head,
204 or head with smallest amount of dots. Note: when merging
205 different heads, dots on the smaller one disappear. */
207 Grob
*dot_wipe_head
= head_up
;
209 if (up_ball_type
== down_ball_type
)
211 if (Rhythmic_head::dot_count (head_down
) < Rhythmic_head::dot_count (head_up
))
213 wipe_ball
= head_down
;
214 dot_wipe_head
= head_down
;
216 else if (Rhythmic_head::dot_count (head_down
) > Rhythmic_head::dot_count (head_up
))
218 dot_wipe_head
= head_up
;
222 dot_wipe_head
= head_up
;
224 else if (down_ball_type
> up_ball_type
)
226 wipe_ball
= head_down
;
227 dot_wipe_head
= head_down
;
229 else if (down_ball_type
< up_ball_type
)
232 dot_wipe_head
= head_up
;
237 if (Grob
*d
= unsmob_grob (dot_wipe_head
->get_object ("dot")))
241 if (wipe_ball
&& wipe_ball
->is_live ())
243 wipe_ball
->set_property ("transparent", SCM_BOOL_T
);
246 /* TODO: these numbers are magic; should devise a set of grob props
247 to tune this behavior. */
248 else if (stem_to_stem
)
249 shift_amount
= -abs (shift_amount
) * 0.65;
250 else if (close_half_collide
&& !touch
)
251 shift_amount
*= 0.52;
252 else if (distant_half_collide
&& !touch
)
254 else if (distant_half_collide
|| close_half_collide
|| full_collide
)
258 else if (Rhythmic_head::dot_count (head_up
) || Rhythmic_head::dot_count (head_down
))
261 shift_amount
*= 0.17;
267 && down_ball_type
* up_ball_type
== 0)
269 if (up_ball_type
== 0 && down_ball_type
== 1)
270 shift_amount
*= 1.25;
271 else if (up_ball_type
== 0 && down_ball_type
== 2)
272 shift_amount
*= 1.35;
273 else if (down_ball_type
== 0 && up_ball_type
== 1)
275 else if (down_ball_type
== 0 && up_ball_type
== 2)
276 shift_amount
*= 0.75;
282 * Dots from left note head collide with right note head. Only occurs
283 * with a close half collide, if the left note head is between
284 * lines and the right note head is on a line, and if right note head
285 * hasn't got any dots.
287 if (close_half_collide
288 && Rhythmic_head::dot_count (head_up
)
289 && !Rhythmic_head::dot_count (head_down
))
291 Grob
*staff
= Staff_symbol_referencer::get_staff_symbol (me
);
292 if (!Staff_symbol_referencer::on_line (staff
, ups
[0]))
294 TODO: consider junking the else body.
296 if (to_boolean (me
->get_property ("prefer-dotted-right")))
302 Grob
*d
= unsmob_grob (head_up
->get_object ("dot"));
303 Grob
*parent
= d
->get_parent (X_AXIS
);
304 if (Dot_column::has_interface (parent
))
305 Side_position_interface::add_support (parent
, head_down
);
309 /* For full or close half collisions, the right hand head may
310 obscure dots. Move dots to the right. */
311 if (abs (shift_amount
) > 1e-6
312 && Rhythmic_head::dot_count (head_down
) > Rhythmic_head::dot_count (head_up
)
313 && (full_collide
|| close_half_collide
))
315 Grob
*d
= unsmob_grob (head_down
->get_object ("dot"));
316 Grob
*parent
= d
->get_parent (X_AXIS
);
326 the . is put right of o which is erroneous o force-shifted
329 if (Dot_column::has_interface (parent
))
331 Grob
*stem
= unsmob_grob (head_up
->get_object ("stem"));
332 extract_grob_set (stem
, "note-heads", heads
);
333 for (vsize i
= 0; i
< heads
.size (); i
++)
334 Side_position_interface::add_support (parent
, heads
[i
]);
341 for (vsize i
= 0; i
< clash_groups
[d
].size (); i
++)
342 (*offsets
)[d
][i
] += d
* shift_amount
;
344 while ((flip (&d
)) != UP
);
348 MAKE_SCHEME_CALLBACK (Note_collision_interface
, calc_positioning_done
, 1)
350 Note_collision_interface::calc_positioning_done (SCM smob
)
352 Grob
*me
= unsmob_grob (smob
);
353 me
->set_property ("positioning-done", SCM_BOOL_T
);
355 Drul_array
<vector
<Grob
*> > clash_groups
= get_clash_groups (me
);
360 for (vsize i
= clash_groups
[d
].size (); i
--; )
365 clash_groups
[d
][i
]->extent (me
, X_AXIS
);
368 while (flip (&d
) != UP
);
370 SCM
autos (automatic_shift (me
, clash_groups
));
371 SCM
hand (forced_shift (me
));
376 if (clash_groups
[d
].size ())
378 Grob
*h
= clash_groups
[d
][0];
379 Grob
*fh
= Note_column::first_head (h
);
381 wid
= fh
->extent (h
, X_AXIS
).length ();
384 while (flip (&d
) != UP
);
387 Real left_most
= 1e6
;
389 vector
<Real
> amounts
;
390 for (; scm_is_pair (hand
); hand
= scm_cdr (hand
))
392 Grob
*s
= unsmob_grob (scm_caar (hand
));
393 Real amount
= scm_to_double (scm_cdar (hand
)) * wid
;
396 amounts
.push_back (amount
);
397 if (amount
< left_most
)
400 for (; scm_is_pair (autos
); autos
= scm_cdr (autos
))
402 Grob
*s
= unsmob_grob (scm_caar (autos
));
403 Real amount
= scm_to_double (scm_cdar (autos
)) * wid
;
405 vsize x
= find (done
, s
) - done
.begin ();
406 if (x
== VPOS
|| x
>= done
.size ())
409 amounts
.push_back (amount
);
410 if (amount
< left_most
)
415 for (vsize i
= 0; i
< amounts
.size (); i
++)
416 done
[i
]->translate_axis (amounts
[i
] - left_most
, X_AXIS
);
421 Drul_array
< vector
<Grob
*> >
422 Note_collision_interface::get_clash_groups (Grob
*me
)
424 Drul_array
<vector
<Grob
*> > clash_groups
;
426 extract_grob_set (me
, "elements", elements
);
427 for (vsize i
= 0; i
< elements
.size (); i
++)
429 Grob
*se
= elements
[i
];
430 if (Note_column::has_interface (se
))
432 if (!Note_column::dir (se
))
434 se
->programming_error ("note-column has no direction");
437 clash_groups
[Note_column::dir (se
)].push_back (se
);
444 vector
<Grob
*> &clashes (clash_groups
[d
]);
445 vector_sort (clashes
, Note_column::shift_less
);
447 while ((flip (&d
)) != UP
);
452 /** This complicated routine moves note columns around horizontally to
453 ensure that notes don't clash.
457 Note_collision_interface::automatic_shift (Grob
*me
,
458 Drul_array
<vector
<Grob
*> > clash_groups
)
460 Drul_array
< vector
<int> > shifts
;
466 vector
<int> &shift (shifts
[d
]);
467 vector
<Grob
*> &clashes (clash_groups
[d
]);
469 for (vsize i
= 0; i
< clashes
.size (); i
++)
472 = clashes
[i
]->get_property ("horizontal-shift");
474 if (scm_is_number (sh
))
475 shift
.push_back (scm_to_int (sh
));
480 for (vsize i
= 1; i
< shift
.size (); i
++)
482 if (shift
[i
- 1] == shift
[i
])
484 clashes
[0]->warning (_ ("ignoring too many clashing note columns"));
489 while ((flip (&d
)) != UP
);
491 Drul_array
<vector
<Slice
> > extents
;
492 Drul_array
<vector
<Real
> > offsets
;
496 for (vsize i
= 0; i
< clash_groups
[d
].size (); i
++)
498 Slice
s (Note_column::head_positions_interval (clash_groups
[d
][i
]));
501 extents
[d
].push_back (s
);
502 offsets
[d
].push_back (d
* 0.5 * i
);
505 while ((flip (&d
)) != UP
);
508 do horizontal shifts of each direction
518 for (vsize i
= 1; i
< clash_groups
[d
].size (); i
++)
520 Slice prev
= extents
[d
][i
- 1];
521 prev
.intersect (extents
[d
][i
]);
522 if (prev
.length () > 0
523 || (extents
[-d
].size () && d
* (extents
[d
][i
][-d
] - extents
[-d
][0][d
]) < 0))
524 for (vsize j
= i
; j
< clash_groups
[d
].size (); j
++)
525 offsets
[d
][j
] += d
* 0.5;
528 while ((flip (&d
)) != UP
);
532 see input/regression/dot-up-voice-collision.ly
534 for (vsize i
= 0; i
< clash_groups
[UP
].size (); i
++)
536 Grob
*g
= clash_groups
[UP
][i
];
537 Grob
*dc
= Note_column::dot_column (g
);
540 for (vsize j
= i
+ 1; j
< clash_groups
[UP
].size (); j
++)
542 Grob
*stem
= Note_column::get_stem (clash_groups
[UP
][j
]);
543 Side_position_interface::add_support (dc
, stem
);
548 Check if chords are meshing
551 check_meshing_chords (me
, &offsets
, extents
, clash_groups
);
555 for (vsize i
= 0; i
< clash_groups
[d
].size (); i
++)
556 tups
= scm_cons (scm_cons (clash_groups
[d
][i
]->self_scm (),
557 scm_from_double (offsets
[d
][i
])),
560 while (flip (&d
) != UP
);
566 Note_collision_interface::forced_shift (Grob
*me
)
570 extract_grob_set (me
, "elements", elements
);
571 for (vsize i
= 0; i
< elements
.size (); i
++)
573 Grob
*se
= elements
[i
];
575 SCM force
= se
->get_property ("force-hshift");
576 if (scm_is_number (force
))
578 tups
= scm_cons (scm_cons (se
->self_scm (), force
),
586 Note_collision_interface::add_column (Grob
*me
, Grob
*ncol
)
588 ncol
->set_property ("X-offset", Grob::x_parent_positioning_proc
);
589 Axis_group_interface::add_element (me
, ncol
);
592 ADD_INTERFACE (Note_collision_interface
,
593 "An object that handles collisions between notes with"
594 " different stem directions and horizontal shifts. Most of"
595 " the interesting properties are to be set in"
596 " @ref{note-column-interface}: these are @code{force-hshift}"
597 " and @code{horizontal-shift}.",
600 "merge-differently-dotted "
601 "merge-differently-headed "
603 "prefer-dotted-right "