1 /*
2 * Geometry.cpp
3 * OpenLieroX
4 *
5 * Created by Karel Petranek on 11.06.09.
6 * code under LGPL
7 *
8 */
9
10 #include <algorithm>
11 #include "Geometry.h"
12 #include "GfxPrimitives.h"
13 #include "CViewport.h"
14
15 //
16 // Line
17 //
18
19 ///////////////////
20 // Returns true if the given point is in the right from the line or if it is at the line
isRightFrom(int x,int y) const21 bool Line::isRightFrom(int x, int y) const {
22 VectorD2<int> rel = end - start;
23 x -= start.x; y -= start.y;
24 return rel.y * x <= y * rel.x;
25 }
26
27 ////////////////////
28 // Returns true if the point is at the line before the starting point
isBeforeStart(int x,int y) const29 bool Line::isBeforeStart(int x, int y) const {
30 VectorD2<int> rel = end - start;
31 x -= start.x; y -= start.y;
32 return rel.x * x + rel.y * y < 0;
33 }
34
35 ////////////////////
36 // Returns true if the point is at the line after the ending point
isAfterEnd(int x,int y) const37 bool Line::isAfterEnd(int x, int y) const {
38 VectorD2<int> rel = start - end;
39 x -= end.x; y -= end.y;
40 return rel.x * x + rel.y * y < 0;
41 }
42
isParallel(int x,int y) const43 bool Line::isParallel(int x, int y) const {
44 return !isBeforeStart(x,y) && !isAfterEnd(x,y);
45 }
46
containsY(int y,int & x,bool aimsDown) const47 bool Line::containsY(int y, int& x, bool aimsDown) const {
48 if(start.y == end.y) {
49 if(start.y == y) {
50 if(!aimsDown) {
51 if(start.x < end.x) x = start.x;
52 else x = end.x;
53 }
54 else {
55 if(start.x < end.x) x = end.x;
56 else x = start.x;
57 }
58 return true;
59 }
60 return false;
61 }
62
63 if(start.y <= y && end.y >= y && aimsDown) {
64 VectorD2<int> rel = end - start;
65 y -= start.y;
66 x = rel.x * y / rel.y;
67 x += start.x;
68 return true;
69 }
70
71 if(start.y >= y && end.y <= y && !aimsDown) {
72 VectorD2<int> rel = start - end;
73 y -= end.y;
74 x = rel.x * y / rel.y;
75 x += end.x;
76 return true;
77 }
78
79 return false;
80 }
81
82 ///////////////////
83 // Returns true if the line is horizontal (dy = 0)
isHorizontal() const84 bool Line::isHorizontal() const
85 {
86 return start.y == end.y;
87 }
88
89 //////////////////
90 // Returns true if the line intersects another line
intersects(const Line & l) const91 bool Line::intersects(const Line& l) const
92 {
93 // Get the analytical intersection and check that it is located on one of the lines
94
95 // ax + by + c = 0
96 const VectorD2<int> v1 = (end - start).orthogonal(); // a = v1.x, b = v1.y
97 const VectorD2<int> v2 = (l.end - l.start).orthogonal();
98 const int c1 = -v1.x * start.x - v1.y * start.y;
99 const int c2 = -v2.x * l.start.x - v2.y * l.start.y;
100
101 // Use determinants
102 // | b1 c1 |
103 // | b2 c2 |
104 // --------- = Xp
105 // | a1 b1 |
106 // | a2 b2 |
107 //
108 // | c1 a1 |
109 // | c2 a2 |
110 // --------- = Yp
111 // | a1 b1 |
112 // | a2 b2 |
113 const int denom = v1.x * v2.y - v2.x * v1.y;
114 if (!denom) // Parallel
115 return (float)v1.x / v2.x == (float)c1/c2; // Check for identity
116 const int xp_numer = v1.y * c2 - v2.y * c1;
117 const int yp_numer = c1 * v2.x - c2 * v1.x;
118
119 const int xp = xp_numer / denom;
120 const int yp = yp_numer / denom;
121
122 int res = abs(end.y - yp) + abs(start.y - yp) - abs(end.y - start.y);
123 res += abs(l.end.y - yp) + abs(l.start.y - yp) - abs(l.end.y - l.start.y);
124 res += abs(end.x - xp) + abs(start.x - xp) - abs(end.x - start.x);
125 res += abs(l.end.x - xp) + abs(l.start.x - xp) - abs(l.end.x - l.start.x);
126
127 return res == 0;
128 }
129
130 ///////////////////
131 // Squared distance of the line from the given point
distFromPoint2(const VectorD2<int> & pt) const132 float Line::distFromPoint2(const VectorD2<int> &pt) const
133 {
134 VectorD2<int> d = pt - start;
135 VectorD2<int> e = end - start;
136 int e2 = e.GetLength2();
137 int ed = e.x * d.x + e.y * d.y;
138 float t = CLAMP((float)ed/e2, 0.0f, 1.0f); // For points that are before/after end of the line we return distance to the ending points
139
140 CVec closest(start.x + t * e.x, start.y + t * e.y);
141 return SQR(closest.x - pt.x) + SQR(closest.y - pt.y);
142 }
143
144 ///////////////////
145 // Distance of the line from the given point
distFromPoint(const VectorD2<int> & pt) const146 float Line::distFromPoint(const VectorD2<int> &pt) const
147 {
148 return sqrtf(distFromPoint2(pt));
149 }
150
151
152
153
154 //
155 // Polygon2D class
156 //
157
Polygon2D(const Points & pts)158 Polygon2D::Polygon2D(const Points& pts)
159 {
160 // Add the points one by one
161 // We have to do this because the internal representation may vary from what we get
162 startPointAdding();
163 for (Points::const_iterator it = pts.begin(); it != pts.end(); ++it)
164 addPoint(*it);
165 endPointAdding();
166 }
167
168 ///////////////////
169 // Updates the convex flag, private
170 // The three points are last two points added to the polygon
checkConvex(const VectorD2<int> & pt1,const VectorD2<int> & pt2,const VectorD2<int> & pt3)171 void Polygon2D::checkConvex(const VectorD2<int>& pt1, const VectorD2<int>& pt2, const VectorD2<int>& pt3)
172 {
173 if (!convex)
174 return;
175
176 // Get the vertex sign
177 // TODO: why does this not work?
178 //int sign = (pt1 - pt2).Scalar(pt3 - pt2); //(pt2.x - pt1.x)*(pt3.y - pt2.y) - (pt2.y - pt1.y) * (pt3.x - pt1.x);
179 int sign = (pt1 - pt2).Cross(pt3 - pt2); // TODO: works only for simple polygons
180 convex = (lastsign * sign >= 0);
181 lastsign = sign;
182 }
183
184 ///////////////////
185 // Add a point to the polygon, startPointAdding() must be called before!
addPoint(const VectorD2<int> & pt)186 void Polygon2D::addPoint(const VectorD2<int>& pt)
187 {
188 assert(addingPoints);
189 points.push_back(pt);
190 if (points.size() > 2) {
191 Points::reverse_iterator p1 = points.rbegin();
192 p1++; p1++;
193 Points::reverse_iterator p2 = points.rbegin();
194 p2++;
195 checkConvex(*p1, *p2, pt);
196 }
197 }
198
199 ///////////////////
200 // Denotes start of polygon initialization (point adding)
startPointAdding()201 void Polygon2D::startPointAdding()
202 {
203 addingPoints = true;
204 }
205
206 ///////////////////
207 // Denotes end of polygon initialization (point adding)
endPointAdding()208 void Polygon2D::endPointAdding()
209 {
210 assert(addingPoints);
211
212 // Build and pre-process the polygon
213 if (points.size() < 3)
214 return;
215
216 int minx = points.begin()->x;
217 int miny = points.begin()->y;
218 int maxx = minx;
219 int maxy = miny;
220
221 // Create the lines
222 Points::iterator it1 = points.begin();
223 Points::iterator it2 = points.begin();
224 ++it2;
225 for (; it2 != points.end(); ++it2, ++it1) {
226 // Update min and max for X
227 minx = MIN(minx, MIN(it2->x, it1->x));
228 maxx = MAX(maxx, MAX(it2->x, it1->x));
229
230 // Make sure it always goes from top to bottom
231 if (it2->y < it1->y) {
232 lines.push_back(Line(*it2, *it1));
233 miny = MIN(miny, it2->y);
234 maxy = MAX(maxy, it1->y);
235 } else if (it2->y == it1->y) {
236 horizLines.push_back(Line(*it1, *it2)); // Horizontal lines are treated special
237 miny = MIN(miny, it1->y);
238 maxy = MAX(maxy, it1->y);
239 } else {
240 lines.push_back(Line(*it1, *it2));
241 miny = MIN(miny, it1->y);
242 maxy = MAX(maxy, it2->y);
243 }
244
245 }
246
247 // Calculate the overlay rect
248 overlay.x = minx;
249 overlay.y = miny;
250 overlay.w = maxx - minx;
251 overlay.h = maxy - miny;
252
253 // Close the polygon if necessary
254 if (*points.begin() != *points.rbegin()) {
255 if (points.begin()->y < points.rbegin()->y)
256 lines.push_back(Line(*points.begin(), *points.rbegin()));
257 else if (points.begin()->y == points.rbegin()->y)
258 horizLines.push_back(Line(*points.begin(), *points.rbegin()));
259 else
260 lines.push_back(Line(*points.rbegin(), *points.begin()));
261
262 Points::reverse_iterator p1 = points.rbegin();
263 p1++;
264 checkConvex(*p1, *points.rbegin(), *points.begin());
265 }
266
267 addingPoints = false;
268 }
269
minOverlayRect() const270 SDL_Rect Polygon2D::minOverlayRect() const {
271 assert(!addingPoints);
272 return overlay;
273 }
274
minOverlayRect(CViewport * v) const275 SDL_Rect Polygon2D::minOverlayRect(CViewport* v) const {
276 assert(!addingPoints);
277 const int wx = v->GetWorldX();
278 const int wy = v->GetWorldY();
279 const int l = v->GetLeft();
280 const int t = v->GetTop();
281
282 #define Tx(x) ((x - wx) * 2 + l)
283 #define Ty(y) ((y - wy) * 2 + t)
284
285 SDL_Rect r = { (SDLRect::Type) Tx(overlay.x), (SDLRect::Type) Ty(overlay.y), (SDLRect::TypeS) (overlay.w * 2), (SDLRect::TypeS) (overlay.h * 2) };
286
287 #undef Tx
288 #undef Ty
289 return r;
290 }
291
isInside(int x,int y) const292 bool Polygon2D::isInside(int x, int y) const
293 {
294 // Check the overlay rect first
295 if (x < overlay.x || x >= overlay.x + overlay.w || y < overlay.y || y >= overlay.y + overlay.h)
296 return false;
297
298 // Run one scanline in the level of the point and check that the point is inside
299 std::vector<int> isc; isc.reserve(lines.size());
300 for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++it) {
301 if (it->start.y <= y && y < it->end.y) {
302 const float slope = (float)(it->start.x - it->end.x) / (it->start.y - it->end.y);
303 isc.push_back( (int)(slope * (y - it->start.y)) + it->start.x ); // Calculate the intersection
304 }
305 }
306
307 std::sort(isc.begin(), isc.end());
308 for (unsigned i = 0; i < isc.size(); i += 2)
309 if (isc[i] <= x && x <= isc[i + 1]) {
310 return true;
311 }
312
313 // Check horizontal lines
314 for (Lines::const_iterator it = horizLines.begin(); it != horizLines.end(); ++it)
315 if (y == it->start.y && it->start.x <= x && x <= it->end.x)
316 return true;
317
318 return false;
319 }
320
intersectsConvex(const Polygon2D & poly) const321 bool Polygon2D::intersectsConvex(const Polygon2D& poly) const
322 {
323 // Collision check using the Separating Axis Theorem
324
325 // TODO...
326
327 // Horizontal lines
328 /*for (Lines::iterator it = horizLines.begin(); it != horizLines.end(); it++) {
329 int minx = poly.horizLines.begin()->start.x;
330 int maxx = minx;
331 for (Lines::iterator it = poly.horizLines.begin(); it != poly.horizLines.end(); it++) {
332 if (it->start.x < minx)
333 minx = it->start.x;
334 if (it->start.x > maxx)
335 maxx = it->start.x;
336 if (it->end.x > maxx)
337 maxx = it->start.x;
338 if (it->end.x < minx)
339 minx = it->start.y;
340 }
341 }*/
342 return false;
343 }
344
intersects(const Polygon2D & poly) const345 bool Polygon2D::intersects(const Polygon2D& poly) const
346 {
347
348 /*if (convex && poly.convex) {
349 return intersectsConvex(poly);
350 }*/
351
352 // First check the overlay rect
353 SDL_Rect tmp = overlay;
354 if (!ClipRefRectWith(tmp.x, tmp.y, tmp.w, tmp.h, (SDLRect)poly.overlay))
355 return false;
356
357 // Check for line intersection
358 for (Lines::const_iterator it1 = lines.begin(); it1 != lines.end(); ++it1) {
359 for (Lines::const_iterator it2 = poly.lines.begin(); it2 != poly.lines.end(); ++it2)
360 if (it1->intersects(*it2))
361 return true;
362 for (Lines::const_iterator it2 = poly.horizLines.begin(); it2 != poly.horizLines.end(); ++it2)
363 if (it1->intersects(*it2))
364 return true;
365 }
366
367 // Horizontal lines
368 for (Lines::const_iterator it1 = horizLines.begin(); it1 != horizLines.end(); ++it1) {
369 for (Lines::const_iterator it2 = poly.lines.begin(); it2 != poly.lines.end(); ++it2)
370 if (it1->intersects(*it2))
371 return true;
372 for (Lines::const_iterator it2 = poly.horizLines.begin(); it2 != poly.horizLines.end(); ++it2) {
373 if (it1->start.y == it2->start.y && it1->intersects(*it2))
374 return true;
375 }
376 }
377
378 // Can happen when the whole polygon is inside the other one
379 return isInside(poly.points.begin()->x, poly.points.begin()->y)
380 || poly.isInside(points.begin()->x, points.begin()->y);
381 }
382
383 /////////////////////
384 // Checks for rect intersection
intersectsRect(const SDL_Rect & r) const385 bool Polygon2D::intersectsRect(const SDL_Rect &r) const
386 {
387 // First check the overlay rect
388 SDL_Rect tmp = overlay;
389 if (!ClipRefRectWith(tmp.x, tmp.y, tmp.w, tmp.h, (SDLRect)r))
390 return false;
391
392 Line l1(VectorD2<int>(r.x, r.y), VectorD2<int>(r.x + r.w, r.y));
393 Line l2(VectorD2<int>(r.x + r.w, r.y), VectorD2<int>(r.x + r.w, r.y + r.h));
394 Line l3(VectorD2<int>(r.x + r.w, r.y + r.h), VectorD2<int>(r.x, r.y + r.h));
395 Line l4(VectorD2<int>(r.x, r.y + r.h), VectorD2<int>(r.x, r.y));
396
397 for (Lines::const_iterator it = lines.begin(); it != lines.end(); it++) {
398 if (it->intersects(l1) || it->intersects(l2) || it->intersects(l3) || it->intersects(l4))
399 return true;
400 }
401
402 for (Lines::const_iterator it = horizLines.begin(); it != horizLines.end(); ++it) {
403 if (it->intersects(l1) || it->intersects(l2) || it->intersects(l3) || it->intersects(l4))
404 return true;
405 }
406
407 return false;
408 }
409
410 //////////////////
411 // Checks for circle intersection
intersectsCircle(VectorD2<int> & midpoint,int radius) const412 bool Polygon2D::intersectsCircle(VectorD2<int> &midpoint, int radius) const
413 {
414
415 for (Lines::const_iterator it = lines.begin(); it != lines.end(); it++) {
416 if (it->distFromPoint(midpoint) <= radius)
417 return true;
418 }
419
420 for (Lines::const_iterator it = horizLines.begin(); it != horizLines.end(); ++it) {
421 if (it->distFromPoint(midpoint) <= radius)
422 return true;
423 }
424
425 return false;
426 }
427