geometry.cpp
author insilmaril
Thu, 10 Dec 2009 17:04:48 +0000
changeset 816 3086ee01554a
parent 798 d251c7b2de54
child 824 36eb4b8f409e
permissions -rw-r--r--
minor fixes
insilmaril@650
     1
#include "geometry.h"
insilmaril@650
     2
insilmaril@656
     3
#include <math.h>
insilmaril@662
     4
#include "misc.h"
insilmaril@656
     5
insilmaril@792
     6
#include <QString>
insilmaril@792
     7
insilmaril@789
     8
#include <iostream>
insilmaril@662
     9
using namespace std;
insilmaril@650
    10
insilmaril@789
    11
insilmaril@650
    12
QRectF addBBox(QRectF r1, QRectF r2)
insilmaril@650
    13
{	
insilmaril@650
    14
	// Find smallest QRectF containing given rectangles
insilmaril@650
    15
insilmaril@650
    16
	QRectF n;
insilmaril@650
    17
	// Set left border
insilmaril@650
    18
	if (r1.left() <= r2.left() )
insilmaril@650
    19
		n.setLeft(r1.left() );
insilmaril@650
    20
	else
insilmaril@650
    21
		n.setLeft(r2.left() );
insilmaril@650
    22
		
insilmaril@650
    23
	// Set top border		
insilmaril@650
    24
	if (r1.top() <= r2.top() )
insilmaril@650
    25
		n.setTop(r1.top() );
insilmaril@650
    26
	else
insilmaril@650
    27
		n.setTop(r2.top() );
insilmaril@650
    28
		
insilmaril@650
    29
	// Set right border
insilmaril@650
    30
	if (r1.right() <= r2.right() )
insilmaril@650
    31
		n.setRight(r2.right() );
insilmaril@650
    32
	else
insilmaril@650
    33
		n.setRight(r1.right() );
insilmaril@650
    34
		
insilmaril@650
    35
	// Set bottom 
insilmaril@650
    36
	if (r1.bottom() <= r2.bottom() )
insilmaril@650
    37
		n.setBottom(r2.bottom() );
insilmaril@650
    38
	else
insilmaril@650
    39
		n.setBottom(r1.bottom() );
insilmaril@650
    40
	return n;
insilmaril@650
    41
}
insilmaril@650
    42
insilmaril@754
    43
bool isInBox(const QPointF &p, const QRectF &box)
insilmaril@650
    44
{
insilmaril@650
    45
    if (p.x() >= box.left() && p.x() <= box.right()  
insilmaril@650
    46
	&& p.y() <= box.bottom() && p.y() >= box.top() )
insilmaril@650
    47
		return true;
insilmaril@650
    48
    return false;	
insilmaril@650
    49
}
insilmaril@656
    50
insilmaril@792
    51
Vector::Vector ():QPointF ()
insilmaril@792
    52
{
insilmaril@792
    53
}
insilmaril@792
    54
insilmaril@792
    55
Vector::Vector (const QPointF &p):QPointF (p)
insilmaril@792
    56
{
insilmaril@792
    57
}
insilmaril@792
    58
insilmaril@792
    59
Vector::Vector (qreal x, qreal y):QPointF (x,y)
insilmaril@792
    60
{
insilmaril@792
    61
}
insilmaril@792
    62
insilmaril@792
    63
//! Normalize vector
insilmaril@792
    64
void Vector::normalize ()
insilmaril@792
    65
{
insilmaril@792
    66
	if (x()==0 && y()==0) return;
insilmaril@792
    67
	qreal l=sqrt ( x()*x() + y()*y() );
insilmaril@792
    68
	setX (x()/l);
insilmaril@792
    69
	setY (y()/l);
insilmaril@792
    70
}
insilmaril@792
    71
insilmaril@792
    72
//! Dot product of two vectors
insilmaril@792
    73
qreal Vector::dotProduct (const QPointF &b)
insilmaril@792
    74
{
insilmaril@792
    75
	return x()*b.x() + y()*b.y();
insilmaril@792
    76
}
insilmaril@792
    77
