花了差不多一个星期写的试验性的碰撞物理引擎
还是试验性阶段,只有球和线段
首先为了统一接口,定义一个 物理实体类 CEntity,存些最简单关键的东西譬如坐标,碰撞系数,可动性等等,还有一个比较重要的参数,是实体类型(球,线段,或者以后加上的什么东西)
接下来就是球和线段的实现了。
当然,有了实体类,球和线段就要继承实体了。
在这个引擎里面,由于线段基本都被当障碍使用,所以线段暂时是不可动的,只存几个参数:两个端点,碰撞系数,没了。函数也是最简单的getter和setter就可以了。
接下来是球
球是这个引擎里面最主要的一个实体。可动的实体。其实就一个圆圈。刚才忘了说,这是一个2d的碰撞物理引擎。
模拟物理运动,力是最重要的,还有速度。我是用了向量来标记这些东西。为了运算简单方便,重载了向量的n个运算符:
1class CVector
2{
3public:
4double degree;
5double x, y;
6public:
7CVector(double x1, double y1, double x2, double y2);
8CVector(double x, double y, double degree);
9CVector(double x, double y);
10CVector();
11CVector operator+(CVector);
12CVector operator-(CVector);
13//CVector operator=(CVector);
14void operator=(CVector);
15CVector operator*(double);
16CVector operator&(CVector);
17CVector operator/(double);
18bool operator<<(CVector);
19void standarlization();
20void positivation();
21void reset();
22CVector reverse();
23};
24
解析一下,向量是由一个二维坐标以及长度指定,有时候长度是没用的(标记方向的时候),所以degree不是一个必要的参数。
关于运算符,* , / 是简单的把向量长度改变,比较特别的是&(投影运算)和 <<(判断方向顺便判断长度。),这些是特殊要求。之后会说到。stan。。。是把方向坐标标准化,pos....是把degree正量化(通过改变方向),reset是用于除0的时候把向量至0,reverse返回相反的向量。
回到来说球,其实球需要的东西不多,存一个速度,然后写一个更新函数就差不多了。辅助参数是摩擦系数和半径什么之类的。当然,我的习惯是把一些用到的东西都封成接口对外。于是这里就有了几个接口:推力,牵引,或者直接重置一个速度。
顺便做名词解析:推力是施加外力;牵引力是他主动运动,来源于摩擦力。
为了解决重叠的问题,另加了一个重置坐标的接口。
这是更新函数:
1void CBall::reflash()
2{
3if(force.degree > 0.01)
4{
5 velocity = velocity + force/gravity;
6}
7if(velocity.degree < 0.01)
8{
9 velocity.degree = 0;
10}
11else
12{
13 velocity.standarlization();
14 double tx = x;
15 x += velocity.degree * velocity.x;
16 y += velocity.degree * velocity.y;
17 CVector griftForce(velocity.x * (-1), velocity.y * (-1), 1);
18 griftForce = griftForce * (gravity * Qgrift * Qgroundgrift);
19 griftForce.degree += velocity.degree * velocity.degree / 1000;
20 //force = force + griftForce;
21 velocity.degree -= griftForce.degree/gravity;
22 if(velocity.degree < 0)
23 {
24 velocity.degree = 0;
25 }
26}
27if(velocity.degree > maxVelocity)
28{
29 velocity.degree = maxVelocity;
30}
31force.reset();
32}
33
力的实现是每一帧清空一次,然后在下一帧内把受到的力全部叠加起来,下一次更新的时候用上。
再做一个备注,大家可能会发现有个Qgroundgrift的东西,这是场景的摩擦系数。
由于一些特殊结构的考虑,我把碰撞的检测移到场景类里边。
事实也证明,移到场景类里边是很好的一个决定。
一说到场景,首先反映是边界。其实场景的函数不多,不过个个都不是容易写的东西。。。
typedef map<int, CEntity*> ObjectMap;
1class CScene
2{
3protected:
4double width, height;
5double grift;
6public:
7bool impulse(CEntity *, CEntity *);
8bool impulseBallBall(CBall*, CBall*);
9bool impulseBallLine(CBall*, CLine*);
10//void scanCrash(CLinkList<CBall>);
11void scanCrash(ObjectMap);
12bool edgeCheck(CEntity *);
13void init(double w, double h, double g);
14double getGrift();
15};
16
这里被水王打击了一下,
“18:24:54
举例子,如果你有10种本质上不同的几何体,你不得不写10×10=100个碰撞函数 ”
这是他的原话
幸好我现在只有两种东西。。其实两种东西可以实现很多样式了。
譬如线段可以组合成多边形。。。。当然没有角速度。。连球都还没实现角速度。
scanCrash是场景的主要逻辑,传入一堆实体对象,让他们在场景里面该碰撞的碰撞,该更新的更新,该撞墙的撞墙,当然,是要两两检测
void CScene::scanCrash(ObjectMap map)
{
CEntity *oa,*ob;
ObjectMap::iterator pos;
if(map.size() > 0)
{
int k = 0;
pos = map.begin();
oa = pos->second;
while(pos != map.end())
{
oa = pos->second;
k++;
pos++;
while(pos != map.end())
{
ob = pos->second;
impulse(oa, ob);
pos++;
}
pos = map.begin();
for(int i = 0; i < k; i++)
{
pos++;
}
}
for(pos = map.begin(); pos != map.end(); ++pos)
{
edgeCheck(pos->second);
}
}
for(pos = map.begin(); pos != map.end(); ++pos)
{
if(pos->second->getType() == BALL)
{
((CBall*)pos->second)->reflash();
}
}
}
也还是不完整的因素,线段都是unmovable的,所以只有ball有reflash函数。哇哈哈哈
edgeCheck就更简单了。生成几条线段(就是围成边界那几条,然后让他们跟实体碰撞就行)
下面把球球碰和球线碰的函数贴出来,
1bool CScene::impulseBallBall(CBall *a, CBall *b)
2{
3CVector atob;
4atob.x = b->getX() - a->getX();
5atob.y = b->getY() - a->getY();
6atob.degree = 1;
7double distance;
8distance = pow((atob.x * atob.x + atob.y * atob.y), 0.5);
9atob.standarlization();
10CVector pva = a->getV();
11CVector pvb = b->getV();
12//CVector rv = pva & atob - pvb & atob;
13CVector tpva = pva & atob;
14CVector tpvb = pvb & atob;
15double rv = tpva.degree - tpvb.degree;
16if(rv < 0)
17{
18 return false;
19}
20if(distance - a->getEage(atob) - b->getEage(atob.reverse()) < rv/2)
21{
22 double qa = a->getCollideQ();
23 double qb = b->getCollideQ();
24 double q = qa * qb;
25 double ga = a->getG();
26 double gb = b->getG();
27 double vad, vbd;
28 if(q < 0)
29 {
30 return true;
31 }
32 else if(q < 0.001)
33 {
34 //vad = ((ga - gb) * tpva.degree + 2 * gb * tpvb.degree) / (ga + gb);
35 //vbd = ((gb - ga) * tpvb.degree + 2 * ga * tpva.degree) / (ga + gb);
36 vad = vbd = (ga * tpva.degree + gb * tpvb.degree) / (ga + gb);
37 }
38 else
39 {
40 vad = tpva.degree - ((1+q) * gb * (tpva.degree - tpvb.degree) / (ga + gb));
41 vbd = tpvb.degree + ((1+q) * ga * (tpva.degree - tpvb.degree) / (ga + gb));
42 }
43 CVector tava = atob;
44 tava.degree = vad;
45 CVector tavb = atob;
46 tavb.degree = vbd;
47 CVector ava = pva - tpva;
48 ava = ava + tava;
49 CVector avb = pvb - tpvb + tavb;
50 a->move(ava);
51 b->move(avb);
52 return true;
53}
54
55else if(a->getEage(atob) + b->getEage(atob.reverse()) - distance > 0.1)
56{
57 double d = (a->getEage(atob) + b->getEage(atob.reverse()) - distance + 0.2)/2;
58 CVector btoa = atob.reverse();
59 a->setXY(a->getX() + btoa.x * d, a->getY() + btoa.y * d);
60 b->setXY(b->getX() + atob.x * d, b->getY() + atob.y * d);
61 return true;
62}
63
64else
65{
66 return false;
67}
68}
69
70bool CScene::impulseBallLine(CBall* a, CLine* b)
71{
72//a,b:线段端点
73//c:球心
74CVector atob(b->getXa(), b->getYa(), b->getXb(), b->getYb());
75CVector ctob(a->getX(), a->getY(), b->getXb(), b->getYb());
76CVector shadow = ctob & atob;
77double sx, sy;
78if(shadow << atob)
79{
80 shadow.standarlization();
81 sx = b->getXb() - shadow.x * shadow.degree;
82 sy = b->getYb() - shadow.y * shadow.degree;
83 double kx = b->getXa();
84 double ky = b->getYa();
85 kx = ky;
86}
87else
88{
89 atob.degree = atob.degree + shadow.degree;
90 if(shadow << atob)
91 {
92 sx = b->getXa();
93 sy = b->getYa();
94 }
95 else
96 {
97 sx = b->getXb();
98 sy = b->getYb();
99 }
100}
101CVector cs(a->getX(),a->getY(),sx,sy);
102//sc.standarlization();
103CVector rv = a->getV() & cs;
104/**//*
105if(xv.degree > 0)
106{
107 //a->setXY(a->getX() + d * sc.x, a->getY() + d * sc.y);
108 return false;
109}
110*/
111double distance = pow((a->getX() - sx) * (a->getX() - sx) + (a->getY() - sy) * (a->getY() - sy), 0.5);
112/**//*
113if(distance < (a->getR() + xv.degree/2))
114{
115 double d = a->getR() + xv.degree - distance;
116 a->setXY(a->getX() + d * cs.x, a->getY() + d * cs.y);
117 //CVector f = a->getF() & sc.reverse();
118 //a->push(f.reverse());
119 return true;
120}
121*/
122if(distance < (a->getR() + rv.degree/2))
123{
124 CVector v = a->getV();
125 //CVector rv = v & cs;
126 v = v - rv;
127 double q = a->getCollideQ() * b->getCollideQ();
128 rv.degree = pow(q * rv.degree * rv.degree, 0.5);
129 v = v - rv;
130 a->move(v);
131 CVector f = a->getF();
132 f = f & cs;
133 if(f.degree > 0)
134 {
135 f.degree = -f.degree;
136 a->push(f);
137 }
138 return true;
139}
140return false;
141}
142
好长啊。。。慢慢看下算了。。不解析
还剩几个问题没解决,一堆球挤压的时候,会出现不规则的重叠问题。这个问题太难了。跟球球碰的顺序还有很大关系,所以就扔着不理了。