用代码描述自然系统-向量与受力
向量
物理系统中,力与加速度都是矢量,是具有大小和方向的量,这些量在数学中有一个名字:向量。
在 p5.js 中,原生支持了向量,通过 createVector 或 p5.Vector 即可使用向量模块,常用的向量方法有:
- add(): 计算两个向量的和向量;
- sub(): 向量相减;
- mult(): 向量与标量相乘;
- div(): 向量与标量相减;
- mag(): 计算向量的长度;
- setMag(): 设置向量的长度;
- normalize(): 将向量单位化,单位化后的向量长度为1;
- limit(): 限制向量的长度;
- dist(): 计算两个向量的几何距离;
- angleBetween(): 计算两个向量的夹角;
- dot(): 向量点乘;
- random2D() : 生成一个随机二维向量;
向量的详细使用可以参考 p5.js reference 的文档。
力与加速度
根据物理规律可知,一个质点的受力等于其加速度与质量的乘积;反之,一个质点的加速度等于受力与其质量之比。
用公式表示为:
F = ma
也就是说,加速度与受力在效果上是等效的。
我们构造一个单位质量的物体,模拟该物体受到重力,今儿得到加速度后的自由落体效果。由于重力实际上就是万有引力,模拟万有引力有些不方便,所以直接模拟重力加速度。
构造
定义一个 Mover 类:
class Mover {
constructor(position, mass = 1) {
this.position = position;
this.velocity = createVector(0, 0);
this.mass = mass;
this.filledColor = color(random(50, 200), random(50, 200), random(50, 200));
}
}
质点具备一些属性:
- position,质点在平面的位置,也可理解为相对于原点的位移;
- velocity,质点的速度,矢量;
当给质点施加受力时,受力转换为加速度,被 velocity 进行累加,代码表现上为:
class Mover {
// ...
addForce(force) {
const a = force.div(this.mass);
this.velocity.add(force);
}
update() {
this.position.add(this.velocity);
}
// ...
}
其中 addForce 方法用于对物体受力,物体将受力转换为加速度,update 方法用于将受力结果最终转换为位移。
绘制
为 Mover 定义 display 方法,用于在二维平面上进行显示:
class Mover {
// ...
display() {
fill(this.filledColor);
noStroke();
ellipse(this.position.x, this.position.y, 20);
}
// ...
}
上面的代码中,使用 ellipse 方法绘制一个圆,填充随机颜色,圆直径为 20 像素。
完整的 Mover 类代码:
class Mover {
constructor(position, mass = 1) {
this.position = position;
this.velocity = createVector(0, 0);
this.mass = mass;
this.diameter = diameter;
this.filledColor = color(random(50, 200), random(50, 200), random(50, 200));
}
addForce(force) {
const a = force.div(this.mass);
this.velocity.add(force);
}
reverseVelocity(attenuation = 0.65) {
this.velocity.mult(-1 * attenuation);
}
update() {
this.position.add(this.velocity);
}
display() {
fill(this.filledColor);
noStroke();
ellipse(this.position.x, this.position.y, this.diameter);
}
}
自由落体
构造一个二维平面,在平面上施加一个垂直向下的力,构造出重力加速度,并将 Mover 实例加入到系统中进行受力计算。借助 p5 的能力,实现二维平面内的自由落体如下:
const G = 0.01;
const C = 60;
const PWidth = 500, PHeight = 200;
class Mover {
constructor(position, mass = 1) {
this.position = position;
this.velocity = createVector(0, 0);
this.mass = mass;
this.diameter = 20;
this.filledColor = color(random(50, 200), random(50, 200), random(50, 200));
}
setColor(filledColor) {
this.filledColor = filledColor || color(random(100));
}
addForce(force) {
const a = force.div(this.mass);
this.velocity.add(force);
}
update() {
this.position.add(this.velocity);
if (this.position.y >= PHeight - this.diameter / 2) {
this.position.y = PHeight - this.diameter / 2;
}
}
display() {
fill(this.filledColor);
noStroke();
ellipse(this.position.x, this.position.y, this.diameter);
}
}
function createMovers(count = 5) {
const list = [];
for (let i = 0; i < count; i++) {
const position = createVector(random(PWidth), random(PHeight));
list.push(new Mover(position));
}
return list;
}
var movers = [];
function setup() {
createCanvas(PWidth, PHeight);
frameRate(60);
movers = createMovers(5);
setInterval(() => {
movers = [];
movers = createMovers(5);
}, 5000);
}
function draw() {
background(255);
movers.forEach(mover => {
mover.addForce(createVector(0, 0.4));
mover.update();
mover.display();
});
}
自由落体效果如下:
反弹效果
假设物体具有一定的弹性,在接触地面碰撞以后,可以被重新弹起。效果如下:
这看起来更符合常识。从物理规律上看,物体反弹后将获得相反的速度,所以为 Mover 增加 reverseVelocity 方法,并设置衰减因子:
class Mover {
// ...
reverseVelocity(attenuation = 0.65) {
this.velocity.mult(-1 * attenuation);
}
// ...
}
当更新物体位置时发现物体与地面发生了碰撞,那么就让该物体的速度翻转,并适量衰减,这会更符合现实效果。增加代码如下:
class Mover {
// ...
update() {
this.position.add(this.velocity);
if (this.position.y >= PHeight - this.diameter / 2) {
this.position.y = PHeight - this.diameter / 2;
// 与地面碰撞时反弹,速度衰减到原速度的 80%
this.reverseVelocity(0.8);
}
}
// ...
}