【Unity】万有引力和轨道计算(1)

2023-10-26 17:36:19 浏览数 (2)

这算是一个忙了大半年的比赛项目了,终于完成了来补个文章Orz

先上效果:

万有引力

首先要让星体之间能相互吸引,互相施加一个万有引力。这个我实现起来就很粗暴了,直接用过刚体施加一个力:

代码语言:javascript复制
//计算万有引力数值
private float CalculateGravityModulus(float targetMass, float distance)
{
   return PhysicBase.GetG() * (GetMass() * targetMass / (distance * distance));
}

//计算万有引力
private Vector3 GetGravityVector3(Rigidbody rigidbody)
{
    var distance            = Vector3.Distance(transform.position, rigidbody.position);
    var normalizedDirection = (transform.position - rigidbody.position).normalized;
    return CalculateGravityModulus(rigidbody.mass, distance) * normalizedDirection;
}

不过由于一个星球会受到好几个其他星球的影响,这里建了一个List来计算合力,用Trigger来判断是否计算两个星球之间的引力:

代码语言:javascript复制
//     对之产生影响的星球
public List<AstralBody> affectedPlanets = new List<AstralBody>();

private void OnTriggerEnter(Collider other)
{
    var astral = other.GetComponent<AstralBody>();
    if (astral != null &&
        !other.isTrigger && 
        enableAffect &&
       !astral.banAffectedPlanets.Contains(this) &&
        !astral.affectedPlanets.Contains(this))
        astral.affectedPlanets.Add(this);
}

private void OnTriggerExit(Collider other)
{
   var astral = other.GetComponent<AstralBody>();
   if (astral != null && enableAffect && astral.affectedPlanets.Contains(astral))
       astral.affectedPlanets.Remove(this);
}


//计算受力
public Vector3 CalculateForce()
{
     var forceResult = new Vector3(0, 0, 0);
     foreach (var astralBody in affectedPlanets)
          forceResult  = astralBody.GetGravityVector3(astralBodyRigidbody);

      return forceResult;
}

然后直接暴力运算:

代码语言:javascript复制
protected virtual void FixedUpdate()
{
  if (!astralBodyRigidbody.isKinematic) lastVelocity = astralBodyRigidbody.velocity;
  astralBodyRigidbody.AddForce(CalculateForce());
}

效果还行:


轨道预测

接下来就是重点了,怎么根据已知星球信息推出它在固定时间之后的位置呢?

我这里的解决方案是,将所有的星球的质量、速度、坐标、影响星球各种信息存在一起,预测时做这样的计算:

  1. 根据星球各自的位置和当前速度,用匀速直线运动近似,计算其在n秒后所处的位置。
  2. 根据每个星球各自的坐标和质量计算它们各自当前受力。
  3. 用加速度公式计算星球在进行前面那段位移后速度的改变量,得出新速度。
  4. 重复上面三步。
代码语言:javascript复制
//开始采样
for (var i = 0; i < sample; i  )
//遍历星体
{
    foreach (var astralBody in _astralBodies)
    {
        //加速度
        /*
         * F=ma
         * delta v=at
         * s = vt   0.5a(t^2)
         */
        var acceleration = CalculateForce(astralBody, i, astralBodyMasses) / astralBodyMasses[astralBody];
        
        _orbitPoints[astralBody].Add(_orbitPoints[astralBody].Last()                
                                     astralBodyVelocities[astralBody] * _deltaTime  
                                     .5f                              * acceleration * Mathf.Pow(_deltaTime, 2));
        //加速后速度
        astralBodyVelocities[astralBody]  = acceleration * _deltaTime;
        
    }
}

这部分计算如果全部放到主线程做真的就挺卡的,这里做了个多线程:

