aboutsummaryrefslogtreecommitdiff
path: root/externals/g3dlite/G3D/Rect2D.h
blob: 2fb58c504656c13155b33d72cf4b22ffb5e378a0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
/**
  @file Rect2D.h
 
  @maintainer Morgan McGuire, http://graphics.cs.williams.edu
 
  @created 2003-11-13
  @created 2009-11-16

  Copyright 2000-2009, Morgan McGuire.
  All rights reserved.
 */

#ifndef G3D_Rect2D_h
#define G3D_Rect2D_h

// Linux defines this as a macro
#ifdef border
#undef border
#endif

#include "G3D/platform.h"
#include "G3D/Array.h"
#include "G3D/Vector2.h"

#ifdef _MSC_VER
// Turn off "conditional expression is constant" warning; MSVC generates this
// for debug assertions in inlined methods.
#   pragma warning (disable : 4127)
#endif


namespace G3D {

class Any;

/**
 If you are using this class for pixel rectangles, keep in mind that the last
 pixel you can draw to is at x0() + width() - 1.
 */
class Rect2D {
private:
    Vector2 min, max;

    /**
     Returns true if the whole polygon is clipped.
     @param p Value of the point
     @param axis Index [0 or 1] of the axis to clip along?
     @param clipGreater Are we clipping greater than or less than the line?
     @param inPoly Polygon being clipped
     @param outPoly The clipped polygon
     */
    template<class T>
    static bool clipSide2D(
        const float p, bool clipGreater, int axis, 
        const Array<T>& inPoly, Array<T>& outPoly) {

        outPoly.clear();
        int i0 = -1;

        Vector2 pt1;
        bool c1 = true;

        float negate = clipGreater ? -1 : 1;

        // Find a point that is not clipped
        for (i0 = 0; (i0 < inPoly.length()) && c1; ++i0) {
            pt1 = inPoly[i0];       
            c1  = (negate * pt1[axis]) < (negate * p);
        }

        // We incremented i0 one time to many
        --i0;

        if (c1) {
            // We could not find an unclipped point
            return true;
        }

        outPoly.append(pt1);

        // for each point in inPoly,
        //     if the point is outside the side and the previous one was also outside, continue
        //     if the point is outside the side and the previous one was inside, cut the line
        //     if the point is inside the side and the previous one was also inside, append the points
        //     if the point is inside the side and the previous one was outside, cut the line    
        for (int i = 1; i <= inPoly.length(); ++i) {
            T pt2 = inPoly[(i + i0) % inPoly.length()];
            bool    c2  = (negate * pt2[axis]) < (negate * p);

            if (c1 ^ c2) {

                if (!c1 && c2 && (i > 1)) {
                    // Unclipped to clipped trasition and not the first iteration
                    outPoly.append(pt1);
                }

                // only one point is clipped, find where the line crosses the clipping plane


                float alpha;
                if (pt2[axis] == pt1[axis]) {
                    alpha = 0;
                } else {
                    alpha = (p - pt1[axis]) / (pt2[axis] - pt1[axis]);
                }
                outPoly.append(pt1.lerp(pt2, alpha));
            } else if (! (c1 || c2) && (i != 1)) {
                // neither point is clipped (don't do this the first time 
                // because we appended the first pt before the loop)
                outPoly.append(pt1);
            }
        
            pt1 = pt2;
            c1 = c2;
        }

        return false;
    }

public:

    /** \param any Must either Rect2D::xywh(#, #, #, #) or Rect2D::xyxy(#, #, #, #)*/
    Rect2D(const Any& any);
    
    /** Converts the Rect2D to an Any. */
    operator Any() const;

    Rect2D() : min(0, 0), max(0, 0) {}

    /** Creates a rectangle at 0,0 with the given width and height*/
    Rect2D(const Vector2& wh) : min(0, 0), max(wh.x, wh.y) {}

