|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653 |
- /*
- ==============================================================================
-
- This file is part of the JUCE library.
- Copyright (c) 2020 - Raw Material Software Limited
-
- JUCE is an open source library subject to commercial or open-source
- licensing.
-
- By using JUCE, you agree to the terms of both the JUCE 6 End-User License
- Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
-
- End User License Agreement: www.juce.com/juce-6-licence
- Privacy Policy: www.juce.com/juce-privacy-policy
-
- Or: You may also use this code under the terms of the GPL v3 (see
- www.gnu.org/licenses).
-
- JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
- EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
- DISCLAIMED.
-
- ==============================================================================
- */
-
- namespace juce
- {
-
- //==============================================================================
- /**
- Maintains a set of rectangles as a complex region.
-
- This class allows a set of rectangles to be treated as a solid shape, and can
- add and remove rectangular sections of it, and simplify overlapping or
- adjacent rectangles.
-
- @see Rectangle
-
- @tags{Graphics}
- */
- template <typename ValueType>
- class RectangleList final
- {
- public:
- using RectangleType = Rectangle<ValueType>;
-
- //==============================================================================
- /** Creates an empty RectangleList */
- RectangleList() = default;
-
- /** Creates a copy of another list */
- RectangleList (const RectangleList& other) : rects (other.rects)
- {
- }
-
- /** Creates a list containing just one rectangle. */
- RectangleList (RectangleType rect)
- {
- addWithoutMerging (rect);
- }
-
- /** Copies this list from another one. */
- RectangleList& operator= (const RectangleList& other)
- {
- rects = other.rects;
- return *this;
- }
-
- /** Move constructor */
- RectangleList (RectangleList&& other) noexcept
- : rects (std::move (other.rects))
- {
- }
-
- /** Move assignment operator */
- RectangleList& operator= (RectangleList&& other) noexcept
- {
- rects = std::move (other.rects);
- return *this;
- }
-
- //==============================================================================
- /** Returns true if the region is empty. */
- bool isEmpty() const noexcept { return rects.isEmpty(); }
-
- /** Returns the number of rectangles in the list. */
- int getNumRectangles() const noexcept { return rects.size(); }
-
- /** Returns one of the rectangles at a particular index.
- @returns the rectangle at the index, or an empty rectangle if the index is out-of-range.
- */
- RectangleType getRectangle (int index) const noexcept { return rects[index]; }
-
- //==============================================================================
- /** Removes all rectangles to leave an empty region. */
- void clear()
- {
- rects.clearQuick();
- }
-
- /** Merges a new rectangle into the list.
-
- The rectangle being added will first be clipped to remove any parts of it
- that overlap existing rectangles in the list, and adjacent rectangles will be
- merged into it.
-
- The rectangle can have any size and may be empty, but if it's floating point
- then it's expected to not contain any INF values.
- */
- void add (RectangleType rect)
- {
- jassert (rect.isFinite()); // You must provide a valid rectangle to this method!
-
- if (! rect.isEmpty())
- {
- if (isEmpty())
- {
- rects.add (rect);
- }
- else
- {
- bool anyOverlaps = false;
-
- for (int j = rects.size(); --j >= 0;)
- {
- auto& ourRect = rects.getReference (j);
-
- if (rect.intersects (ourRect))
- {
- if (rect.contains (ourRect))
- rects.remove (j);
- else if (! ourRect.reduceIfPartlyContainedIn (rect))
- anyOverlaps = true;
- }
- }
-
- if (anyOverlaps && ! isEmpty())
- {
- RectangleList r (rect);
-
- for (auto& ourRect : rects)
- {
- if (rect.intersects (ourRect))
- {
- r.subtract (ourRect);
-
- if (r.isEmpty())
- return;
- }
- }
-
- rects.addArray (r.rects);
- }
- else
- {
- rects.add (rect);
- }
- }
- }
- }
-
- /** Merges a new rectangle into the list.
-
- The rectangle being added will first be clipped to remove any parts of it
- that overlap existing rectangles in the list.
- */
- void add (ValueType x, ValueType y, ValueType width, ValueType height)
- {
- add (RectangleType (x, y, width, height));
- }
-
- /** Dumbly adds a rectangle to the list without checking for overlaps.
-
- This simply adds the rectangle to the end, it doesn't merge it or remove
- any overlapping bits.
-
- The rectangle can have any size and may be empty, but if it's floating point
- then it's expected to not contain any INF values.
- */
- void addWithoutMerging (RectangleType rect)
- {
- jassert (rect.isFinite()); // You must provide a valid rectangle to this method!
-
- if (! rect.isEmpty())
- rects.add (rect);
- }
-
- /** Merges another rectangle list into this one.
-
- Any overlaps between the two lists will be clipped, so that the result is
- the union of both lists.
- */
- void add (const RectangleList& other)
- {
- for (auto& r : other)
- add (r);
- }
-
- /** Removes a rectangular region from the list.
-
- Any rectangles in the list which overlap this will be clipped and subdivided
- if necessary.
- */
- void subtract (RectangleType rect)
- {
- if (auto numRects = rects.size())
- {
- auto x1 = rect.getX();
- auto y1 = rect.getY();
- auto x2 = x1 + rect.getWidth();
- auto y2 = y1 + rect.getHeight();
-
- for (int i = numRects; --i >= 0;)
- {
- auto& r = rects.getReference (i);
-
- auto rx1 = r.getX();
- auto ry1 = r.getY();
- auto rx2 = rx1 + r.getWidth();
- auto ry2 = ry1 + r.getHeight();
-
- if (! (x2 <= rx1 || x1 >= rx2 || y2 <= ry1 || y1 >= ry2))
- {
- if (x1 > rx1 && x1 < rx2)
- {
- if (y1 <= ry1 && y2 >= ry2 && x2 >= rx2)
- {
- r.setWidth (x1 - rx1);
- }
- else
- {
- r.setX (x1);
- r.setWidth (rx2 - x1);
-
- rects.insert (++i, RectangleType (rx1, ry1, x1 - rx1, ry2 - ry1));
- ++i;
- }
- }
- else if (x2 > rx1 && x2 < rx2)
- {
- r.setX (x2);
- r.setWidth (rx2 - x2);
-
- if (y1 > ry1 || y2 < ry2 || x1 > rx1)
- {
- rects.insert (++i, RectangleType (rx1, ry1, x2 - rx1, ry2 - ry1));
- ++i;
- }
- }
- else if (y1 > ry1 && y1 < ry2)
- {
- if (x1 <= rx1 && x2 >= rx2 && y2 >= ry2)
- {
- r.setHeight (y1 - ry1);
- }
- else
- {
- r.setY (y1);
- r.setHeight (ry2 - y1);
-
- rects.insert (++i, RectangleType (rx1, ry1, rx2 - rx1, y1 - ry1));
- ++i;
- }
- }
- else if (y2 > ry1 && y2 < ry2)
- {
- r.setY (y2);
- r.setHeight (ry2 - y2);
-
- if (x1 > rx1 || x2 < rx2 || y1 > ry1)
- {
- rects.insert (++i, RectangleType (rx1, ry1, rx2 - rx1, y2 - ry1));
- ++i;
- }
- }
- else
- {
- rects.remove (i);
- }
- }
- }
- }
- }
-
- /** Removes all areas in another RectangleList from this one.
-
- Any rectangles in the list which overlap this will be clipped and subdivided
- if necessary.
-
- @returns true if the resulting list is non-empty.
- */
- bool subtract (const RectangleList& otherList)
- {
- for (auto& r : otherList)
- {
- if (isEmpty())
- return false;
-
- subtract (r);
- }
-
- return ! isEmpty();
- }
-
- /** Removes any areas of the region that lie outside a given rectangle.
-
- Any rectangles in the list which overlap this will be clipped and subdivided
- if necessary.
-
- Returns true if the resulting region is not empty, false if it is empty.
-
- @see getIntersectionWith
- */
- bool clipTo (RectangleType rect)
- {
- jassert (rect.isFinite()); // You must provide a valid rectangle to this method!
-
- bool notEmpty = false;
-
- if (rect.isEmpty())
- {
- clear();
- }
- else
- {
- for (int i = rects.size(); --i >= 0;)
- {
- auto& r = rects.getReference (i);
-
- if (! rect.intersectRectangle (r))
- rects.remove (i);
- else
- notEmpty = true;
- }
- }
-
- return notEmpty;
- }
-
- /** Removes any areas of the region that lie outside a given rectangle list.
-
- Any rectangles in this object which overlap the specified list will be clipped
- and subdivided if necessary.
-
- Returns true if the resulting region is not empty, false if it is empty.
-
- @see getIntersectionWith
- */
- template <typename OtherValueType>
- bool clipTo (const RectangleList<OtherValueType>& other)
- {
- if (isEmpty())
- return false;
-
- RectangleList result;
-
- for (auto& rect : rects)
- {
- for (auto& r : other)
- {
- auto clipped = r.template toType<ValueType>();
-
- if (rect.intersectRectangle (clipped))
- result.rects.add (clipped);
- }
- }
-
- swapWith (result);
- return ! isEmpty();
- }
-
- /** Creates a region which is the result of clipping this one to a given rectangle.
-
- Unlike the other clipTo method, this one doesn't affect this object - it puts the
- resulting region into the list whose reference is passed-in.
-
- Returns true if the resulting region is not empty, false if it is empty.
-
- @see clipTo
- */
- bool getIntersectionWith (RectangleType rect, RectangleList& destRegion) const
- {
- jassert (rect.isFinite()); // You must provide a valid rectangle to this method!
-
- destRegion.clear();
-
- if (! rect.isEmpty())
- for (auto r : rects)
- if (rect.intersectRectangle (r))
- destRegion.rects.add (r);
-
- return ! destRegion.isEmpty();
- }
-
- /** Swaps the contents of this and another list.
-
- This swaps their internal pointers, so is hugely faster than using copy-by-value
- to swap them.
- */
- void swapWith (RectangleList& otherList) noexcept
- {
- rects.swapWith (otherList.rects);
- }
-
- //==============================================================================
- /** Checks whether the region contains a given point.
- @returns true if the point lies within one of the rectangles in the list
- */
- bool containsPoint (Point<ValueType> point) const noexcept
- {
- for (auto& r : rects)
- if (r.contains (point))
- return true;
-
- return false;
- }
-
- /** Checks whether the region contains a given point.
- @returns true if the point lies within one of the rectangles in the list
- */
- bool containsPoint (ValueType x, ValueType y) const noexcept
- {
- return containsPoint (Point<ValueType> (x, y));
- }
-
- /** Checks whether the region contains the whole of a given rectangle.
-
- @returns true all parts of the rectangle passed in lie within the region
- defined by this object
- @see intersectsRectangle, containsPoint
- */
- bool containsRectangle (RectangleType rectangleToCheck) const
- {
- if (rects.size() > 1)
- {
- RectangleList r (rectangleToCheck);
-
- for (auto& rect : rects)
- {
- r.subtract (rect);
-
- if (r.isEmpty())
- return true;
- }
- }
- else if (! isEmpty())
- {
- return rects.getReference (0).contains (rectangleToCheck);
- }
-
- return false;
- }
-
- /** Checks whether the region contains any part of a given rectangle.
-
- @returns true if any part of the rectangle passed in lies within the region
- defined by this object
- @see containsRectangle
- */
- bool intersectsRectangle (RectangleType rectangleToCheck) const noexcept
- {
- for (auto& r : rects)
- if (r.intersects (rectangleToCheck))
- return true;
-
- return false;
- }
-
- /** Checks whether this region intersects any part of another one.
- @see intersectsRectangle
- */
- bool intersects (const RectangleList& other) const noexcept
- {
- for (auto& r : rects)
- if (other.intersectsRectangle (r))
- return true;
-
- return false;
- }
-
- //==============================================================================
- /** Returns the smallest rectangle that can enclose the whole of this region. */
- RectangleType getBounds() const noexcept
- {
- if (isEmpty())
- return {};
-
- auto& r = rects.getReference (0);
-
- if (rects.size() == 1)
- return r;
-
- auto minX = r.getX();
- auto minY = r.getY();
- auto maxX = minX + r.getWidth();
- auto maxY = minY + r.getHeight();
-
- for (int i = rects.size(); --i > 0;)
- {
- auto& r2 = rects.getReference (i);
-
- minX = jmin (minX, r2.getX());
- minY = jmin (minY, r2.getY());
- maxX = jmax (maxX, r2.getRight());
- maxY = jmax (maxY, r2.getBottom());
- }
-
- return { minX, minY, maxX - minX, maxY - minY };
- }
-
- /** Optimises the list into a minimum number of constituent rectangles.
-
- This will try to combine any adjacent rectangles into larger ones where
- possible, to simplify lists that might have been fragmented by repeated
- add/subtract calls.
- */
- void consolidate()
- {
- for (int i = 0; i < rects.size() - 1; ++i)
- {
- auto& r = rects.getReference (i);
- auto rx1 = r.getX();
- auto ry1 = r.getY();
- auto rx2 = rx1 + r.getWidth();
- auto ry2 = ry1 + r.getHeight();
-
- for (int j = rects.size(); --j > i;)
- {
- auto& r2 = rects.getReference (j);
- auto jrx1 = r2.getX();
- auto jry1 = r2.getY();
- auto jrx2 = jrx1 + r2.getWidth();
- auto jry2 = jry1 + r2.getHeight();
-
- // if the vertical edges of any blocks are touching and their horizontals don't
- // line up, split them horizontally..
- if (jrx1 == rx2 || jrx2 == rx1)
- {
- if (jry1 > ry1 && jry1 < ry2)
- {
- r.setHeight (jry1 - ry1);
- rects.add (RectangleType (rx1, jry1, rx2 - rx1, ry2 - jry1));
- i = -1;
- break;
- }
-
- if (jry2 > ry1 && jry2 < ry2)
- {
- r.setHeight (jry2 - ry1);
- rects.add (RectangleType (rx1, jry2, rx2 - rx1, ry2 - jry2));
- i = -1;
- break;
- }
- else if (ry1 > jry1 && ry1 < jry2)
- {
- r2.setHeight (ry1 - jry1);
- rects.add (RectangleType (jrx1, ry1, jrx2 - jrx1, jry2 - ry1));
- i = -1;
- break;
- }
- else if (ry2 > jry1 && ry2 < jry2)
- {
- r2.setHeight (ry2 - jry1);
- rects.add (RectangleType (jrx1, ry2, jrx2 - jrx1, jry2 - ry2));
- i = -1;
- break;
- }
- }
- }
- }
-
- for (int i = 0; i < rects.size() - 1; ++i)
- {
- auto& r = rects.getReference (i);
-
- for (int j = rects.size(); --j > i;)
- {
- if (r.enlargeIfAdjacent (rects.getReference (j)))
- {
- rects.remove (j);
- i = -1;
- break;
- }
- }
- }
- }
-
- /** Adds an x and y value to all the coordinates. */
- void offsetAll (Point<ValueType> offset) noexcept
- {
- for (auto& r : rects)
- r += offset;
- }
-
- /** Adds an x and y value to all the coordinates. */
- void offsetAll (ValueType dx, ValueType dy) noexcept
- {
- offsetAll (Point<ValueType> (dx, dy));
- }
-
- /** Scales all the coordinates. */
- template <typename ScaleType>
- void scaleAll (ScaleType scaleFactor) noexcept
- {
- for (auto& r : rects)
- r *= scaleFactor;
- }
-
- /** Applies a transform to all the rectangles.
- Obviously this will create a mess if the transform involves any
- rotation or skewing.
- */
- void transformAll (const AffineTransform& transform) noexcept
- {
- for (auto& r : rects)
- r = r.transformedBy (transform);
- }
-
- //==============================================================================
- /** Creates a Path object to represent this region. */
- Path toPath() const
- {
- Path p;
-
- for (auto& r : rects)
- p.addRectangle (r);
-
- return p;
- }
-
- //==============================================================================
- /** Standard method for iterating the rectangles in the list. */
- const RectangleType* begin() const noexcept { return rects.begin(); }
- /** Standard method for iterating the rectangles in the list. */
- const RectangleType* end() const noexcept { return rects.end(); }
-
- /** Increases the internal storage to hold a minimum number of rectangles.
- Calling this before adding a large number of rectangles means that
- the array won't have to keep dynamically resizing itself as the elements
- are added, and it'll therefore be more efficient.
- @see Array::ensureStorageAllocated
- */
- void ensureStorageAllocated (int minNumRectangles)
- {
- rects.ensureStorageAllocated (minNumRectangles);
- }
-
- private:
- //==============================================================================
- Array<RectangleType> rects;
- };
-
- } // namespace juce
|