mirror of https://gitee.com/cf-fz/WebCAD.git
parent
932b36437f
commit
bbda2d0a14
@ -0,0 +1,154 @@
|
||||
import { Vector2 } from "./Vector2";
|
||||
import { Point } from "./Point";
|
||||
|
||||
export class Box2
|
||||
{
|
||||
min: Vector2;
|
||||
max: Vector2;
|
||||
constructor(min = new Vector2(+ Infinity, + Infinity), max = new Vector2(- Infinity, - Infinity))
|
||||
{
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
}
|
||||
set(min: Vector2, max: Vector2): Box2
|
||||
{
|
||||
this.min.copy(min);
|
||||
this.max.copy(max);
|
||||
return this;
|
||||
}
|
||||
setFromPoints(points: Vector2[]): Box2
|
||||
{
|
||||
this.makeEmpty();
|
||||
for (let i = 0, il = points.length; i < il; i++)
|
||||
{
|
||||
this.expandByPoint(points[i]);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
private static _setFromCenterAndSize_v1 = new Vector2();
|
||||
setFromCenterAndSize(center: Vector2, size: Vector2): Box2
|
||||
{
|
||||
const v1 = Box2._setFromCenterAndSize_v1;
|
||||
const halfSize = v1.copy(size).multiplyScalar(0.5);
|
||||
this.min.copy(center).sub(halfSize);
|
||||
this.max.copy(center).add(halfSize);
|
||||
return this;
|
||||
}
|
||||
clone(): Box2
|
||||
{
|
||||
return new (this.constructor as any)().copy(this);
|
||||
}
|
||||
copy(box: Box2): Box2
|
||||
{
|
||||
this.min.copy(box.min);
|
||||
this.max.copy(box.max);
|
||||
return this;
|
||||
}
|
||||
makeEmpty(): Box2
|
||||
{
|
||||
this.min.x = this.min.y = + Infinity;
|
||||
this.max.x = this.max.y = - Infinity;
|
||||
return this;
|
||||
}
|
||||
isEmpty(): boolean
|
||||
{
|
||||
// this is a more robust check for empty than (volume <= 0) because volume can get positive with two negative axes
|
||||
return (this.max.x < this.min.x) || (this.max.y < this.min.y);
|
||||
}
|
||||
getCenter(result: Vector2 = new Vector2()): Vector2
|
||||
{
|
||||
return this.isEmpty() ? result.set(0, 0) : result.addVectors(this.min, this.max).multiplyScalar(0.5);
|
||||
}
|
||||
getSize(result: Vector2 = new Vector2()): Vector2
|
||||
{
|
||||
return this.isEmpty() ? result.set(0, 0) : result.subVectors(this.max, this.min);
|
||||
}
|
||||
expandByPoint(point: Vector2): Box2
|
||||
{
|
||||
this.min.min(point);
|
||||
this.max.max(point);
|
||||
return this;
|
||||
}
|
||||
expandByVector(vector: Vector2): Box2
|
||||
{
|
||||
this.min.sub(vector);
|
||||
this.max.add(vector);
|
||||
return this;
|
||||
}
|
||||
expandByScalar(scalar: number): Box2
|
||||
{
|
||||
this.min.addScalar(- scalar);
|
||||
this.max.addScalar(scalar);
|
||||
return this;
|
||||
}
|
||||
containsPoint(point: Vector2): boolean
|
||||
{
|
||||
if (point.x < this.min.x || point.x > this.max.x ||
|
||||
point.y < this.min.y || point.y > this.max.y)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
containsBox(box: Box2): boolean
|
||||
{
|
||||
if ((this.min.x <= box.min.x) && (box.max.x <= this.max.x) &&
|
||||
(this.min.y <= box.min.y) && (box.max.y <= this.max.y))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
getParameter(point: Vector2, result: Vector2 = new Vector2()): Vector2
|
||||
{
|
||||
// This can potentially have a divide by zero if the box
|
||||
// has a size dimension of 0.
|
||||
return result.set(
|
||||
(point.x - this.min.x) / (this.max.x - this.min.x),
|
||||
(point.y - this.min.y) / (this.max.y - this.min.y)
|
||||
);
|
||||
}
|
||||
intersectsBox(box: Box2): boolean
|
||||
{
|
||||
// using 6 splitting planes to rule out intersections.
|
||||
if (box.max.x < this.min.x || box.min.x > this.max.x ||
|
||||
box.max.y < this.min.y || box.min.y > this.max.y)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
clampPoint(point: Vector2, result: Vector2 = new Vector2()): Vector2
|
||||
{
|
||||
return result.copy(point).clamp(this.min, this.max);
|
||||
}
|
||||
private static _distanceToPoint_v1 = new Vector2();
|
||||
distanceToPoint(point: Vector2): number
|
||||
{
|
||||
const v1 = Box2._distanceToPoint_v1;
|
||||
const clampedPoint = v1.copy(point).clamp(this.min, this.max);
|
||||
return clampedPoint.sub(point).length();
|
||||
}
|
||||
intersect(box: Box2): Box2
|
||||
{
|
||||
this.min.max(box.min);
|
||||
this.max.min(box.max);
|
||||
return this;
|
||||
}
|
||||
union(box: Box2): Box2
|
||||
{
|
||||
this.min.min(box.min);
|
||||
this.max.max(box.max);
|
||||
return this;
|
||||
}
|
||||
translate(offset: Point): Box2
|
||||
{
|
||||
this.min.add(offset);
|
||||
this.max.add(offset);
|
||||
return this;
|
||||
}
|
||||
equals(box: Box2): boolean
|
||||
{
|
||||
return box.min.equals(this.min) && box.max.equals(this.max);
|
||||
}
|
||||
};
|
@ -0,0 +1,21 @@
|
||||
import * as clipperLib from "js-angusj-clipper/web"; // nodejs style require
|
||||
|
||||
export let clipperCpp: { lib?: clipperLib.ClipperLibWrapper; } = {};
|
||||
export function InitClipperCpp(): Promise<void>
|
||||
{
|
||||
if (clipperCpp.lib) return;
|
||||
if (!globalThis.document)
|
||||
globalThis.document = {} as any;
|
||||
return new Promise((res, rej) =>
|
||||
{
|
||||
clipperLib.loadNativeClipperLibInstanceAsync(
|
||||
// let it autodetect which one to use, but also available WasmOnly and AsmJsOnly
|
||||
clipperLib.NativeClipperLibRequestedFormat.WasmWithAsmJsFallback
|
||||
).then(c =>
|
||||
{
|
||||
clipperCpp.lib = c;
|
||||
res();
|
||||
console.log("载入成功!");
|
||||
});
|
||||
});
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
import { Path } from "./Path";
|
||||
let width = 1221;
|
||||
let height = 2441;
|
||||
export let DefaultBin = new Path([{ x: 0, y: 0 }, { x: width, y: 0 }, { x: width, y: height }, { x: 0, y: height }]);
|
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* CAD文件数据
|
||||
*/
|
||||
export class CADFiler
|
||||
{
|
||||
private readIndex: number = 0;
|
||||
constructor(public _datas: any[] = []) { }
|
||||
|
||||
Clear()
|
||||
{
|
||||
this._datas.length = 0;
|
||||
return this.Reset();
|
||||
}
|
||||
Reset()
|
||||
{
|
||||
this.readIndex = 0;
|
||||
return this;
|
||||
}
|
||||
|
||||
Write(data: any)
|
||||
{
|
||||
this._datas.push(data);
|
||||
return this;
|
||||
}
|
||||
|
||||
Read(): any
|
||||
{
|
||||
return this._datas[this.readIndex++];
|
||||
}
|
||||
}
|
@ -1,6 +1,27 @@
|
||||
const ctx: Worker = self as any;
|
||||
ctx.addEventListener("message", (event) =>
|
||||
import { InitClipperCpp } from "./ClipperCpp";
|
||||
import { CADFiler } from "./Filer";
|
||||
import { NestDatabase } from "./NestDatabase";
|
||||
import { OptimizeMachine } from "./OptimizeMachine";
|
||||
|
||||
ctx.addEventListener("message", async (event) =>
|
||||
{
|
||||
await InitClipperCpp();
|
||||
let f = new CADFiler(event.data);
|
||||
let db = new NestDatabase;
|
||||
db.ReadFile(f);
|
||||
|
||||
let m = new OptimizeMachine;
|
||||
m.Bin = db.Bin;
|
||||
m.PutParts(db.Parts);
|
||||
|
||||
m.callBack = async (inv) =>
|
||||
{
|
||||
let f = new CADFiler();
|
||||
inv.WriteFile(f);
|
||||
ctx.postMessage(f._datas);
|
||||
};
|
||||
await m.Start();
|
||||
});
|
||||
|
||||
export default {} as typeof Worker & (new () => Worker);
|
||||
|
@ -1,179 +0,0 @@
|
||||
import { Clipper, ClipType, Paths, PolyFillType, PolyType } from "clipper-js-fpoint";
|
||||
import { Box3, Vector3 } from "three";
|
||||
import { TestDraw } from "../../Add-on/test/TestUtil";
|
||||
import { app } from "../../ApplicationServices/Application";
|
||||
import { arrayRemoveIf } from "../../Common/ArrayExt";
|
||||
import { JigUtils } from "../../Editor/JigUtils";
|
||||
import { AsVector3, equaln } from "../../Geometry/GeUtils";
|
||||
import { Container } from "../Container";
|
||||
import { Path2Polyline } from "../Converter/Path2Polyline";
|
||||
import { NestCache } from "../NestCache";
|
||||
import { Part } from "../Part";
|
||||
|
||||
export class TestContainer extends Container
|
||||
{
|
||||
async PutPart2(part: Part): Promise<boolean>
|
||||
{
|
||||
//------------先放置在孔洞内------------
|
||||
if (this.Holes)
|
||||
{
|
||||
for (let c of this.Holes)
|
||||
{
|
||||
if (c.PutPart(part))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
let cacheKey = this.StatusKey + "," + part.State.Contour.Id;
|
||||
//读取缓存位置
|
||||
if (NestCache.PositionCache.has(cacheKey))
|
||||
{
|
||||
NestCache.count1++;
|
||||
let p = NestCache.PositionCache.get(cacheKey);
|
||||
part.PlacePosition = p;
|
||||
this.AppendPartHoles(part);
|
||||
return true;
|
||||
}
|
||||
|
||||
//------------无法容纳------------
|
||||
if (this.BinPath.Area - this.PlacedArea < part.State.Contour.Area)
|
||||
return false;
|
||||
|
||||
let binNfp = this.BinPath.GetInsideNFP(part.State.Contour);
|
||||
if (!binNfp || binNfp.length === 0) return false;
|
||||
|
||||
//------------首个------------
|
||||
if (this.Placed.length === 0)
|
||||
{
|
||||
part.PlacePosition = this.GetFarLeftP(binNfp);
|
||||
this.AppendPartHoles(part);
|
||||
return true;
|
||||
}
|
||||
|
||||
//如果已经缓存过,证明已经无法放置(否则应该已经缓存了放置点)
|
||||
// if (NestCache.PartCaled.has(cacheKey)) return false;
|
||||
// NestCache.PartCaled.add(cacheKey);
|
||||
|
||||
//------------计算nfp------------
|
||||
//合并(零件和所有已经放置零件的NFP)
|
||||
let clipper = new Clipper();
|
||||
|
||||
let i = 1;
|
||||
for (let placedPart of this.Placed)
|
||||
{
|
||||
let nfp = placedPart.State.Contour.GetOutsideNFP(part.State.Contour);
|
||||
if (!nfp)
|
||||
return false;
|
||||
let x = placedPart.PlacePosition.x;
|
||||
let y = placedPart.PlacePosition.y;
|
||||
|
||||
if (i === 2 && this.Placed.length === 4)
|
||||
{
|
||||
let pl = placedPart.State.Contour.Polyline.Clone();
|
||||
let pl2 = part.State.Contour.Polyline.Clone();
|
||||
|
||||
pl.Position = pl.Position.add(new Vector3(0, -5000));
|
||||
pl2.Position = pl2.Position.add(new Vector3(0, -5000));
|
||||
|
||||
TestDraw(pl, 1);
|
||||
TestDraw(pl2, 1);
|
||||
}
|
||||
|
||||
for (let n of nfp)
|
||||
{
|
||||
let f = n.map(v => { return { x: v.x + x, y: v.y + y }; });
|
||||
|
||||
//绘制所有的NFP
|
||||
let pl = Path2Polyline(f);
|
||||
pl.ColorIndex = i;
|
||||
JigUtils.Draw(pl);
|
||||
|
||||
clipper.AddPath(f, PolyType.ptSubject, true);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
app.Editor.UpdateScreen();
|
||||
await app.Editor.GetPoint();
|
||||
|
||||
let combinedNfp = [];
|
||||
if (!clipper.Execute(ClipType.ctUnion, combinedNfp, PolyFillType.pftNonZero, PolyFillType.pftNonZero))
|
||||
return false;
|
||||
|
||||
if (combinedNfp.length === 0) return false;
|
||||
|
||||
//binNfp 减去 combinedNfp 得到最终的nfp
|
||||
clipper = new Clipper();
|
||||
clipper.AddPaths(binNfp, PolyType.ptSubject, true);
|
||||
clipper.AddPaths(combinedNfp, PolyType.ptClip, true);
|
||||
let finalNfp: Paths = [];
|
||||
if (!clipper.Execute(ClipType.ctDifference, finalNfp, PolyFillType.pftNonZero, PolyFillType.pftNonZero))
|
||||
return false;
|
||||
|
||||
//清理NFP
|
||||
finalNfp = Clipper.CleanPolygons(finalNfp, 1);
|
||||
arrayRemoveIf(finalNfp, nfp =>
|
||||
{
|
||||
return (nfp.length < 3 || Math.abs(Clipper.Area(nfp)) < 2);
|
||||
});
|
||||
|
||||
if (finalNfp.length === 0)
|
||||
return false;
|
||||
|
||||
//绘制结果nfp
|
||||
JigUtils.End();
|
||||
for (let n of finalNfp)
|
||||
{
|
||||
let pl = Path2Polyline(n);
|
||||
pl.ColorIndex = 6;
|
||||
JigUtils.Draw(pl);
|
||||
}
|
||||
app.Editor.UpdateScreen();
|
||||
await app.Editor.GetPoint();
|
||||
|
||||
/**
|
||||
* 选择零件放置后的最优占地空间(box).
|
||||
* 使用凸包看起来不是最佳的
|
||||
* TODO:使用重力方向
|
||||
*/
|
||||
let minWidth = Infinity;
|
||||
let minArea: number = Infinity;
|
||||
let minX: number = Infinity;
|
||||
let minY: number = Infinity;
|
||||
let minBox: Box3;
|
||||
let translate: Vector3;
|
||||
for (let nfp of finalNfp)
|
||||
{
|
||||
for (let p of nfp)
|
||||
{
|
||||
let translateTemp = AsVector3(p);
|
||||
let box2 = part.State.Contour.BoundingBox.clone();
|
||||
box2.translate(translateTemp);
|
||||
let rectBox = this.PlacedBox.clone().union(box2);
|
||||
let size = rectBox.getSize(new Vector3());
|
||||
//宽度的权重更大,使得零件的放置优先宽度较小
|
||||
let area = (size.x * 1.1) * size.y;
|
||||
if (area < minArea
|
||||
|| (equaln(minArea, area) && translateTemp.x < minX)
|
||||
|| (equaln(minArea, area) && translateTemp.y < minY))
|
||||
{
|
||||
minArea = area;
|
||||
minWidth = size.x;
|
||||
translate = translateTemp;
|
||||
minX = translateTemp.x;
|
||||
minY = translateTemp.y;
|
||||
minBox = rectBox;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (translate)
|
||||
{
|
||||
part.PlacePosition = translate;
|
||||
this.AppendPartHoles(part);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
import { Command } from "../../Editor/CommandMachine";
|
||||
import { app } from "../../ApplicationServices/Application";
|
||||
import { Polyline } from "../../DatabaseServices/Entity/Polyline";
|
||||
import { PromptStatus } from "../../Editor/PromptResult";
|
||||
import { Polylin2Points } from "../Converter/ConverBoard2Part";
|
||||
import { TestDraw } from "../../Add-on/test/TestUtil";
|
||||
import { Path2Polyline } from "../Converter/Path2Polyline";
|
||||
import { HotCMD } from "../../Hot/HotCommand";
|
||||
|
||||
@HotCMD
|
||||
export class Command_Polyline2Path implements Command
|
||||
{
|
||||
async exec()
|
||||
{
|
||||
let plRes = await app.Editor.GetEntity({ Filter: { filterTypes: [Polyline] } });
|
||||
if (plRes.Status !== PromptStatus.OK) return;
|
||||
let pl = plRes.Entity as Polyline;
|
||||
|
||||
|
||||
// TestDraw(Path2Polyline(Polylin2Points(pl, true, 3)), 1);
|
||||
// TestDraw(Path2Polyline(Polylin2Points(pl, false, 3)), 2);
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
import { Matrix4 } from "three";
|
||||
import { TestDraw } from "../../Add-on/test/TestUtil";
|
||||
import { app } from "../../ApplicationServices/Application";
|
||||
import { Polyline } from "../../DatabaseServices/Entity/Polyline";
|
||||
import { PromptStatus } from "../../Editor/PromptResult";
|
||||
import { HotCMD } from "../../Hot/HotCommand";
|
||||
import { DefaultBin } from "../Converter/ConverBoard2Part";
|
||||
import { Curve2Path } from "../Converter/Curves2Parts";
|
||||
import { Part } from "../Part";
|
||||
import { PathGeneratorSingle } from "../PathGenerator";
|
||||
|
||||
@HotCMD
|
||||
export class Test_Translate
|
||||
{
|
||||
async exec()
|
||||
{
|
||||
PathGeneratorSingle.Clear();
|
||||
|
||||
let eRes = await app.Editor.GetEntity({ Filter: { filterTypes: [Polyline] } });
|
||||
if (eRes.Status !== PromptStatus.OK) return;
|
||||
let pl = eRes.Entity as Polyline;
|
||||
|
||||
let path = Curve2Path(pl, true);
|
||||
|
||||
let part = new Part();
|
||||
part.Init(path, DefaultBin, 36);
|
||||
|
||||
|
||||
for (let i = 0; i < part.RotatedStates.length; i++)
|
||||
{
|
||||
let p = part.RotatedStates[i];
|
||||
let plx = pl.Clone();
|
||||
let m = new Matrix4().setPosition(-p.OrigionMinPoint.x, -p.OrigionMinPoint.y, 0);
|
||||
plx.ApplyMatrix(m);
|
||||
|
||||
let r = new Matrix4().makeRotationZ(p.Rotation);
|
||||
plx.ApplyMatrix(r);
|
||||
TestDraw(plx, i + 1);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,179 +0,0 @@
|
||||
import React from "react";
|
||||
import { Matrix4, Vector3 } from "three";
|
||||
import { TestDraw } from "../../Add-on/test/TestUtil";
|
||||
import { app } from "../../ApplicationServices/Application";
|
||||
import { Board } from "../../DatabaseServices/Entity/Board";
|
||||
import { Circle } from "../../DatabaseServices/Entity/Circle";
|
||||
import { Curve } from "../../DatabaseServices/Entity/Curve";
|
||||
import { Polyline } from "../../DatabaseServices/Entity/Polyline";
|
||||
import { Command, commandMachine } from "../../Editor/CommandMachine";
|
||||
import { JigUtils } from "../../Editor/JigUtils";
|
||||
import { PromptStatus } from "../../Editor/PromptResult";
|
||||
import { AsVector3, IdentityMtx4 } from "../../Geometry/GeUtils";
|
||||
import { HotCMD } from "../../Hot/HotCommand";
|
||||
import { AppToaster } from "../../UI/Components/Toaster";
|
||||
import { ConverBoard2Part, DefaultBin } from "../Converter/ConverBoard2Part";
|
||||
import { Curves2Parts } from "../Converter/Curves2Parts";
|
||||
import { NestCache } from "../NestCache";
|
||||
import { OptimizeMachine } from "../OptimizeMachine";
|
||||
import { TestContainer } from "./TestContainer";
|
||||
import { NestVariant } from "./TestVariant";
|
||||
|
||||
@HotCMD
|
||||
export class Command_TestYH implements Command
|
||||
{
|
||||
async exec()
|
||||
{
|
||||
NestVariant.curveMap.clear();
|
||||
let plsRes = await app.Editor.GetSelection({
|
||||
Filter: {
|
||||
filterTypes: [Polyline, Circle, Board]
|
||||
}
|
||||
});
|
||||
|
||||
if (plsRes.Status !== PromptStatus.OK)
|
||||
return;
|
||||
|
||||
let pls: (Polyline | Circle)[] = [];
|
||||
let brs: Board[] = [];
|
||||
for (let e of plsRes.SelectSet.SelectEntityList)
|
||||
{
|
||||
if (e instanceof Board)
|
||||
brs.push(e);
|
||||
else
|
||||
pls.push(e as Polyline);
|
||||
}
|
||||
|
||||
let binPath = DefaultBin;
|
||||
|
||||
let parts = Curves2Parts(pls, binPath, 3);
|
||||
for (let br of brs)
|
||||
{
|
||||
parts.push(ConverBoard2Part(br));
|
||||
}
|
||||
|
||||
//购买机器
|
||||
let machie = new OptimizeMachine();
|
||||
machie.Bin = binPath;
|
||||
machie.PutParts(parts);
|
||||
|
||||
let key = AppToaster.show({
|
||||
message: <>
|
||||
持续优化中....
|
||||
<button onClick={e =>
|
||||
{
|
||||
machie.Suspend();
|
||||
AppToaster.dismiss(key);
|
||||
}}>停止</button>
|
||||
</>,
|
||||
timeout: 0,
|
||||
});
|
||||
|
||||
let binCurves: Polyline[] = [];
|
||||
const DrawBin = (count: number) =>
|
||||
{
|
||||
for (let i = binCurves.length; i < count; i++)
|
||||
{
|
||||
let pl = binPath.Polyline.Clone();
|
||||
pl.Position = new Vector3(1.1 * i * 1220);
|
||||
TestDraw(pl, 6);
|
||||
binCurves.push(pl);
|
||||
}
|
||||
|
||||
for (let i = 0; i < binCurves.length; i++)
|
||||
{
|
||||
binCurves[i].Visible = i < count;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
for (let part of parts)
|
||||
{
|
||||
for (let c of part.UserData)
|
||||
NestVariant.curveMap.set(c, c.OCS);
|
||||
}
|
||||
machie.callBack = async (i) =>
|
||||
{
|
||||
let size = binPath.BoundingBox.getSize(new Vector3).setY(0);
|
||||
size.x *= 1.1;
|
||||
let p = AsVector3(binPath.OrigionMinPoint);
|
||||
DrawBin(i.Containers.length);
|
||||
for (let c of i.Containers)
|
||||
{
|
||||
let ocs = new Matrix4().setPosition(p);
|
||||
let i = 1;
|
||||
for (let p of c.PlacedAll)
|
||||
{
|
||||
let curves = p.UserData as Curve[];
|
||||
for (let c of curves)
|
||||
{
|
||||
c.ColorIndex = i;
|
||||
c.OCS = NestVariant.curveMap.get(c) || IdentityMtx4;
|
||||
c.ApplyMatrix(p.PlaceCS)
|
||||
.ApplyMatrix(p.Container.OCS)
|
||||
.ApplyMatrix(ocs);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
p.add(size);
|
||||
}
|
||||
app.Editor.UpdateScreen();
|
||||
};
|
||||
await machie.Start();
|
||||
}
|
||||
}
|
||||
|
||||
@HotCMD
|
||||
class RePlace
|
||||
{
|
||||
async exec()
|
||||
{
|
||||
if (!NestVariant.best) return;
|
||||
NestCache.Clear();
|
||||
|
||||
let binPath = DefaultBin;
|
||||
let size = binPath.BoundingBox.getSize(new Vector3).setY(0);
|
||||
size.x *= 1.1;
|
||||
let p = new Vector3;
|
||||
// for (let c of NestVariant.best.Containers)
|
||||
let c = NestVariant.best.Containers[1];
|
||||
{
|
||||
let ocs = new Matrix4().setPosition(p);
|
||||
|
||||
let nc = new TestContainer(binPath);
|
||||
let parts = c.PlacedAll.map(p => p.Clone());
|
||||
|
||||
let i = 1;
|
||||
for (let p of parts)
|
||||
{
|
||||
JigUtils.End();
|
||||
await nc.PutPart2(p);
|
||||
let curves = p.UserData as Curve[];
|
||||
if (curves[0].IsErase)
|
||||
{
|
||||
curves = curves.map(c =>
|
||||
{
|
||||
let nc = c.Clone();
|
||||
TestDraw(nc);
|
||||
return nc;
|
||||
});
|
||||
}
|
||||
for (let c of curves)
|
||||
{
|
||||
c.ColorIndex = i;
|
||||
c.OCS = NestVariant.curveMap.get(c) || IdentityMtx4;
|
||||
c.ApplyMatrix(p.PlaceCS)
|
||||
.ApplyMatrix(p.Container.OCS)
|
||||
.ApplyMatrix(ocs);
|
||||
}
|
||||
JigUtils.End();
|
||||
await app.Editor.GetPoint({ Msg: "放置完毕" });
|
||||
i++;
|
||||
}
|
||||
p.add(size);
|
||||
}
|
||||
app.Editor.UpdateScreen();
|
||||
}
|
||||
}
|
||||
|
||||
commandMachine.RegisterCommand("reyh", new RePlace);
|
@ -1,57 +0,0 @@
|
||||
import { Command } from "../../Editor/CommandMachine";
|
||||
import { app } from "../../ApplicationServices/Application";
|
||||
import { Polyline } from "../../DatabaseServices/Entity/Polyline";
|
||||
import { Circle } from "../../DatabaseServices/Entity/Circle";
|
||||
import { Board } from "../../DatabaseServices/Entity/Board";
|
||||
import { PromptStatus } from "../../Editor/PromptResult";
|
||||
import { DefaultBin, ConverBoard2Part } from "../Converter/ConverBoard2Part";
|
||||
import { Curves2Parts } from "../Converter/Curves2Parts";
|
||||
import { PathGeneratorSingle } from "../PathGenerator";
|
||||
import { NestDatabase } from "../NestDatabase";
|
||||
import { CADFiler } from "../../DatabaseServices/CADFiler";
|
||||
import { HotCMD } from "../../Hot/HotCommand";
|
||||
|
||||
@HotCMD
|
||||
export class Command_TestYH2 implements Command
|
||||
{
|
||||
async exec()
|
||||
{
|
||||
PathGeneratorSingle.Clear();
|
||||
|
||||
PathGeneratorSingle.RegisterId(DefaultBin);
|
||||
|
||||
let plsRes = await app.Editor.GetSelection({
|
||||
Filter: {
|
||||
filterTypes: [Polyline, Circle, Board]
|
||||
}
|
||||
});
|
||||
|
||||
if (plsRes.Status !== PromptStatus.OK)
|
||||
return;
|
||||
|
||||
let pls: (Polyline | Circle)[] = [];
|
||||
let brs: Board[] = [];
|
||||
for (let e of plsRes.SelectSet.SelectEntityList)
|
||||
{
|
||||
if (e instanceof Board)
|
||||
brs.push(e);
|
||||
else
|
||||
pls.push(e as Polyline);
|
||||
}
|
||||
|
||||
let binPath = DefaultBin;
|
||||
|
||||
let parts = Curves2Parts(pls, binPath, 3);
|
||||
for (let br of brs)
|
||||
parts.push(ConverBoard2Part(br));
|
||||
|
||||
|
||||
let db = new NestDatabase();
|
||||
db.Paths = PathGeneratorSingle.paths;
|
||||
db.Parts = parts;
|
||||
db.Bin = DefaultBin;
|
||||
|
||||
let f = new CADFiler();
|
||||
db.WriteFile(f);
|
||||
}
|
||||
}
|
@ -0,0 +1,182 @@
|
||||
import React from "react";
|
||||
import { Matrix4, Vector3 } from "three";
|
||||
import { TestDraw } from "../../Add-on/test/TestUtil";
|
||||
import { app } from "../../ApplicationServices/Application";
|
||||
import { Board } from "../../DatabaseServices/Entity/Board";
|
||||
import { Circle } from "../../DatabaseServices/Entity/Circle";
|
||||
import { Curve } from "../../DatabaseServices/Entity/Curve";
|
||||
import { Polyline } from "../../DatabaseServices/Entity/Polyline";
|
||||
import { Command } from "../../Editor/CommandMachine";
|
||||
import { PromptStatus } from "../../Editor/PromptResult";
|
||||
import { AsVector3, IdentityMtx4 } from "../../Geometry/GeUtils";
|
||||
import { HotCMD } from "../../Hot/HotCommand";
|
||||
import { AppToaster } from "../../UI/Components/Toaster";
|
||||
import { ConverBoard2Part } from "../Converter/ConverBoard2Part";
|
||||
import { Curves2Parts } from "../Converter/Curves2Parts";
|
||||
import { Path2Polyline } from "../Converter/Path2Polyline";
|
||||
import { DefaultBin } from "../DefaultBin";
|
||||
import { NestCache } from "../NestCache";
|
||||
import { NestDatabase } from "../NestDatabase";
|
||||
import { OptimizeMachine } from "../OptimizeMachine";
|
||||
import { PathGeneratorSingle } from "../PathGenerator";
|
||||
import { NestVariant } from "./TestVariant";
|
||||
|
||||
@HotCMD
|
||||
export class Command_TestYH2 implements Command
|
||||
{
|
||||
async exec()
|
||||
{
|
||||
PathGeneratorSingle.Clear();
|
||||
NestCache.Clear();
|
||||
DefaultBin.InsideNFPCache = {};
|
||||
|
||||
let plsRes = await app.Editor.GetSelection({
|
||||
Filter: {
|
||||
filterTypes: [Polyline, Circle, Board]
|
||||
}
|
||||
});
|
||||
|
||||
if (plsRes.Status !== PromptStatus.OK)
|
||||
return;
|
||||
|
||||
let pls: (Polyline | Circle)[] = [];
|
||||
let brs: Board[] = [];
|
||||
for (let e of plsRes.SelectSet.SelectEntityList)
|
||||
{
|
||||
if (e instanceof Board)
|
||||
brs.push(e);
|
||||
else
|
||||
pls.push(e as Polyline);
|
||||
}
|
||||
|
||||
let binPath = DefaultBin;
|
||||
binPath.Id = undefined;
|
||||
PathGeneratorSingle.RegisterId(binPath);
|
||||
|
||||
let parts = Curves2Parts(pls, binPath, 3);
|
||||
for (let br of brs)
|
||||
parts.push(ConverBoard2Part(br));
|
||||
|
||||
parts = parts.filter(p => p.RotatedStates.length > 0);
|
||||
if (parts.length === 0)
|
||||
{
|
||||
app.Editor.Prompt("没有可以优化的板件!");
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < parts.length; i++)
|
||||
parts[i].Id = i;
|
||||
|
||||
let db = new NestDatabase();
|
||||
db.Paths = PathGeneratorSingle.paths;
|
||||
db.Parts = parts;
|
||||
db.Bin = DefaultBin;
|
||||
|
||||
let m = new OptimizeMachine;
|
||||
m.Bin = db.Bin;
|
||||
m.PutParts(db.Parts);
|
||||
|
||||
let binCurves: Polyline[] = [];
|
||||
const DrawBin = (count: number) =>
|
||||
{
|
||||
for (let i = binCurves.length; i < count; i++)
|
||||
{
|
||||
let pl = Path2Polyline(binPath.Points);
|
||||
pl.Position = new Vector3(1.1 * i * 1220);
|
||||
TestDraw(pl, 6);
|
||||
binCurves.push(pl);
|
||||
}
|
||||
|
||||
for (let i = 0; i < binCurves.length; i++)
|
||||
{
|
||||
binCurves[i].Visible = i < count;
|
||||
}
|
||||
};
|
||||
|
||||
for (let part of parts)
|
||||
{
|
||||
for (let c of part.UserData)
|
||||
NestVariant.curveMap.set(c, c.OCS);
|
||||
}
|
||||
|
||||
m.callBack = async (inv) =>
|
||||
{
|
||||
for (let part of parts)
|
||||
{
|
||||
part.Parent = undefined;
|
||||
for (let c of part.UserData)
|
||||
{
|
||||
c.OCS = NestVariant.curveMap.get(c) || IdentityMtx4;
|
||||
}
|
||||
}
|
||||
|
||||
DrawBin(inv.Containers.length);
|
||||
for (let i = 0; i < inv.Containers.length; i++)
|
||||
{
|
||||
for (let part of inv.Containers[i].Placed)
|
||||
{
|
||||
let r = new Matrix4().makeRotationZ(part.State.Rotation);
|
||||
let m = new Matrix4().setPosition(-part.State.OrigionMinPoint.x - 0.5, -part.State.OrigionMinPoint.y - 0.5, 0);
|
||||
let p = new Matrix4().setPosition(AsVector3(part.PlacePosition).multiplyScalar(1e-4));
|
||||
|
||||
let partOld = parts[part.Id];
|
||||
partOld.PlaceCS = p.multiply(r).multiply(m);
|
||||
if (i !== 0)
|
||||
partOld.PlaceCS = new Matrix4().setPosition(new Vector3(1.1 * i * 1220)).multiply(partOld.PlaceCS);
|
||||
|
||||
for (let c of partOld.UserData)
|
||||
{
|
||||
let cu = c as Curve;
|
||||
cu.ApplyMatrix(partOld.PlaceCS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let hole of inv.HoleContainers)
|
||||
{
|
||||
let pm = new Matrix4().setPosition(hole.ParentM.x, hole.ParentM.y, 0);
|
||||
for (let part of hole.Placed)
|
||||
{
|
||||
let r = new Matrix4().makeRotationZ(part.State.Rotation);
|
||||
let m = new Matrix4().setPosition(-part.State.OrigionMinPoint.x, -part.State.OrigionMinPoint.y, 0);
|
||||
let p = new Matrix4().setPosition(AsVector3(part.PlacePosition).multiplyScalar(1e-4));
|
||||
part.PlaceCS = pm.clone().multiply(p).multiply(r).multiply(m);//pm.multiply (p).multiply pm.multiply(p).multiply
|
||||
part.Parent = parts[hole.ParentId];
|
||||
}
|
||||
}
|
||||
|
||||
for (let hole of inv.HoleContainers)
|
||||
{
|
||||
for (let part of hole.Placed)
|
||||
{
|
||||
for (let c of part.UserData)
|
||||
{
|
||||
let cu = c as Curve;
|
||||
cu.Visible = true;
|
||||
cu.ApplyMatrix(part.PlaceCS);
|
||||
let p = part.Parent;
|
||||
while (p)
|
||||
{
|
||||
cu.ApplyMatrix(p.PlaceCS);
|
||||
p = p.Parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let key = AppToaster.show({
|
||||
message: <>
|
||||
持续优化中....
|
||||
<button onClick={e =>
|
||||
{
|
||||
m.Suspend();
|
||||
AppToaster.dismiss(key);
|
||||
}}>停止</button>
|
||||
</>,
|
||||
timeout: 0,
|
||||
});
|
||||
|
||||
await m.Start();
|
||||
}
|
||||
}
|
@ -0,0 +1,212 @@
|
||||
import React from "react";
|
||||
import { Matrix4, Vector3 } from "three";
|
||||
import { TestDraw } from "../../Add-on/test/TestUtil";
|
||||
import { app } from "../../ApplicationServices/Application";
|
||||
import { Board } from "../../DatabaseServices/Entity/Board";
|
||||
import { Circle } from "../../DatabaseServices/Entity/Circle";
|
||||
import { Curve } from "../../DatabaseServices/Entity/Curve";
|
||||
import { Polyline } from "../../DatabaseServices/Entity/Polyline";
|
||||
import { Command, CommandWrap } from "../../Editor/CommandMachine";
|
||||
import { PromptStatus } from "../../Editor/PromptResult";
|
||||
import { AsVector3, IdentityMtx4 } from "../../Geometry/GeUtils";
|
||||
import { HotCMD } from "../../Hot/HotCommand";
|
||||
import { AppToaster } from "../../UI/Components/Toaster";
|
||||
import { InitClipperCpp } from "../ClipperCpp";
|
||||
import { ConverBoard2Part } from "../Converter/ConverBoard2Part";
|
||||
import { Curves2Parts } from "../Converter/Curves2Parts";
|
||||
import { Path2Polyline } from "../Converter/Path2Polyline";
|
||||
import { DefaultBin } from "../DefaultBin";
|
||||
import { CADFiler } from "../Filer";
|
||||
import { Individual } from "../Individual";
|
||||
import { NestCache } from "../NestCache";
|
||||
import { NestDatabase } from "../NestDatabase";
|
||||
import Worker from "../OptimizeWorker.worker";
|
||||
import { PathGeneratorSingle } from "../PathGenerator";
|
||||
import { NestVariant } from "./TestVariant";
|
||||
|
||||
@HotCMD
|
||||
export class Command_TestYHWorker implements Command
|
||||
{
|
||||
async exec()
|
||||
{
|
||||
PathGeneratorSingle.Clear();
|
||||
NestCache.Clear();
|
||||
DefaultBin.InsideNFPCache = {};
|
||||
|
||||
let plsRes = await app.Editor.GetSelection({
|
||||
Filter: { filterTypes: [Polyline, Circle, Board] }
|
||||
});
|
||||
|
||||
await InitClipperCpp();
|
||||
if (plsRes.Status !== PromptStatus.OK)
|
||||
return;
|
||||
|
||||
let pls: (Polyline | Circle)[] = [];
|
||||
let brs: Board[] = [];
|
||||
for (let e of plsRes.SelectSet.SelectEntityList)
|
||||
{
|
||||
if (e instanceof Board)
|
||||
brs.push(e);
|
||||
else
|
||||
pls.push(e as Polyline);
|
||||
}
|
||||
|
||||
let binPath = DefaultBin;
|
||||
binPath.Id = undefined;
|
||||
PathGeneratorSingle.RegisterId(binPath);
|
||||
|
||||
let parts = Curves2Parts(pls, binPath, 3.5);
|
||||
for (let br of brs)
|
||||
parts.push(ConverBoard2Part(br));
|
||||
|
||||
parts = parts.filter(p => p.RotatedStates.length > 0);
|
||||
if (parts.length === 0)
|
||||
{
|
||||
app.Editor.Prompt("没有可以优化的板件!");
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < parts.length; i++)
|
||||
parts[i].Id = i;
|
||||
|
||||
let db = new NestDatabase();
|
||||
db.Paths = PathGeneratorSingle.paths;
|
||||
db.Parts = parts;
|
||||
db.Bin = DefaultBin;
|
||||
|
||||
let f = new CADFiler();
|
||||
db.WriteFile(f);
|
||||
|
||||
let db2 = new NestDatabase();
|
||||
f.Reset();
|
||||
db2.ReadFile(f);
|
||||
|
||||
let binCurves: Polyline[] = [];
|
||||
const DrawBin = (count: number) =>
|
||||
{
|
||||
for (let i = binCurves.length; i < count; i++)
|
||||
{
|
||||
let pl = Path2Polyline(binPath.Points);
|
||||
pl.Position = new Vector3(1.1 * i * 1220);
|
||||
TestDraw(pl, 6);
|
||||
binCurves.push(pl);
|
||||
}
|
||||
|
||||
for (let i = 0; i < binCurves.length; i++)
|
||||
{
|
||||
binCurves[i].Visible = i < count;
|
||||
}
|
||||
app.Editor.UpdateScreen();
|
||||
};
|
||||
|
||||
|
||||
for (let part of parts)
|
||||
{
|
||||
for (let c of part.UserData)
|
||||
NestVariant.curveMap.set(c, c.OCS);
|
||||
}
|
||||
|
||||
let workers: Worker[] = [];
|
||||
let key = AppToaster.show({
|
||||
message: <>
|
||||
持续优化中....
|
||||
<button onClick={e =>
|
||||
{
|
||||
for (let w of workers)
|
||||
w.terminate();
|
||||
AppToaster.dismiss(key);
|
||||
}}>停止</button>
|
||||
</>,
|
||||
timeout: 0,
|
||||
});
|
||||
|
||||
let best = Infinity;
|
||||
const fff = (e) =>
|
||||
{
|
||||
let f = new CADFiler(e.data);
|
||||
let inv = new Individual(db.Parts);
|
||||
inv.ReadFile(f);
|
||||
if (best <= inv.Fitness)
|
||||
return;
|
||||
else
|
||||
best = inv.Fitness;
|
||||
CommandWrap(() =>
|
||||
{
|
||||
app.Editor.Prompt(`优化率:${inv.Fitness}`);
|
||||
|
||||
for (let part of parts)
|
||||
{
|
||||
part.Parent = undefined;
|
||||
for (let c of part.UserData)
|
||||
{
|
||||
c.OCS = NestVariant.curveMap.get(c) || IdentityMtx4;
|
||||
}
|
||||
}
|
||||
|
||||
DrawBin(inv.Containers.length);
|
||||
for (let i = 0; i < inv.Containers.length; i++)
|
||||
{
|
||||
let j = 1;
|
||||
for (let part of inv.Containers[i].Placed)
|
||||
{
|
||||
let r = new Matrix4().makeRotationZ(part.State.Rotation);
|
||||
let m = new Matrix4().setPosition(-part.State.OrigionMinPoint.x - 0.5, -part.State.OrigionMinPoint.y - 0.5, 0);
|
||||
let p = new Matrix4().setPosition(AsVector3(part.PlacePosition).multiplyScalar(1e-4));
|
||||
|
||||
let partOld = parts[part.Id];
|
||||
partOld.PlaceCS = p.multiply(r).multiply(m);
|
||||
if (i !== 0)
|
||||
partOld.PlaceCS = new Matrix4().setPosition(new Vector3(1.1 * i * 1220)).multiply(partOld.PlaceCS);
|
||||
|
||||
for (let c of partOld.UserData)
|
||||
{
|
||||
let cu = c as Curve;
|
||||
cu.ColorIndex = j;
|
||||
cu.ApplyMatrix(partOld.PlaceCS);
|
||||
}
|
||||
j++;
|
||||
}
|
||||
}
|
||||
|
||||
for (let hole of inv.HoleContainers)
|
||||
{
|
||||
let pm = new Matrix4().setPosition(hole.ParentM.x, hole.ParentM.y, 0);
|
||||
for (let part of hole.Placed)
|
||||
{
|
||||
let r = new Matrix4().makeRotationZ(part.State.Rotation);
|
||||
let m = new Matrix4().setPosition(-part.State.OrigionMinPoint.x, -part.State.OrigionMinPoint.y, 0);
|
||||
let p = new Matrix4().setPosition(AsVector3(part.PlacePosition).multiplyScalar(1e-4));
|
||||
part.PlaceCS = pm.clone().multiply(p).multiply(r).multiply(m);//pm.multiply (p).multiply pm.multiply(p).multiply
|
||||
part.Parent = parts[hole.ParentId];
|
||||
}
|
||||
}
|
||||
|
||||
for (let hole of inv.HoleContainers)
|
||||
{
|
||||
for (let part of hole.Placed)
|
||||
{
|
||||
for (let c of part.UserData)
|
||||
{
|
||||
let cu = c as Curve;
|
||||
cu.Visible = true;
|
||||
cu.ApplyMatrix(part.PlaceCS);
|
||||
let p = part.Parent;
|
||||
while (p)
|
||||
{
|
||||
cu.ApplyMatrix(p.PlaceCS);
|
||||
p = p.Parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, "应用优化");
|
||||
};
|
||||
for (let i = 0; i < navigator.hardwareConcurrency - 2; i++)// ||
|
||||
{
|
||||
let w = new Worker;
|
||||
workers.push(w);
|
||||
w.postMessage(f._datas);
|
||||
w.onmessage = fff;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
export function equaln(v1: number, v2: number, fuzz = 1e-5)
|
||||
{
|
||||
return Math.abs(v1 - v2) <= fuzz;
|
||||
}
|
||||
|
||||
export function FixIndex(index: number, arr: Array<any> | number)
|
||||
{
|
||||
let count = (arr instanceof Array) ? arr.length : arr;
|
||||
if (index < 0)
|
||||
return count + index;
|
||||
else if (index >= count)
|
||||
return index - count;
|
||||
else
|
||||
return index;
|
||||
}
|
@ -0,0 +1,292 @@
|
||||
import { Point } from "./Point";
|
||||
|
||||
export class Vector2
|
||||
{
|
||||
x: number;
|
||||
y: number;
|
||||
readonly isVector2: boolean = true;
|
||||
constructor(x: number = 0, y: number = 0)
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
get width(): number { return this.x; }
|
||||
set width(value: number) { this.x = value; }
|
||||
get height(): number { return this.y; }
|
||||
set height(value: number) { this.y = value; }
|
||||
set(x: number, y: number): Vector2
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
return this;
|
||||
}
|
||||
setScalar(scalar: number): Vector2
|
||||
{
|
||||
this.x = scalar;
|
||||
this.y = scalar;
|
||||
return this;
|
||||
}
|
||||
setX(x: number): Vector2
|
||||
{
|
||||
this.x = x;
|
||||
return this;
|
||||
}
|
||||
setY(y: number): Vector2
|
||||
{
|
||||
this.y = y;
|
||||
return this;
|
||||
}
|
||||
setComponent(index: number, value: number): Vector2
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0: this.x = value; break;
|
||||
case 1: this.y = value; break;
|
||||
default: throw new Error('index is out of range: ' + index);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
getComponent(index: number): number
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0: return this.x;
|
||||
case 1: return this.y;
|
||||
default: throw new Error('index is out of range: ' + index);
|
||||
}
|
||||
}
|
||||
clone(): Vector2
|
||||
{
|
||||
return new (this.constructor as any)().copy(this);
|
||||
}
|
||||
copy(v: Vector2): Vector2
|
||||
{
|
||||
this.x = v.x;
|
||||
this.y = v.y;
|
||||
return this;
|
||||
}
|
||||
add(v: Point): Vector2
|
||||
{
|
||||
this.x += v.x;
|
||||
this.y += v.y;
|
||||
return this;
|
||||
}
|
||||
addScalar(s: number): Vector2
|
||||
{
|
||||
this.x += s;
|
||||
this.y += s;
|
||||
return this;
|
||||
}
|
||||
addVectors(a: Vector2, b: Vector2): Vector2
|
||||
{
|
||||
this.x = a.x + b.x;
|
||||
this.y = a.y + b.y;
|
||||
return this;
|
||||
}
|
||||
addScaledVector(v: Vector2, s: number): Vector2
|
||||
{
|
||||
this.x += v.x * s;
|
||||
this.y += v.y * s;
|
||||
return this;
|
||||
}
|
||||
sub(v: Vector2, w?: Vector2): Vector2
|
||||
{
|
||||
if (w !== undefined)
|
||||
{
|
||||
console.warn('THREE.Vector2: .sub() now only accepts one argument. Use .subVectors(a, b) instead.');
|
||||
return this.subVectors(v, w);
|
||||
}
|
||||
this.x -= v.x;
|
||||
this.y -= v.y;
|
||||
return this;
|
||||
}
|
||||
subScalar(s: number): Vector2
|
||||
{
|
||||
this.x -= s;
|
||||
this.y -= s;
|
||||
return this;
|
||||
}
|
||||
subVectors(a: Vector2, b: Vector2): Vector2
|
||||
{
|
||||
this.x = a.x - b.x;
|
||||
this.y = a.y - b.y;
|
||||
return this;
|
||||
}
|
||||
multiply(v: Vector2): Vector2
|
||||
{
|
||||
this.x *= v.x;
|
||||
this.y *= v.y;
|
||||
return this;
|
||||
}
|
||||
multiplyScalar(scalar: number): Vector2
|
||||
{
|
||||
if (isFinite(scalar))
|
||||
{
|
||||
this.x *= scalar;
|
||||
this.y *= scalar;
|
||||
} else
|
||||
{
|
||||
this.x = 0;
|
||||
this.y = 0;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
divide(v: Vector2): Vector2
|
||||
{
|
||||
this.x /= v.x;
|
||||
this.y /= v.y;
|
||||
return this;
|
||||
}
|
||||
divideScalar(scalar: number): Vector2
|
||||
{
|
||||
return this.multiplyScalar(1 / scalar);
|
||||
}
|
||||
min(v: Vector2): Vector2
|
||||
{
|
||||
this.x = Math.min(this.x, v.x);
|
||||
this.y = Math.min(this.y, v.y);
|
||||
return this;
|
||||
}
|
||||
max(v: Vector2): Vector2
|
||||
{
|
||||
this.x = Math.max(this.x, v.x);
|
||||
this.y = Math.max(this.y, v.y);
|
||||
return this;
|
||||
}
|
||||
clamp(min: Vector2, max: Vector2): Vector2
|
||||
{
|
||||
// This function assumes min < max, if this assumption isn't true it will not operate correctly
|
||||
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;
|
||||
}
|
||||
private static clampScalar_min = new Vector2();
|
||||
private static clampScalar_max = new Vector2();
|
||||
clampScalar(minVal: number, maxVal: number): Vector2
|
||||
{
|
||||
const min: Vector2 = Vector2.clampScalar_min.set(minVal, minVal);
|
||||
const max: Vector2 = Vector2.clampScalar_max.set(maxVal, maxVal);
|
||||
return this.clamp(min, max);
|
||||
}
|
||||
clampLength(min: number, max: number): Vector2
|
||||
{
|
||||
const length: number = this.length();
|
||||
return this.multiplyScalar(Math.max(min, Math.min(max, length)) / length);
|
||||
}
|
||||
floor(): Vector2
|
||||
{
|
||||
this.x = Math.floor(this.x);
|
||||
this.y = Math.floor(this.y);
|
||||
return this;
|
||||
}
|
||||
ceil(): Vector2
|
||||
{
|
||||
this.x = Math.ceil(this.x);
|
||||
this.y = Math.ceil(this.y);
|
||||
return this;
|
||||
}
|
||||
round(): Vector2
|
||||
{
|
||||
this.x = Math.round(this.x);
|
||||
this.y = Math.round(this.y);
|
||||
return this;
|
||||
}
|
||||
roundToZero(): Vector2
|
||||
{
|
||||
this.x = (this.x < 0) ? Math.ceil(this.x) : Math.floor(this.x);
|
||||
this.y = (this.y < 0) ? Math.ceil(this.y) : Math.floor(this.y);
|
||||
return this;
|
||||
}
|
||||
negate(): Vector2
|
||||
{
|
||||
this.x = - this.x;
|
||||
this.y = - this.y;
|
||||
return this;
|
||||
}
|
||||
dot(v: Vector2): number
|
||||
{
|
||||
return this.x * v.x + this.y * v.y;
|
||||
}
|
||||
lengthSq(): number
|
||||
{
|
||||
return this.x * this.x + this.y * this.y;
|
||||
}
|
||||
length(): number
|
||||
{
|
||||
return Math.sqrt(this.x * this.x + this.y * this.y);
|
||||
}
|
||||
lengthManhattan(): number
|
||||
{
|
||||
return Math.abs(this.x) + Math.abs(this.y);
|
||||
}
|
||||
normalize(): Vector2
|
||||
{
|
||||
return this.divideScalar(this.length());
|
||||
}
|
||||
angle(): number
|
||||
{
|
||||
// computes the angle in radians with respect to the positive x-axis
|
||||
let angle: number = Math.atan2(this.y, this.x);
|
||||
if (angle < 0) angle += 2 * Math.PI;
|
||||
return angle;
|
||||
}
|
||||
distanceTo(v: Vector2): number
|
||||
{
|
||||
return Math.sqrt(this.distanceToSquared(v));
|
||||
}
|
||||
distanceToSquared(v: Vector2): number
|
||||
{
|
||||
const dx: number = this.x - v.x, dy: number = this.y - v.y;
|
||||
return dx * dx + dy * dy;
|
||||
}
|
||||
distanceToManhattan(v: Vector2): number
|
||||
{
|
||||
return Math.abs(this.x - v.x) + Math.abs(this.y - v.y);
|
||||
}
|
||||
setLength(length: number): Vector2
|
||||
{
|
||||
return this.multiplyScalar(length / this.length());
|
||||
}
|
||||
lerp(v: Vector2, alpha: number): Vector2
|
||||
{
|
||||
this.x += (v.x - this.x) * alpha;
|
||||
this.y += (v.y - this.y) * alpha;
|
||||
return this;
|
||||
}
|
||||
lerpVectors(v1: Vector2, v2: Vector2, alpha: number): Vector2
|
||||
{
|
||||
return this.subVectors(v2, v1).multiplyScalar(alpha).add(v1);
|
||||
}
|
||||
equals(v: Vector2): boolean
|
||||
{
|
||||
return ((v.x === this.x) && (v.y === this.y));
|
||||
}
|
||||
fromArray(array: Float32Array | number[], offset: number = 0): Vector2
|
||||
{
|
||||
this.x = array[offset];
|
||||
this.y = array[offset + 1];
|
||||
return this;
|
||||
}
|
||||
toArray(array: Float32Array | number[] = [], offset: number = 0): Float32Array | number[]
|
||||
{
|
||||
array[offset] = this.x;
|
||||
array[offset + 1] = this.y;
|
||||
return array;
|
||||
}
|
||||
fromAttribute(attribute: any, index: number, offset: number = 0): Vector2
|
||||
{
|
||||
index = index * attribute.itemSize + offset;
|
||||
this.x = attribute.array[index];
|
||||
this.y = attribute.array[index + 1];
|
||||
return this;
|
||||
}
|
||||
rotateAround(center: Vector2, angle: number): Vector2
|
||||
{
|
||||
const c: number = Math.cos(angle), s: number = Math.sin(angle);
|
||||
const x: number = this.x - center.x;
|
||||
const y: number = this.y - center.y;
|
||||
this.x = x * c - y * s + center.x;
|
||||
this.y = x * s + y * c + center.y;
|
||||
return this;
|
||||
}
|
||||
}
|
Loading…
Reference in new issue