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 : #include <cfloat>
23 :
24 : #include "shape.h"
25 :
26 : #include "draw/painter.h"
27 :
28 : #include "dom/engravingitem.h"
29 :
30 : #include "log.h"
31 :
32 : using namespace mu;
33 : using namespace muse;
34 : using namespace muse::draw;
35 : using namespace mu::engraving;
36 :
37 196 : Shape::Shape(const std::vector<RectF>& rects, const EngravingItem* p)
38 : {
39 196 : m_type = Type::Composite;
40 196 : m_elements.reserve(rects.size());
41 894 : for (const RectF& rect : rects) {
42 698 : m_elements.emplace_back(ShapeElement(rect, p));
43 : }
44 196 : }
45 :
46 : //---------------------------------------------------------
47 : // addHorizontalSpacing
48 : // This methods creates "walls". They are represented by
49 : // rectangles of zero height, and it is assumed that rectangles
50 : // of zero height vertically collide with everything. Use this
51 : // method ONLY when you want to create space that cannot tuck
52 : // above/below other elements of the staff.
53 : //---------------------------------------------------------
54 :
55 21890 : void Shape::addHorizontalSpacing(EngravingItem* item, double leftEdge, double rightEdge)
56 : {
57 21890 : constexpr double eps = 100 * std::numeric_limits<double>::epsilon();
58 21890 : if (leftEdge == rightEdge) { // HACK zero-width shapes collide with everything currently.
59 2171 : rightEdge += eps;
60 : }
61 21890 : add(RectF(leftEdge, 0, rightEdge - leftEdge, 0), item);
62 21890 : }
63 :
64 : //---------------------------------------------------------
65 : // translate
66 : //---------------------------------------------------------
67 :
68 2894290 : Shape& Shape::translate(const PointF& pt)
69 : {
70 6886614 : for (RectF& r : m_elements) {
71 3992324 : r.translate(pt);
72 : }
73 2894290 : invalidateBBox();
74 2894290 : return *this;
75 : }
76 :
77 13700 : void Shape::translateX(double xo)
78 : {
79 60985 : for (RectF& r : m_elements) {
80 47285 : r.setLeft(r.left() + xo);
81 47285 : r.setRight(r.right() + xo);
82 : }
83 13700 : invalidateBBox();
84 13700 : }
85 :
86 92555 : void Shape::translateY(double yo)
87 : {
88 232055 : for (RectF& r : m_elements) {
89 139500 : r.setTop(r.top() + yo);
90 139500 : r.setBottom(r.bottom() + yo);
91 : }
92 92555 : invalidateBBox();
93 92555 : }
94 :
95 : //---------------------------------------------------------
96 : // translated
97 : //---------------------------------------------------------
98 :
99 180288 : Shape Shape::translated(const PointF& pt) const
100 : {
101 180288 : Shape s;
102 180288 : s.m_elements.reserve(m_elements.size());
103 526558 : for (const ShapeElement& r : m_elements) {
104 346270 : s.add(r.translated(pt), r.item(), r.ignoreForLayout());
105 : }
106 180288 : return s;
107 0 : }
108 :
109 0 : Shape& Shape::scale(const SizeF& mag)
110 : {
111 0 : for (RectF& r : m_elements) {
112 0 : r.scale(mag);
113 : }
114 0 : invalidateBBox();
115 0 : return *this;
116 : }
117 :
118 43520 : Shape Shape::scaled(const SizeF& mag) const
119 : {
120 43520 : Shape s;
121 43520 : s.m_elements.reserve(m_elements.size());
122 179120 : for (const ShapeElement& r : m_elements) {
123 135600 : s.add(r.scaled(mag), r.item(), r.ignoreForLayout());
124 : }
125 43520 : return s;
126 0 : }
127 :
128 2530 : Shape& Shape::adjust(double xp1, double yp1, double xp2, double yp2)
129 : {
130 21603 : for (ShapeElement& element : m_elements) {
131 19073 : element.adjust(xp1, yp1, xp2, yp2);
132 : }
133 2530 : return *this;
134 : }
135 :
136 514 : Shape Shape::adjusted(double xp1, double yp1, double xp2, double yp2) const
137 : {
138 514 : Shape s;
139 514 : s.m_elements.reserve(m_elements.size());
140 1388 : for (const ShapeElement& element : m_elements) {
141 874 : s.add(element.adjusted(xp1, yp1, xp2, yp2));
142 : }
143 :
144 514 : return s;
145 0 : }
146 :
147 999 : Shape& Shape::pad(double p)
148 : {
149 1998 : for (ShapeElement& el : m_elements) {
150 999 : el.pad(p);
151 : }
152 999 : return *this;
153 : }
154 :
155 0 : Shape Shape::padded(double p) const
156 : {
157 0 : Shape s;
158 0 : s.m_elements.reserve(m_elements.size());
159 0 : for (const ShapeElement& el : m_elements) {
160 0 : s.add(el.padded(p));
161 : }
162 0 : return s;
163 0 : }
164 :
165 0 : mu::engraving::Shape& mu::engraving::Shape::transform(const muse::draw::Transform& t)
166 : {
167 0 : for (ShapeElement& el : m_elements) {
168 0 : bool ignoreForLayout = el.ignoreForLayout();
169 0 : const EngravingItem* item = el.item();
170 0 : RectF rect = t.map(el);
171 0 : el = ShapeElement(rect, item, ignoreForLayout);
172 : }
173 :
174 0 : return *this;
175 : }
176 :
177 0 : Shape Shape::transformed(const muse::draw::Transform& t)
178 : {
179 0 : Shape s(*this);
180 0 : return s.transform(t);
181 0 : }
182 :
183 8346949 : void Shape::invalidateBBox()
184 : {
185 8346949 : m_bbox = RectF();
186 8346949 : }
187 :
188 3003584 : const RectF& Shape::bbox() const
189 : {
190 3003584 : if (m_elements.size() == 0) {
191 : static const RectF _dummy;
192 335032 : return _dummy;
193 2668552 : } else if (m_elements.size() == 1) {
194 2596165 : return m_elements.at(0);
195 : } else {
196 72387 : if (m_bbox.isNull()) {
197 240726 : for (const ShapeElement& e : m_elements) {
198 211920 : m_bbox.unite(e);
199 : }
200 : }
201 72387 : return m_bbox;
202 : }
203 : }
204 :
205 468 : std::optional<ShapeElement> Shape::find_if(const std::function<bool(const ShapeElement&)>& func) const
206 : {
207 468 : auto it = std::find_if(m_elements.begin(), m_elements.end(), func);
208 468 : if (it == m_elements.end()) {
209 0 : return std::nullopt;
210 : }
211 468 : return std::make_optional(*it);
212 : }
213 :
214 28299 : std::optional<ShapeElement> Shape::find_first(ElementType type) const
215 : {
216 28583 : for (const ShapeElement& i : m_elements) {
217 27726 : if (i.item() && i.item()->type() == type) {
218 27442 : return std::make_optional(i);
219 : }
220 : }
221 :
222 857 : return std::nullopt;
223 : }
224 :
225 857 : std::optional<ShapeElement> Shape::get_first() const
226 : {
227 857 : if (m_elements.empty()) {
228 719 : return std::nullopt;
229 : }
230 138 : return std::make_optional(m_elements.at(0));
231 : }
232 :
233 : //-------------------------------------------------------------------
234 : // minVerticalDistance
235 : // a is located below this shape.
236 : // Calculates the minimum distance between two shapes.
237 : //-------------------------------------------------------------------
238 :
239 28099 : double Shape::minVerticalDistance(const Shape& a, double minHorizontalClearance) const
240 : {
241 28099 : if (empty() || a.empty()) {
242 315 : return 0.0;
243 : }
244 :
245 27784 : double dist = -DBL_MAX; // min real
246 481463 : for (const RectF& r2 : a.m_elements) {
247 453679 : if (r2.height() <= 0.0) {
248 1493 : continue;
249 : }
250 452186 : double bx1 = r2.left();
251 452186 : double bx2 = r2.right();
252 7813301 : for (const RectF& r1 : m_elements) {
253 7361115 : if (r1.height() <= 0.0) {
254 507 : continue;
255 : }
256 7360608 : double ax1 = r1.left();
257 7360608 : double ax2 = r1.right();
258 7360608 : if (mu::engraving::intersects(ax1, ax2, bx1, bx2, minHorizontalClearance)) {
259 644003 : dist = std::max(dist, r1.bottom() - r2.top());
260 : }
261 : }
262 : }
263 27784 : return dist;
264 : }
265 :
266 : //-------------------------------------------------------------------
267 : // verticalClearance
268 : // a is located below this shape.
269 : // Claculates the amount of clearance between the two shapes.
270 : // If there is an overlap, returns a negative value corresponging
271 : // to the amount of overlap.
272 : //-------------------------------------------------------------------
273 :
274 2087 : double Shape::verticalClearance(const Shape& a, double minHorizontalDistance) const
275 : {
276 2087 : if (empty() || a.empty()) {
277 126 : return 0.0;
278 : }
279 :
280 1961 : double dist = DBL_MAX; // max real
281 10992 : for (const RectF& r2 : a.m_elements) {
282 9031 : if (r2.height() <= 0.0) {
283 26 : continue;
284 : }
285 9005 : double bx1 = r2.left();
286 9005 : double bx2 = r2.right();
287 36996 : for (const RectF& r1 : m_elements) {
288 27991 : if (r1.height() <= 0.0) {
289 8 : continue;
290 : }
291 27983 : double ax1 = r1.left();
292 27983 : double ax2 = r1.right();
293 27983 : if (mu::engraving::intersects(ax1, ax2, bx1, bx2, minHorizontalDistance)) {
294 5965 : dist = std::min(dist, r2.top() - r1.bottom());
295 : }
296 : }
297 : }
298 1961 : return dist;
299 : }
300 :
301 : //----------------------------------------------------------------
302 : // clearsVertically()
303 : // a is located below this shape
304 : // returns true if, within the horizontal width of both shapes,
305 : // all parts of this shape are above all parts of a
306 : //----------------------------------------------------------------
307 32277 : bool Shape::clearsVertically(const Shape& a) const
308 : {
309 286221 : for (const RectF& r1 : a.m_elements) {
310 717966 : for (const RectF& r2 : m_elements) {
311 464022 : if (mu::engraving::intersects(r1.left(), r1.right(), r2.left(), r2.right())) {
312 31904 : if (std::min(r1.top(), r1.bottom()) <= std::max(r2.top(), r2.bottom())) {
313 1961 : return false;
314 : }
315 : }
316 : }
317 : }
318 30316 : return true;
319 : }
320 :
321 : //---------------------------------------------------------
322 : // left
323 : // compute left border
324 : //---------------------------------------------------------
325 :
326 199580 : double Shape::left() const
327 : {
328 199580 : double dist = DBL_MAX;
329 507326 : for (const ShapeElement& r : m_elements) {
330 : // TURBO-HACK: the only purpose of this is to ignore fingering for spacing the first CR segment after the header.
331 307746 : if (!RealIsNull(r.height()) && !(r.item() && r.item()->isFingering()) && r.left() < dist) {
332 235948 : dist = r.left();
333 : }
334 : }
335 199580 : return dist;
336 : }
337 :
338 : //---------------------------------------------------------
339 : // right
340 : // compute right border
341 : //---------------------------------------------------------
342 :
343 499600 : double Shape::right() const
344 : {
345 499600 : double dist = -DBL_MAX;
346 1152347 : for (const RectF& r : m_elements) {
347 652747 : if (r.right() > dist) {
348 538139 : dist = r.right();
349 : }
350 : }
351 499600 : return dist;
352 : }
353 :
354 : //---------------------------------------------------------
355 : // top
356 : //---------------------------------------------------------
357 :
358 37655 : double Shape::top() const
359 : {
360 37655 : double dist = DBL_MAX;
361 254718 : for (const RectF& r : m_elements) {
362 217063 : if (r.top() < dist) {
363 102542 : dist = r.top();
364 : }
365 : }
366 37655 : return dist;
367 : }
368 :
369 : //---------------------------------------------------------
370 : // bottom
371 : //---------------------------------------------------------
372 :
373 51485 : double Shape::bottom() const
374 : {
375 51485 : double dist = -DBL_MAX;
376 566267 : for (const RectF& r : m_elements) {
377 514782 : if (r.bottom() > dist) {
378 94682 : dist = r.bottom();
379 : }
380 : }
381 51485 : return dist;
382 : }
383 :
384 0 : double Shape::width() const
385 : {
386 0 : double left = DBL_MAX;
387 0 : double right = -DBL_MAX;
388 0 : for (const RectF& r : m_elements) {
389 0 : left = std::min(left, r.left());
390 0 : right = std::max(right, r.right());
391 : }
392 :
393 0 : return right - left;
394 : }
395 :
396 0 : double Shape::height() const
397 : {
398 0 : double top = DBL_MAX;
399 0 : double bottom = -DBL_MAX;
400 0 : for (const RectF& r : m_elements) {
401 0 : top = std::min(top, r.top());
402 0 : bottom = std::max(bottom, r.bottom());
403 : }
404 :
405 0 : return bottom - top;
406 : }
407 :
408 0 : double Shape::topAtX(double x) const
409 : {
410 0 : double localTop = DBL_MAX;
411 0 : for (const ShapeElement& el : m_elements) {
412 0 : if (el.left() < x && el.right() > x) {
413 0 : localTop = std::min(localTop, el.top());
414 : }
415 : }
416 :
417 0 : return localTop != DBL_MAX ? localTop : top();
418 : }
419 :
420 0 : double Shape::bottomAtX(double x) const
421 : {
422 0 : double localBottom = -DBL_MAX;
423 0 : for (const ShapeElement& el : m_elements) {
424 0 : if (el.left() < x && el.right() > x) {
425 0 : localBottom = std::max(localBottom, el.bottom());
426 : }
427 : }
428 :
429 0 : return localBottom != DBL_MAX ? localBottom : bottom();
430 : }
431 :
432 1452 : double Shape::rightMostEdgeAtHeight(double yAbove, double yBelow) const
433 : {
434 1452 : double edge = -DBL_MAX;
435 7090 : for (const ShapeElement& sh : m_elements) {
436 5638 : if (sh.bottom() > yAbove && sh.top() < yBelow) {
437 2757 : edge = std::max(edge, sh.right());
438 : }
439 : }
440 :
441 1452 : return edge;
442 : }
443 :
444 1421 : double Shape::leftMostEdgeAtHeight(double yAbove, double yBelow) const
445 : {
446 1421 : double edge = DBL_MAX;
447 6509 : for (const ShapeElement& sh : m_elements) {
448 5088 : if (sh.bottom() > yAbove && sh.top() < yBelow) {
449 2303 : edge = std::min(edge, sh.left());
450 : }
451 : }
452 :
453 1421 : return edge;
454 : }
455 :
456 0 : double Shape::leftMostEdgeAtTop() const
457 : {
458 0 : double edge = DBL_MAX;
459 0 : const double shapeTop = top();
460 0 : for (const ShapeElement& sh : m_elements) {
461 0 : if (muse::RealIsEqual(sh.top(), shapeTop)) {
462 0 : edge = std::min(edge, sh.left());
463 : }
464 : }
465 :
466 0 : return edge;
467 : }
468 :
469 : //---------------------------------------------------------
470 : // topDistance
471 : // p is on top of shape
472 : // returns negative values if there is an overlap
473 : //---------------------------------------------------------
474 :
475 0 : double Shape::topDistance(const PointF& p) const
476 : {
477 0 : double dist = DBL_MAX;
478 0 : for (const RectF& r : m_elements) {
479 0 : if (p.x() >= r.left() && p.x() < r.right()) {
480 0 : dist = std::min(dist, r.top() - p.y());
481 : }
482 : }
483 0 : return dist;
484 : }
485 :
486 : //---------------------------------------------------------
487 : // bottomDistance
488 : // p is below the shape
489 : // returns negative values if there is an overlap
490 : //---------------------------------------------------------
491 :
492 0 : double Shape::bottomDistance(const PointF& p) const
493 : {
494 0 : double dist = DBL_MAX;
495 0 : for (const RectF& r : m_elements) {
496 0 : if (p.x() >= r.left() && p.x() < r.right()) {
497 0 : dist = std::min(dist, p.y() - r.bottom());
498 : }
499 : }
500 0 : return dist;
501 : }
502 :
503 1909825 : void Shape::setBBox(const RectF& r, const EngravingItem* p)
504 : {
505 1909825 : IF_ASSERT_FAILED(type() == Type::Fixed) {
506 0 : return;
507 : }
508 :
509 1909825 : if (m_elements.empty()) {
510 1909825 : m_elements.push_back(ShapeElement(r, p));
511 : } else {
512 0 : m_elements[0] = ShapeElement(r, p);
513 : }
514 : }
515 :
516 0 : void Shape::addBBox(const RectF& r)
517 : {
518 : // IF_ASSERT_FAILED(type() == Type::Fixed) {
519 : // return;
520 : // }
521 :
522 0 : if (m_elements.empty()) {
523 0 : m_elements.push_back(RectF());
524 : }
525 :
526 0 : m_elements[0].unite(r);
527 0 : }
528 :
529 : //---------------------------------------------------------
530 : // add
531 : //---------------------------------------------------------
532 :
533 2247897 : void Shape::add(const Shape& s)
534 : {
535 2247897 : m_type = Type::Composite;
536 2247897 : m_elements.insert(m_elements.end(), s.m_elements.begin(), s.m_elements.end());
537 2247897 : invalidateBBox();
538 2247897 : }
539 :
540 2403888 : void Shape::add(const ShapeElement& shapeEl)
541 : {
542 2403888 : m_type = Type::Composite;
543 2403888 : m_elements.push_back(shapeEl);
544 2403888 : invalidateBBox();
545 2403888 : }
546 :
547 : //---------------------------------------------------------
548 : // remove
549 : //---------------------------------------------------------
550 :
551 0 : void Shape::remove(const RectF& r)
552 : {
553 0 : for (auto i = m_elements.begin(); i != m_elements.end(); ++i) {
554 0 : if (*i == r) {
555 0 : m_elements.erase(i);
556 0 : return;
557 : }
558 : }
559 :
560 0 : ASSERT_X("Shape::remove: RectF not found in Shape");
561 :
562 : invalidateBBox();
563 : }
564 :
565 0 : void Shape::remove(const Shape& s)
566 : {
567 0 : for (const RectF& r : s.m_elements) {
568 0 : remove(r);
569 : }
570 :
571 0 : invalidateBBox();
572 0 : }
573 :
574 0 : std::vector<RectF> Shape::toRects() const
575 : {
576 0 : std::vector<RectF> rects;
577 0 : rects.reserve(m_elements.size());
578 :
579 0 : for (const RectF& shapeEl : m_elements) {
580 0 : rects.push_back(shapeEl);
581 : }
582 :
583 0 : return rects;
584 0 : }
585 :
586 549 : void Shape::removeInvisibles()
587 : {
588 549 : muse::remove_if(m_elements, [](ShapeElement& shapeElement) {
589 1529 : return !shapeElement.item() || !shapeElement.item()->visible();
590 : });
591 549 : invalidateBBox();
592 549 : }
593 :
594 637 : void Shape::removeTypes(const std::set<ElementType>& types)
595 : {
596 637 : muse::remove_if(m_elements, [&types](ShapeElement& shapeElement) {
597 1850 : return shapeElement.item() && muse::contains(types, shapeElement.item()->type());
598 : });
599 637 : invalidateBBox();
600 637 : }
601 :
602 : //---------------------------------------------------------
603 : // contains
604 : //---------------------------------------------------------
605 :
606 0 : bool Shape::contains(const PointF& p) const
607 : {
608 0 : for (const RectF& r : m_elements) {
609 0 : if (r.contains(p)) {
610 0 : return true;
611 : }
612 : }
613 0 : return false;
614 : }
615 :
616 : //---------------------------------------------------------
617 : // intersects
618 : //---------------------------------------------------------
619 :
620 607319 : bool Shape::intersects(const RectF& rr) const
621 : {
622 1071021 : for (const RectF& r : m_elements) {
623 805994 : if (r.intersects(rr)) {
624 342292 : return true;
625 : }
626 : }
627 265027 : return false;
628 : }
629 :
630 : //---------------------------------------------------------
631 : // intersects
632 : //---------------------------------------------------------
633 :
634 521629 : bool Shape::intersects(const Shape& other) const
635 : {
636 754259 : for (const RectF& r : other.m_elements) {
637 571913 : if (intersects(r)) {
638 339283 : return true;
639 : }
640 : }
641 182346 : return false;
642 : }
643 :
644 0 : void Shape::paint(Painter& painter) const
645 : {
646 0 : for (const RectF& r : m_elements) {
647 0 : painter.drawRect(r);
648 : }
649 0 : }
650 :
651 0 : void mu::engraving::dump(const ShapeElement& se, std::stringstream& ss)
652 : {
653 0 : const RectF* r = &se;
654 0 : ss << "item: " << (se.item() ? se.item()->typeName() : "no") << " rect: ";
655 0 : muse::dump(*r, ss);
656 0 : }
657 :
658 0 : void mu::engraving::dump(const Shape& sh, std::stringstream& ss)
659 : {
660 0 : ss << "Shape size: " << sh.size() << "\n";
661 0 : for (const ShapeElement& r : sh.elements()) {
662 0 : ss << " ";
663 0 : dump(r, ss);
664 0 : ss << "\n";
665 : }
666 0 : }
667 :
668 0 : std::string mu::engraving::dump(const Shape& sh)
669 : {
670 0 : std::stringstream ss;
671 0 : dump(sh, ss);
672 0 : return ss.str();
673 0 : }
|