    /** Computes a rectangle that contains both @a a and @a b.  
        Note that even if @a or @b has zero area, its origin will be included.*/
    Rect2D(const Rect2D& a, const Rect2D& b) {
        min = a.min.min(b.min);
        max = a.max.max(b.max);
    }

    /** @brief Uniformly random point on the interior */
    Vector2 randomPoint() const {
        return Vector2(uniformRandom(0, max.x - min.x) + min.x,
                       uniformRandom(0, max.y - min.y) + min.y);
    }

    float width() const {
        return max.x - min.x;
    }

    float height() const {
        return max.y - min.y;
    }

    float x0() const {
        return min.x;
    }

    float x1() const {
        return max.x;
    }

    float y0() const {
        return min.y;
    }

    float y1() const {
        return max.y;
    }

    /** Min, min corner */
    Vector2 x0y0() const {
        return min;
    }

    Vector2 x1y0() const {
        return Vector2(max.x, min.y);
    }

    Vector2 x0y1() const {
        return Vector2(min.x, max.y);
    }

    /** Max,max corner */
    Vector2 x1y1() const {
        return max;
    }

    /** Width and height */
    Vector2 wh() const {
        return max - min;
    }

    Vector2 center() const {
        return (max + min) * 0.5;
    }

    float area() const {
        return width() * height();
    }

    bool isFinite() const {
        return (min.isFinite() && max.isFinite());
    }

    Rect2D lerp(const Rect2D& other, float alpha) const {
        Rect2D out;
        
        out.min = min.lerp(other.min, alpha);
        out.max = max.lerp(other.max, alpha);

        return out;
    }

    static Rect2D xyxy(float x0, float y0, float x1, float y1) {
        Rect2D r;
        
        r.min.x = G3D::min(x0, x1);
        r.min.y = G3D::min(y0, y1);
        r.max.x = G3D::max(x0, x1);
        r.max.y = G3D::max(y0, y1);

        return r;
    }

    static Rect2D xyxy(const Vector2& v0, const Vector2& v1) {
        Rect2D r;

        r.min = v0.min(v1);
        r.max = v0.max(v1);

        return r;
    }

    static Rect2D xywh(float x, float y, float w, float h) {
        return xyxy(x, y, x + w, y + h);
    }

    static Rect2D xywh(const Vector2& v, const Vector2& w) {
        return xyxy(v.x, v.y, v.x + w.x, v.y + w.y);
    }

    /** Constructs a Rect2D with infinite boundaries.
        Use isFinite() to test either min or max.
     */
    static Rect2D inf() {
        return xyxy(Vector2::inf(), Vector2::inf());
    }

    bool contains(const Vector2& v) const {
        return (v.x >= min.x) && (v.y >= min.y) && (v.x <= max.x) && (v.y <= max.y);
    }

    bool contains(const Rect2D& r) const {
        return (min.x <= r.min.x) && (min.y <= r.min.y) &&
               (max.x >= r.max.x) && (max.y >= r.max.y);
    }

    /** True if there is non-zero area to the intersection between @a this and @a r.
        Note that two rectangles that are adjacent do not intersect because there is
        zero area to the overlap, even though one of them "contains" the corners of the other.*/
    bool intersects(const Rect2D& r) const {
        return (min.x < r.max.x) && (min.y < r.max.y) &&
               (max.x > r.min.x) && (max.y > r.min.y);
    }

    /** Like intersection, but counts the adjacent case as touching. */
    bool intersectsOrTouches(const Rect2D& r) const {
        return (min.x <= r.max.x) && (min.y <= r.max.y) &&
               (max.x >= r.min.x) && (max.y >= r.min.y);
    }

    Rect2D operator*(float s) const {
        return xyxy(min.x * s, min.y * s, max.x * s, max.y * s);
    }

    Rect2D operator/(float s) const {
        return xyxy(min / s, max / s);
    }

    Rect2D operator/(const Vector2& s) const {
        return xyxy(min / s, max / s);
    }

    Rect2D operator+(const Vector2& v) const {
        return xyxy(min + v, max + v);
    }