insilmaril@792
    78
insilmaril@792
    79
void Vector::scale (const qreal &f)
insilmaril@792
    80
{
insilmaril@792
    81
	setX (x()*f);
insilmaril@792
    82
	setY (y()*f);
insilmaril@792
    83
}
insilmaril@792
    84
insilmaril@792
    85
void Vector::invert ()
insilmaril@792
    86
{
insilmaril@792
    87
	setX (-x());
insilmaril@792
    88
	setY (-y());
insilmaril@792
    89
}
insilmaril@792
    90
insilmaril@798
    91
QPointF Vector::toQPointF ()
insilmaril@798
    92
{
insilmaril@798
    93
	return QPointF (x(),y());
insilmaril@798
    94
}
insilmaril@798
    95
insilmaril@792
    96
/*! Calculate the projection of a polygon on an axis
insilmaril@792
    97
    and returns it as a [min, max] interval  */
insilmaril@789
    98
ConvexPolygon::ConvexPolygon ()
insilmaril@789
    99
{
insilmaril@789
   100
}
insilmaril@789
   101
insilmaril@789
   102
ConvexPolygon::ConvexPolygon (QPolygonF p):QPolygonF (p)
insilmaril@789
   103
{
insilmaril@789
   104
}
insilmaril@789
   105
insilmaril@789
   106
void ConvexPolygon::calcCentroid() 
insilmaril@789
   107
{
insilmaril@789
   108
	// Calculate area and centroid
insilmaril@789
   109
	// http://en.wikipedia.org/wiki/Centroid
insilmaril@789
   110
	qreal cx,cy,p;
insilmaril@789
   111
	cx=cy=0;
insilmaril@789
   112
	_area=0;
insilmaril@789
   113
insilmaril@789
   114
	append (at(0));
insilmaril@789
   115
	for (int i=0;i<size()-1;i++)
insilmaril@789
   116
	{
insilmaril@789
   117
		p=at(i).x() * at(i+1).y() - at(i+1).x() * at(i).y();
insilmaril@789
   118
		_area+=p;
insilmaril@789
   119
		cx+=(at(i).x()+at(i+1).x()) * p;
insilmaril@789
   120
		cy+=(at(i).y()+at(i+1).y()) * p;
insilmaril@789
   121
	}	
insilmaril@789
   122
	pop_back();
insilmaril@789
   123
	// area is negative if vertices ordered counterclockwise
insilmaril@789
   124
	// (in mirrored graphicsview!)
insilmaril@789
   125
	_area=_area/2;	
insilmaril@789
   126
	p=_area*6;
insilmaril@789
   127
	_centroid.setX (cx/p);
insilmaril@789
   128
	_centroid.setY (cy/p);
insilmaril@789
   129
}
insilmaril@789
   130
insilmaril@789
   131
QPointF ConvexPolygon::centroid() const
insilmaril@789
   132
{
insilmaril@789
   133
	return _centroid;
insilmaril@789
   134
}
insilmaril@789
   135
insilmaril@789
   136
qreal ConvexPolygon::weight() const
insilmaril@789
   137
{
insilmaril@789
   138
	return _area;
insilmaril@789
   139
}
insilmaril@789
   140
insilmaril@792
   141
std::string ConvexPolygon::toStdString()
insilmaril@656
   142
{
insilmaril@792
   143
	QString s ("(");
insilmaril@792
   144
	for (int i=0;i<size();++i)
insilmaril@792
   145
	{
insilmaril@792
   146
		s+=QString("(%1,%2)").arg(at(i).x()).arg(at(i).y());
insilmaril@792
   147
		if (i<size()-1) s+=",";
insilmaril@792
   148
	}
insilmaril@792
   149
	s+=")";	
insilmaril@792
   150
	return s.toStdString();
insilmaril@656
   151
}
insilmaril@656
   152
insilmaril@792
   153
