2 // Copyright (C) 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
4 // This program is free software; you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation; either version 3 of the License, or
7 // (at your option) any later version.
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // You should have received a copy of the GNU General Public License
15 // along with this program; if not, write to the Free Software
16 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 #ifndef GNASH_SNAPPINGRANGE_H
20 #define GNASH_SNAPPINGRANGE_H
28 #include <boost/cstdint.hpp>
35 /// Snapping range class. Can hold a number of 2D ranges and combines
36 /// ranges that come very close. This class is used for multiple invalidated
37 /// bounds calculation.
39 /// Additionally to merge intersecting ranges this class also "snaps" ranges
40 /// together which "probably" would be rendered in one single step. When the
41 /// area of the potential merged range is not much greater than the sum of
42 /// two single range areas, then these two ranges are merged together to one
43 /// single range. The "snap factor" defines how much bigger the merged area
44 /// can become to be a snap candidate (it's the maximum allowed factor that
45 /// the merged area can be greater than the sum).
47 /// Optimally the factor 1.0 would be best, but multiple snapping ranges also
48 /// increase rendering preprocessing overhead and the factor tries to equalize
49 /// this. The default value is usually fine for most situations.
51 /// The factor method makes sure that very close, but also very different
52 /// shapes, don't get merged, like in the following example:
61 /// +-----------------------------------+
63 /// +-----------------------------------+
65 /// Merging these two ranges would create a much bigger range which in some
66 /// situations means that rendering is notably slower (for example, when
67 /// there is a scaled bitmap behind these shapes).
69 // Forward declarations.
72 /// returns true when two ranges should be merged together
73 template<typename T
> inline bool snaptest(
74 const geometry::Range2d
<T
>& range1
,
75 const geometry::Range2d
<T
>& range2
, const float snapFactor
);
79 class SnappingRanges2d
82 typedef geometry::Range2d
<T
> RangeType
;
83 typedef std::vector
<RangeType
> RangeList
;
84 typedef typename
RangeList::size_type size_type
;
86 template<typename U
> friend std::ostream
& operator<<(std::ostream
& os
,
87 const SnappingRanges2d
<U
>& r
);
98 /// Templated copy constructor, for casting between range types
100 SnappingRanges2d(const SnappingRanges2d
<U
>& from
)
102 _snapFactor(from
.getSnapFactor()),
103 _singleMode(from
.getSingleMode()),
104 _rangesLimit(from
.getRangeCountLimit()),
107 if (from
.isWorld()) setWorld();
108 else if (from
.isNull()) setNull();
110 // TODO: can we safely assume that the 'from' parameter was
113 // TODO: use visitor pattern !
114 for (size_type i
= 0, e
= from
.size(); i
!= e
; ++i
) {
115 const Range2d
<U
>& r
= from
.getRange(i
);
122 /// Sets the snapping factor (which must be > 1.0). Higher factors make
123 /// the ranges more attractive for snapping. A good value is usually 1.3.
124 void setSnapFactor(const float factor
) {
125 assert(factor
> 1.0f
);
126 _snapFactor
= factor
;
129 float getSnapFactor() const {
133 /// if mode==true, then the snapping ranges will act like a normal Range2d
134 void setSingleMode(const bool mode
) {
138 bool getSingleMode() const {
142 /// Sets the maximum number of ranges allowed (to avoid lots of small
144 void setRangeCountLimit(const size_type limit
) {
145 _rangesLimit
= limit
;
148 size_type
getRangeCountLimit() const {
152 /// Copy the snapping settings from another ranges list, without
153 /// copying the ranges itself
154 void inheritConfig(const SnappingRanges2d
<T
>& from
) {
155 _snapFactor
= from
._snapFactor
;
156 _singleMode
= from
._singleMode
;
159 /// Merge two ranges based on snaptest.
160 struct ExpandToIfSnap
163 ExpandToIfSnap(const RangeType
& rt
, const float snapFactor
)
166 _snapFactor(snapFactor
)
169 bool operator()(RangeType
& r
) {
170 if (snaptest(r
, _rt
, _snapFactor
)) {
177 const RangeType
& _rt
;
178 const float _snapFactor
;
184 Scale(const float scale
) : _scale(scale
) {}
185 void operator()(RangeType
& r
) {
195 GrowBy(const float factor
) : _factor(factor
) {}
196 void operator()(RangeType
& r
) {
206 AddTo(SnappingRanges2d
<T
>& us
) : _this(us
) {}
207 void operator()(const RangeType
& r
) {
211 SnappingRanges2d
<T
>& _this
;
214 class IntersectsRange
217 IntersectsRange(const RangeType
& range
) : _range(range
) {}
218 bool operator()(const RangeType
& us
) {
219 return us
.intersects(_range
);
222 const RangeType
& _range
;
228 ContainsPoint(const T x
, const T y
) : _x(x
), _y(y
) {}
229 bool operator()(const RangeType
& us
) {
230 return us
.contains(_x
, _y
);
239 ContainsRange(const RangeType
& range
) : _range(range
) {}
240 bool operator()(const RangeType
& us
) {
241 return us
.contains(_range
);
244 const RangeType
& _range
;
248 /// Add a Range to the set, merging when possible and appropriate
249 void add(const RangeType
& range
) {
250 if (range
.isWorld()) {
255 if (range
.isNull()) return;
258 if (_ranges
.empty()) _ranges
.resize(1);
259 _ranges
[0].expandTo(range
);
263 ExpandToIfSnap
exp(range
, _snapFactor
);
264 if (visit(exp
)) return;
266 // reached this point we need a new range
267 _ranges
.push_back(range
);
272 /// combines two snapping ranges
273 void add(const SnappingRanges2d
<T
>& other
) {
274 const RangeList
& rl
= other
._ranges
;
275 std::for_each(rl
.begin(), rl
.end(), AddTo(*this));
278 /// Grows all ranges by the specified amount
279 void growBy(const T amount
) {
281 if (isWorld() || isNull()) return;
283 std::for_each(_ranges
.begin(), _ranges
.end(), GrowBy(amount
));
287 /// Scale all ranges by the specified factor
288 void scale(const float factor
) {
290 if (isWorld() || isNull()) return;
292 std::for_each(_ranges
.begin(), _ranges
.end(), Scale(factor
));
296 /// Resets to NULL range
301 /// Resets to one range with world flags
303 if (isWorld()) return;
305 _ranges
[0].setWorld();
308 /// Returns true, when the ranges equal world range
309 bool isWorld() const {
310 return ((size()==1) && (_ranges
.front().isWorld()));
313 /// Returns true, when there is no range
314 bool isNull() const {
315 return _ranges
.empty();
318 /// Returns the number of ranges in the list
319 size_type
size() const {
321 return _ranges
.size();
324 /// Returns the range at the specified index
325 const RangeType
& getRange(size_type index
) const {
327 assert(index
<size());
328 return _ranges
[index
];
331 /// Return a range that surrounds *all* added ranges. This is used mainly
332 /// for compatibilty issues.
333 RangeType
getFullArea() const {
338 int rcount
= _ranges
.size();
340 for (int rno
=0; rno
<rcount
; rno
++)
341 range
.expandTo(_ranges
[rno
]);
347 /// Returns true if any of the ranges intersect the given range
349 /// Note that a NULL range doesn't intersect anything
350 /// and a WORLD range intersects everything except a NULL Range.
352 bool intersects(const RangeType
& r
) const {
355 return std::find_if(_ranges
.begin(), _ranges
.end(), IntersectsRange(r
))
359 /// Returns true if any of the ranges contains the point
360 bool contains(T x
, T y
) const {
363 return std::find_if(_ranges
.begin(), _ranges
.end(), ContainsPoint(x
, y
))
367 /// Returns true if any of the ranges contains the range
369 /// Note that a NULL range is not contained in any range and
370 /// a WORLD range is onluy contained in another WORLD range.
372 bool contains(const RangeType
& r
) const {
375 return std::find_if(_ranges
.begin(), _ranges
.end(), ContainsRange(r
))
380 /// Returns true if all ranges in the given SnappingRanges2d
381 /// are contained in at least one of the ranges composing this
384 /// Note that a NULL range is not contained in any range and
385 /// a WORLD range is onluy contained in another WORLD range.
387 bool contains(const SnappingRanges2d
<T
>& o
) const
391 // o.finalize(); // should I finalize the other range too ?
393 // Null range set doesn't contain and isn't contained by anything
394 if ( isNull() ) return false;
395 if ( o
.isNull() ) return false;
397 // World range contains everything (except null ranges)
398 if ( isWorld() ) return true;
400 // This snappingrange is neither NULL nor WORLD
401 // The other can still be WORLD, but in that case the
402 // first iteration would return false
404 /// TODO: use a visitor !
405 for (unsigned rno
=0, rcount
=o
.size(); rno
<rcount
; rno
++)
407 RangeType r
= o
.getRange(rno
);
419 /// Intersect this ranges list with the given ranges list,
420 /// updating the current ranges list.
421 /// Note this is currently a relatively expensive operation
422 /// for complex lists.
424 void intersect(const SnappingRanges2d
<T
>& o
)
431 if (o
.isWorld()) return;
433 // We create a new ranges set for each range in "o" and
434 // then update ourselves with the *union* of these ranges.
435 // Anybody knows a better method (in terms of efficieny) ?
437 std::vector
<SnappingRanges2d
<T
> > list
;
438 list
.reserve(o
.size());
440 //TODO: use a visitor !
441 for (unsigned rno
=0, rcount
=o
.size(); rno
<rcount
; rno
++) {
443 // add a copy of ourselves to the list
444 list
.push_back(*this);
446 // intersect that copy with the single range
447 list
.back().intersect(o
.getRange(rno
));
451 // update ourselves with the union of the "list"
453 for (size_type lno
=0, lcount
=list
.size(); lno
<lcount
; lno
++) {
460 /// Intersects this ranges list with the given single range,
461 /// updating the current ranges list.
462 void intersect(const RangeType
& r
)
467 if (isWorld()) { // world intersection with X = X
473 if (isNull()) return; // NULL will always remain NULL
475 if (r
.isNull()) { // X intersection with NULL = NULL
480 if (r
.isWorld()) return; // X intersection with WORLD = X
482 // TODO: use a vector (remember to walk in reverse dir.)
483 for (int rno
=_ranges
.size()-1; rno
>=0; rno
--) {
485 RangeType newrange
= Intersection(_ranges
[rno
], r
);
487 if (newrange
.isNull())
488 _ranges
.erase(_ranges
.begin() + rno
);
490 _ranges
[rno
] = newrange
;
494 /// Combines known ranges. Previously merged ranges may have come close
495 /// to other ranges. Algorithm could be optimized.
496 void combineRanges() const {
498 // makes no sense in single mode
499 if (_singleMode
) return;
507 int rcount
= _ranges
.size();
511 for (int i
=0; i
<rcount
; i
++) {
513 for (int j
=i
+1; j
<rcount
; j
++) {
515 if (snaptest(_ranges
[i
], _ranges
[j
], _snapFactor
)) {
517 _ranges
[i
].expandTo(_ranges
[j
]);
519 _ranges
.erase(_ranges
.begin() + j
);
521 restart
=true; // restart from beginning
531 // limit number of ranges
532 if (_ranges
.size() > _rangesLimit
) {
534 // We found way too much ranges, so reduce to just one single range.
535 // We could also double the factor and try again, but that probably
536 // won't make much difference, so we avoid the trouble...
538 RangeType single
= getFullArea();
546 /// Visit the current Ranges set
548 /// Visitor functor will be invoked
549 /// for each RangeType in the current set.
551 /// The visitor functor will
552 /// receive a RangeType reference; must return true if
553 /// it wants next item or true to exit the loop.
555 /// @return false if the visitor reached the end.
556 template<class V
> inline bool visit(V
& visitor
) const
558 typename
RangeList::iterator it
, e
;
559 for (it
= _ranges
.begin(), e
= _ranges
.end(); it
!= e
; ++it
) {
560 if (!visitor(*it
)) break;
562 return it
!= _ranges
.end();
565 /// Visit the current Ranges set
567 /// Visitor functor will be invoked inconditionally
568 /// for each RangeType in the current set.
570 /// The visitor functor will receive a RangeType reference.
572 template<class V
> inline void visitAll(V
& visitor
) const
574 for_each(_ranges
.begin(), _ranges
.end(), visitor
);
580 /// Calls combineRanges() once in a while, but not always. Avoids too many
581 /// combineRanges() checks, which could slow down everything.
582 void combineRangesLazy() {
583 const size_type max
= 5;
585 if (_combineCounter
> max
) combineRanges();
588 void finalize() const {
589 if (_combineCounter
> 0) combineRanges();
592 /// The current Ranges list.
594 /// Mutable due to lazy finalization. This isn't very nice, but better than
596 mutable RangeList _ranges
;
598 /// snapping factor - see setSnapFactor()
601 /// if set, only a single, outer range is maintained (extended).
604 /// maximum number of ranges allowed
605 size_type _rangesLimit
;
607 /// Counter used in finalizing ranges.
608 mutable size_type _combineCounter
;
614 operator<< (std::ostream
& os
, const SnappingRanges2d
<T
>& r
)
616 if ( r
.isNull() ) return os
<< "NULL";
617 if ( r
.isWorld() ) return os
<< "WORLD";
619 typedef typename SnappingRanges2d
<T
>::RangeList R
;
621 const R
& ranges
= r
._ranges
;
623 std::copy(ranges
.begin(), ranges
.end(),
624 std::ostream_iterator
<typename
R::value_type
>(os
, ","));
632 inline bool snaptest(const geometry::Range2d
<T
>& range1
,
633 const geometry::Range2d
<T
>& range2
, const float snapFactor
)
636 // when they intersect anyway, they should of course be merged!
637 // TODO: not really, a "+" style ranges list might be worth to
638 // remain unmerged (but needs special handling, i.e. create three
639 // ranges out of two)...
640 if (range1
.intersects(range2
)) return true;
642 geometry::Range2d
<T
> temp
= range1
;
643 temp
.expandTo(range2
);
645 return (range1
.getArea() + range2
.getArea()) * snapFactor
>
650 } // anonymous namespace
651 } // namespace geometry
653 /// Standard snapping 2d ranges type for invalidated bounds calculation
654 typedef geometry::SnappingRanges2d
<boost::int32_t> InvalidatedRanges
;