cut-demo/src/Nest/Core/Path.ts
2020-04-27 16:39:48 +08:00

346 lines
9.6 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 { 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;
}