    Rect2D operator-(const Vector2& v) const {
        return xyxy(min - v, max - v);
    }

    bool operator==(const Rect2D& other) const {
        return (min == other.min) && (max == other.max);
    }

    bool operator!=(const Rect2D& other) const {
        return (min != other.min) || (max != other.max);
    }

    /** Returns the corners in the order: (min,min), (max,min), (max,max), (min,max). */
    Vector2 corner(int i) const {
        debugAssert(i >= 0 && i < 4);
        switch (i & 3) {
        case 0:
            return Vector2(min.x, min.y);
        case 1:
            return Vector2(max.x, min.y);
        case 2:
            return Vector2(max.x, max.y);
        case 3:
            return Vector2(min.x, max.y);
        default:
            // Should never get here
            return Vector2(0, 0);
        }
    }


    /** @deprecated  
     @sa expand() */
    Rect2D border(float delta) const {
        return Rect2D::xywh(x0() + delta, 
                     y0() + delta, 
                     width() - 2.0f * delta, 
                     height() - 2.0f * delta);
    }

    /** Returns a new Rect2D that is bigger/smaller by the specified amount 
        (negative is shrink.) */
    Rect2D expand(float delta) const {
        float newX = x0() - delta;
        float newY = y0() - delta;
        float newW = width() + 2.0f * delta;
        float newH = height() + 2.0f * delta;

        if (newW < 0.0f) {
            newX = (x0() + width()) / 2.0f;
            newW = 0.0f;
        }

        if (newH < 0.0f) {
            newY = (y0() + height()) / 2.0f;
            newH = 0.0f;
        }
        return Rect2D::xywh(newX, newY, newW, newH);
    }

    /** 
     Clips so that the rightmost point of the outPoly is at rect.x1 (e.g. a 800x600 window produces
     rightmost point 799, not 800).  The results are suitable for pixel rendering if iRounded.
     Templated so that it will work for Vector2,3,4 (the z and w components are interpolated linearly).
     The template parameter must define T.lerp and contain x and y components.

     If the entire polygon is clipped by a single side, the result will be empty.
     The result might also have zero area but not be empty.
     */
    template<class T>
    void clip(const Array<T>& inPoly, Array<T>& outPoly) const {

        const bool greaterThan = true;
        const bool lessThan    = false;
        const int  X = 0;
        const int  Y = 1;

        Array<T> temp;

        bool entirelyClipped =
          clipSide2D(x0(), lessThan,    X,    inPoly,  temp)    ||
          clipSide2D(x1(), greaterThan, X,    temp,    outPoly) ||
          clipSide2D(y0(), lessThan,    Y,    outPoly, temp)    || 
          clipSide2D(y1(), greaterThan, Y,    temp,    outPoly);

        if (entirelyClipped) {
            outPoly.clear();
        }
    }


    /** Returns the largest, centered Rect2D that can fit inside this
        while maintaining the aspect ratio of x:y.  Convenient for
        displaying images in odd-shaped windows.
    */
    Rect2D largestCenteredSubRect(float ww, float hh) const {
        float textureAspect = hh / ww;
        float viewAspect = height() / width();

        if (viewAspect > textureAspect) {
            // The view is too tall
            float h = width() * textureAspect;
            float y = (height() - h) / 2;
            return Rect2D::xywh(0, y, width(), h) + corner(0);
        } else {
            // The view is too wide
            float w = height() / textureAspect;
            float x = (width() - w) / 2;
            return Rect2D::xywh(x, 0, w, height()) + corner(0);
        }
    }

    /**
     Returns the overlap region between the two rectangles.  This may have zero area
     if they do not intersect.  See the two-Rect2D constructor for a way to compute
     a union-like rectangle.
     */
    Rect2D intersect(const Rect2D& other) const {
        if (intersects(other)) {
            return Rect2D::xyxy(min.max(other.min), max.min(other.max));
        }else{
            return Rect2D::xywh(0, 0, 0, 0);
        }
    }
};

typedef Rect2D AABox2D;
}

#endif