2 collision.cc -- implement Collision
4 source file of the GNU LilyPond music typesetter
6 (c) 1997--2007 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
*cu
= clash_groups
[UP
][0];
37 Grob
*cd
= clash_groups
[DOWN
][0];
39 /* Every note column should have a stem, but avoid a crash. */
40 if (!Note_column::get_stem (cu
) || !Note_column::get_stem (cd
))
43 Drul_array
<Grob
*> stems (Note_column::get_stem (cd
),
44 Note_column::get_stem (cu
));
46 Grob
*nu
= Note_column::first_head (cu
);
47 Grob
*nd
= Note_column::first_head (cd
);
49 vector
<int> ups
= Stem::note_head_positions (Note_column::get_stem (cu
));
50 vector
<int> dps
= Stem::note_head_positions (Note_column::get_stem (cd
));
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 (nu
->get_property ("style"),
62 nd
->get_property ("style")))
63 merge_possible
= false;
65 int upball_type
= Rhythmic_head::duration_log (nu
);
66 int dnball_type
= Rhythmic_head::duration_log (nd
);
68 /* Do not merge whole notes (or longer, like breve, longa, maxima). */
69 if (merge_possible
&& (upball_type
<= 0 || dnball_type
<= 0))
70 merge_possible
= false;
73 && Rhythmic_head::dot_count (nu
) != Rhythmic_head::dot_count (nd
)
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 && upball_type
!= dnball_type
81 && !to_boolean (me
->get_property ("merge-differently-headed")))
82 merge_possible
= false;
85 && nu
->get_property ("style") == ly_symbol2scm ("fa")
86 && nd
->get_property ("style") == ly_symbol2scm ("fa"))
88 Interval uphead_size
= nu
->extent (nu
, Y_AXIS
);
89 Offset att
= Offset (0.0, -1.0);
90 nu
->set_property ("stem-attachment", ly_offset2scm (att
));
91 nu
->set_property ("transparent", SCM_BOOL_T
);
94 /* Should never merge quarter and half notes, as this would make
95 them indistinguishable. */
97 && ((Stem::duration_log (stems
[UP
]) == 1
98 && Stem::duration_log (stems
[DOWN
]) == 2)
99 || (Stem::duration_log (stems
[UP
]) == 2
100 && Stem::duration_log (stems
[DOWN
]) == 1)))
101 merge_possible
= false;
104 this case (distant half collide),
111 the noteheads may be closer than this case (close half collide)
122 /* TODO: filter out the 'o's in this configuration, since they're no
123 part in the collision.
132 bool close_half_collide
= false;
133 bool distant_half_collide
= false;
134 bool full_collide
= false;
136 for (vsize i
= 0, j
= 0; i
< ups
.size () && j
< dps
.size (); )
138 if (abs (ups
[i
] - dps
[j
]) == 1)
140 merge_possible
= false;
142 close_half_collide
= true;
144 distant_half_collide
= true;
146 else if (ups
[i
] == dps
[j
])
148 else if (ups
[i
] > dps
[0] && ups
[i
] < dps
.back ())
149 merge_possible
= false;
150 else if (dps
[j
] > ups
[0] && dps
[j
] < ups
.back ())
151 merge_possible
= false;
155 else if (ups
[i
] > dps
[j
])
164 full_collide
= full_collide
|| (close_half_collide
165 && distant_half_collide
);
167 Drul_array
<Real
> center_note_shifts
;
168 center_note_shifts
[LEFT
] = 0.0;
169 center_note_shifts
[RIGHT
] = 0.0;
171 Real shift_amount
= 1;
173 bool touch
= (ups
[0] >= dps
.back ());
177 /* For full collisions, the right hand head may obscure dots, so
178 make sure the dotted heads go to the right. */
179 bool stem_to_stem
= false;
181 if (Rhythmic_head::dot_count (nu
) > Rhythmic_head::dot_count (nd
))
183 else if (Rhythmic_head::dot_count (nu
) < Rhythmic_head::dot_count (nd
))
190 /* If possible, don't wipe any heads. Else, wipe shortest head,
191 or head with smallest amount of dots. Note: when merging
192 different heads, dots on the smaller one disappear. */
194 Grob
*dot_wipe_head
= nu
;
196 if (upball_type
== dnball_type
)
198 if (Rhythmic_head::dot_count (nd
) < Rhythmic_head::dot_count (nu
))
203 else if (Rhythmic_head::dot_count (nd
) > Rhythmic_head::dot_count (nu
))
211 else if (dnball_type
> upball_type
)
216 else if (dnball_type
< upball_type
)
224 if (Grob
*d
= unsmob_grob (dot_wipe_head
->get_object ("dot")))
228 if (wipe_ball
&& wipe_ball
->is_live ())
230 wipe_ball
->set_property ("transparent", SCM_BOOL_T
);
233 /* TODO: these numbers are magic; should devise a set of grob props
234 to tune this behavior. */
235 else if (stem_to_stem
)
236 shift_amount
= -abs (shift_amount
) * 0.65;
237 else if (close_half_collide
&& !touch
)
238 shift_amount
*= 0.52;
239 else if (distant_half_collide
&& !touch
)
241 else if (distant_half_collide
|| close_half_collide
|| full_collide
)
245 else if (Rhythmic_head::dot_count (nu
) || Rhythmic_head::dot_count (nd
))
248 shift_amount
*= 0.17;
254 && dnball_type
* upball_type
== 0)
256 if (upball_type
== 0 && dnball_type
== 1)
257 shift_amount
*= 1.25;
258 else if (upball_type
== 0 && dnball_type
== 2)
259 shift_amount
*= 1.35;
260 else if (dnball_type
== 0 && upball_type
== 1)
262 else if (dnball_type
== 0 && upball_type
== 2)
263 shift_amount
*= 0.75;
269 * Dots from left note head collide with right note head. Only occurs
270 * with a close half collide, if the left note head is between
271 * lines and the right note head is on a line, and if right note head
272 * hasn't got any dots.
274 if (close_half_collide
275 && Rhythmic_head::dot_count (nu
)
276 && !Rhythmic_head::dot_count (nd
))
278 Grob
*staff
= Staff_symbol_referencer::get_staff_symbol (me
);
279 if (!Staff_symbol_referencer::on_line (staff
, ups
[0]))
281 Grob
*d
= unsmob_grob (nu
->get_object ("dot"));
282 Grob
*parent
= d
->get_parent (X_AXIS
);
283 if (Dot_column::has_interface (parent
))
284 Side_position_interface::add_support (parent
, nd
);
288 /* For full or close half collisions, the right hand head may
289 obscure dots. Move dots to the right. */
290 if (abs (shift_amount
) > 1e-6
291 && Rhythmic_head::dot_count (nd
) > Rhythmic_head::dot_count (nu
)
292 && (full_collide
|| close_half_collide
))
294 Grob
*d
= unsmob_grob (nd
->get_object ("dot"));
295 Grob
*parent
= d
->get_parent (X_AXIS
);
305 the . is put right of o which is erroneous o force-shifted
308 if (Dot_column::has_interface (parent
))
310 Grob
*stem
= unsmob_grob (nu
->get_object ("stem"));
311 extract_grob_set (stem
, "note-heads", heads
);
312 for (vsize i
= 0; i
< heads
.size (); i
++)
313 Side_position_interface::add_support (parent
, heads
[i
]);
320 for (vsize i
= 0; i
< clash_groups
[d
].size (); i
++)
321 (*offsets
)[d
][i
] += d
* shift_amount
;
323 while ((flip (&d
)) != UP
);
327 MAKE_SCHEME_CALLBACK (Note_collision_interface
, calc_positioning_done
, 1)
329 Note_collision_interface::calc_positioning_done (SCM smob
)
331 Grob
*me
= unsmob_grob (smob
);
332 me
->set_property ("positioning-done", SCM_BOOL_T
);
334 Drul_array
<vector
<Grob
*> > cg
= get_clash_groups (me
);
339 for (vsize i
= cg
[d
].size (); i
--; )
344 cg
[d
][i
]->extent (me
, X_AXIS
);
347 while (flip (&d
) != UP
);
349 SCM
autos (automatic_shift (me
, cg
));
350 SCM
hand (forced_shift (me
));
358 Grob
*fh
= Note_column::first_head (h
);
360 wid
= fh
->extent (h
, X_AXIS
).length ();
363 while (flip (&d
) != UP
);
366 Real left_most
= 1e6
;
368 vector
<Real
> amounts
;
369 for (; scm_is_pair (hand
); hand
= scm_cdr (hand
))
371 Grob
*s
= unsmob_grob (scm_caar (hand
));
372 Real amount
= scm_to_double (scm_cdar (hand
)) * wid
;
375 amounts
.push_back (amount
);
376 if (amount
< left_most
)
379 for (; scm_is_pair (autos
); autos
= scm_cdr (autos
))
381 Grob
*s
= unsmob_grob (scm_caar (autos
));
382 Real amount
= scm_to_double (scm_cdar (autos
)) * wid
;
384 vsize x
= find (done
, s
) - done
.begin ();
385 if (x
== VPOS
|| x
>= done
.size ())
388 amounts
.push_back (amount
);
389 if (amount
< left_most
)
394 for (vsize i
= 0; i
< amounts
.size (); i
++)
395 done
[i
]->translate_axis (amounts
[i
] - left_most
, X_AXIS
);
400 Drul_array
< vector
<Grob
*> >
401 Note_collision_interface::get_clash_groups (Grob
*me
)
403 Drul_array
<vector
<Grob
*> > clash_groups
;
405 extract_grob_set (me
, "elements", elements
);
406 for (vsize i
= 0; i
< elements
.size (); i
++)
408 Grob
*se
= elements
[i
];
409 if (Note_column::has_interface (se
))
411 if (!Note_column::dir (se
))
413 se
->programming_error ("note-column has no direction");
416 clash_groups
[Note_column::dir (se
)].push_back (se
);
423 vector
<Grob
*> &clashes (clash_groups
[d
]);
424 vector_sort (clashes
, Note_column::shift_less
);
426 while ((flip (&d
)) != UP
);
431 /** This complicated routine moves note columns around horizontally to
432 ensure that notes don't clash.
436 Note_collision_interface::automatic_shift (Grob
*me
,
437 Drul_array
<vector
<Grob
*> > clash_groups
)
439 Drul_array
< vector
<int> > shifts
;
445 vector
<int> &shift (shifts
[d
]);
446 vector
<Grob
*> &clashes (clash_groups
[d
]);
448 for (vsize i
= 0; i
< clashes
.size (); i
++)
451 = clashes
[i
]->get_property ("horizontal-shift");
453 if (scm_is_number (sh
))
454 shift
.push_back (scm_to_int (sh
));
459 for (vsize i
= 1; i
< shift
.size (); i
++)
461 if (shift
[i
- 1] == shift
[i
])
463 clashes
[0]->warning (_ ("ignoring too many clashing note columns"));
468 while ((flip (&d
)) != UP
);
470 Drul_array
<vector
<Slice
> > extents
;
471 Drul_array
<vector
<Real
> > offsets
;
475 for (vsize i
= 0; i
< clash_groups
[d
].size (); i
++)
477 Slice
s (Note_column::head_positions_interval (clash_groups
[d
][i
]));
480 extents
[d
].push_back (s
);
481 offsets
[d
].push_back (d
* 0.5 * i
);
484 while ((flip (&d
)) != UP
);
487 do horizontal shifts of each direction
497 for (vsize i
= 1; i
< clash_groups
[d
].size (); i
++)
499 Slice prev
= extents
[d
][i
- 1];
500 prev
.intersect (extents
[d
][i
]);
501 if (prev
.length () > 0
502 || (extents
[-d
].size () && d
* (extents
[d
][i
][-d
] - extents
[-d
][0][d
]) < 0))
503 for (vsize j
= i
; j
< clash_groups
[d
].size (); j
++)
504 offsets
[d
][j
] += d
* 0.5;
507 while ((flip (&d
)) != UP
);
511 see input/regression/dot-up-voice-collision.ly
513 for (vsize i
= 0; i
< clash_groups
[UP
].size (); i
++)
515 Grob
*g
= clash_groups
[UP
][i
];
516 Grob
*dc
= Note_column::dot_column (g
);
519 for (vsize j
= i
+ 1; j
< clash_groups
[UP
].size (); j
++)
521 Grob
*stem
= Note_column::get_stem (clash_groups
[UP
][j
]);
522 Side_position_interface::add_support (dc
, stem
);
527 Check if chords are meshing
530 check_meshing_chords (me
, &offsets
, extents
, clash_groups
);
534 for (vsize i
= 0; i
< clash_groups
[d
].size (); i
++)
535 tups
= scm_cons (scm_cons (clash_groups
[d
][i
]->self_scm (),
536 scm_from_double (offsets
[d
][i
])),
539 while (flip (&d
) != UP
);
545 Note_collision_interface::forced_shift (Grob
*me
)
549 extract_grob_set (me
, "elements", elements
);
550 for (vsize i
= 0; i
< elements
.size (); i
++)
552 Grob
*se
= elements
[i
];
554 SCM force
= se
->get_property ("force-hshift");
555 if (scm_is_number (force
))
557 tups
= scm_cons (scm_cons (se
->self_scm (), force
),
565 Note_collision_interface::add_column (Grob
*me
, Grob
*ncol
)
567 ncol
->set_property ("X-offset", Grob::x_parent_positioning_proc
);
568 Axis_group_interface::add_element (me
, ncol
);
571 ADD_INTERFACE (Note_collision_interface
,
572 "An object that handles collisions between notes with different stem "
573 "directions and horizontal shifts. Most of the interesting properties "
574 "are to be set in @ref{note-column-interface}: these are "
575 "@code{force-hshift} and @code{horizontal-shift}.",
579 "merge-differently-dotted "
580 "merge-differently-headed "
581 "positioning-done ");