Vector ConvexPolygon::at(const int &i) const
insilmaril@656
   154
{
insilmaril@792
   155
	return Vector (QPolygonF::at(i).x(),QPolygonF::at(i).y());
insilmaril@656
   156
}
insilmaril@656
   157
insilmaril@792
   158
void ConvexPolygon::translate ( const Vector & offset )
insilmaril@792
   159
{ translate (offset.x(),offset.y());}
insilmaril@656
   160
insilmaril@792
   161
void ConvexPolygon::translate ( qreal dx, qreal dy )
insilmaril@789
   162
{
insilmaril@792
   163
	QPolygonF::translate (dx,dy);
insilmaril@792
   164
	_centroid=_centroid+QPointF (dx,dy);
insilmaril@789
   165
}
insilmaril@789
   166
insilmaril@792
   167
void projectPolygon(Vector axis, ConvexPolygon polygon, qreal &min, qreal &max) 
insilmaril@656
   168
{
insilmaril@656
   169
    // To project a point on an axis use the dot product
insilmaril@656
   170
insilmaril@789
   171
	//cout << "Projecting on "<< axis<<endl;
insilmaril@792
   172
    qreal d = axis.dotProduct(polygon.at(0));
insilmaril@656
   173
    min = d;
insilmaril@656
   174
    max = d;
insilmaril@789
   175
    for (int i = 0; i < polygon.size(); i++) 
insilmaril@789
   176
	{
insilmaril@792
   177
        d= polygon.at(i).dotProduct (axis);
insilmaril@656
   178
        if (d < min) 
insilmaril@656
   179
            min = d;
insilmaril@656
   180
        else 
insilmaril@656
   181
            if (d> max) max = d;
insilmaril@789
   182
	//	cout << "p="<<polygon.at(i)<<"  d="<<d<<"  (min, max)=("<<min<<","<<max<<")\n";	
insilmaril@656
   183
    }
insilmaril@656
   184
}
insilmaril@656
   185
insilmaril@789
   186
// Calculate the signed distance between [minA, maxA] and [minB, maxB]
insilmaril@789
   187
// The distance will be negative if the intervals overlap
insilmaril@656
   188
insilmaril@656
   189
qreal intervalDistance(qreal minA, qreal maxA, qreal minB, qreal maxB) {
insilmaril@656
   190
    if (minA < minB) {
insilmaril@656
   191
        return minB - maxA;
insilmaril@656
   192
    } else {
insilmaril@656
   193
        return minA - maxB;
insilmaril@656
   194
    }
insilmaril@656
   195
}
insilmaril@789
   196
insilmaril@789
   197
/*!
insilmaril@656
   198
 Check if polygon A is going to collide with polygon B.
insilmaril@656
   199
 The last parameter is the *relative* velocity 
insilmaril@656
   200
 of the polygons (i.e. velocityA - velocityB)
insilmaril@789
   201
*/
insilmaril@656
   202
insilmaril@792
   203
PolygonCollisionResult polygonCollision(ConvexPolygon polygonA, 
insilmaril@792
   204
                              ConvexPolygon polygonB, Vector velocity) 
