Files
ProcessCodeConverter/src/processors/NcConverter/NcConverter.ts

794 lines
26 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.

import { CCode, CCodeParams, FaceType, GCode, GCodeParams, INcWriter, IPoint, Knife, NcAction, NcActionType, } from "cut-abstractions";
// // import { CArc2GCode, NcArcType, NcReductionType } from "mes-processors"
// import { Vector2 } from "node_modules/mes-processors/src/math/vector2";
/** 用以对接 NC类型的加工文件--即 解析器 */
export class NcConverter implements INcWriter {
/** NC加工动作记录 */
private lines: string[] = [];
/** 刀库 */
knifeList: Array<Knife> = []
/** 当前刀具 */
private currentKnife?: Knife = undefined;
private actionRecord: NcAction[] = [];
arcType: NcArcType = 'R';
/** 最后一行代码参数 */
// private lastParams?: any
private lastParams: GCodeParams = new GCodeParams();
private lastCode: string = '';
/** 可以做代码转换 如G2转G3,G3转G2等 */
codeMap: Record<string, string> = {};
get ncActions(): NcAction[] {
return this.actionRecord;
}
reductionType: NcReductionType = NcReductionType.None;
/** 配置 这里给默认值*/
config: NcConverterConfig = {
isEnableConverterAxis: false,
thickness: 18,
doSimpleFirstCode: false,
isNcFileComment: true,
isNcLinePrefixEnabled: false,
ncLinePrefix: '',
isUseSimpleCode: false,
isSimpleFirstCode: false,
arcType: 'R',
reverseArcCode: false,
NcCodeFreeMove: 'G00',
NcCodeLineInterpolation: 'G01',
NcCodeClockwiseArcInterpolation: 'G02',
NcCodeAnticlockwiseArcInterpolation: 'G03',
NcCodeAxisX: 'X',
NcCodeAxisY: 'Y',
NcCodeAxisZ: 'Z',
NcCodeSpeed: 'F',
NcCodeIncrementAxisX: 'I',
NcCodeIncrementAxisY: 'J',
NcCodeIncrementAxisZ: 'K',
leaderChar: '//',
boardLength: 2440,
boardWidth: 1220,
boardHeight: 50,
originPointPosition: BoardPosition.LEFT_TOP,
originZ0Position: OriginZPosition.WorkTop,
heightAxis: AxisType.Z_POS,
decimalPointPrecision: 3,
fixFloatNumberEndZero: true,
intNumberAddDecimalPoint: true,
lineBreak: '\n'
}
/** G代码转换关系 */
codeTransform = {
[GCode.G0]: this.config.NcCodeFreeMove,
[GCode.G1]: this.config.NcCodeLineInterpolation,
[GCode.G2]: this.config.NcCodeClockwiseArcInterpolation,
[GCode.G3]: this.config.NcCodeAnticlockwiseArcInterpolation
}
initConfig(conf: NcConverterConfig) {
this.config = { ...this.config, ...conf }
/** 更新下代码转换关系 */
this.codeTransform = {
[GCode.G0]: this.config.NcCodeFreeMove,
[GCode.G1]: this.config.NcCodeLineInterpolation,
[GCode.G2]: this.config.NcCodeClockwiseArcInterpolation,
[GCode.G3]: this.config.NcCodeAnticlockwiseArcInterpolation
}
}
protected code(code: keyof typeof GCode, params: Partial<GCodeParams>) {
const line: any[] = [];
/**
* G0 - G3 代码 显示的时候可以配置
* 例
* G00 - G03
*/
if (this.reductionType & NcReductionType.Code) {
if (this.lastCode != code) {
line.push(this.codeTransform[code]);
}
} else {
line.push(this.codeTransform[code]);
}
this.lastCode = code;
let x = params.x ??= this.lastParams.x;
let y = params.y ??= this.lastParams.y;
let z = params.z ??= this.lastParams.z;
let x_val = this.handleValue_DecimalPointPrecision(x)
let y_val = this.handleValue_DecimalPointPrecision(y)
let z_val = this.handleValue_DecimalPointPrecision(z)
let x_code = this.config.NcCodeAxisX || 'X'
let y_code = this.config.NcCodeAxisY || 'Y'
let z_code = this.config.NcCodeAxisZ || 'Z'
if (this.reductionType & NcReductionType.Position) {
if (x != this.lastParams.x) {
line.push(x_code + x_val);
}
if (y != this.lastParams.y) {
line.push(y_code + y_val);
}
if (z != this.lastParams.z) {
line.push(z_code + z_val);
}
} else {
line.push(x_code + x_val);
line.push(y_code + y_val);
line.push(z_code + z_val);
}
if (code == 'G2' || code == 'G3') {
if (this.config.arcType == 'R') {
let r = params.r ??= this.lastParams.r;
let r_val = this.handleValue_DecimalPointPrecision(r)
line.push('R' + r_val);
}
if (this.config.arcType == 'IJK') {
let i = params.i ??= this.lastParams.i;
let j = params.j ??= this.lastParams.j;
let i_val = this.handleValue_DecimalPointPrecision(i)
let j_val = this.handleValue_DecimalPointPrecision(j)
line.push('I' + i_val);
line.push('J' + j_val);
}
}
const speed = params.f ??= this.lastParams.f
if (speed != 0) {
if (this.reductionType & NcReductionType.Speed) {
if (speed != this.lastParams.f) {
line.push('F' + speed);
}
} else {
line.push('F' + speed);
}
}
Object.assign(this.lastParams, params); // 更新上一次参数
if (this.codeMap[line[0]]) { // 命令转换
line[0] = this.codeMap[line[0]];
}
this.lines.push(line.join(' '));
}
gCode<TCode extends (keyof typeof GCode | keyof typeof CCode)>(code: TCode, params: Partial<TCode extends keyof typeof GCode ? GCodeParams : CCodeParams>): void {
switch (code) {
case 'G0':
case 'G1':
case 'G2':
case 'G3': {
this.code(code, params);
break;
}
// 自定义代码
case 'CArc': {
// 凸度转GCode
const cParam = params as Partial<CCodeParams>;
if (!cParam.x || !cParam.y || !cParam.b) throw new Error("CArc命令缺少必要参数(X, Y, B)");
const targetPoint = { x: cParam.x, y: cParam.y };
if (this.config.arcType === 'R') {
const result = CArc2GCode(this.lastParams, targetPoint, cParam.b, 'R');
this.code(result.gCode, { ...targetPoint, r: result.r });
} else {
const result = CArc2GCode(this.lastParams, targetPoint, cParam.b, 'IJK');
this.code(result.gCode, { ...targetPoint, i: result.i, j: result.j });
}
}
}
}
/** 处理值 最终显示的值 小数点后X位功能 */
handleValue_DecimalPointPrecision(val: number) {
const { fixFloatNumberEndZero, intNumberAddDecimalPoint, decimalPointPrecision } = this.config
/**
* 2种方式 末尾补零 或者 直接保留小数点后N位
*/
let isToFix = false
let resVal
if (fixFloatNumberEndZero == true) {
// 末尾补零
isToFix = true
} else if (intNumberAddDecimalPoint == true) {
// 整数值末尾加小数点
if (Number.isInteger(val)) {
isToFix = true
}
}
if (isToFix) {
resVal = val.toFixed(decimalPointPrecision)
} else {
resVal = val.toString()
}
return resVal
}
/** 更换刀具 */
changeKnife() {
}
/** 校验值是否有效 不为'' 或 undefined */
checkVal(val: any): boolean {
let r = true
if ((val == undefined || val == '')) {
r = false
}
return r
}
toString() {
return this.lines.join(this.config.lineBreak);
}
comment(content: string): string {
let markContent = content + this.config.lineBreak
let isShowMark = this.config.isNcFileComment || false
if (isShowMark) {
let leaderChar = this.config.leaderChar || ''
markContent = `${leaderChar} ${markContent}`
} else {
markContent = ''
}
return markContent + this.config.lineBreak
}
recordAction(type: NcActionType): string {
const id = this.createActionId();
const act: NcAction = {
id: id,
type,
lineIndex: this.lines.length
};
this.comment(`CMP ${act.id} ${act.type}`);
this.actionRecord.push(act);
return id;
}
private _actionIdx = 0;
private createActionId() {
return `A${this._actionIdx++}`;
}
append(str: string) {
// this.lines.push(str);
}
appendLine(str: string) {
}
/**
*
* @param point 加工项的点
* @param processItemInfo 加工项的信息 水平基准、垂直基准、轴方向x、y、z板件方向x、y、z
* // 这里加工项的点数据 都是经过数据处理的 假定这里拿到的数据都是基于左上角 台面
*
* @returns 实际加工项的点
*/
getXYZ(point: CodeParams, processItemInfo: ProcessInfo): CodeParams {
let newPoint: any = {}
if (this.config.isEnableConverterAxis) {
// 进行坐标轴转换
for (const key in point) {
if (point[key] != undefined) {
Reflect.set(newPoint, key, parseFloat(point[key]))
}
}
// /** 有2个部分
// * 一个是基于机台和板件的转换 依据板件定位
// * 另外一个是基于板件和加工项的转换 依据板件长高方向*/
// switch (this.config.originZ0Position) {
// case 0:
// // 台面 不操作
// break;
// case 1:
// // 板面 Z坐标需要转换
// newPoint.z = newPoint.z - this.config.thickness
// break;
// default:
// break;
// }
// /** step 先转换板件的位置 */
// // 大板定位 不同 根据不同的定位点修改
// // processItemInfo
// switch (this.config.originPointPosition) {
// case BoardPosition.LEFT_TOP:
// // 不操作
// newPoint = this.changeXYZAxiosSide(newPoint)
// break;
// case BoardPosition.LEFT_BOTTOM:
// // 左下角 x坐标要转换
// newPoint.x = newPoint.x + this.config.boardWidth - processItemInfo.block.cutWidth //400
// newPoint = this.changeXYZAxiosSide(newPoint)
// break;
// case BoardPosition.RIGHT_TOP:
// // 右上角 y坐标要转换
// newPoint.y = newPoint.y + this.config.boardLength - processItemInfo.block.cutLength // 600
// newPoint = this.changeXYZAxiosSide(newPoint)
// break;
// case BoardPosition.RIGHT_BOTTOM:
// // 右下角 xy 坐标要转换
// newPoint.x = newPoint.x + this.config.boardWidth - processItemInfo.block.cutWidth
// newPoint.y = newPoint.y + this.config.boardLength - processItemInfo.block.cutLength
// newPoint = this.changeXYZAxiosSide(newPoint)
// break;
// default:
// break;
// }
// 这里做 数值的小数点处理
for (const key in newPoint) {
if (['x', 'y', 'z', 'r', 'i', 'j', 'k'].includes(key)) {
let isTofix = false
if (this.config.fixFloatNumberEndZero == true) {
// 末尾补零
isTofix = true
} else if (this.config.intNumberAddDecimalPoint == true) {
// 整数值末尾加小数点
if (Number.isInteger(newPoint[key])) {
isTofix = true
}
}
if (isTofix) {
newPoint[key] = parseFloat(newPoint[key]).toFixed(this.config.decimalPointPrecision)
}
}
}
return newPoint
} else {
return point
}
}
/** 根据 轴向变更坐标 */
changeXYZAxiosSide(point: CodeParams) {
let newPoint: any = {}
for (const key in point) {
if (point[key] != undefined) {
Reflect.set(newPoint, key, parseFloat(point[key]))
}
}
let width = this.config.boardWidth
let length = this.config.boardLength
let height = this.config.boardHeight
if (this.config.widthSideAxis == AxisType.X_POS && this.config.lengthSideAxis == AxisType.Y_POS) {
// 默认 为 X = x 正 Y = y 正 不操作
} else if (this.config.widthSideAxis == AxisType.Y_POS && this.config.lengthSideAxis == AxisType.X_POS) {
// x = y正 y = x正 X Y坐标 倒转
newPoint = { ...newPoint, x: newPoint.y, y: newPoint.x }
}
else if (this.config.widthSideAxis == AxisType.X_NEG && this.config.lengthSideAxis == AxisType.Y_POS) {
// x = x负 y = y正
newPoint = { ...newPoint, x: newPoint.x - width, y: newPoint.y }
} else if (this.config.widthSideAxis == AxisType.Y_POS && this.config.lengthSideAxis == AxisType.X_NEG) {
// x = y正 y = x负
newPoint = { ...newPoint, x: newPoint.y - width, y: newPoint.x }
}
else if (this.config.widthSideAxis == AxisType.X_NEG && this.config.lengthSideAxis == AxisType.Y_NEG) {
// x = x负 y = y负
newPoint = { ...newPoint, x: newPoint.x - width, y: newPoint.y - length }
} else if (this.config.widthSideAxis == AxisType.Y_NEG && this.config.lengthSideAxis == AxisType.X_NEG) {
// x = y负 y = x负
newPoint = { ...newPoint, x: newPoint.y - width, y: newPoint.x - length }
}
else if (this.config.widthSideAxis == AxisType.X_POS && this.config.lengthSideAxis == AxisType.Y_NEG) {
// x = x正 y = y负
newPoint = { ...newPoint, x: newPoint.x, y: newPoint.y - length }
} else if (this.config.widthSideAxis == AxisType.Y_NEG && this.config.lengthSideAxis == AxisType.X_POS) {
// x = y负 y = x正
newPoint = { ...newPoint, x: newPoint.y, y: newPoint.x - length }
}
if (this.config.heightAxis == AxisType.Z_NEG) {
// Z轴负
newPoint = { ...newPoint, z: newPoint.z - height }
}
return newPoint
}
}
export type NcConverterConfig = {
/** 是否启用解析器的坐标系转换 */
isEnableConverterAxis?: boolean,
/** 板厚 */
thickness: number,
/**是否执行换刀后的第一行精简指令 */
doSimpleFirstCode?: boolean
/** 是否添加注释信息 */
isNcFileComment?: boolean
/** 是否空行插入前缀 */
isNcLinePrefixEnabled?: boolean
/** 空行插入前缀 前缀内容*/
ncLinePrefix?: string
/** 使用精简指令 */
isUseSimpleCode?: boolean
/** 精简换刀后第一行指令 */
isSimpleFirstCode?: boolean
/** 圆弧指令模式类型 */
arcType?: NcArcType
/** 反转圆弧指令 */
reverseArcCode?: boolean
/** 空程移动指令 */
NcCodeFreeMove?: string
/** 直线插补标识 */
NcCodeLineInterpolation?: string
/** 顺时针圆弧插补标识 */
NcCodeClockwiseArcInterpolation?: string
/** 逆时针圆弧插补标识 */
NcCodeAnticlockwiseArcInterpolation?: string
/** 水平坐标横轴标识 */
NcCodeAxisX?: string
/** 水平坐标纵轴标识 */
NcCodeAxisY?: string
/** 垂直坐标轴标识 */
NcCodeAxisZ?: string
/** 速度标识 */
NcCodeSpeed?: string
/** 水平坐标横轴增量标识 */
NcCodeIncrementAxisX?: string
/** 水平坐标纵轴增量标识 */
NcCodeIncrementAxisY?: string
/** 垂直坐标轴增量标识 */
NcCodeIncrementAxisZ?: string
/** 注释标识符 */
leaderChar?: string
/** 工作区域长 x */
boardLength: number
/** 工作区域宽 y */
boardWidth: number
/** 工作区域高 z */
boardHeight: number
/** 水平基准点 */
originPointPosition?: BoardPosition
/** 垂直基准点 */
originZ0Position?: OriginZPosition
/** 水平纵轴坐标轴向 */
widthSideAxis?: AxisType
/** 水平横轴坐标轴向 */
lengthSideAxis?: AxisType
/** 垂直轴坐标轴向 */
heightAxis?: AxisType
/** 保留小数点位数 */
decimalPointPrecision?: number
/** 末尾补零 */
fixFloatNumberEndZero?: boolean
/** 整数值末尾加小数点 */
intNumberAddDecimalPoint?: boolean
/** 换行符 个别文件需要用 ;\n 结束*/
lineBreak?: string
}
/** 枚举 大板边角位置 */
export enum BoardPosition {
/** 左上角 */
LEFT_TOP = 3,
/** 左下角 */
LEFT_BOTTOM = 0,
/** 右下角 */
RIGHT_BOTTOM = 1,
/** 右上角 */
RIGHT_TOP = 2,
}
/** 枚举 坐标轴类型 */
export enum OriginZPosition {
/** 台面向上Z轴正 */
WorkTop = 0,
/** 板面向上Z轴正 */
BoardFace = 1,
}
/** 枚举 坐标轴类型 */
export enum AxisType {
/** X轴正 */
X_POS = 0,
/** X轴负 */
X_NEG = 1,
/** Y轴正 */
Y_POS = 2,
/** Y轴负 */
Y_NEG = 3,
/** 向上Z轴正 */
Z_POS = 4,
/** 向下Z轴负 */
Z_NEG = 5,
}
// 加工项 点数据
export class CodeParams {
/** x坐标 */
x?: Number | String
/** y坐标 */
y?: Number | String
/** z坐标 */
z?: Number | String
/** 调用的代码编号 */
dir?: Number | String
/** 圆弧半径 */
r?: Number | String
/** 速度 */
f?: Number | String
/** IJK 模式的i */
i?: Number | String
/** IJK 模式的j */
j?: Number | String
/** IJK 模式的k */
k?: Number | String
/** 代码标识 */
codeKey?: String
/** x坐标 */
xKey?: String
/** y坐标 */
yKey?: String
/** z坐标 */
zKey?: String
/** 圆弧半径 */
rKey?: String
/** 速度 */
fKey?: String
/** IJK 模式的i */
iKey?: String
/** IJK 模式的j */
jKey?: String
/** IJK 模式的k */
kKey?: String
}
/** 加工项对应的信息 */
export class ProcessInfo {
/**当前加工项的下标*/
i?: Number
/** 加工项 对应刀具的数据 */
knife?: Knife
/** 加工项的类型 */
type?: processItemType
/** 加工项所在的 文件名 */
belong?: string
/** 该加工项基于哪个加工面 传入数据 主要用于 加工项坐标转换 感觉可能没用 */
belongFace?: FaceType
/** 板件信息 */
block?: any
// /** 垂直基准点 */
// originZ0Position?: OriginZPosition
// /** 水平基准点 */
// originPointPosition?: BoardPosition
// /** 大板定位 */
// boardLocation?: BoardPosition
// /** 加工项的宽方向 x */
// widthSideAxis?: AxisType
// /** 加工项的长方向 y */
// lengthSideAxis ?: AxisType
// /** 加工项的高方向 */
// heightSideAxis ?: AxisType
}
/** 加工项的类别
* 用于区分加工项的类别,不同的类别 在文件生成的时候 可能需要对应某些独立的节点
*/
export enum processItemType {
/**排钻 */
Hole = 'hole',
/** 铣孔 */
MillingHole = 'millingHole',
/** 造型 非槽加工 铣型 */
Model = 'model',
/** 造型 槽-- 拉槽 */
Grooves = 'grooves',
/** 造型 铣槽 */
MillingGrooves = 'millingGrooves',
/** 侧面造型 */
SideModel = 'SideModel',
/** 侧面槽 - 拉槽 */
SideGrooves = 'sideGrooves',
/** 侧面槽 - 拉槽 */
SideMillingGrooves = 'SideMillingGrooves',
/** 侧面孔 排钻 */
SideHole = 'sideHole',
/** 侧面孔 铣孔 */
MillingSideHole = 'MillingSideHole',
/** 开料 */
CutBlock = 'cutBlock',
/** 修边 */
MillingBlockBoard = 'MillingBlockBoard'
}
export type NcArcType = 'R' | 'IJK';
/**
* 将基于凸度的圆弧转换为基于圆弧半径/圆心坐标表示的圆弧,暂不支持三维圆弧(Z轴或K分量)
* @param source 圆弧起始点
* @param target 圆弧终点
* @param bulge 凸度值
* @param mode 圆弧模式
* @returns 圆弧参数(IJ或R)
*/
export function CArc2GCode(source: IPoint, target: IPoint, bulge: number, mode: 'R'): { gCode: 'G2' | 'G3'; r: number; };
export function CArc2GCode(source: IPoint, target: IPoint, bulge: number, mode: 'IJK'): { gCode: 'G2' | 'G3'; i: number; j: number; };
export function CArc2GCode(source: IPoint, target: IPoint, bulge: number, mode: NcArcType): { gCode: 'G2' | 'G3'; r?: number; i?: number; j?: number; } {
/*
* ♿♿♿ 修改必看!!!
* 凸度为圆弧圆心角的1/4正切值
* Bulge = tan(θ / 4) θ为圆心角
* 凸度为正数,则从起点顺时针绘制圆弧到终点
* 凸度为负数,则逆时针绘制
* 当凸度为0时绘制直线
* 当凸度为1时圆心角为180度 tan(180 / 4) = 1
* 凸度转换公式见 https://www.lee-mac.com/bulgeconversion.html
*
* NC圆弧规则
* 圆弧半径(R)模式:
* 从起点到终点绘制半径为|R|的圆弧R越大圆弧越平滑越小圆弧越弯曲
* 当R为正数时表示绘制圆的短弧
* 当R为负数时表示绘制圆的长弧
* ⚠️注意 起点到终点的距离不可大于 2 * |R|,否则应当抛出错误
*
* 圆心坐标(IJ)模式:
* 从起点到终点绘制圆弧,圆心坐标为(I, J)I为圆心在X轴上的坐标带符号J为圆心在Y轴上的坐标带符号
* ⚠️注意 圆心到圆弧起点和圆心到圆弧终点的距离必须相等(都等于圆弧的半径),否则应当抛出错误
*/
if (bulge === 0) {
throw new Error("圆弧模式下凸度值不能为0");
}
const p1 = Vector2.FromPoint(source);
const p2 = Vector2.FromPoint(target);
const gCode = bulge > 0 ? 'G2' : 'G3';
const delta = p2.Subtract(p1);
const dist = delta.Magnitude;
if (dist < 1e-6) {
throw new Error("起点与终点距离过近");
}
// 通过凸度值计算圆弧半径
// 圆弧半径 r = (弦长 / 2) * (1 + bulge²) / (2 * |bulge|)
// 详见: https://www.lee-mac.com/bulgeconversion.html#bulgeradius
const chordLength = delta.Magnitude;
const radius = (chordLength / 2) * (1 + bulge * bulge) / (2 * Math.abs(bulge));
// R模式
if (mode === 'R') {
return { gCode: gCode, r: radius };
}
// IJK模式
// 圆心位于弦的垂直平分线
// 计算弦的中点
const midPoint = p1.Add(delta.Multiply(0.5));
// 计算弦心距d
// 公式d = √(r² - a²)r为半径a为弦长的一半勾股定理
const d = Math.sqrt(radius * radius - (dist / 2) * (dist / 2));
// 垂直平分线单位向量
const perpVec = new Vector2(-delta.y, delta.x).Normalize();
// 从弦的中点向垂直平分线向量移动弦心距d来计算出圆心的坐标
const center = midPoint.Add(perpVec.Multiply(d * -Math.sign(bulge)));
const i = center.x - p1.x;
const j = center.y - p1.y;
return { gCode, i, j };
}
export enum NcReductionType {
None = 0,
Position = 1 << 0,
Speed = 1 << 1,
Code = 1 << 2,
/**
* 换刀重置
*/
SwitchKnifeReset = 1 << 3
}
export class Vector2 implements IPoint {
static Zero = new Vector2(0, 0);
static One = new Vector2(1, 1);
static Up = new Vector2(0, 1);
static Down = new Vector2(0, -1);
static Left = new Vector2(-1, 0);
static Right = new Vector2(1, 0);
static FromPoint(pt: IPoint) {
if (pt instanceof Vector2) return pt as Vector2;
return new Vector2(pt.x, pt.y);
}
x: number;
y: number;
/** 获取向量的模长 */
get Magnitude() {
return Math.sqrt(Math.pow(this.x, 2) + Math.pow(this.y, 2));
}
/** 获取向量的平方模长,这个属性比 `Magnitude` 属性更快 */
get SquaredMagnitude() {
return Math.pow(this.x, 2) + Math.pow(this.y, 2);
}
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
/** 克隆向量 */
Clone(): Vector2 {
return new Vector2(this.x, this.y);
}
/** 计算两个向量之间的距离 */
Distance(other: Vector2): number {
return Math.sqrt(Math.pow(this.x - other.x, 2) + Math.pow(this.y - other.y, 2));
}
/** 计算两个向量之间的平方距离,这个属性比 `Distance` 属性更快 */
SquaredDistance(other: Vector2): number {
return Math.pow(this.x - other.x, 2) + Math.pow(this.y - other.y, 2);
}
/** 向量和 */
Add(other: Vector2): Vector2 {
return new Vector2(this.x + other.x, this.y + other.y);
}
/** 向量差 */
Subtract(other: Vector2): Vector2 {
return new Vector2(this.x - other.x, this.y - other.y);
}
/** 向量点乘 */
Dot(other: Vector2): number {
return this.x * other.x + this.y * other.y;
}
/** 向量叉乘 */
Cross(other: Vector2): number {
return this.x * other.y - this.y * other.x;
}
/** 向量与标量相乘 */
Multiply(scalar: number): Vector2 {
return new Vector2(this.x * scalar, this.y * scalar);
}
/** 向量归一化 */
Normalize(): Vector2 {
const magnitude = this.Magnitude;
if (magnitude === 0) {
return Vector2.Zero;
}
return new Vector2(this.x / magnitude, this.y / magnitude);
}
}