前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Unity】万有引力和轨道计算(1)

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

作者头像
HkingAuditore
发布2023-10-26 17:36:19
2630
发布2023-10-26 17:36:19
举报
文章被收录于专栏:HkingAuditoreHkingAuditore

这算是一个忙了大半年的比赛项目了,终于完成了来补个文章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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-06-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 万有引力
  • 轨道预测
  • 路径显示
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档