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