概述
Vector2
是GDSCript中表示二维向量的类型,你会发现无论在任何编程语言中,只要你想很好的实现2D绘图以及几何和物理相关,Vector2
是你必须要实现的一个类。我之前学C++时就写过一个C++的版本。
本篇就介绍我自己在JavaScript中定义的Vector2
类型。它将是我接下来基于Canvas和SVG进行绘图的类的基础类型。
定义Vector2类型
// =======================================================
// Vector2
// 自定义类,Javascript版本的2D向量类型
// 巽星石
// 2024年10月6日13:18:59
// 2024年10月6日17:19:54
// 2D向量类型
// =======================================================
class Vector2{
/*==================== 构造函数 ====================*/
constructor(x,y){ // 构造函数
this.x = x
this.y = y
}
/*==================== 静态方法 ====================*/
static ZERO(){
return new Vector2(0,0);
}
static LEFT(){
return new Vector2(-1,0);
}
static RIGHT(){
return new Vector2(1,0);
}
static UP(){
return new Vector2(0,-1);
}
static DOWN(){
return new Vector2(0,1);
}
static ONE(){
return new Vector2(1,1);
}
static ROTATED(deg){ //返回与X轴夹角为deg度的单位向量
var vec = this.RIGHT();
vec.rotated(deg);
return vec;
}
static pVector2(ang,len){ // 返回极坐标点
if(typeof(ang) == "number" && typeof(len) == "number"){
return this.ROTATED(ang).times(len);
}
}
static rad_to_deg(rad){ // 弧度转角度
return rad * (180.0 / Math.PI) ;
}
static deg_to_rad(deg){ // 角度转弧度
return deg * (Math.PI / 180.0) ;
}
static sin(deg){ //修正后的正弦函数
const PI2 = Math.PI * 2 //2π
let ang_rad = Vector2.deg_to_rad(deg); //转为弧度
let yu = Math.abs(ang_rad % PI2)
//所有正负180度或360度 + 任意圈数的sin值设为0
return (yu != Math.PI && yu != 0) ? Math.sin(ang_rad) : 0
}
static cos(deg){ //修正后的余弦函数
const PI2 = Math.PI * 2 //2π
let ang_rad = Vector2.deg_to_rad(deg); //转为弧度
let yu = Math.abs(ang_rad % PI2)
//所有正负90度 + 任意圈数的cos值设为0
return (yu != Math.PI/2) ? Math.cos(ang_rad) : 0
}
/*==================== 方法 ====================*/
distance_to(b){ // 返回到b点的距离
if(b.constructor.name == "Vector2"){
const dx = this.x - b.x;
const dy = this.y - b.y;
return Math.sqrt(Math.pow(dx,2) + Math.pow(dy,2))
}
}
direction_to(b){ // 返回到b点的方向
if(b.constructor.name == "Vector2"){
let vec = b.minus(this); // AB向量 = B - A
return vec.normalized();
}
}
length(){ //返回向量的长度
const o = new Vector2(0,0); //坐标系原点
return this.distance_to(o)
}
normalized(){ // 获取归一化向量
return this.divide(this.length());
}
/*==================== 角度相关 ====================*/
angle(){ // 与X轴夹角(角度)
return Vector2.rad_to_deg(Math.asin(this.normalized().y));
}
angle_to(b){ // 到b向量的夹角(角度)
if(b.constructor.name == "Vector2"){
return b.angle() - this.angle();
}
}
rotated(deg){ // 旋转向量
const r = this.length();
const ang = this.angle();
var ang_rad = deg + ang;
this.x = r * Vector2.cos(ang_rad);
this.y = r * Vector2.sin(ang_rad);
}
/*==================== 运算 ====================*/
plus(b){ //加法
switch (typeof(b)) {
case "object":
if(b.constructor.name == "Vector2"){
return new Vector2(this.x + b.x,this.y + b.y)
}
break;
default:
break;
}
}
minus(b){ //减法
switch (typeof(b)) {
case "object":
if(b.constructor.name == "Vector2"){
return new Vector2(this.x - b.x,this.y - b.y)
}
break;
default:
break;
}
}
times(b){ //乘法
switch (typeof(b)) {
case "number":
return new Vector2(this.x * b,this.y * b)
break;
case "object":
if(b.constructor.name == "Vector2"){
return new Vector2(this.x * b.x,this.y * b.y)
}
break;
default:
break;
}
}
divide(b){ //除法
switch (typeof(b)) {
case "number":
return new Vector2(this.x/b,this.y/b)
break;
case "object":
if(b.constructor.name == "Vector2"){
return new Vector2(this.x / b.x,this.y / b.y)
}
break;
default:
break;
}
}
}
在网页中使用Vector2类型
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="1.js"></script>
</head>
<body>
</body>
<script>
var a = new Vector2(100,100);
console.log(a);
</script>
</html>
运行效果:
向量的长度
向量的长度是指到坐标系原点(0,0)
的距离。
var a = new Vector2(100,100);
var b = new Vector2(200,100);
console.log(a.length());
console.log(b.length());
两点间距离
var a = new Vector2(100,100);
var b = new Vector2(200,100);
console.log(a.distance_to(b));
到点B的方向向量
var a = new Vector2(100,100);
var b = new Vector2(200,100);
console.log(a.direction_to(b));
归一化向量
var a = new Vector2(100,100);
var b = new Vector2(200,100);
console.log(a.normalized());
四则运算
加法
var a = new Vector2(100,100);
var b = new Vector2(200,100);
console.log(a.plus(b));
减法
var a = new Vector2(100,100);
var b = new Vector2(200,100);
console.log(a.minus(b));
乘法
var a = new Vector2(100,100);
var b = new Vector2(200,100);
console.log(a.times(b)); # 乘以一个Vector2
console.log(a.times(2)); # 乘以一个标量
除法
var a = new Vector2(100,100);
var b = new Vector2(200,100);
console.log(a.divide(b)); # 除以一个Vector2
console.log(a.divide(2)); # 除以一个标量
求角度
我们知道$ \sin \theta = \frac{y}{r} ,其中 r = 1 ,则 ,其中r = 1,则 ,其中r=1,则 \sin \theta = y ,则 ,则 ,则 \theta = \arcsin (y) $。
也就是我们要求一个向量与X轴的夹角θ,只需要求其归一化向量的y坐标的反正弦函数即可。
angle(){ // 与X轴夹角(角度)
return this.rad_to_deg(Math.asin(this.normalized().y));
}
与X轴正方向的夹角
var a = new Vector2(100,100);
var b = new Vector2(200,100);
console.log(a.angle());
console.log(b.angle());
到B向量的夹角
var a = new Vector2(100,100);
var b = new Vector2(200,100);
console.log(a.angle_to(b));
console.log(b.angle_to(a));
向量旋转
var a = new Vector2(100,100);
console.log(a.length());
a.rotated(45)
console.log(a);
console.log(a.length());
常用单位向量和极坐标点
仿照GDSCript中的Vector2,设计了一些静态方法用于获取一些常用的单位向量。
static ZERO(){
return new Vector2(0,0);
}
static LEFT(){
return new Vector2(-1,0);
}
static RIGHT(){
return new Vector2(1,0);
}
static UP(){
return new Vector2(0,-1);
}
static DOWN(){
return new Vector2(0,1);
}
static ONE(){
return new Vector2(1,1);
}
static ROTATED(deg){ //返回与X轴夹角为deg度的单位向量
var vec = this.RIGHT();
vec.rotated(deg);
return vec;
}
static pVector2(ang,len){ // 返回极坐标点
if(typeof(ang) == "number" && typeof(len) == "number"){
return this.ROTATED(ang).times(len);
}
}
ROTATED()
是我自己原创的,可以快速的获取与X轴夹角为deg
度的单位向量。
pVector2()
返回与X轴夹角为deg
度,长度为len
的向量。
var a = Vector2.pVector2(-90,100)
console.log(a)
角度与弧度互转
static rad_to_deg(rad){ // 弧度转角度
return rad * (180.0 / Math.PI) ;
}
static deg_to_rad(deg){ // 角度转弧度
return deg * (Math.PI / 180.0) ;
}
修正后的正弦和余弦函数
Math.sin(Math.PI * n)
、Math.sin(Math.PI * 2 * n)
以及Math.cos((Math.PI / 2) * n)
返回的都不是0,而是一个极小数。这对极坐标和向量旋转来说是没法用的,所以自己在Vector2
类型中定义了cos()
和sin()
,并做了一些修正。
static sin(deg){ //修正后的正弦函数
const PI2 = Math.PI * 2 //2π
let ang_rad = Vector2.deg_to_rad(deg); //转为弧度
let yu = Math.abs(ang_rad % PI2)
//所有正负180度或360度 + 任意圈数的sin值设为0
return (yu != Math.PI && yu != 0) ? Math.sin(ang_rad) : 0
}
static cos(deg){ //修正后的余弦函数
const PI2 = Math.PI * 2 //2π
let ang_rad = Vector2.deg_to_rad(deg); //转为弧度
let yu = Math.abs(ang_rad % PI2)
//所有正负90度 + 任意圈数的cos值设为0
return (yu != Math.PI/2) ? Math.cos(ang_rad) : 0
}
在HTML5 Canvas中使用
在HTML中定义canvas
标签:
<body>
<canvas id="canvas01" width="300" height="300"></canvas>
</body>
使用Vector2
定义点并进行绘制:
// 获取canvas和上下文对象
let canvas = document.getElementById("canvas01");
let ctx = canvas.getContext('2d');
// 定义两点
let p1 = Vector2.ZERO()
let p2 = Vector2.pVector2(45,100)
// 绘制
ctx.beginPath()
ctx.strokeStyle = "red"; //轮廓颜色
ctx.lineWidth = 2; //轮廓线宽
// 绘制直线
ctx.moveTo(p1.x,p1.y);
ctx.lineTo(p2.x,p2.y);
ctx.stroke();
为了方便查看画布,定义如下CSS样式:
body{
background-color: #444;
}
canvas{
background-color: #fff;
}
绘制效果:
总结
本文基于JavaScript编写了Vector2
类型,基本实现与GDSCript内置Vector2
类型相同的效果。
点积、叉积等内容后续将逐步实现和添加。