知乎视频www.zhihu.com
起因
最近在做一个类似坦克大战的局域网联机版Demo,其中炮弹想做成可以弹性碰撞的球体(滑稽)。然后在网上只找到了JS、C#、C实现的代码,所以我参考它们用Java重新实现了一遍(本人数学比较渣,花了一天时间...)。
参考链接 https://www.jianshu.com/p/02ecbbb6afeb
![e06497ce-bd23-eb11-8da9-e4434bdf6706.png](http://p05.5ceimg.com/content/e06497ce-bd23-eb11-8da9-e4434bdf6706.png)
小球弹性碰撞原理
将两个小球的速度分别在球心线(连接两球心)和垂直球心线上投影得到它们的分速度(球心线上有:球1分速度、球2分速度;垂直球心线上有:球1分速度、球2分速度)。
垂直球心线上的分速度不会影响碰撞过程, 所以我们只需处理球心线上的两个分速度了(so easy.png)。
如果两个小球质量相等的话,事情将会变得非常简单!只需交换它们在球心线上的分速度就ok了,最后将它们各自的球心线分速度和垂直球心线分速度合并,就是它们碰撞后的速度了。
至于质量不相等,我就贴个公式吧,你们自己去体会体会(我就看一眼)。
// 表示小球a的球心线分速度, 同理; 表示小球a的质量, 同理
=( •( - )+2• • )/( + )
=( •( - )+2• • )/( + )
最后是实现代码
/**
* 两小球弹性碰撞
* <p>
* 参考链接: https://www.jianshu.com/p/02ecbbb6afeb
*/
private static void ballCollide(MovableUnit ball1, MovableUnit ball2) {
// 初始速度向量
Vector2d ball1SpeedInitial = ball1.getVector();
Vector2d ball2SpeedInitial = ball2.getVector();
// 球心方向单位向量
final double radian = computeRadian(ball1.getCentrePosition(), ball2.getCentrePosition());
Vector2d horizontalVector = getVector(radian, 1);
// 垂直球心方向单位向量
Vector2d perpendicularVector = getVector(radian + Constant.HALF_PI, 1);
// 速度在球心向量上的分速度投影长度
double ball1SpeedHorizontalProjectionLength = ball1SpeedInitial.dotProduct(horizontalVector);
double ball2SpeedHorizontalProjectionLength = ball2SpeedInitial.dotProduct(horizontalVector);
// 速度在垂直球心向量上的分速度投影长度
double ball1SpeedPerpendicularProjectionLength = ball1SpeedInitial.dotProduct(perpendicularVector);
double ball2SpeedPerpendicularProjectionLength = ball2SpeedInitial.dotProduct(perpendicularVector);
// 碰撞后球心方向上的分速度投影长度
double ball1SpeedHorizontalProjectionFinalLength;
double ball2SpeedHorizontalProjectionFinalLength;
if (ball1.weight == ball2.weight) {
// 质量相等
ball1SpeedHorizontalProjectionFinalLength = ball2SpeedHorizontalProjectionLength;
ball2SpeedHorizontalProjectionFinalLength = ball1SpeedHorizontalProjectionLength;
} else {
// 质量不相等
ball1SpeedHorizontalProjectionFinalLength = (ball1SpeedHorizontalProjectionLength * (ball1.weight - ball2.weight) + 2 * ball2.weight * ball2SpeedHorizontalProjectionLength)
/ (ball1.weight + ball2.weight);
ball2SpeedHorizontalProjectionFinalLength = (ball2SpeedHorizontalProjectionLength * (ball2.weight - ball1.weight) + 2 * ball1.weight * ball1SpeedHorizontalProjectionLength)
/ (ball1.weight + ball2.weight);
}
// 碰撞后球心方向上的分速度向量
Vector2d ball1SpeedHorizontalProjectionFinalVector = horizontalVector.multiply(ball1SpeedHorizontalProjectionFinalLength);
Vector2d ball2SpeedHorizontalProjectionFinalVector = horizontalVector.multiply(ball2SpeedHorizontalProjectionFinalLength);
// 碰撞后垂直球心方向上的分速度向量
Vector2d ball1SpeedPerpendicularProjectionVector = perpendicularVector.multiply(ball1SpeedPerpendicularProjectionLength);
Vector2d ball2SpeedPerpendicularProjectionVector = perpendicularVector.multiply(ball2SpeedPerpendicularProjectionLength);
// 两个球最终的速度向量
Vector2d ball1SpeedFinalVector = ball1SpeedHorizontalProjectionFinalVector.add(ball1SpeedPerpendicularProjectionVector);
Vector2d ball2SpeedFinalVector = ball2SpeedHorizontalProjectionFinalVector.add(ball2SpeedPerpendicularProjectionVector);
// 更新速度
applyVector(ball1SpeedFinalVector, ball1);
applyVector(ball2SpeedFinalVector, ball2);
}
/**
* 平面向量工具类
* <p>
* 参考链接: https://www.cnblogs.com/vokie/p/3602063.html
*/
public class Vector2d {
private double x;
private double y;
public Vector2d() {
x = 0;
y = 0;
}
public Vector2d(double x, double y) {
this.x = x;
this.y = y;
}
//获取弧度
public double getRadian() {
return Math.atan2(y, x);
}
//获取角度
public double getAngle() {
return getRadian() / Math.PI * 180;
}
@Override
public Vector2d clone() {
return new Vector2d(x, y);
}
/**
* 获取长度
*/
public double getLength() {
return Math.sqrt(getLengthSQ());
}
public double getLengthSQ() {
return x * x + y * y;
}
//向量置零
public Vector2d Zero() {
x = 0;
y = 0;
return this;
}
public boolean isZero() {
return x == 0 && y == 0;
}
//向量的长度设置为我们期待的value
public void setLength(double value) {
double _angle = getAngle();
x = Math.cos(_angle) * value;
y = Math.sin(_angle) * value;
}
//向量的标准化(方向不变,长度为1)
public Vector2d normalize() {
double length = getLength();
x = x / length;
y = y / length;
return this;
}
//是否已经标准化
public boolean isNormalized() {
return getLength() == 1.0;
}
//向量的方向翻转
public Vector2d reverse() {
x = -x;
y = -y;
return this;
}
//2个向量的数量积(点积)
public double dotProduct(Vector2d v) {
return x * v.x + y * v.y;
}
//2个向量的向量积(叉积)
public double crossProduct(Vector2d v) {
return x * v.y - y * v.x;
}
//计算2个向量的夹角弧度
//参考点积公式:v1 * v2 = cos<v1,v2> * |v1| *|v2|
public static double radianBetween(Vector2d v1, Vector2d v2) {
if (!v1.isNormalized()) v1 = v1.clone().normalize(); // |v1| = 1
if (!v2.isNormalized()) v2 = v2.clone().normalize(); // |v2| = 1
return Math.acos(v1.dotProduct(v2));
}
//弧度 = 角度乘以PI后再除以180、 推理可得弧度换算角度的公式
//弧度转角度
public static double radian2Angle(double radian) {
return radian / Math.PI * 180;
}
//向量加
public Vector2d add(Vector2d v) {
return new Vector2d(x + v.x, y + v.y);
}
//向量减
public Vector2d subtract(Vector2d v) {
return new Vector2d(x - v.x, y - v.y);
}
//向量乘
public Vector2d multiply(double value) {
return new Vector2d(x * value, y * value);
}
//向量除
public Vector2d divide(double value) {
return new Vector2d(x / value, y / value);
}
}