Files
esengine/packages/framework/math/src/Vector2.ts
YHH 155411e743 refactor: reorganize package structure and decouple framework packages (#338)
* refactor: reorganize package structure and decouple framework packages

## Package Structure Reorganization
- Reorganized 55 packages into categorized subdirectories:
  - packages/framework/ - Generic framework (Laya/Cocos compatible)
  - packages/engine/ - ESEngine core modules
  - packages/rendering/ - Rendering modules (WASM dependent)
  - packages/physics/ - Physics modules
  - packages/streaming/ - World streaming
  - packages/network-ext/ - Network extensions
  - packages/editor/ - Editor framework and plugins
  - packages/rust/ - Rust WASM engine
  - packages/tools/ - Build tools and SDK

## Framework Package Decoupling
- Decoupled behavior-tree and blueprint packages from ESEngine dependencies
- Created abstracted interfaces (IBTAssetManager, IBehaviorTreeAssetContent)
- ESEngine-specific code moved to esengine/ subpath exports
- Framework packages now usable with Cocos/Laya without ESEngine

## CI Configuration
- Updated CI to only type-check and lint framework packages
- Added type-check:framework and lint:framework scripts

## Breaking Changes
- Package import paths changed due to directory reorganization
- ESEngine integrations now use subpath imports (e.g., '@esengine/behavior-tree/esengine')

* fix: update es-engine file path after directory reorganization

* docs: update README to focus on framework over engine

* ci: only build framework packages, remove Rust/WASM dependencies

* fix: remove esengine subpath from behavior-tree and blueprint builds

ESEngine integration code will only be available in full engine builds.
Framework packages are now purely engine-agnostic.

* fix: move network-protocols to framework, build both in CI

* fix: update workflow paths from packages/core to packages/framework/core

* fix: exclude esengine folder from type-check in behavior-tree and blueprint

* fix: update network tsconfig references to new paths

* fix: add test:ci:framework to only test framework packages in CI

* fix: only build core and math npm packages in CI

* fix: exclude test files from CodeQL and fix string escaping security issue
2025-12-26 14:50:35 +08:00

563 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 2D 向量数据接口
*
* 轻量级数据结构,用于组件属性和序列化。
* Lightweight data structure for component properties and serialization.
*/
export interface IVector2 {
x: number;
y: number;
}
/**
* 2D向量类
*
* 提供完整的2D向量运算功能包括
* - 基础运算(加减乘除)
* - 向量运算(点积、叉积、归一化)
* - 几何运算(距离、角度、投影)
* - 变换操作(旋转、反射、插值)
*/
export class Vector2 implements IVector2 {
/** X分量 */
public x: number;
/** Y分量 */
public y: number;
/**
* 创建2D向量
* @param x X分量默认为0
* @param y Y分量默认为0
*/
constructor(x: number = 0, y: number = 0) {
this.x = x;
this.y = y;
}
// 静态常量
/** 零向量 (0, 0) */
static readonly ZERO = new Vector2(0, 0);
/** 单位向量 (1, 1) */
static readonly ONE = new Vector2(1, 1);
/** 右方向向量 (1, 0) */
static readonly RIGHT = new Vector2(1, 0);
/** 左方向向量 (-1, 0) */
static readonly LEFT = new Vector2(-1, 0);
/** 上方向向量 (0, 1) */
static readonly UP = new Vector2(0, 1);
/** 下方向向量 (0, -1) */
static readonly DOWN = new Vector2(0, -1);
// 基础属性
/**
* 获取向量长度(模)
*/
get length(): number {
return Math.sqrt(this.x * this.x + this.y * this.y);
}
/**
* 获取向量长度的平方
*/
get lengthSquared(): number {
return this.x * this.x + this.y * this.y;
}
/**
* 获取向量角度(弧度)
*/
get angle(): number {
return Math.atan2(this.y, this.x);
}
/**
* 检查是否为零向量
*/
get isZero(): boolean {
return this.x === 0 && this.y === 0;
}
/**
* 检查是否为单位向量
*/
get isUnit(): boolean {
const lenSq = this.lengthSquared;
return Math.abs(lenSq - 1) < Number.EPSILON;
}
// 基础运算
/**
* 设置向量分量
* @param x X分量
* @param y Y分量
* @returns 当前向量实例(链式调用)
*/
set(x: number, y: number): this {
this.x = x;
this.y = y;
return this;
}
/**
* 复制另一个向量的值
* @param other 源向量
* @returns 当前向量实例(链式调用)
*/
copy(other: Vector2): this {
this.x = other.x;
this.y = other.y;
return this;
}
/**
* 克隆当前向量
* @returns 新的向量实例
*/
clone(): Vector2 {
return new Vector2(this.x, this.y);
}
/**
* 向量加法
* @param other 另一个向量
* @returns 当前向量实例(链式调用)
*/
add(other: Vector2): this {
this.x += other.x;
this.y += other.y;
return this;
}
/**
* 向量减法
* @param other 另一个向量
* @returns 当前向量实例(链式调用)
*/
subtract(other: Vector2): this {
this.x -= other.x;
this.y -= other.y;
return this;
}
/**
* 向量数乘
* @param scalar 标量
* @returns 当前向量实例(链式调用)
*/
multiply(scalar: number): this {
this.x *= scalar;
this.y *= scalar;
return this;
}
/**
* 向量数除
* @param scalar 标量
* @returns 当前向量实例(链式调用)
*/
divide(scalar: number): this {
if (scalar === 0) {
throw new Error('不能除以零');
}
this.x /= scalar;
this.y /= scalar;
return this;
}
/**
* 向量取反
* @returns 当前向量实例(链式调用)
*/
negate(): this {
this.x = -this.x;
this.y = -this.y;
return this;
}
// 向量运算
/**
* 计算与另一个向量的点积
* @param other 另一个向量
* @returns 点积值
*/
dot(other: Vector2): number {
return this.x * other.x + this.y * other.y;
}
/**
* 计算与另一个向量的叉积2D中返回标量
* @param other 另一个向量
* @returns 叉积值
*/
cross(other: Vector2): number {
return this.x * other.y - this.y * other.x;
}
/**
* 向量归一化(转换为单位向量)
* @returns 当前向量实例(链式调用)
*/
normalize(): this {
const len = this.length;
if (len === 0) {
return this;
}
return this.divide(len);
}
/**
* 获取归一化后的向量(不修改原向量)
* @returns 新的单位向量
*/
normalized(): Vector2 {
return this.clone().normalize();
}
// 几何运算
/**
* 计算到另一个向量的距离
* @param other 另一个向量
* @returns 距离值
*/
distanceTo(other: Vector2): number {
const dx = this.x - other.x;
const dy = this.y - other.y;
return Math.sqrt(dx * dx + dy * dy);
}
/**
* 计算到另一个向量的距离平方
* @param other 另一个向量
* @returns 距离平方值
*/
distanceToSquared(other: Vector2): number {
const dx = this.x - other.x;
const dy = this.y - other.y;
return dx * dx + dy * dy;
}
/**
* 计算与另一个向量的夹角(弧度)
* @param other 另一个向量
* @returns 夹角0到π
*/
angleTo(other: Vector2): number {
const dot = this.dot(other);
const lenProduct = this.length * other.length;
if (lenProduct === 0) return 0;
return Math.acos(Math.max(-1, Math.min(1, dot / lenProduct)));
}
/**
* 计算向量在另一个向量上的投影
* @param onto 投影目标向量
* @returns 新的投影向量
*/
projectOnto(onto: Vector2): Vector2 {
const dot = this.dot(onto);
const lenSq = onto.lengthSquared;
if (lenSq === 0) return new Vector2();
return onto.clone().multiply(dot / lenSq);
}
/**
* 计算向量在另一个向量上的投影长度
* @param onto 投影目标向量
* @returns 投影长度(带符号)
*/
projectOntoLength(onto: Vector2): number {
const len = onto.length;
if (len === 0) return 0;
return this.dot(onto) / len;
}
/**
* 获取垂直向量顺时针旋转90度
* Get perpendicular vector (clockwise 90 degrees)
* @returns 新的垂直向量
*/
perpendicular(): Vector2 {
// Clockwise 90° rotation: (x, y) -> (y, -x)
// 顺时针旋转 90°
return new Vector2(this.y, -this.x);
}
// 变换操作
/**
* 向量旋转(顺时针为正)
* Rotate vector (clockwise positive)
*
* 使用左手坐标系约定:正角度 = 顺时针旋转
* Uses left-hand coordinate system: positive angle = clockwise
*
* @param angle 旋转角度(弧度)
* @returns 当前向量实例(链式调用)
*/
rotate(angle: number): this {
const cos = Math.cos(angle);
const sin = Math.sin(angle);
// Clockwise rotation: x' = x*cos + y*sin, y' = -x*sin + y*cos
// 顺时针旋转公式
const x = this.x * cos + this.y * sin;
const y = -this.x * sin + this.y * cos;
this.x = x;
this.y = y;
return this;
}
/**
* 获取旋转后的向量(不修改原向量)
* @param angle 旋转角度(弧度)
* @returns 新的旋转后向量
*/
rotated(angle: number): Vector2 {
return this.clone().rotate(angle);
}
/**
* 围绕一个点旋转
* @param center 旋转中心点
* @param angle 旋转角度(弧度)
* @returns 当前向量实例(链式调用)
*/
rotateAround(center: Vector2, angle: number): this {
return this.subtract(center).rotate(angle).add(center);
}
/**
* 反射向量(关于法线)
* @param normal 法线向量(应为单位向量)
* @returns 当前向量实例(链式调用)
*/
reflect(normal: Vector2): this {
const dot = this.dot(normal);
this.x -= 2 * dot * normal.x;
this.y -= 2 * dot * normal.y;
return this;
}
/**
* 获取反射后的向量(不修改原向量)
* @param normal 法线向量(应为单位向量)
* @returns 新的反射向量
*/
reflected(normal: Vector2): Vector2 {
return this.clone().reflect(normal);
}
// 插值和限制
/**
* 线性插值
* @param target 目标向量
* @param t 插值参数0到1
* @returns 当前向量实例(链式调用)
*/
lerp(target: Vector2, t: number): this {
this.x += (target.x - this.x) * t;
this.y += (target.y - this.y) * t;
return this;
}
/**
* 限制向量长度
* @param maxLength 最大长度
* @returns 当前向量实例(链式调用)
*/
clampLength(maxLength: number): this {
const lenSq = this.lengthSquared;
if (lenSq > maxLength * maxLength) {
return this.normalize().multiply(maxLength);
}
return this;
}
/**
* 限制向量分量
* @param min 最小值向量
* @param max 最大值向量
* @returns 当前向量实例(链式调用)
*/
clamp(min: Vector2, max: Vector2): this {
this.x = Math.max(min.x, Math.min(max.x, this.x));
this.y = Math.max(min.y, Math.min(max.y, this.y));
return this;
}
// 比较操作
/**
* 检查两个向量是否相等
* @param other 另一个向量
* @param epsilon 容差默认为Number.EPSILON
* @returns 是否相等
*/
equals(other: Vector2, epsilon: number = Number.EPSILON): boolean {
return Math.abs(this.x - other.x) < epsilon &&
Math.abs(this.y - other.y) < epsilon;
}
/**
* 检查两个向量是否完全相等
* @param other 另一个向量
* @returns 是否完全相等
*/
exactEquals(other: Vector2): boolean {
return this.x === other.x && this.y === other.y;
}
// 静态方法
/**
* 向量加法(静态方法)
* @param a 向量a
* @param b 向量b
* @returns 新的结果向量
*/
static add(a: Vector2, b: Vector2): Vector2 {
return new Vector2(a.x + b.x, a.y + b.y);
}
/**
* 向量减法(静态方法)
* @param a 向量a
* @param b 向量b
* @returns 新的结果向量
*/
static subtract(a: Vector2, b: Vector2): Vector2 {
return new Vector2(a.x - b.x, a.y - b.y);
}
/**
* 向量数乘(静态方法)
* @param vector 向量
* @param scalar 标量
* @returns 新的结果向量
*/
static multiply(vector: Vector2, scalar: number): Vector2 {
return new Vector2(vector.x * scalar, vector.y * scalar);
}
/**
* 向量点积(静态方法)
* @param a 向量a
* @param b 向量b
* @returns 点积值
*/
static dot(a: Vector2, b: Vector2): number {
return a.x * b.x + a.y * b.y;
}
/**
* 向量叉积(静态方法)
* @param a 向量a
* @param b 向量b
* @returns 叉积值
*/
static cross(a: Vector2, b: Vector2): number {
return a.x * b.y - a.y * b.x;
}
/**
* 计算两点间距离(静态方法)
* @param a 点a
* @param b 点b
* @returns 距离值
*/
static distance(a: Vector2, b: Vector2): number {
const dx = a.x - b.x;
const dy = a.y - b.y;
return Math.sqrt(dx * dx + dy * dy);
}
/**
* 线性插值(静态方法)
* @param a 起始向量
* @param b 目标向量
* @param t 插值参数0到1
* @returns 新的插值结果向量
*/
static lerp(a: Vector2, b: Vector2, t: number): Vector2 {
return new Vector2(
a.x + (b.x - a.x) * t,
a.y + (b.y - a.y) * t
);
}
/**
* 从角度创建单位向量(静态方法)
* @param angle 角度(弧度)
* @returns 新的单位向量
*/
static fromAngle(angle: number): Vector2 {
return new Vector2(Math.cos(angle), Math.sin(angle));
}
/**
* 从极坐标创建向量(静态方法)
* @param length 长度
* @param angle 角度(弧度)
* @returns 新的向量
*/
static fromPolar(length: number, angle: number): Vector2 {
return new Vector2(length * Math.cos(angle), length * Math.sin(angle));
}
/**
* 获取两个向量中的最小分量向量(静态方法)
* @param a 向量a
* @param b 向量b
* @returns 新的最小分量向量
*/
static min(a: Vector2, b: Vector2): Vector2 {
return new Vector2(Math.min(a.x, b.x), Math.min(a.y, b.y));
}
/**
* 获取两个向量中的最大分量向量(静态方法)
* @param a 向量a
* @param b 向量b
* @returns 新的最大分量向量
*/
static max(a: Vector2, b: Vector2): Vector2 {
return new Vector2(Math.max(a.x, b.x), Math.max(a.y, b.y));
}
// 字符串转换
/**
* 转换为字符串
* @returns 字符串表示
*/
toString(): string {
return `Vector2(${this.x.toFixed(3)}, ${this.y.toFixed(3)})`;
}
/**
* 转换为数组
* @returns [x, y] 数组
*/
toArray(): [number, number] {
return [this.x, this.y];
}
/**
* 转换为普通对象
* @returns {x, y} 对象
*/
toObject(): { x: number; y: number } {
return { x: this.x, y: this.y };
}
}