水一篇博客,做一个基础数学搬运工。计算切线应该说算是很基础的数学计算之一,但是涉及到了微积分,而且在简单物理运算中很常用。
比如我们要做一个自由落体运动,但是为了性能考虑不使用物理组件,而是使用水平方向匀速运动s = v*t,垂直方向自由落体运动h = g*t^2/2,得到函数图像,同时求函数图象中某个点的切线。这种做法在需要预计算的逻辑处理中很常见。
首先我们把自由落体运动的函数求解出来,如下:
如果我们将时间t作为变量,那么x方向就存在函数f(t) = v*t,y方向f(t) = -0.5*g*t^2,综合起来的线性映射f(v*t) = -0.5*g*t^2。
程序中模拟一下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GravTest : MonoBehaviour
{
public Transform ball;
[Range(1,20)]
[SerializeField] public float HorConstSpeed = 5.0f;
[SerializeField] public int MaxDisperseCount = 100;
private const float GravAccelerat = 9.8f;
private int disperseCount = 0;
private float t = 0f;
private float time = 0.0f;
void Start()
{
}
void Update()
{
#if UNITY_EDITOR
t = 0f;
for (int i = 0; i < MaxDisperseCount; i++)
{
t += Time.deltaTime;
float x = t;
float y = -0.5f * GravAccelerat / HorConstSpeed * t * t;
Vector3 fpos = new Vector3(x, y, 0);
float t1 = t + Time.deltaTime;
x = t1;
y = -0.5f * GravAccelerat / HorConstSpeed * t1 * t1;
Vector3 tpos = new Vector3(x, y, 0);
Debug.DrawLine(fpos, tpos, Color.black);
}
#endif
time += Time.deltaTime;
float tx = time;
float ty = -0.5f * GravAccelerat / HorConstSpeed * time * time;
Vector3 pos = new Vector3(tx, ty, 0);
ball.position = pos;
disperseCount++;
if (disperseCount >= MaxDisperseCount)
{
disperseCount = 0;
time = 0.0f;
}
}
}
我们离散100个数据量,去做模拟,效果如下:
同时我们可以看到一个问题,那就是小球的朝向不会改变,并不会“朝着运动方向”,我猜很多同学的做法肯定很简单粗暴,那就是根据下个position-上个position得到的向量去修改小球的朝向向量。但是严格来讲, 这并不是切线方向,虽然很近似切线方向。
如果要计算严格切线方向,我们用微积分的角度来理解:
切线是什么?如果我们拿一把直尺和圆柱体水杯,那么直尺贴近水杯,相交的点为切点,直尺所在的直线为切线。
函数切线怎么算?以上面的函数为例,我们两个离散的坐标的朝向方向就十分近似切线方向,为了便于形象理解,我们先改造一下函数代码,如下:
//假设我们已1000个deltatime为x总长度
private float getDisperseDelta(int count)
{
return 1000 * Time.deltaTime / (float)count;
}
void Update()
{
#if UNITY_EDITOR
t = 0f;
for (int i = 0; i < MaxDisperseCount; i++)
{
float x = t;
float y = -0.5f * GravAccelerat / HorConstSpeed * t * t;
Vector3 fpos = new Vector3(x, y, 0);
float t1 = t + getDisperseDelta(MaxDisperseCount);
x = t1;
y = -0.5f * GravAccelerat / HorConstSpeed * t1 * t1;
Vector3 tpos = new Vector3(x, y, 0);
Color col = i % 2 == 0 ? Color.black : Color.red;
Debug.DrawLine(fpos, tpos, col);
t += getDisperseDelta(MaxDisperseCount);
}
#endif
time += getDisperseDelta(MaxDisperseCount);
float tx = time;
float ty = -0.5f * GravAccelerat / HorConstSpeed * time * time;
Vector3 pos = new Vector3(tx, ty, 0);
//ball.position = pos;
disperseCount++;
if (disperseCount >= MaxDisperseCount)
{
disperseCount = 0;
time = 0.0f;
}
}
我们把离散的数量参数进行改变,会看到以下现象,如图:
看得出来,如果我们将离散数量增大,那么两点之间的朝向方向就越近似于起点的切线方向。
那么假设我们离散到两个点的t变化量无限小,也就是Δt无限小(英文delta),就可以算出起点的切线向量,当然我们可以反过来说如果我们离散的数量无限大,MaxDisperseCount设置为int.MaxValue,我们也就可以通过两点的坐标算出切线,如果你的CPU是量子CPU的话。
这里我们还是用微分运算来进行计算,我们假设t存在Δt的变化(也就是x轴变化),同时得到f(t)到f(t+Δt)的变化(也就是y轴的变化),那么可以通过起点二维向量(t,f(t))和Δ变化的终点二维向量(t+Δt,f(t+Δt))得到切线的斜率,同时根据起点就能算出切线的向量了,如下:
上面根据微分lim Δt->0(即=0)计算出斜率,然后我们再根据起点二维坐标计算出切线向量,代码如下:
Vector3 pos = new Vector3(tx, ty, 0);
float slope = -GravAccelerat / HorConstSpeed * time;
Vector3 tan = pos + new Vector3(1, slope, 0) * 5.0f;
#if UNITY_EDITOR
Debug.DrawLine(pos, tan, Color.white);
#endif
下面看下效果,如图:
明显看得出来白色的切线向量吧。
所以我们也可以说得到一个结论:瞬间的弦的斜率=切线的斜率
好,我继续潜水学习,偶尔出来写一篇博客冒泡。