import { Box2 } from "../Common/Box2"; import { clipperCpp } from "../Common/ClipperCpp"; import { NestFiler } from "../Common/Filer"; import { Point } from "../Common/Point"; import { equaln } from "../Common/Util"; import { Vector2 } from "../Common/Vector2"; /** * 轮廓路径类 * 可以求NFP,和保存NFPCahce * 因为NFP结果是按照最低点移动的,所以将点旋转后,按照盒子将点移动到0点. */ export class Path { Id: number; Points: Point[]; OutsideNFPCache: { [key: number]: Point[][]; } = {}; InsideNFPCache: { [key: number]: Point[][]; } = {}; constructor(origionPoints?: Point[], rotation: number = 0) { if (origionPoints) this.Init(origionPoints, rotation); } Origion: Path; //点表在旋转后的原始最小点.使用这个点将轮廓移动到0点 OrigionMinPoint: Vector2; Rotation: number; Size: Vector2;//序列化 Init(origionPoints: Point[], rotation: number) { this.Rotation = rotation; if (rotation === 0) this.Points = origionPoints.map(p => { return { ...p }; }); else { let c = Math.cos(rotation); let s = Math.sin(rotation); let npts: Point[] = []; for (let p of origionPoints) { let x = p.x; let y = p.y; const x1 = x * c - y * s; const y1 = x * s + y * c; npts.push({ x: x1, y: y1 }); } this.Points = npts; } let box = new Box2(); let v2 = new Vector2(); for (let p of this.Points) { v2.x = p.x; v2.y = p.y; box.expandByPoint(v2); } this.OrigionMinPoint = box.min; this.Size = box.max.sub(box.min); for (let p of this.Points) { p.x -= box.min.x; p.y -= box.min.y; } } GetNFPs(path: Path, outside: boolean): Point[][] { // 寻找内轮廓时,面积应该比本path小,这个判断移交给使用者自己判断 // if (!outside && this.Area < path.Area) return []; let nfps = clipperCpp.lib.minkowskiSumPath(this.BigIntPoints, path.MirrorPoints, true); nfps = nfps.filter((nfp) => { let area = Area(nfp); if (area > 1) return outside;//第一个不一定是外轮廓,但是面积为正时肯定为外轮廓 if (Math.abs(area) < 10) return false;//应该不用在移除这个了 let { x, y } = nfp[0]; if (outside) { if (this.Area > path.Area) { let p = { x: path.InPoint.x + x, y: path.InPoint.y + y }; if (p.x < 0 || p.y < 0 || p.x > this.BigSize.x || p.y > this.BigSize.y) return true; let dir = clipperCpp.lib.pointInPolygon(p, this.BigIntPoints); return dir === 0; } else { let p = { x: this.InPoint.x - x, y: this.InPoint.y - y }; if (p.x < 0 || p.y < 0 || p.x > path.BigSize.x || p.y > path.BigSize.y) return true; let dir = clipperCpp.lib.pointInPolygon(p, path.BigIntPoints); return dir === 0; } } else { let p = { x: path.InPoint.x + x, y: path.InPoint.y + y }; if (p.x < 0 || p.y < 0 || p.x > this.BigSize.x || p.y > this.BigSize.y) return false; let dir = clipperCpp.lib.pointInPolygon(p, this.BigIntPoints); return dir === 1; } }); return nfps; } GetOutsideNFP(path: Path): Point[][] { let nfps = this.OutsideNFPCache[path.Id]; if (nfps) return nfps; if (this.IsRect && path.IsRect) { let [ax, ay] = [this.Size.x * 1e4, this.Size.y * 1e4]; let [bx, by] = [path.Size.x * 1e4, path.Size.y * 1e4]; nfps = [[ { x: -bx, y: -by }, { x: ax, y: -by }, { x: ax, y: ay }, { x: -bx, y: ay }, ]]; } else nfps = this.GetNFPs(path, true); this.OutsideNFPCache[path.Id] = nfps; //虽然有这种神奇的特性,但是好像并不会提高性能。 // path.OutsideNFPCache[this.id] = (this, nfps.map(nfp => // { // return nfp.map(p => // { // return { x: -p.x, y: -p.y }; // }); // })); return nfps; } GetInsideNFP(path: Path): Point[][] { if (path.Area > this.Area) return; let nfp = this.InsideNFPCache[path.Id]; if (nfp) return nfp; let nfps: Point[][]; if (this.IsRect) { let [ax, ay] = [this.Size.x * 1e4, this.Size.y * 1e4]; let [bx, by] = [path.Size.x * 1e4, path.Size.y * 1e4]; if (ax === bx) ax += 1; if (ay === by) ay += 1; if (bx > ax || by > ay) return; nfps = [[ { x: 0, y: 0 }, { x: ax - bx, y: 0 }, { x: ax - bx, y: ay - by }, { x: 0, y: ay - by } ]]; } else nfps = this.GetNFPs(path, false); if (path.Id !== undefined) this.InsideNFPCache[path.Id] = nfps; return nfps; } private _InPoint: Point; /** * 用这个点来检测是否在Path内部 */ private get InPoint() { if (this._InPoint) return this._InPoint; let mp = { x: (this.Points[0].x + this.Points[1].x) / 2, y: (this.Points[0].y + this.Points[1].y) / 2 }; let normal = new Vector2(this.Points[1].x - this.Points[0].x, this.Points[1].y - this.Points[0].y).normalize(); // [normal.x, normal.y] = [normal.y, -normal.x]; mp.x -= normal.y; mp.y += normal.x; mp.x *= 1e4; mp.y *= 1e4; this._InPoint = mp; return mp; } protected _BigIntPoints: Point[]; get BigIntPoints() { if (this._BigIntPoints) return this._BigIntPoints; this._BigIntPoints = this.Points.map(p => { return { x: Math.round(p.x * 1e4), y: Math.round(p.y * 1e4), }; }); return this._BigIntPoints; } private _BigSize: Vector2; get BigSize() { if (this._BigSize) return this._BigSize; this._BigSize = new Vector2(this.Size.x * 1e4, this.Size.y * 1e4); return this._BigSize; } protected _MirrorPoints: Point[]; get MirrorPoints() { if (!this._MirrorPoints) this._MirrorPoints = this.BigIntPoints.map(p => { return { x: -p.x, y: -p.y }; }); return this._MirrorPoints; } protected _BoundingBox: Box2; get BoundingBox() { if (!this._BoundingBox) this._BoundingBox = new Box2(new Vector2, this.Size); return this._BoundingBox; } protected _Area: number; get Area() { if (this._Area === undefined) this._Area = Area(this.Points); return this._Area; } set Area(a: number) { this._Area = a; } private _IsRect: boolean; get IsRect() { if (this._IsRect === undefined) { let s = this.BoundingBox.getSize(new Vector2); this._IsRect = equaln(this.Area, s.x * s.y, 1); } return this._IsRect; } ReadFile(file: NestFiler): void { let ver = file.Read(); this.Id = file.Read(); let arr = file.Read(); this.Points = []; for (let i = 0; i < arr.length; i += 2) { let p = { x: arr[i], y: arr[i + 1] }; this.Points.push(p); } this.Size = new Vector2(file.Read(), file.Read()); this._Area = file.Read(); let id = file.Read(); if (id !== -1) { this.Origion = id; this.Rotation = file.Read(); this.OrigionMinPoint = new Vector2(file.Read(), file.Read()); } } WriteFile(file: NestFiler): void { file.Write(1);//ver file.Write(this.Id); let arr: number[] = []; for (let p of this.Points) arr.push(p.x, p.y); file.Write(arr); file.Write(this.Size.x); file.Write(this.Size.y); file.Write(this._Area); if (this.Origion && this.Origion.Id) { //如果有原始的id,则传递它,以便后续进行NFP复用. file.Write(this.Origion.Id); file.Write(this.Rotation); file.Write(this.OrigionMinPoint.x); file.Write(this.OrigionMinPoint.y); } else file.Write(-1); } } //点表面积 export function Area(pts: Point[]): number { let cnt = pts.length; if (cnt < 3) return 0; let a = 0; for (let i = 0, j = cnt - 1; i < cnt; ++i) { a += (pts[j].x + pts[i].x) * (pts[j].y - pts[i].y); j = i; } return -a * 0.5; } /** * 平移点表,返回新点表 */ export function TranslatePath(pts: Point[], p: Point): Point[] { return pts.map(px => { return { x: p.x + px.x, y: p.y + px.y }; }); } //缩放点表,返回原始点表 export function PathScale(pts: Point[], scale: number): Point[] { for (let p of pts) { p.x *= scale; p.y *= scale; } return pts; }