在JS中定义和使用Vector2

概述

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类型相同的效果。

点积、叉积等内容后续将逐步实现和添加。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

巽星石

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值