代码语言:javascript复制
//引力步进
private void TraceGravity()
{
    var astralBodyVelocities = new Dictionary<ITraceable, Vector3>();
    foreach (var astralBody in _astralBodies)
    {
        astralBodyVelocities[astralBody] = astralBody.GetVelocity();
        //起始点改为当前位置
        if (_orbitPoints.ContainsKey(astralBody)) _orbitPoints[astralBody].Clear();
        _orbitPoints[astralBody].Add(astralBody.GetPosition());
    }
    var astralBodyMasses = new Dictionary<ITraceable, float>();
    _astralBodies.ForEach(a => astralBodyMasses.Add(a, a.GetMass()));
    StartNewThread(new[]
                   {
                       astralBodyVelocities,
                       (object) astralBodyMasses
                   });
}

private void StartNewThread(object[] dict)
{
    _thread = new Thread(Sample);
    _thread.Start(dict);
}

private void Sample(object objs)
{
    var dicts                = (object[]) objs;
    var astralBodyVelocities = (Dictionary<ITraceable, Vector3>) dicts[0];
    var astralBodyMasses     = (Dictionary<ITraceable, float>) dicts[1];
    //开始采样
    for (var i = 0; i < sample; i  )
        //遍历星体
    {
        foreach (var astralBody in _astralBodies)
        {
            //加速度
            /*
         * F=ma
         * delta v=at
         * s = vt   0.5a(t^2)
         */
            var acceleration = CalculateForce(astralBody, i, astralBodyMasses) / astralBodyMasses[astralBody];
            _orbitPoints[astralBody].Add(_orbitPoints[astralBody].Last()                
                                         astralBodyVelocities[astralBody] * _deltaTime  
                                         .5f                              * acceleration * Mathf.Pow(_deltaTime, 2));
            //加速后速度
            astralBodyVelocities[astralBody]  = acceleration * _deltaTime;
        }
    }
    _actionTypes.Add(ActionType.Finished);
}

路径显示

显示其实挺容易的,用个LineRenderer把坐标全都放进去就可以了。后来发现了一个叫Dreamteck Splines的插件可以做自动平滑,输进去的点能少一些,之后在做轨道拟合的时候用的还挺爽的,不过当时工期紧张之前的代码就没有重构233

这里随便写了个Shader来做线的显示:

代码语言:javascript复制
Shader "Line/UILine"
{
    Properties
    {
        _MainTex ("Base (RGB)", 2D) = "white" {}
        [HDR]_Color("Color",Color) = (0,0,0,0)
        _Speed ("Speed",Range(0,1)) = .5
        _Density("Density",float) = 30
    }
    SubShader
    {
        Tags
        {
            "RenderType"="Transparent" "IgnoreProjector"="True" "Queue"="Transparent"
        }
        ZTest Off
        LOD 200
        Blend SrcAlpha OneMinusSrcAlpha
        CGPROGRAM
        #pragma surface surf NoLight vertex:vert alpha noforwardadd

        float4 LightingNoLight(SurfaceOutput s, float3 lightDir, half3 viewDir, half atten)
        {
            float4 c;
            c.rgb = s.Albedo;
            c.a = s.Alpha;
            return c;
        }

        sampler2D _MainTex;
        fixed4 _SelfCol;
        fixed4 _Color;

        fixed _Speed;
        fixed _Density;


        struct Input
        {
            float2 uv_MainTex;
            float4 vertColor;
        };

        void vert(inout appdata_full v, out Input o)
        {
            o.vertColor = v.color;
            o.uv_MainTex = v.texcoord;
        }

        void surf(Input IN, inout SurfaceOutput o)
        {
            fixed2 uv = float2(IN.uv_MainTex.x  * _Density ,IN.uv_MainTex.y)   float2((IN.uv_MainTex.x- _Time.y * _Speed ) 1,0);
            half4 c = tex2D(_MainTex, frac(uv));
            o.Alpha = IN.vertColor.a * c.a;
            o.Albedo = c * _Color;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

效果:

多个相互影响的星球放在一起也没有问题:

项目仓库:

HkingAuditore/Kepler

0 人点赞