Line data Source code
1 : /*
2 : * SPDX-License-Identifier: GPL-3.0-only
3 : * MuseScore-Studio-CLA-applies
4 : *
5 : * MuseScore Studio
6 : * Music Composition & Notation
7 : *
8 : * Copyright (C) 2023 MuseScore Limited and others
9 : *
10 : * This program is free software: you can redistribute it and/or modify
11 : * it under the terms of the GNU General Public License version 3 as
12 : * published by the Free Software Foundation.
13 : *
14 : * This program is distributed in the hope that it will be useful,
15 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : * GNU General Public License for more details.
18 : *
19 : * You should have received a copy of the GNU General Public License
20 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 : */
22 :
23 : #include "skyline.h"
24 :
25 : #include "realfn.h"
26 : #include "draw/painter.h"
27 :
28 : #include "../dom/arpeggio.h"
29 : #include "../dom/beam.h"
30 : #include "../dom/chord.h"
31 : #include "../dom/stem.h"
32 : #include "../dom/tremolotwochord.h"
33 :
34 : #include "shape.h"
35 :
36 : using namespace mu;
37 : using namespace muse::draw;
38 :
39 : namespace mu::engraving {
40 452878 : void Skyline::add(const ShapeElement& r)
41 : {
42 452878 : if (r.ignoreForLayout()) {
43 35 : return;
44 : }
45 452843 : const EngravingItem* item = r.item();
46 452843 : bool crossSouth = false;
47 452843 : bool crossNorth = false;
48 452843 : if (item && item->isStem()) {
49 43867 : Chord* chord = toStem(item)->chord();
50 43867 : if (chord) {
51 43867 : Beam* beam = chord->beam();
52 43867 : TremoloTwoChord* tremolo = chord->tremoloTwoChord();
53 14577 : bool isCross = (beam && beam->cross())
54 58444 : || (tremolo && tremolo->chord1()->staffMove() != tremolo->chord2()->staffMove());
55 43867 : if (isCross) {
56 35 : std::vector<ChordRest*> elements;
57 35 : if (beam) {
58 35 : elements = beam->elements();
59 0 : } else if (tremolo) {
60 0 : elements = { tremolo->chord1(), tremolo->chord2() };
61 : }
62 35 : int thisStaffMove = chord->staffMove();
63 35 : if (thisStaffMove < 0) {
64 7 : crossNorth = true;
65 28 : } else if (thisStaffMove > 0) {
66 11 : crossSouth = true;
67 : }
68 162 : for (ChordRest* element : elements) {
69 127 : int staffMove = element->staffMove();
70 127 : if (staffMove < thisStaffMove) {
71 31 : crossNorth = true;
72 : }
73 127 : if (staffMove > thisStaffMove) {
74 31 : crossSouth = true;
75 : }
76 : }
77 35 : }
78 : }
79 : }
80 452843 : if (item && (item->isArpeggio() || item->isChordBracket())) {
81 311 : const Arpeggio* arpeggio = toArpeggio(item);
82 311 : if (arpeggio->crossStaff()) {
83 52 : crossSouth = true;
84 : }
85 : }
86 452843 : if (!crossNorth) {
87 452819 : _north.add(r);
88 : }
89 452843 : if (!crossSouth) {
90 452762 : _south.add(r);
91 : }
92 : }
93 :
94 22392 : void SkylineLine::add(const Shape& s)
95 : {
96 106027 : for (const ShapeElement& se : s.elements()) {
97 83635 : add(se);
98 : }
99 22392 : }
100 :
101 18088 : SkylineLine SkylineLine::getFilteredCopy(std::function<bool(const ShapeElement&)> filterOut) const
102 : {
103 18088 : SkylineLine newSkylineLine(*this);
104 :
105 18088 : newSkylineLine.m_shape.clear();
106 :
107 470013 : for (const ShapeElement& shapeEl : m_shape.elements()) {
108 451925 : if (filterOut(shapeEl)) {
109 41134 : continue;
110 : }
111 410791 : newSkylineLine.m_shape.add(shapeEl);
112 : }
113 :
114 18088 : return newSkylineLine;
115 0 : }
116 :
117 989576 : void SkylineLine::add(const ShapeElement& r)
118 : {
119 989576 : if (r.ignoreForLayout()) {
120 0 : return;
121 : }
122 :
123 989576 : double x = r.x();
124 989576 : double top = r.top();
125 989576 : double bottom = r.bottom();
126 989576 : double staffLinesTop = staffLinesTopAtX(x);
127 989576 : double staffLinesBottom = staffLinesBottomAtX(x);
128 :
129 989576 : if (r.item() && r.item()->isStaffLines()) {
130 93644 : if (!(muse::RealIsEqual(top, staffLinesTop) && muse::RealIsEqual(bottom, staffLinesBottom))) {
131 22384 : m_staffLineEdges.emplace(round(x), StaffLineEdge(top, bottom));
132 : }
133 895932 : } else if (hasValidStaffLineEdges()) {
134 872406 : if (m_isNorth && muse::RealIsEqualOrMore(top, staffLinesTop)) {
135 318699 : return; // Only add if it pokes above the staff
136 : }
137 553707 : if (!m_isNorth && muse::RealIsEqualOrLess(bottom, staffLinesBottom)) {
138 297839 : return; // Only add if it pokes below the staff
139 : }
140 : }
141 :
142 373038 : m_shape.add(r);
143 : }
144 :
145 989576 : double SkylineLine::staffLinesTopAtX(double x) const
146 : {
147 989576 : if (m_staffLineEdges.empty()) {
148 45910 : return 0.0;
149 : }
150 :
151 943666 : auto upperBound = m_staffLineEdges.upper_bound(std::round(x)); // iterator to first element in map with key larger than x
152 943666 : if (upperBound != m_staffLineEdges.begin()) {
153 940246 : --upperBound; // back by one (i.e. get last element with key smaller than x)
154 : }
155 :
156 943666 : return (*upperBound).second.top;
157 : }
158 :
159 989576 : double SkylineLine::staffLinesBottomAtX(double x) const
160 : {
161 989576 : if (m_staffLineEdges.empty()) {
162 45910 : return 0.0;
163 : }
164 :
165 943666 : auto upperBound = m_staffLineEdges.upper_bound(std::round(x)); // iterator to first element in map with key larger than x
166 943666 : if (upperBound != m_staffLineEdges.begin()) {
167 940246 : --upperBound; // back by one (i.e. get last element with key smaller than x)
168 : }
169 :
170 943666 : return (*upperBound).second.bottom;
171 : }
172 :
173 183744 : void Skyline::add(const Shape& s)
174 : {
175 585565 : for (const auto& r : s.elements()) {
176 401821 : add(r);
177 : }
178 183744 : }
179 :
180 22221 : void Skyline::clear()
181 : {
182 22221 : _north.clear();
183 22221 : _south.clear();
184 22221 : }
185 :
186 44442 : void SkylineLine::clear()
187 : {
188 44442 : m_staffLineEdges.clear();
189 44442 : m_shape.clear();
190 44442 : }
191 :
192 : //-------------------------------------------------------------------
193 : // minDistance
194 : // a is located below this skyline.
195 : // Calculates the minimum distance between two skylines
196 : //-------------------------------------------------------------------
197 :
198 10869 : double Skyline::minDistance(const Skyline& s, double minHorizontalClearance) const
199 : {
200 10869 : return south().minDistance(s.north(), minHorizontalClearance);
201 : }
202 :
203 11761 : double SkylineLine::minDistance(const SkylineLine& sl, double minHorizontalClearance) const
204 : {
205 11761 : return m_shape.minVerticalDistance(sl.m_shape, minHorizontalClearance);
206 : }
207 :
208 13153 : double SkylineLine::minDistanceToShapeAbove(const Shape& shapeAbove, double minHorizontalClearance) const
209 : {
210 13153 : return shapeAbove.minVerticalDistance(m_shape, minHorizontalClearance);
211 : }
212 :
213 3105 : double SkylineLine::minDistanceToShapeBelow(const Shape& shapeBelow, double minHorizontalClearance) const
214 : {
215 3105 : return m_shape.minVerticalDistance(shapeBelow, minHorizontalClearance);
216 : }
217 :
218 257 : double SkylineLine::verticalClearanceAbove(const Shape& shapeAbove) const
219 : {
220 257 : return shapeAbove.verticalClearance(m_shape);
221 : }
222 :
223 257 : double SkylineLine::verticalClearanceBelow(const Shape& shapeBelow) const
224 : {
225 257 : return m_shape.verticalClearance(shapeBelow);
226 : }
227 :
228 0 : void Skyline::paint(Painter& painter, double lineWidth) const // DEBUG only
229 : {
230 0 : painter.save();
231 :
232 0 : painter.setBrush(BrushStyle::NoBrush);
233 :
234 0 : for (const ShapeElement& northShapeEl : _north.elements()) {
235 0 : painter.setPen(Pen(Color(144, 238, 144), lineWidth));
236 0 : for (const ShapeElement& southShapeEl : _south.elements()) {
237 0 : if (northShapeEl.item() == southShapeEl.item()) {
238 : // Paint in different color items that belong to both north and south skyline
239 0 : painter.setPen(Pen(Color(144, 191, 191), lineWidth));
240 0 : break;
241 : }
242 : }
243 0 : painter.drawRect(northShapeEl);
244 : }
245 :
246 0 : painter.setPen(Pen(Color(144, 144, 238), lineWidth));
247 0 : for (const ShapeElement& southShapeEl : _south.elements()) {
248 0 : bool alreadyPainted = false;
249 0 : for (const ShapeElement& northShapeEl : _north.elements()) {
250 0 : if (northShapeEl.item() == southShapeEl.item()) {
251 0 : alreadyPainted = true;
252 0 : break;
253 : }
254 : }
255 0 : if (!alreadyPainted) {
256 0 : painter.drawRect(southShapeEl);
257 : }
258 : }
259 :
260 0 : painter.restore();
261 0 : }
262 :
263 0 : bool SkylineLine::valid() const
264 : {
265 0 : return !m_shape.empty();
266 : }
267 :
268 129 : SkylineLine& SkylineLine::translateY(double y)
269 : {
270 129 : m_shape.translateY(y);
271 129 : return *this;
272 : }
273 :
274 0 : double SkylineLine::top(double startX, double endX)
275 : {
276 0 : double top = DBL_MAX;
277 0 : for (const ShapeElement& element : m_shape.elements()) {
278 0 : if (element.right() > startX && element.left() < endX) {
279 0 : top = std::min(top, element.top());
280 : }
281 : }
282 :
283 0 : if (top == DBL_MAX) {
284 0 : top = 0.0;
285 : }
286 :
287 0 : return top;
288 : }
289 :
290 0 : double SkylineLine::bottom(double startX, double endX)
291 : {
292 0 : double bottom = -DBL_MAX;
293 0 : for (const ShapeElement& element : m_shape.elements()) {
294 0 : if (element.right() > startX && element.left() < endX) {
295 0 : bottom = std::max(bottom, element.bottom());
296 : }
297 : }
298 :
299 0 : if (bottom == -DBL_MAX) {
300 0 : bottom = 0.0;
301 : }
302 :
303 0 : return bottom;
304 : }
305 :
306 32743 : double SkylineLine::max() const
307 : {
308 32743 : return m_isNorth ? m_shape.top() : m_shape.bottom();
309 : }
310 : } // namespace mu::engraving
311 :
312 0 : std::string mu::engraving::dump(const Skyline& skyline)
313 : {
314 0 : std::stringstream ss;
315 0 : ss << "Skyline dump.\n";
316 0 : dump(skyline.north(), ss);
317 0 : dump(skyline.south(), ss);
318 0 : return ss.str();
319 0 : }
320 :
321 0 : void mu::engraving::dump(const SkylineLine& skylineLine, std::stringstream& ss)
322 : {
323 0 : ss << "North: " << skylineLine.isNorth();
324 0 : for (const ShapeElement& se : skylineLine.elements()) {
325 0 : dump(se, ss);
326 : }
327 0 : }
|