Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
intersection-by-smashing.cpp
Go to the documentation of this file.
1#include <2geom/d2.h>
2#include <2geom/sbasis.h>
5
6#include <cstdlib>
7#include <cstdio>
8#include <vector>
9#include <algorithm>
10
11namespace Geom {
12using namespace Geom;
13
14/*
15 * Computes the top and bottom boundaries of the L_\infty neighborhood
16 * of a curve. The curve is supposed to be a graph over the x-axis.
17 */
18static
19void computeLinfinityNeighborhood( D2<SBasis > const &f, double tol, D2<Piecewise<SBasis> > &topside, D2<Piecewise<SBasis> > &botside ){
20 double signx = ( f[X].at0() > f[X].at1() )? -1 : 1;
21 double signy = ( f[Y].at0() > f[Y].at1() )? -1 : 1;
22
23 Piecewise<D2<SBasis> > top, bot;
24 top = Piecewise<D2<SBasis> > (f);
25 top.cuts.insert( top.cuts.end(), 2);
26 top.segs.insert( top.segs.end(), D2<SBasis>(SBasis(Linear( f[X].at1(), f[X].at1()+2*tol*signx)),
27 SBasis(Linear( f[Y].at1() )) ));
28 bot = Piecewise<D2<SBasis> >(f);
29 bot.cuts.insert( bot.cuts.begin(), - 1 );
30 bot.segs.insert( bot.segs.begin(), D2<SBasis>(SBasis(Linear( f[X].at0()-2*tol*signx, f[X].at0())),
31 SBasis(Linear( f[Y].at0() )) ));
32 top += Point(-tol*signx, tol);
33 bot += Point( tol*signx, -tol);
34
35 if ( signy < 0 ){
36 std::swap( top, bot );
37 top += Point( 0, 2*tol);
38 bot += Point( 0, -2*tol);
39 }
40 topside = make_cuts_independent(top);
41 botside = make_cuts_independent(bot);
42}
43
44
45/*
46 * Compute top and bottom boundaries of the L^infty nbhd of the graph of a *monotonic* function f.
47 * if f is increasing, it is given by [f(t-tol)-tol, f(t+tol)+tol].
48 * if not, it is [f(t+tol)-tol, f(t-tol)+tol].
49 */
50static
52 top = f + tol;
53 top.offsetDomain( - tol );
54 top.cuts.insert( top.cuts.end(), f.domain().max() + tol);
55 top.segs.insert( top.segs.end(), SBasis(Linear( f.lastValue() + tol )) );
56
57 bot = f - tol;
58 bot.offsetDomain( tol );
59 bot.cuts.insert( bot.cuts.begin(), f.domain().min() - tol);
60 bot.segs.insert( bot.segs.begin(), SBasis(Linear( f.firstValue() - tol )) );
61
62 if (f.firstValue() > f.lastValue()) {
63 std::swap(top, bot);
64 top += 2 * tol;
65 bot -= 2 * tol;
66 }
67}
68
69/*
70 * Returns the intervals over which the curve keeps its slope
71 * in one of the 8 sectors delimited by x=0, y=0, y=x, y=-x.
72 */
73std::vector<Interval> monotonicSplit(D2<SBasis> const &p){
74 std::vector<Interval> result;
75
77
78 std::vector<double> someroots;
79 std::vector<double> cuts (2,0.);
80 cuts[1] = 1.;
81
82 someroots = roots(v[X]);
83 cuts.insert( cuts.end(), someroots.begin(), someroots.end() );
84
85 someroots = roots(v[Y]);
86 cuts.insert( cuts.end(), someroots.begin(), someroots.end() );
87
88 //we could split in the middle to avoid computing roots again...
89 someroots = roots(v[X]-v[Y]);
90 cuts.insert( cuts.end(), someroots.begin(), someroots.end() );
91
92 someroots = roots(v[X]+v[Y]);
93 cuts.insert( cuts.end(), someroots.begin(), someroots.end() );
94
95 sort(cuts.begin(),cuts.end());
96 unique(cuts.begin(), cuts.end() );
97
98 for (unsigned i=1; i<cuts.size(); i++){
99 result.push_back( Interval( cuts[i-1], cuts[i] ) );
100 }
101 return result;
102}
103
104//std::vector<Interval> level_set( D2<SBasis> const &f, Rect region){
105// std::vector<Interval> x_in_reg = level_set( f[X], region[X] );
106// std::vector<Interval> y_in_reg = level_set( f[Y], region[Y] );
107// std::vector<Interval> result = intersect ( x_in_reg, y_in_reg );
108// return result;
109//}
110
111/*TODO: remove this!!!
112 * the minimum would be to move it to piecewise.h but this would be stupid.
113 * The best would be to let 'compose' be aware of extension modes (constant, linear, polynomial..)
114 * (I think the extension modes (at start and end) should be properties of the pwsb).
115 */
116static
117void prolongateByConstants( Piecewise<SBasis> &f, double paddle_width ){
118 if ( f.size() == 0 ) return; //do we have a covention about the domain of empty pwsb?
119 f.cuts.insert( f.cuts.begin(), f.cuts.front() - paddle_width );
120 f.segs.insert( f.segs.begin(), SBasis( f.segs.front().at0() ) );
121 f.cuts.insert( f.cuts.end(), f.cuts.back() + paddle_width );
122 f.segs.insert( f.segs.end(), SBasis( f.segs.back().at1() ) );
123}
124
125static
127 return inter1.times[X].min() < inter2.times[Y].min();
128}
129/*Fuse contiguous intersection domains
130 *
131 */
132static
133void cleanup_and_fuse( std::vector<SmashIntersection> &inters ){
134 std::sort( inters.begin(), inters.end(), compareIntersectionsTimesX);
135 for (unsigned i=0; i < inters.size(); i++ ){
136 for (unsigned j=i+1; j < inters.size() && inters[i].times[X].intersects( inters[j].times[X]) ; j++ ){
137 if (inters[i].times[Y].intersects( inters[j].times[Y] ) ){
138 inters[i].times.unionWith(inters[j].times);
139 inters[i].bbox.unionWith(inters[j].bbox);
140 inters.erase( inters.begin() + j );
141 }
142 }
143 }
144}
145
146/* Computes the intersection of two sets given as (ordered) union intervals.
147 */
148static
149std::vector<Interval> intersect( std::vector<Interval> const &a, std::vector<Interval> const &b){
150 std::vector<Interval> result;
151 //TODO: use order to optimize this!
152 for (auto i : a){
153 for (auto j : b){
154 OptInterval c( i );
155 c &= j;
156 if ( c ) {
157 result.push_back( *c );
158 }
159 }
160 }
161 return result;
162}
163
164/* Returns the intervals over which the curves are in the
165 * tol-neighborhood one of the other for the L_\infty metric.
166 * WARNING: each curve is supposed to be a graph over x or y axis
167 * (but not necessarily the same axis for both) and the smaller
168 * the slope the better (typically <=45°).
169 */
170std::vector<SmashIntersection> monotonic_smash_intersect( D2<SBasis> const &a, D2<SBasis> const &b, double tol){
171 using std::swap;
172
173 // a and b or X and Y may have to be exchanged, so make local copies.
174 D2<SBasis> aa = a;
175 D2<SBasis> bb = b;
176 bool swapresult = false;
177 bool swapcoord = false;//debug only!
178
179 //if the (enlarged) bounding boxes don't intersect, stop.
180 OptRect abounds = bounds_fast( a );
181 OptRect bbounds = bounds_fast( b );
182 if ( !abounds || !bbounds ) return std::vector<SmashIntersection>();
183 abounds->expandBy(tol);
184 if ( !(abounds->intersects(*bbounds))){
185 return std::vector<SmashIntersection>();
186 }
187
188 //Choose the best curve to be re-parametrized by x or y values.
189 OptRect dabounds = bounds_exact(derivative(a));
190 OptRect dbbounds = bounds_exact(derivative(b));
191 if ( dbbounds->min().length() > dabounds->min().length() ){
192 aa=b;
193 bb=a;
194 swap( dabounds, dbbounds );
195 swapresult = true;
196 }
197
198 //Choose the best coordinate to use as new parameter
199 double dxmin = std::min( std::abs((*dabounds)[X].max()), std::abs((*dabounds)[X].min()) );
200 double dymin = std::min( std::abs((*dabounds)[Y].max()), std::abs((*dabounds)[Y].min()) );
201 if ( (*dabounds)[X].max()*(*dabounds)[X].min() < 0 ) dxmin=0;
202 if ( (*dabounds)[Y].max()*(*dabounds)[Y].min() < 0 ) dymin=0;
203 assert (dxmin>=0 && dymin>=0);
204
205 if (dxmin < dymin) {
206 aa = D2<SBasis>( aa[Y], aa[X] );
207 bb = D2<SBasis>( bb[Y], bb[X] );
208 swapcoord = true;
209 }
210
211 //re-parametrize aa by the value of x.
212 Interval x_range_strict( aa[X].at0(), aa[X].at1() );
213 Piecewise<SBasis> y_of_x = pw_compose_inverse(aa[Y],aa[X], 2, 1e-5);
214
215 //Compute top and bottom boundaries of the L^infty nbhd of aa.
216 Piecewise<SBasis> top_ay, bot_ay;
217 computeLinfinityNeighborhood( y_of_x, tol, top_ay, bot_ay);
218
219 Interval ax_range = top_ay.domain();//i.e. aa[X] domain ewpanded by tol.
220 std::vector<Interval> bx_in_ax_range = level_set(bb[X], ax_range );
221
222 // find times when bb is in the neighborhood of aa.
223 std::vector<Interval> tbs;
224 for (auto & i : bx_in_ax_range){
225 D2<Piecewise<SBasis> > bb_in;
226 bb_in[X] = Piecewise<SBasis> ( portion( bb[X], i ) );
227 bb_in[Y] = Piecewise<SBasis> ( portion( bb[Y], i) );
228 bb_in[X].setDomain( i );
229 bb_in[Y].setDomain( i );
230
232 Interval level;
233 h = bb_in[Y] - compose( top_ay, bb_in[X] );
234 level = Interval( -infinity(), 0 );
235 std::vector<Interval> rts_lo = level_set( h, level);
236 h = bb_in[Y] - compose( bot_ay, bb_in[X] );
237 level = Interval( 0, infinity());
238 std::vector<Interval> rts_hi = level_set( h, level);
239
240 std::vector<Interval> rts = intersect( rts_lo, rts_hi );
241 tbs.insert(tbs.end(), rts.begin(), rts.end() );
242 }
243
244 std::vector<SmashIntersection> result(tbs.size(), SmashIntersection());
245
246 /* for each solution I, find times when aa is in the neighborhood of bb(I).
247 * (Note: the preimage of bb[X](I) by aa[X], enlarged by tol, is a good approximation of this:
248 * it would give points in the 2*tol neighborhood of bb (if the slope of aa is never more than 1).
249 * + faster computation.
250 * - implies little jumps depending on the subdivision of the input curve into monotonic pieces
251 * and on the choice of preferred axis. If noticeable, these jumps would feel random to the user :-(
252 */
253 for (unsigned j=0; j<tbs.size(); j++){
254 result[j].times[Y] = tbs[j];
255 std::vector<Interval> tas;
256 //TODO: replace this by some option in the "compose(pw,pw)" method!
257 Piecewise<SBasis> fat_y_of_x = y_of_x;
258 prolongateByConstants( fat_y_of_x, 100*(1+tol) );
259
260 D2<Piecewise<SBasis> > top_b, bot_b;
261 D2<SBasis> bbj = portion( bb, tbs[j] );
262 computeLinfinityNeighborhood( bbj, tol, top_b, bot_b );
263
265 Interval level;
266 h = top_b[Y] - compose( fat_y_of_x, top_b[X] );
267 level = Interval( +infinity(), 0 );
268 std::vector<Interval> rts_top = level_set( h, level);
269 for (auto & idx : rts_top){
270 idx = Interval( top_b[X].valueAt( idx.min() ),
271 top_b[X].valueAt( idx.max() ) );
272 }
273 assert( rts_top.size() == 1 );
274
275 h = bot_b[Y] - compose( fat_y_of_x, bot_b[X] );
276 level = Interval( 0, -infinity());
277 std::vector<Interval> rts_bot = level_set( h, level);
278 for (auto & idx : rts_bot){
279 idx = Interval( bot_b[X].valueAt( idx.min() ),
280 bot_b[X].valueAt( idx.max() ) );
281 }
282 assert( rts_bot.size() == 1 );
283 rts_top = intersect( rts_top, rts_bot );
284 assert (rts_top.size() == 1);
285 Interval x_dom = rts_top[0];
286
287 if ( x_dom.max() <= x_range_strict.min() ){
288 tas.push_back( Interval ( ( aa[X].at0() < aa[X].at1() ) ? 0 : 1 ) );
289 }else if ( x_dom.min() >= x_range_strict.max() ){
290 tas.push_back( Interval ( ( aa[X].at0() < aa[X].at1() ) ? 1 : 0 ) );
291 }else{
292 tas = level_set(aa[X], x_dom );
293 }
294 assert( tas.size()==1 );
295 result[j].times[X] = tas.front();
296
297 result[j].bbox = Rect( bbj.at0(), bbj.at1() );
298 Interval y_dom( aa[Y](result[j].times[X].min()), aa[Y](result[j].times[X].max()) );
299 result[j].bbox.unionWith( Rect( x_dom, y_dom ) );
300 }
301
302 if (swapresult) {
303 for (auto & i : result){
304 swap( i.times[X], i.times[Y]);
305 }
306 }
307 if (swapcoord) {
308 for (auto & i : result){
309 swap( i.bbox[X], i.bbox[Y] );
310 }
311 }
312
313 //TODO: cleanup result? fuse contiguous intersections?
314 return result;
315}
316
317std::vector<SmashIntersection> smash_intersect( D2<SBasis> const &a, D2<SBasis> const &b, double tol){
318 std::vector<SmashIntersection> result;
319
320 std::vector<Interval> acuts = monotonicSplit(a);
321 std::vector<Interval> bcuts = monotonicSplit(b);
322 for (auto & acut : acuts){
323 D2<SBasis> ai = portion( a, acut);
324 for (auto & bcut : bcuts){
325 D2<SBasis> bj = portion( b, bcut);
326 std::vector<SmashIntersection> ai_cap_bj = monotonic_smash_intersect( ai, bj, tol );
327 for (auto & k : ai_cap_bj){
328 k.times[X] = k.times[X] * acut.extent() + acut.min();
329 k.times[Y] = k.times[Y] * bcut.extent() + bcut.min();
330 }
331 result.insert( result.end(), ai_cap_bj.begin(), ai_cap_bj.end() );
332 }
333 }
335 return result;
336}
337
338}
339
340/*
341 Local Variables:
342 mode:c++
343 c-file-style:"stroustrup"
344 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
345 indent-tabs-mode:nil
346 fill-column:99
347 End:
348*/
349// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
Adaptor that creates 2D functions from 1D ones.
Definition d2.h:55
Point at1() const
Definition d2.h:125
Point at0() const
Definition d2.h:121
Point valueAt(double t) const
Definition d2.h:133
constexpr C min() const
constexpr C max() const
bool intersects(CRect const &r) const
Check whether the rectangles have any common points.
CPoint min() const
Get the corner of the rectangle with smallest coordinate values.
Range of real numbers that is never empty.
Definition interval.h:59
Function that interpolates linearly between two values.
Definition linear.h:55
Range of real numbers that can be empty.
Definition interval.h:199
Axis-aligned rectangle that can be empty.
Definition rect.h:203
Function defined as discrete pieces.
Definition piecewise.h:71
Interval domain() const
Definition piecewise.h:215
unsigned size() const
Definition piecewise.h:131
void offsetDomain(double o)
Definition piecewise.h:196
output_type lastValue() const
Definition piecewise.h:109
std::vector< double > cuts
Definition piecewise.h:75
output_type firstValue() const
Definition piecewise.h:106
std::vector< T > segs
Definition piecewise.h:76
Two-dimensional point that doubles as a vector.
Definition point.h:66
Axis aligned, non-empty rectangle.
Definition rect.h:92
Polynomial in symmetric power basis.
Definition sbasis.h:70
Css & result
double c[8][4]
Lifts one dimensional objects into 2D.
constexpr Coord infinity()
Get a value representing infinity.
Definition coord.h:88
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
Various utility functions.
Definition affine.h:22
D2< Piecewise< SBasis > > make_cuts_independent(Piecewise< D2< SBasis > > const &a)
Definition d2-sbasis.cpp:75
OptInterval bounds_exact(Bezier const &b)
Definition bezier.cpp:310
MultiDegree< n > max(MultiDegree< n > const &p, MultiDegree< n > const &q)
Returns the maximal degree appearing in the two arguments for each variables.
Definition sbasisN.h:158
static void computeLinfinityNeighborhood(D2< SBasis > const &f, double tol, D2< Piecewise< SBasis > > &topside, D2< Piecewise< SBasis > > &botside)
std::vector< SmashIntersection > smash_intersect(D2< SBasis > const &a, D2< SBasis > const &b, double tol)
static void prolongateByConstants(Piecewise< SBasis > &f, double paddle_width)
D2< T > compose(D2< T > const &a, T const &b)
Definition d2.h:405
@ intersects
Definition geom.cpp:16
std::vector< double > roots(SBasis const &s)
static bool compareIntersectionsTimesX(SmashIntersection const &inter1, SmashIntersection const &inter2)
std::vector< SmashIntersection > monotonic_smash_intersect(D2< SBasis > const &a, D2< SBasis > const &b, double tol)
static void cleanup_and_fuse(std::vector< SmashIntersection > &inters)
Bezier portion(const Bezier &a, double from, double to)
Definition bezier.cpp:250
Bezier derivative(Bezier const &a)
Definition bezier.cpp:282
Piecewise< SBasis > pw_compose_inverse(SBasis const &f, SBasis const &g, unsigned order, double zero)
Piecewise< SBasis > min(SBasis const &f, SBasis const &g)
Return the more negative of the two functions pointwise.
std::vector< Interval > monotonicSplit(D2< SBasis > const &p)
OptInterval bounds_fast(Bezier const &b)
Definition bezier.cpp:305
std::vector< Point > intersect(const xAx &C1, const xAx &C2)
Definition conicsec.cpp:361
std::vector< Interval > level_set(D2< SBasis > const &f, Rect region)
two-dimensional geometric operators.
Polynomial in symmetric power basis (S-basis)