在这次模拟中,我们只考虑椭圆轨道的情形,双曲线和抛物线轨道等以后再进行完善。首先,我们需要画出一个椭圆轨道,这个用Unity的LineRenderer
功能就能实现。
private List<Vector3> points = new List<Vector3>();
public LineRenderer line;
public float w;
public float h;
public int num = 100;
void AddPoints()
{
points.Clear();
for(int i = 0; i <= num; i++)
{
float angle = i * 2.0f * Mathf.PI / num;
float x = w * Mathf.Cos(angle);
float y = h * Mathf.Sin(angle);
Vector3 pt = new Vector3(x, y, 0);
points.Add(pt);
}
line.positionCount = points.Count;
line.SetPositions(points.ToArray());
}
然后在Unity中建一个空物体Trajectory挂上LineRenderer
,材质里挂上自己建的材质
接着另外建一个球体Planet挂上drawOval
,然后LineRenderer选择Trajectory
当然把LineRenderer也挂在Planet下面也是可以的,但是后期要移动物体会很不方便,因为球体和轨迹的质心会合并。
现在,我们就能画出一个椭圆了!
由长短半轴画椭圆实在太low了,我们要模拟行星轨道,当然要仅仅依靠初始速度,位置,中心天体质量等参数来计算出椭圆的参数。
当然,这需要一点点的天文知识和物理学知识(鬼知道我被这一点点卡了多久 (╯‵□′)╯︵┻━┻)
不过现在我可以直接给出我们所需的公式,即轨道速度公式1和开普勒第二定律2
OK,我知道你们肯定不关心怎么推导,那么我们直接上代码
r = this.transform.position - center.position;
v = velocity.position - this.transform.position;
//利用轨道线速度公式求出轨道半长轴
w = 1 / ( 2 / r.magnitude - Mathf.Pow(v.magnitude, 2) / GM );
//利用开普勒第二定律求出轨道半短轴
h = Vector3.Cross(v, r).magnitude * Mathf.Sqrt(w / GM);
//求出焦距
f = Mathf.Sqrt(w * w - h * h);
其中GM就是引力常数和中心天体质量的乘积。r是中心天体指向环绕天体的位置矢量,v是环绕天体初始的方向矢量,后面还有别的矢量,千万别搞混了。
为了实现以中心天体为中心的效果我们还需要稍稍修改一下之前的代码,将中心调整到焦点上
Vector3 pt = new Vector3(x - f, y, 0) + center.transform.position;
然后实时更新
void FixedUpdate()
{
Analyse();
AddPoints();
}
现在我们就能动态控制椭圆大小了!当然,在这之前Unity也要进行一些小设置。我们需要设置一个中心天体,挂在drawOval
脚本的center上,还需要设置一个Velocity物体,Planet与Velocity的连线代表初始速度。
但其实现在这个系统还非常菜,你们可能注意到了,Center,Planet和Velocity必须都在XoY平面上!而且r和v的方向也不能随意变更,接下来我们就要逐步解决这两个问题。
仔细思考一下,我们所拥有的初始变量除了假设恒定的GM以外只有r和v。两个向量可以构造一个平面,而我们所要构造的椭圆轨道其实完全处在这个平面之内。因此,先将三维坐标系转换成二维坐标系,求出椭圆轨道以后再还原到三维坐标系就可以了。当然,直接在三维空间中用向量和椭圆体公式莽应该也是可以的,但我不会_(:з)∠)_
构造平面坐标系很简单,将r表示成(|r| , 0),将v表示成(|v|cosθ , |v|sinθ)即可
但其实由于在任何坐标系中r和v的夹角都是不变的,而我们只需要这个夹角,所以v就不用转化了。
rr = new Vector3(r.magnitude, 0, 0);
在上面其实我们已经可以用r和v获得一个正椭圆了,接下来我们要做的就是让这个正椭圆绕质心旋转一定角度,然后让我们的Planet刚好在这个轨道上,v也刚好相切。
利用我们已经知道的|r|和r和v的夹角,我们可以在正椭圆中确定唯一的r0
//利用准线求出x0的值
float x0 = w * w / f - w / f * r.magnitude;
//理论上r^2大于等于(x0-f)^2,但是由于精度问题可能略小于0,因此使用Abs函数
float y0 = Mathf.Sqrt(Mathf.Abs(r.magnitude * r.magnitude - (x0 - f) * (x0 - f)));
Vector3 r0 = new Vector3(x0 - f, y0, 0);
//v0是在当前r0情况下应该具有的速度,用夹角来确定y0应该为正还是为负
Vector3 v0 = new Vector3(-y0 * w / h, x0 * h / w, 0);
if (Mathf.Abs(Vector3.Angle(v, r) - Vector3.Angle(v0, r0)) > 0.01)
y0 = -y0;
//先求出在正坐标系中与质心距离为r的点的矢量r0,然后测量需要倾斜alpha使行星可以位于轨道上
r0 = new Vector3(x0 - f, y0, 0);
//如果r处在XOY平面中可以直接用r替换rr
alpha = Vector3.Angle(r0, rr);
if (Vector3.Cross(r0, rr).z < 0) alpha = -alpha;
AddPoints()函数需要稍微修改一下,把所有点旋转一下
float angle = i * 2.0f * Mathf.PI / num;
float x = w * Mathf.Cos(angle);
float y = h * Mathf.Sin(angle);
Vector3 pt = new Vector3(x - f, y, 0) + center.transform.position;
Quaternion q = Quaternion.AngleAxis(alpha, new Vector3(0, 0, 1));
pt = q * pt;
points.Add(pt);
将Analyse末尾的rr替换成r就可以初步检验我们的成果了。然而如果不替换的话由于rr和r有个夹角,结果是不准确的。
最后这一步其实非常简单:
根据r和v得到法向量,然后计算出单位向量i和j,最后将二维坐标转换成三维坐标就OK了。
Vector3 n = Vector3.Cross(v, r);
Yj = Vector3.Cross(r, n);
Yj = Yj / Yj.magnitude;
Xi = r / r.magnitude;
float angle = i * 2.0f * Mathf.PI / num;
float x = w * Mathf.Cos(angle);
float y = h * Mathf.Sin(angle);
Vector3 pt = new Vector3(x - f, y, 0);
//此时的点其实还都在平面坐标系上
Quaternion q = Quaternion.AngleAxis(alpha, new Vector3(0, 0, 1));
pt = q * pt;
//转换到三维坐标系
pt = pt.x * Xi + pt.y * Yj;
pt = pt + center.transform.position;
points.Add(pt);
最后放上三维空间中的椭圆行星轨道
第一次不看教程自己捣鼓,真的是累啊。不到100行代码用了三天时间才写完,中间还费了无数张草稿纸?
下一步是添加双曲线和抛物线轨道,以及让行星动起来。希望行星运动的时间函数能有一个比较好实现的解析解,不然只能靠积分解了。。。
https://zh.wikipedia.org/wiki/轨道速度 ↩︎
https://zh.wikipedia.org/wiki/开普勒定律#开普勒第二定律 ↩︎