先上效果
两个小球之间有一根弹簧,这里有一条线表示,其中左球固定,在点击开始后,右球开始做自由落体
思路
先做受力分析
经过受力分析
可以发现,整个系统一共有三个力
在起作用,我们分别把他们求出来并合成为一个力
,然后这个力
的加速度
即可
重力
右边小球全程受到方向向下的重力
,其公式为
G = m * g
弹力
右边小球全程受到从自身中心指向左边小球中心方向的的弹力
,其公式为
// 胡克定律
F = -△x * k
空气阻力
可以简化成和速度方向
相反,乘一个小于1的阻力系数
的力
// dragCoefficient值越大说明阻力越大
F = -v * dragCoefficient
我们先创建重力
的单位向量代表重力
的方向,因为重力
永远是垂直向下的所以向量
其单位向量是(0, 1),然后给定一个重力
大小,根据重力
公式,质量乘重力加速度
即可,这里质量
我给了 10,重力加速度
给 9.8,代码如下
// 获取重力单位向量
function getGravityNorVector() {
return {
x: 0,
y: 1,
};
}
// 获取重力大小
function getGravitySize() {
return m * g;
}
然后创建弹力
的单位向量
表示弹力
的方向,其方向永远是右球的球心指向左球的球心,其大小根据胡克定律
给到,即形变距离
乘以 弹簧弹性系数
// 获取弹力单位向量
function getElasticityNorVector() {
// 左球球心坐标减右球球心坐标,然后做向量归一化
return normalized(createVector(originPosition, targetPosition));
}
// 获取弹力大小
function getElasticitySize() {
// 计算Δx
const offsetX =
length(createVector(originPosition, targetPosition)) - originElasticitySize;
// 胡克定律,只要大小,所以取绝对值
return Math.abs(-offsetX * K);
}
然后我们计算空气阻力
,其方向和速度方向相反,阻力系数
我给了个 0.1,由于一开始初速度为 0,所以阻力为 0,只用等到动起来有速度的时候才会产生阻力
// 空气阻力,dragCoefficient为阻力系数,输出是一个向量
const other = {
x: -vx * dragCoefficient,
y: -vy * dragCoefficient,
};
现在我们所有力的大小和方向都求出来了,然后根据向量平行四边形法则
,先合成弹力
和重力
// 单位向量 * 向量长度可以得到矢量力,输出是一个向量
const elasticityAndGravity = add(
mul(getElasticitySize(), getElasticityNorVector()),
mul(getGravitySize(), getGravityNorVector())
);
然后再和空气阻力
合成最终的力
// 所有力的合成,输出是一个向量
const total = add(elasticityAndGravity, other);
然后再把合成后的力分解成水平和垂直的两个分力
,这里用到向量投影
算法就能实现,即可以先通过向量点积
的形式,求出与水平单位向量
(1,0)和垂直单位向量
(0,1)的余弦值
,然后用合成力
的大小乘以这个余弦值
得到投影
到水平方向和垂直方向的大小,再用这个大小分别乘以水平单位向量
(1,0)和垂直单位向量
(0,1)就能得到水平和垂直的两个分力向量
了
// 获取力在x轴和y轴分量向量
function getXYAxisVector(fNorVector, fSize) {
const yAxisNorVector = createVector(
{
x: targetPosition.x,
y: targetPosition.y - 1,
},
targetPosition
);
const xAxisNorVector = createVector(
{
x: targetPosition.x - 1,
y: targetPosition.y,
},
targetPosition
);
// 分别点击求向量在水平单位向量和垂直单位向量的余弦值
const cosYAxis = dot(yAxisNorVector, fNorVector);
const cosXAxis = dot(xAxisNorVector, fNorVector);
return {
yVector: mul(fSize * cosYAxis, yAxisNorVector),
xVector: mul(fSize * cosXAxis, xAxisNorVector),
};
}
// 获取水平和垂直分力向量
const result = getXYAxisVector(normalized(total), length(total));
然后根据公式
F = m * a;
把得到的两个分力
分别除以质量 m
,目前 m
给的是 10,得到 x 和 y 方向的加速度
// 获取水平和垂直方向加速度
const xAcceleration = result.xVector.x / m;
const yAcceleration = result.yVector.y / m;
然后再每一帧渲染的时候,把加速度
加到速度
上,右球的坐标
加上速度
,即可以渲染出效果,这里除以100是防止速度太快
vx += xAcceleration;
vy += yAcceleration;
targetPosition.x += vx / 100;
targetPosition.y += vy / 100;
就能得到最终这个效果了
源码
可以关注下我的公众号 - 前端不是只有切图,找到对应的文章有源码,还有其他有趣的干货文章哦~