cut-demo/src/Nest/Core/Path.ts

346 lines
9.6 KiB
TypeScript
Raw Normal View History

2020-04-27 16:39:48 +08:00
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";
/**
*
* NFPNFPCahce
* 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;
}