insilmaril@789
   205
{
insilmaril@656
   206
    PolygonCollisionResult result;
insilmaril@662
   207
    result.intersect = true;
insilmaril@662
   208
    result.willIntersect = true;
insilmaril@656
   209
insilmaril@656
   210
    int edgeCountA = polygonA.size();
insilmaril@656
   211
    int edgeCountB = polygonB.size();
insilmaril@656
   212
    qreal minIntervalDistance = 1000000000;
insilmaril@656
   213
    QPointF translationAxis;
insilmaril@656
   214
    QPointF edge;
insilmaril@656
   215
insilmaril@789
   216
/*
insilmaril@662
   217
	cout << "\nA: ";
insilmaril@662
   218
	for (int k=0; k<edgeCountA;k++)
insilmaril@662
   219
		cout <<polygonA.at(k);
insilmaril@662
   220
	cout << "\nB: ";
insilmaril@662
   221
	for (int k=0; k<edgeCountB;k++)
insilmaril@662
   222
		cout <<polygonB.at(k);
insilmaril@789
   223
	cout <<endl;	
insilmaril@789
   224
*/		
insilmaril@662
   225
		
insilmaril@656
   226
    // Loop through all the edges of both polygons
insilmaril@789
   227
	for (int i=0;i<edgeCountA + edgeCountB;i++)
insilmaril@656
   228
	{
insilmaril@656
   229
        if (i< edgeCountA) 
insilmaril@662
   230
		{
insilmaril@789
   231
			// Loop through polygon A
insilmaril@789
   232
			if (i<edgeCountA-1)
insilmaril@662
   233
				edge = QPointF (
insilmaril@662
   234
					polygonA.at(i+1).x()-polygonA.at(i).x(), 
insilmaril@662
   235
					polygonA.at(i+1).y()-polygonA.at(i).y());
insilmaril@662
   236
			else		
insilmaril@662
   237
				edge = QPointF (
insilmaril@662
   238
					polygonA.at(0).x()-polygonA.at(i).x(), 
insilmaril@662
   239
					polygonA.at(0).y()-polygonA.at(i).y());
insilmaril@662
   240
        } else 
insilmaril@662
   241
		{
insilmaril@789
   242
			// Loop through polygon B
insilmaril@789
   243
			if (i < edgeCountA +edgeCountB -1 )
insilmaril@662
   244
				edge = QPointF (
insilmaril@789
   245
					polygonB.at(i-edgeCountA+1).x() - polygonB.at(i-edgeCountA).x(), 
insilmaril@789
   246
					polygonB.at(i-edgeCountA+1).y() - polygonB.at(i-edgeCountA).y());
insilmaril@662
   247
			else	
insilmaril@662
   248
				edge = QPointF (
insilmaril@789
   249
					polygonB.at(0).x() - polygonB.at(i-edgeCountA).x(), 
insilmaril@789
   250
					polygonB.at(0).y() - polygonB.at(i-edgeCountA).y());
insilmaril@662
   251
		}
insilmaril@656
   252
insilmaril@656
   253
        // ===== 1. Find if the polygons are currently intersecting =====
insilmaril@656
   254
insilmaril@656
   255
        // Find the axis perpendicular to the current edge
insilmaril@656
   256
insilmaril@792
   257
        Vector axis (-edge.y(), edge.x());
insilmaril@792
   258
        axis.normalize();
insilmaril@656
   259
insilmaril@656
   260
        // Find the projection of the polygon on the current axis
insilmaril@656
   261
insilmaril@656
   262
        qreal minA = 0; qreal minB = 0; qreal maxA = 0; qreal maxB = 0;
insilmaril@789
   263
        projectPolygon(axis, polygonA, minA, maxA);
insilmaril@789
   264
        projectPolygon(axis, polygonB, minB, maxB);
insilmaril@656
   265
insilmaril@656
   266
        // Check if the polygon projections are currentlty intersecting
insilmaril@656
   267
insilmaril@789
   268
        qreal d = intervalDistance(minA, maxA, minB, maxB);
insilmaril@789
   269
        if (d > 0) result.intersect = false;
insilmaril@656
   270
insilmaril@789
   271
       // ===== 2. Now find if the polygons *will* intersect =====
insilmaril@656
   272
insilmaril@656
   273
insilmaril@656
   274
        // Project the velocity on the current axis
insilmaril@656
   275
insilmaril@792
   276
        qreal velocityProjection = axis.dotProduct(velocity);
insilmaril@656
   277
insilmaril@656
   278
        // Get the projection of polygon A during the movement
insilmaril@656
   279
insilmaril@662
   280
        if (velocityProjection < 0) 
insilmaril@656
   281
            minA += velocityProjection;
insilmaril@662
   282
        else 
insilmaril@656
   283
            maxA += velocityProjection;
insilmaril@656
   284
insilmaril@656
   285
        // Do the same test as above for the new projection
insilmaril@656
   286
insilmaril@789
   287
        // d = intervalDistance(minA, maxA, minB, maxB);
insilmaril@789
   288
        //if (d > 0) result.willIntersect = false;
insilmaril@662
   289
		/*
insilmaril@662
   290
		cout <<"   ";
insilmaril@789
   291
		cout << "edge="<<edge<<"  ";
insilmaril@789
   292
		cout <<"axis="<<axis<<"  ";
insilmaril@789
   293
		cout <<"dA=("<<minA<<","<<maxA<<")  dB=("<<minB<<","<<maxB<<")";
insilmaril@662
   294
		cout <<"  d="<<d<<"   ";
insilmaril@789
   295
		//cout <<"minD="<<minIntervalDistance<<"  ";
insilmaril@662
   296
		cout <<"int="<<result.intersect<<"  ";
insilmaril@789
   297
		//cout <<"wint="<<result.willIntersect<<"  ";
insilmaril@662
   298
		//cout <<"velProj="<<velocityProjection<<"  ";
insilmaril@662
   299
		cout <<endl;
insilmaril@789
   300
		*/
insilmaril@662
   301
	
insilmaril@789
   302
        if (result.intersect )// || result.willIntersect) 
insilmaril@662
   303
		{
insilmaril@662
   304
			// Check if the current interval distance is the minimum one. If so
insilmaril@662
   305
			// store the interval distance and the current distance.  This will
insilmaril@662
   306
			// be used to calculate the minimum translation vector
insilmaril@656
   307
insilmaril@662
   308
			if (d<0) d=-d;
insilmaril@662
   309
			if (d < minIntervalDistance) {
insilmaril@662
   310
				minIntervalDistance = d;
insilmaril@789
   311
				//translationAxis = axis;
insilmaril@789
   312
				//cout << "tAxix="<<translationAxis<<endl;
insilmaril@656
   313
insilmaril@662
   314
				//QPointF t = polygonA.Center - polygonB.Center;
insilmaril@789
   315
				//QPointF t = polygonA.at(0) - polygonB.at(0);
insilmaril@789
   316
				//if (dotProduct(t,translationAxis) < 0)
insilmaril@789
   317
				//	translationAxis = -translationAxis;
insilmaril@662
   318
			}
insilmaril@662
   319
		}
insilmaril@656
   320
    }
insilmaril@656
   321
insilmaril@656
   322
    // The minimum translation vector
insilmaril@656
   323
    // can be used to push the polygons appart.
insilmaril@656
   324
insilmaril@662
   325
    if (result.willIntersect)
insilmaril@662
   326
        result.minTranslation = 
insilmaril@656
   327
               translationAxis * minIntervalDistance;
insilmaril@656
   328
    
insilmaril@656
   329
    return result;
insilmaril@656
   330
}
insilmaril@656
   331
insilmaril@656
   332
/* The function can be used this way: 
insilmaril@656
   333
   QPointF polygonATranslation = new QPointF();
insilmaril@656
   334
*/   
insilmaril@656
   335
insilmaril@656
   336
insilmaril@656
   337
/*
insilmaril@656
   338
PolygonCollisionResult r = PolygonCollision(polygonA, polygonB, velocity);
insilmaril@656
   339
insilmaril@656
   340
if (r.WillIntersect) 
insilmaril@656
   341
  // Move the polygon by its velocity, then move
insilmaril@656
   342
  // the polygons appart using the Minimum Translation Vector
insilmaril@662
   343
  polygonATranslation = velocity + r.minTranslation;
insilmaril@656
   344
else 
insilmaril@656
   345
  // Just move the polygon by its velocity
insilmaril@656
   346
  polygonATranslation = velocity;
insilmaril@656
   347
insilmaril@656
   348
polygonA.Offset(polygonATranslation);
insilmaril@656
   349
insilmaril@656
   350
*/
insilmaril@656
   351
insilmaril@656
   352