作为物理模拟开场的第一进程,当然就要来讲一下最经典的物理模拟案例:『弹跳球』~
其实很多国外的Canvas特效教程都会把这一篇当成第一个介绍案例,比方说
这边推荐一下 Apress Physics for JavaScript Games Animation and Simulations
这本书,因为在学习物理模拟的路上这本书给了我不少帮助XD~
在这个案例中我们除了会介绍弹跳球的案例,还会介绍一些关於这个案例的基础物理常识,最後还会带到一些更进阶的物理模拟实作。
在一开始我们还不会马上的带到程序源码,而是要先来讨论高中数理的向量
、反射
与斜向抛射
,由於我们在这个案例中会持续用到的三个基础概念,所以我打算在一开始就讲清楚物理模拟在这三部分的相关概念。
我们在这篇文章中会先讨论到Canvas
中向量类
的建立,就让我们接着开始吧~
我们其实在前面的文章有提过向量
,向量指的是一种从座标A移动到座标B的附带方向的移动量,从数学的角度上来看,假设今天有一个质点即将从(1,2)
移动到(2,4)
,则我们可说这个质点被附加了一个(1,2)
的移动向量。
向量如果要转变成纯量,那麽就必须要取该向量X,Y值的平方和,然後再开根号(毕氏定理),以我们刚刚提到的(1,2)
,他的纯量就是√5
(也就是该质点一共移动了√5
的距离长度)。
向量再转变成纯量的过程中会丢失他的方向属性,而变成单纯的量值,所以如果今天换成另外一个案例,假设我们只知道移动的距离是√5
而不知道这个移动的起始点和结束点; 想要把√5这个距离转变成向量(也就是要知道水平和垂直移动的距离),那我们就必须要先获知该纯量的方向(也就是下图中的角度θ
),然後用三角函数来把√5
转变成1
(水平移动量)和2
(垂直移动量)。
(cosθ * √5, sinθ * √5) = (1,2)
除了向量变纯量, 纯量变向量的运算以外,向量之间有其他类型的运算,像是:
以下面这张图为例,我们可以可以把紫色向量看作是向量a(红色向量)和向量b(蓝色向量)的和。
所以反过来也可以推导紫色向量
- 向量a
= 向量b
内积
是一个有趣的概念,求取两个向量内积
的方法如下:
假设向量a为(ax,ay),向量b则是(bx,by)
则向量a与向量b的内积是ax*bx+ay*by
内积的结果会是一个纯量,他的几何意义在於我们可以透过内积取得两个向量的夹角。
透过内积取得夹角的公式如下:
一般来说,内积的值大於0
,代表两向量夹角低於90度
,
内积的值等於0
,代表两个向量互相垂直
,
内积的值小於0
,代表两个向量夹角介於90度到180度
之间。
对公式推导有兴趣的人可以看这边
在前端开发的环境下,我们其实可以利用ES6的class(当然也可以用ES5的构筑式)去给向量
建立一个独立的类。
class Vector2D {
constructor(x, y) {
this.x = x;
this.y = y;
}
/**
* 求纯量值
*
* @returns
* @memberof Vector2D
*/
length() {
return Math.sqrt(this.lengthSquared());
}
/**
* 复制该向量
*
* @returns
* @memberof Vector2D
*/
clone() {
return new Vector2D(this.x, this.y);
}
/**
*倒转该向量
*
* @memberof Vector2D
*/
negate() {
this.x = - this.x;
this.y = - this.y;
}
/**
* 把该向量转变成单位向量
*
* @returns
* @memberof Vector2D
*/
normalize() {
let length = this.length(); if (length > 0) {
this.x /= length;
this.y /= length;
}
return this.length();
}
/**
* 回传与某向量的向量和
*
* @param {*} vec
* @returns
* @memberof Vector2D
*/
add(vec) {
return new Vector2D(this.x + vec.x, this.y + vec.y);
}
/**
* 加上某向量
*
* @param {*} vec
* @memberof Vector2D
*/
incrementBy(vec) {
this.x += vec.x;
this.y += vec.y;
}
/**
*
* 回传与某向量的向量差
* @param {*} vec
* @returns
* @memberof Vector2D
*/
subtract(vec) {
return new Vector2D(this.x - vec.x, this.y - vec.y);
}
/**
* 扣除某向量
*
* @param {*} vec
* @memberof Vector2D
*/
decrementBy(vec) {
this.x -= vec.x;
this.y -= vec.y;
}
/**
* 回传扩增k倍後的向量
*
* @param {*} k
* @memberof Vector2D
*/
multiply(k) {
return new Vector2D(k * this.x, k * this.y);
}
/**
* 扩增该向量
*
* @param {*} k
* @memberof Vector2D
*/
scaleBy(k) {
this.x *= k; this.y *= k;
}
/**
* 求取该向量与其他向量的内积
*
* @param {*} vec
* @returns
* @memberof Vector2D
*/
dotProduct(vec) {
return this.x * vec.x + this.y * vec.y;
}
/**
* 求取此向量映射在某向量上的长度
*
* @param {*} vec
* @returns
* @memberof Vector2D
*/
projection(vec) {
const length = this.length();
const lengthVec = vec.length();
let proj;
if ((length == 0) || (lengthVec == 0)) {
proj = 0;
} else {
proj = (this.x * vec.x + this.y * vec.y) / lengthVec;
}
return proj;
}
/**
* 回传一个新向量,新向量的方向会跟作为参数向量相同,但是量值上是作为此向量投射在参数向量上的长度
*
* @param {*} vec
* @returns
* @memberof Vector2D
*/
project(vec) {
return vec.para(this.projection(vec));
}
/**
* 回传垂直与此向量的u倍单位向量
*
* @param {*} vec
* @returns
* @memberof Vector2D
*/
perp(u,anticlockwise = true){
if (typeof(anticlockwise)==='undefined') anticlockwise = true;
var length = this.length();
var vec = new Vector2D(this.y, -this.x);
if (length > 0) {
if (anticlockwise){
vec.scaleBy(u/length);
}else{
vec.scaleBy(-u/length);
}
}else{
vec = new Vector2D(0,0);
}
return vec;
}
/**
* 根据传入的u值来回传一个u倍(或-u倍)的单位向量
*
* @param {*} vec
* @returns
* @memberof Vector2D
*/
para(u, positive = true) {
const length = this.length();
const vec = new Vector2D(this.x, this.y);
if (positive) {
vec.scaleBy(u / length);
} else {
vec.scaleBy(-u / length);
}
return vec;
}
/**
* 求取该向量与其他向量的夹角
*
* @param {*} vec
* @returns
* @memberof Vector2D
*/
static angleBetween(vec1, vec2) {
return Math.acos(vec1.dotProduct(vec2) / (vec1.length() * vec2.length()));
}
}
这边我其实是参照Apress Physics for JavaScript Games Animation and Simulations, With HTML5 Canvas
上的写法,改写成ES6 Class,并删除部分不常用到的方法。
我们在接下来的文章中会持续的用到由这边建立好的向量类,所以各位同学可以看一下这个类里面都有些什麽方法~
下一篇文我们将会讲到如何在Canvas中实作反射(Reflection)行为,敬请期待~
<<: Day 11 用 Context 来组织你的测试区块
>>: Day 20 Compose UI Animation II (change color & gradient)
VS 2022 Preview | 64位元 | Browser IDE 🐄点此填写今日份随堂测验 ...
为什麽要刷题? 在经历过了二十天左右的刷题练习经验,我们从不同的题目中尝试了各种有趣的程序题目。 ...
目前我们写好了一个新增的画面 需求 接下来,常见的需求是,人员的新增之後是人员的编辑。 新增用的画面...
CI 持续整合。 为什麽要 CI 呢? 想想我们前面写了那麽辛苦的自动测试,结果有人不跑测试就上传。...
一、整理变数重声明与重名变数的描述。 变数重声明,对已经声明过的变数,再次声明。 前提条件如下: 变...