feat:处理器初步实现---有接上了新优化,回显需要再看下
This commit is contained in:
392
samples/handleAbility/common/ArrayExt.ts
Normal file
392
samples/handleAbility/common/ArrayExt.ts
Normal file
@@ -0,0 +1,392 @@
|
||||
export class List<Item> extends Array<Item>
|
||||
{
|
||||
/** 返回符合条件的第一个元素 */
|
||||
first(fn: (item: Item) => boolean): Item
|
||||
{
|
||||
if (this.length == 0)
|
||||
return null
|
||||
for (const item of this)
|
||||
{
|
||||
if (fn(item))
|
||||
return item
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/** 返回符合条件的最后一元素 */
|
||||
last(fn: (t: Item) => boolean): Item
|
||||
{
|
||||
if (this.length == 0)
|
||||
return null
|
||||
for (let i = this.length - 1; i >= 0; i--)
|
||||
{
|
||||
if (fn(this[i]))
|
||||
return this[i]
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/** 最大值 */
|
||||
max<T>(fn: (item: Item) => T): T
|
||||
{
|
||||
let maxV: T
|
||||
for (const i of this)
|
||||
{
|
||||
let v = fn(i)
|
||||
if (maxV == null)
|
||||
{
|
||||
maxV = fn(i)
|
||||
}
|
||||
else
|
||||
{
|
||||
if (v > maxV)
|
||||
{
|
||||
maxV = v
|
||||
}
|
||||
}
|
||||
}
|
||||
return maxV
|
||||
}
|
||||
|
||||
/** 最小值 */
|
||||
min<T>(fn: (item: Item) => T): T
|
||||
{
|
||||
let minV: T
|
||||
for (const i of this)
|
||||
{
|
||||
let v = fn(i)
|
||||
if (minV == null)
|
||||
{
|
||||
minV = fn(i)
|
||||
}
|
||||
else
|
||||
{
|
||||
if (v < minV)
|
||||
{
|
||||
minV = v
|
||||
}
|
||||
}
|
||||
}
|
||||
return minV
|
||||
}
|
||||
|
||||
/** 累加 */
|
||||
sum(fn: (item: Item) => number): number
|
||||
{
|
||||
let v: number = 0
|
||||
for (const t of this)
|
||||
{
|
||||
v = v + fn(t)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
/** 平均值 */
|
||||
avg(fn: (item: Item) => number): number
|
||||
{
|
||||
if (this.length == 0)
|
||||
return 0
|
||||
let sum = this.sum(fn)
|
||||
return sum / this.length
|
||||
}
|
||||
|
||||
/** 满足条件的元素数量 */
|
||||
count(fn: (item: Item) => number): number
|
||||
{
|
||||
if (this.length == 0)
|
||||
return 0
|
||||
let c = 0
|
||||
for (const item of this)
|
||||
{
|
||||
if (fn(item))
|
||||
c = c + 1
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
FindMax<T>(fn: (item: Item) => T): Item
|
||||
{
|
||||
return this.reduce((a: Item, b: Item): Item => fn(a) > fn(b) ? a : b)
|
||||
}
|
||||
}
|
||||
|
||||
export class ArrayExt
|
||||
{
|
||||
/** 返回满足条件的元素数量 */
|
||||
static count<Item>(list: Item[], fn: (item: Item) => boolean): number
|
||||
{
|
||||
if (list.length == 0)
|
||||
return 0
|
||||
let c = 0
|
||||
for (const item of list)
|
||||
{
|
||||
if (fn(item))
|
||||
c = c + 1
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
/** 移除 */
|
||||
static remove<Item>(list: Item[], obj: Item)
|
||||
{
|
||||
let index = list.findIndex(t => t == obj)
|
||||
if (index == -1)
|
||||
return
|
||||
list.splice(index, 1)
|
||||
}
|
||||
|
||||
/** 返回符合条件的第一个元素 */
|
||||
static first<Item>(list: Item[], fn: (item: Item) => boolean, orderFn1: (item: Item) => number = null, orderFn2: (item: Item) => number = null): Item
|
||||
{
|
||||
if (list.length == 0)
|
||||
return null
|
||||
if (orderFn1 == null)
|
||||
{
|
||||
for (const item of list)
|
||||
{
|
||||
if (fn(item))
|
||||
return item
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
let minValue1: number
|
||||
let minValue2: number
|
||||
let minItem: Item
|
||||
for (const item of list)
|
||||
{
|
||||
if (fn(item) == false)
|
||||
continue
|
||||
let v1 = orderFn1(item)
|
||||
let v2 = orderFn2 != null ? orderFn2(item) : 0
|
||||
if (minValue1 == null || v1 < minValue1 || (v1 == minValue1 && v2 < minValue2))
|
||||
{
|
||||
minValue1 = v1
|
||||
minValue2 = v2
|
||||
minItem = item
|
||||
}
|
||||
}
|
||||
return minItem
|
||||
}
|
||||
|
||||
/** 返回符合条件的最后一元素 */
|
||||
static last<Item>(list: Item[], fn: (t: Item) => boolean, orderFn1: (item: Item) => number = null, orderFn2: (item: Item) => number = null): Item
|
||||
{
|
||||
if (list.length == 0)
|
||||
return null
|
||||
if (orderFn1 == null)
|
||||
{
|
||||
for (let i = list.length - 1; i >= 0; i--)
|
||||
{
|
||||
if (fn(list[i]))
|
||||
return list[i]
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
//
|
||||
let maxValue1: number
|
||||
let maxValue2: number
|
||||
let maxItem: Item
|
||||
for (const item of list)
|
||||
{
|
||||
if (fn(item) == false)
|
||||
continue
|
||||
let v1 = orderFn1(item)
|
||||
let v2 = orderFn2 ? orderFn2(item) : 0
|
||||
if (maxValue1 == null || v1 > maxValue1 || (v1 == maxValue1 && v2 > maxValue2))
|
||||
{
|
||||
maxValue1 = v1
|
||||
maxValue2 = v2
|
||||
maxItem = item
|
||||
}
|
||||
}
|
||||
return maxItem
|
||||
}
|
||||
|
||||
/** 取最大值 */
|
||||
static max<T1, T2>(list: T1[], fn: (item: T1) => T2, whereF: (item: T1) => boolean = null, defaultV: T2 = null): T2
|
||||
{
|
||||
let maxV: T2
|
||||
for (const i of list)
|
||||
{
|
||||
if (whereF && whereF(i) == false)
|
||||
continue
|
||||
let v = fn(i)
|
||||
if (maxV == undefined)
|
||||
{
|
||||
maxV = fn(i)
|
||||
}
|
||||
else
|
||||
{
|
||||
if (v > maxV)
|
||||
{
|
||||
maxV = v
|
||||
}
|
||||
}
|
||||
}
|
||||
if (maxV != undefined)
|
||||
return maxV
|
||||
return defaultV
|
||||
}
|
||||
|
||||
/** 最小值 */
|
||||
static min<Item, T>(list: Item[], fn: (item: Item) => T, whereF: (item: Item) => boolean = null, defaultV: T = null): T
|
||||
{
|
||||
let minV: T
|
||||
for (const i of list)
|
||||
{
|
||||
if (whereF && whereF(i) == false)
|
||||
continue
|
||||
let v = fn(i)
|
||||
if (minV == undefined)
|
||||
{
|
||||
minV = v
|
||||
}
|
||||
else
|
||||
{
|
||||
if (v < minV)
|
||||
{
|
||||
minV = v
|
||||
}
|
||||
}
|
||||
}
|
||||
if (minV != undefined)
|
||||
return minV
|
||||
return defaultV
|
||||
}
|
||||
|
||||
/** 累加 */
|
||||
static sum<Item>(list: Item[], fn: (item: Item) => number, wn?: (item: Item) => boolean): number
|
||||
{
|
||||
let v: number = 0
|
||||
for (const t of list)
|
||||
{
|
||||
if (wn && wn(t) == false)
|
||||
continue
|
||||
v = v + fn(t)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
/** 平均值 */
|
||||
static avg<Item>(list: Item[], fn: (item: Item) => number): number
|
||||
{
|
||||
if (this.length == 0)
|
||||
return 0
|
||||
let sum = ArrayExt.sum(list, fn)
|
||||
return sum / this.length
|
||||
}
|
||||
|
||||
/** 排序 */
|
||||
static sortBy<Item>(list: Item[], fn: (item: Item) => number, fn2: (item: Item) => number = null)
|
||||
{
|
||||
if (fn2 == null)
|
||||
return list.sort((a: Item, b: Item): number => fn(a) - fn(b))
|
||||
else
|
||||
return list.sort((a: Item, b: Item): number => fn(a) == fn(b) ? (fn2(a) - fn2(b)) : fn(a) - fn(b))
|
||||
}
|
||||
|
||||
/** 降序 排序 */
|
||||
static sortByDescending<Item>(list: Item[], fn: (item: Item) => number)
|
||||
{
|
||||
list.sort((a: Item, b: Item): number => fn(b) - fn(a))
|
||||
}
|
||||
|
||||
/** 排序成新的数组 */
|
||||
static orderBy<Item>(list: Item[], fn: (item: Item) => number, fn2: (item: Item) => number = null): Item[]
|
||||
{
|
||||
let newList = list.concat([])
|
||||
if (fn2 == null)
|
||||
return newList.sort((a: Item, b: Item): number => fn(a) - fn(b))
|
||||
else
|
||||
return newList.sort((a: Item, b: Item): number => fn(a) == fn(b) ? (fn2(a) - fn2(b)) : fn(a) - fn(b))
|
||||
}
|
||||
|
||||
/** 降序成新的数组 */
|
||||
static orderByDescending<Item>(list: Item[], fn: (item: Item) => number, fn2: (item: Item) => number = null): Item[]
|
||||
{
|
||||
let newList = list.concat([])
|
||||
if (fn2 == null)
|
||||
return list.sort((a: Item, b: Item): number => fn(b) - fn(a))
|
||||
else
|
||||
return list.sort((a: Item, b: Item): number => fn(a) == fn(b) ? (fn2(b) - fn2(a)) : fn(b) - fn(a))
|
||||
}
|
||||
|
||||
/** 分组 */
|
||||
static groupBy<Item, gT>(list: Item[], fn: (item: Item) => gT): GroupItem<gT, Item>[]
|
||||
{
|
||||
let groups = new Array<GroupItem<gT, Item>>()
|
||||
|
||||
for (const item of list)
|
||||
{
|
||||
let key = fn(item)
|
||||
let group = groups.find(t => t.key == key)
|
||||
if (group == null)
|
||||
{
|
||||
group = new GroupItem(key)
|
||||
groups.push(group)
|
||||
}
|
||||
group.push(item)
|
||||
}
|
||||
return groups
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择
|
||||
* let newObjectList = ArrayExt.Select(list,t=>({pA:t.name, pB:t.age + "_"+ t.month}) ) ;
|
||||
*/
|
||||
static Select<Item, rT>(list: Item[], fn: (item: Item) => rT): rT[]
|
||||
{
|
||||
let newList = new Array<rT>()
|
||||
for (const t of list)
|
||||
{
|
||||
newList.push(fn(t))
|
||||
}
|
||||
return newList
|
||||
}
|
||||
|
||||
/** 过来,并按顺序排序 */
|
||||
static where<Item>(list: Item[], whereFn: (item: Item) => boolean, orderfn1: (item: Item) => number = null, orderfn2: (item: Item) => number = null): Item[]
|
||||
{
|
||||
let newList = list.filter(whereFn)
|
||||
if (orderfn1 == null && orderfn2 == null)
|
||||
return newList
|
||||
return ArrayExt.sortBy(newList, orderfn1, orderfn2)
|
||||
}
|
||||
}
|
||||
|
||||
export class GroupItem<gT, Item>
|
||||
{
|
||||
key: gT
|
||||
get count() { return this.list.length }
|
||||
list: Item[]
|
||||
|
||||
constructor(k: gT)
|
||||
{
|
||||
this.key = k
|
||||
this.list = []
|
||||
}
|
||||
|
||||
push(d: Item)
|
||||
{
|
||||
this.list.push(d)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 对排序好的数组进行去重操作
|
||||
* @param {(e1, e2) => boolean} [checkFuction] 校验对象相等函数
|
||||
* @returns {Array<T>} 返回自身
|
||||
*/
|
||||
export function arrayRemoveDuplicateBySort<T>(arr: Array<T>, checkFuction: (e1: T, e2: T) => boolean = checkEqual): Array<T>
|
||||
{
|
||||
if (arr.length < 2)
|
||||
return arr
|
||||
let j = 1
|
||||
for (let i = 1, l = arr.length; i < l; i++)
|
||||
if (!checkFuction(arr[j - 1], arr[i]))
|
||||
arr[j++] = arr[i]
|
||||
arr.length = j
|
||||
return arr
|
||||
}
|
183
samples/handleAbility/common/Box2.ts
Normal file
183
samples/handleAbility/common/Box2.ts
Normal file
@@ -0,0 +1,183 @@
|
||||
import { Vector2 } from './Vector2.js'
|
||||
import type { Point } from './Point.js'
|
||||
|
||||
export class Box2
|
||||
{
|
||||
min: Vector2
|
||||
max: Vector2
|
||||
constructor(min = new Vector2(+Number.POSITIVE_INFINITY, +Number.POSITIVE_INFINITY), max = new Vector2(Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY))
|
||||
{
|
||||
this.min = min
|
||||
this.max = max
|
||||
}
|
||||
|
||||
/** 获取面积 */
|
||||
get area(): number
|
||||
{
|
||||
return (this.max.x - this.min.x) * (this.max.y - this.min.y)
|
||||
}
|
||||
|
||||
/** */
|
||||
set(min: Vector2, max: Vector2): Box2
|
||||
{
|
||||
this.min.copy(min)
|
||||
this.max.copy(max)
|
||||
return this
|
||||
}
|
||||
|
||||
setFromPoints(points: Iterable<Point>): Box2
|
||||
{
|
||||
this.makeEmpty()
|
||||
for (let p of points)
|
||||
{
|
||||
this.expandByPoint(p)
|
||||
}
|
||||
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 = +Number.POSITIVE_INFINITY
|
||||
this.max.x = this.max.y = Number.NEGATIVE_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: Point): 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)
|
||||
}
|
||||
};
|
25
samples/handleAbility/common/ClipperCpp.ts
Normal file
25
samples/handleAbility/common/ClipperCpp.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import * as clipperLib from 'js-angusj-clipper/web/index.js'
|
||||
|
||||
// nodejs style require
|
||||
|
||||
export const 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
|
||||
clipperLib.NativeClipperLibRequestedFormat.WasmOnly,
|
||||
).then((c) =>
|
||||
{
|
||||
clipperCpp.lib = c
|
||||
res()
|
||||
console.log('载入成功!')
|
||||
})
|
||||
})
|
||||
}
|
31
samples/handleAbility/common/Filer.ts
Normal file
31
samples/handleAbility/common/Filer.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* CAD文件数据
|
||||
*/
|
||||
export class NestFiler
|
||||
{
|
||||
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++]
|
||||
}
|
||||
}
|
255
samples/handleAbility/common/LayoutEngine/Curves2Parts.ts
Normal file
255
samples/handleAbility/common/LayoutEngine/Curves2Parts.ts
Normal file
@@ -0,0 +1,255 @@
|
||||
import type { PolylineProps } from 'cadapi'
|
||||
import { Circle, Polyline, Polyline2Points } from 'cadapi'
|
||||
import { EndType, JoinType } from 'js-angusj-clipper/web'
|
||||
import type { Box3, Vector3 } from 'three'
|
||||
import { Vector2 } from 'three'
|
||||
import { clipperCpp } from '../ClipperCpp'
|
||||
import type { Point } from '../Point'
|
||||
import { Path, PathScale } from '../core/Path'
|
||||
import type { IOffset } from './Simplify2'
|
||||
import { SimplifyDouglasPeucker } from './Simplify2'
|
||||
|
||||
/** 内外接多边形 */
|
||||
export function Circle2Points(circle: Circle, knifRadius: number, splitSize = 10, outside = false): Point[] {
|
||||
let radius = circle.Radius
|
||||
const an = Math.PI * 2 / splitSize
|
||||
|
||||
if (outside)
|
||||
radius = radius / Math.cos(an / 2) + knifRadius
|
||||
else
|
||||
radius -= knifRadius
|
||||
|
||||
const cenP = circle.Center
|
||||
const pts: Vector3[] = []
|
||||
for (let i = 0; i < splitSize; i++)
|
||||
pts.push(polar(cenP.clone(), an * i, radius))
|
||||
|
||||
return pts as Point[]
|
||||
}
|
||||
|
||||
export function Curve2Path(curve: Polyline, outside = false): Path {
|
||||
if (curve.IsClockWise)
|
||||
curve.Reverse()
|
||||
const w = new CurveWrap(curve, 3, outside)
|
||||
return new Path(outside ? w.GetOutsidePoints() : w.GetInsidePoints())
|
||||
}
|
||||
|
||||
export class CurveWrap {
|
||||
BoundingBox: Box3
|
||||
|
||||
Area: number
|
||||
|
||||
SimplyPolyline: Polyline
|
||||
SimplyOffset: IOffset
|
||||
Used = false
|
||||
Holes: CurveWrap[] = []
|
||||
|
||||
Points: Point[]
|
||||
|
||||
constructor(public Curve: Polyline | Circle, public KnifRadius: number, public IsOutside: boolean) {
|
||||
this.BoundingBox = Curve.BoundingBox
|
||||
|
||||
if (Curve instanceof Polyline) {
|
||||
const pts = Polyline2Points(Curve, IsOutside, 0)[1]
|
||||
/**
|
||||
* 精简算法SimplifyDouglasPeucker 会导致轮廓变大,
|
||||
* 修改成直接取点 陈雄 QQ聊天记录 23.9.18
|
||||
* 23.10.9 by lrx
|
||||
*/
|
||||
|
||||
const [spts, offset] = SimplifyDouglasPeucker(pts, KnifRadius ** 2 + KnifRadius)
|
||||
if (spts.length !== pts.length && spts.length > 2) {
|
||||
this.SimplyOffset = offset
|
||||
|
||||
this.SimplyPolyline = Path2Polyline(spts)
|
||||
this.Curve = this.SimplyPolyline// 保险起见,也更新它
|
||||
this.Area = this.SimplyPolyline.Area
|
||||
}
|
||||
else// 此处更新多段线
|
||||
{ this.Curve = Path2Polyline(pts) }
|
||||
this.Points = spts
|
||||
|
||||
// 以下修改后的
|
||||
// this.Curve = Path2Polyline(pts);
|
||||
// this.Points = pts;
|
||||
}
|
||||
|
||||
if (this.Area === undefined)
|
||||
this.Area = this.Curve.Area
|
||||
}
|
||||
|
||||
ContainsCurve(curve: CurveWrap): boolean {
|
||||
if (this.SimplyPolyline)
|
||||
return this.SimplyPolyline.PtInCurve(curve.Curve.StartPoint)
|
||||
return this.Curve.PtInCurve(curve.Curve.StartPoint)
|
||||
}
|
||||
|
||||
GetOutsidePoints(): Point[] {
|
||||
if (this.Curve instanceof Circle) {
|
||||
const pts = Circle2Points(this.Curve, this.KnifRadius, 10, true)
|
||||
return pts
|
||||
}
|
||||
else {
|
||||
const pl = this.SimplyPolyline || this.Curve
|
||||
let offset = this.KnifRadius
|
||||
if (this.SimplyOffset)
|
||||
offset += this.SimplyOffset.positiveOffset
|
||||
|
||||
if (offset > 0) {
|
||||
let pts = pl.GetStretchPoints() as Point[]
|
||||
pts = clipperCpp.lib.offsetToPaths({
|
||||
delta: offset * 1e4,
|
||||
offsetInputs: [{ data: PathScale(pts, 1e4), joinType: JoinType.Miter, endType: EndType.ClosedPolygon }],
|
||||
})[0]
|
||||
try {
|
||||
PathScale(pts, 1e-4)
|
||||
}
|
||||
catch {
|
||||
console.log('err')
|
||||
}
|
||||
return pts
|
||||
}
|
||||
else { return this.Points }
|
||||
}
|
||||
}
|
||||
|
||||
GetInsidePoints(): Point[] {
|
||||
return this.GetInsidePoints2(this.KnifRadius)
|
||||
}
|
||||
|
||||
GetInsidePoints2(d: number): Point[] {
|
||||
if (this.Curve instanceof Circle) {
|
||||
const pts = Circle2Points(this.Curve, d, 10, false)
|
||||
return pts
|
||||
}
|
||||
else {
|
||||
const pl = this.SimplyPolyline || this.Curve
|
||||
let offset = -d
|
||||
if (this.SimplyOffset)
|
||||
offset += this.SimplyOffset.negativeOffset
|
||||
|
||||
if (offset < -0.01) {
|
||||
const pls = pl.GetOffsetCurves(offset)
|
||||
if (pls.length)
|
||||
return pls[0].GetStretchPoints()
|
||||
}
|
||||
else { return this.Points }
|
||||
}
|
||||
}
|
||||
|
||||
/** 引入Polyline 已经含刀半径, 获得精简后的点阵 */
|
||||
GetOrgPoints(outside = true): Point[] {
|
||||
if (this.Curve instanceof Circle) {
|
||||
const pts = Circle2Points(this.Curve, 0, 10, outside)
|
||||
return pts
|
||||
}
|
||||
else {
|
||||
const pl = this.SimplyPolyline || this.Curve
|
||||
let offset = 0
|
||||
if (this.SimplyOffset)
|
||||
offset += this.SimplyOffset.positiveOffset
|
||||
|
||||
if (offset > 0) {
|
||||
let pts = pl.GetStretchPoints() as Point[]
|
||||
pts = clipperCpp.lib.offsetToPaths({
|
||||
delta: offset * 1e4,
|
||||
offsetInputs: [{ data: PathScale(pts, 1e4), joinType: JoinType.Miter, endType: EndType.ClosedPolygon }],
|
||||
})[0]
|
||||
try {
|
||||
PathScale(pts, 1e-4)
|
||||
}
|
||||
catch {
|
||||
console.log('err')
|
||||
}
|
||||
return pts
|
||||
}
|
||||
else {
|
||||
return this.Points
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 多段线 转点整 已弃用,整合到CAD api 23.11.2 */
|
||||
// export function Polylin2Points(pl: Polyline, outside: boolean, knifRadius: number): [Polyline, Point[]]
|
||||
// {
|
||||
// let pts: Point[] = [];
|
||||
|
||||
// if (!outside) knifRadius = -knifRadius;
|
||||
// if (pl.IsClockWise) pl.Reverse();
|
||||
// for (let i = 0; i < pl.EndParam; i++)
|
||||
// {
|
||||
// pts.push(pl.GetPointAtParam(i));
|
||||
|
||||
// let bul = pl.GetBulgeAt(i);
|
||||
// if (bul !== 0)
|
||||
// {
|
||||
// let arc = pl.GetCurveAtIndex(i) as Arc;
|
||||
|
||||
// let allAngle = arc.AllAngle;
|
||||
// let arcLength = arc.Length;
|
||||
|
||||
// // let splitCount = Math.round(allAngle / 0.4);
|
||||
// // if (arcLength < 300)
|
||||
// // splitCount = 2;
|
||||
// // else
|
||||
// // splitCount = Math.max(arcLength / 200, splitCount,2);
|
||||
|
||||
// let minCount = Math.floor(allAngle * 4 / Math.PI);
|
||||
// let splitCount = Math.round(allAngle / 0.4);
|
||||
// if (arcLength < 300)
|
||||
// splitCount = Math.max(2, minCount);
|
||||
// else
|
||||
// splitCount = Math.max(Math.floor(arcLength / 200), splitCount,2, minCount);
|
||||
|
||||
// let radius = arc.Radius;
|
||||
// if (outside === bul > 0)
|
||||
// radius = radius / Math.cos(allAngle / (splitCount * 2));
|
||||
|
||||
// let cp = arc.Center;
|
||||
// for (let j = outside ? 0.5 : 0; j < splitCount; j++)
|
||||
// {
|
||||
// let a = arc.GetAngleAtParam(j * (1 / splitCount));
|
||||
// let p = polar(cp.clone(), a, radius);
|
||||
// pts.push(p);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (knifRadius !== 0)
|
||||
// {
|
||||
// pts = clipperCpp.lib.offsetToPaths({
|
||||
// delta: knifRadius * 1e4,
|
||||
// offsetInputs: [{ data: PathScale(pts, 1e4), joinType: JoinType.Miter, endType: EndType.ClosedPolygon }]
|
||||
// })[0];
|
||||
// PathScale(pts, 1e-4);
|
||||
// }
|
||||
// return [pl, pts];
|
||||
// }
|
||||
|
||||
export function Path2Polyline(path: Point[]): Polyline {
|
||||
const pl = new Polyline()
|
||||
pl.LineData = path.map((p) => {
|
||||
return { pt: new Vector2(p.x, p.y), bul: 0 }
|
||||
})
|
||||
pl.CloseMark = true
|
||||
return pl
|
||||
}
|
||||
|
||||
export function Points2Polyline(pts: any[]): Polyline {
|
||||
const lined: PolylineProps[] = []
|
||||
const count = pts.length
|
||||
for (let i = 0; i < count; i++) {
|
||||
const p0 = pts[i]
|
||||
lined.push({ pt: new Vector2(p0.x, p0.y), bul: p0.bul })
|
||||
}
|
||||
const pls = new Polyline(lined)
|
||||
pls.CloseMark = true
|
||||
return pls
|
||||
}
|
||||
|
||||
export function polar<T extends Vector2 | Vector3>(v: T, an: number, dis: number): T {
|
||||
v.x += Math.cos(an) * dis
|
||||
v.y += Math.sin(an) * dis
|
||||
return v
|
||||
}
|
369
samples/handleAbility/common/LayoutEngine/PolylineHelper.ts
Normal file
369
samples/handleAbility/common/LayoutEngine/PolylineHelper.ts
Normal file
@@ -0,0 +1,369 @@
|
||||
import type { PolylineProps } from 'cadapi'
|
||||
import { CADFiler, Circle, Polyline, Status, VKnifToolPath, isTargetCurInOrOnSourceCur } from 'cadapi'
|
||||
import type { Box3 } from 'three'
|
||||
import { Vector2, Vector3 } from 'three'
|
||||
import { arrayRemoveDuplicateBySort } from '../ArrayExt'
|
||||
import type { Curve2d } from '../base/CAD'
|
||||
import { Arc2d, Point2d, copyTextToClipboard } from '../base/CAD'
|
||||
import { CurveWrap } from './Curves2Parts'
|
||||
|
||||
// import type { Curve2d } from '../../common/base/CAD'
|
||||
|
||||
export class PolylineHelper {
|
||||
/** 创建闭合多段线 */
|
||||
static create(pts: any[], closeMark = false): Polyline {
|
||||
let lined: PolylineProps[] = []
|
||||
let count = pts.length
|
||||
for (let i = 0; i < count; i++) {
|
||||
let p0 = pts[i]
|
||||
|
||||
lined.push({ pt: new Vector2(p0.x, p0.y), bul: p0.bul || 0 })
|
||||
}
|
||||
let pls = new Polyline(lined)
|
||||
pls.CloseMark = closeMark
|
||||
return pls
|
||||
}
|
||||
|
||||
static createByCurve2d(curs: Curve2d[], closeMark = true): Polyline {
|
||||
let lined: PolylineProps[] = []
|
||||
for (let cur of curs) {
|
||||
let x = cur.StartPoint.m_X
|
||||
let y = cur.StartPoint.m_Y
|
||||
let bul = 0
|
||||
if (cur instanceof Arc2d)
|
||||
bul = cur.Bul || 0
|
||||
lined.push({ pt: new Vector2(x, y), bul })
|
||||
}
|
||||
let pls = new Polyline(lined)
|
||||
pls.CloseMark = true
|
||||
return pls
|
||||
}
|
||||
|
||||
static createByPts(pts: any[], buls: number[], closeMark = false): Polyline {
|
||||
let plps: PolylineProps[] = []
|
||||
let count = pts.length
|
||||
for (let i = 0; i < count; i++) {
|
||||
let p0 = pts[i]
|
||||
plps.push({ pt: new Vector2(p0.x, p0.y), bul: buls[i] })
|
||||
}
|
||||
let pls = new Polyline(plps)
|
||||
pls.CloseMark = closeMark
|
||||
return pls
|
||||
}
|
||||
|
||||
static getSimplePoints(pts: any[], offset: number): any[] {
|
||||
let pl = PolylineHelper.create(pts)
|
||||
pl.CloseMark = true
|
||||
let cureW = new CurveWrap(pl, offset, true)
|
||||
let pts2 = cureW.GetOutsidePoints()
|
||||
arrayRemoveDuplicateBySort(pts2, (p1, p2) => (p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y) < 1e-2)
|
||||
return pts2
|
||||
}
|
||||
|
||||
static createByWidthLength(w: number, l: number): Polyline {
|
||||
let plps: PolylineProps[] = []
|
||||
plps.push({ pt: new Vector2(0, 0), bul: 0 })
|
||||
plps.push({ pt: new Vector2(w, 0), bul: 0 })
|
||||
plps.push({ pt: new Vector2(w, l), bul: 0 })
|
||||
plps.push({ pt: new Vector2(0, l), bul: 0 })
|
||||
let pls = new Polyline(plps)
|
||||
pls.CloseMark = true
|
||||
return pls
|
||||
}
|
||||
|
||||
/** 多段线,添加位置移动 返回新多段线 */
|
||||
static moveTo(pl: Polyline, x: number, y: number): Polyline {
|
||||
let lindData = pl.LineData
|
||||
let pos = pl.Position
|
||||
|
||||
let newPts: PolylineProps[] = []
|
||||
for (let p of lindData) {
|
||||
let nx = p.pt.x + pos.x + x
|
||||
let ny = p.pt.y + pos.y + y
|
||||
if (ny < 7.9) {
|
||||
// console.log('修边小于 7.9????', ny)
|
||||
}
|
||||
let bul = p.bul
|
||||
newPts.push({ pt: new Vector2(nx, ny), bul })
|
||||
}
|
||||
let npl = new Polyline(newPts)
|
||||
npl.CloseMark = pl.CloseMark
|
||||
return npl
|
||||
}
|
||||
|
||||
/** 重设 多段线的几点 */
|
||||
static resetPosition(pl: Polyline): Polyline {
|
||||
let lindData = pl.LineData
|
||||
let pos = pl.Position
|
||||
|
||||
let newPts: PolylineProps[] = []
|
||||
for (let p of lindData) {
|
||||
let nx = p.pt.x + pos.x
|
||||
let ny = p.pt.y + pos.y
|
||||
let bul = p.bul
|
||||
newPts.push({ pt: new Vector2(nx, ny), bul })
|
||||
}
|
||||
let npl = new Polyline(newPts)
|
||||
npl.CloseMark = pl.CloseMark
|
||||
return npl
|
||||
}
|
||||
|
||||
/** 获得v型刀走刀路径 */o
|
||||
static getVModelPoints(pl: Polyline, depth: number, ang: number): any[] {
|
||||
// let ang = Math.PI * (0.5 * angle) / 180 ;
|
||||
let ps = []
|
||||
let bx = pl.Position.x
|
||||
let by = pl.Position.y
|
||||
if (ang > 0.01) {
|
||||
let rt = VKnifToolPath(pl, depth, ang / 2)
|
||||
ps = rt.map((t) => { return { x: t.pt.x + bx, y: t.pt.y + by, z: t.pt.z, bul: t.bul, r: 0 } })
|
||||
}
|
||||
else {
|
||||
ps = pl.LineData.map((t) => { return { x: t.pt.x + bx, y: t.pt.y + by, z: 0, bul: t.bul, r: 0 } })
|
||||
}
|
||||
for (let i = 0; i < ps.length; i++) {
|
||||
let p = ps[i]
|
||||
if (p.bul == 0)
|
||||
continue
|
||||
let p2 = (i == ps.length - 1 ? ps[0] : ps[i + 1])
|
||||
let r = this.getArcRadius(p.x, p.y, p2.x, p2.y, p.bul)
|
||||
p.r = r
|
||||
}
|
||||
return ps
|
||||
}
|
||||
|
||||
static ConverPolyLin2Circle(polyline: Polyline, fuzz = 0.1): Circle | undefined {
|
||||
let box = polyline.BoundingBox
|
||||
let size = box.getSize(new Vector3())
|
||||
if (!this.equaln(size.x, size.y, fuzz))// 盒子四方
|
||||
return undefined
|
||||
|
||||
let circleLength = 2 * Math.PI * size.x
|
||||
if (!this.equaln(circleLength, polyline.Length, fuzz * 2))
|
||||
return undefined
|
||||
|
||||
let circleArea = Math.PI * size.x * size.x
|
||||
if (!this.equaln(circleArea, polyline.Area, fuzz * 2))
|
||||
return undefined
|
||||
|
||||
let r = size.x// 必须备份(因为我们要复用这个vector变量)
|
||||
return new Circle(box.getCenter(size), r)
|
||||
}
|
||||
// 有问题
|
||||
static getVModelPoints_offset(pl: Polyline, offset: number, depth: number, angle: number) {
|
||||
let npl = offset == 0 ? pl : pl.GetOffsetCurves(offset)[0]
|
||||
// if(offset != 0)
|
||||
// {
|
||||
// ClipboardTest.write2PolyLine(pl,npl);
|
||||
// }
|
||||
return PolylineHelper.getVModelPoints(npl, depth, angle)
|
||||
}
|
||||
|
||||
static getArcRadius(x1: number, y1: number, x2: number, y2: number, bul: number): number {
|
||||
let bul2 = Math.abs(bul)
|
||||
let d = Math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2) / 2
|
||||
return 0.5 * d * (1 + bul2 ** 2) / bul2
|
||||
}
|
||||
|
||||
// 圆 转 多段线
|
||||
static cicleToPolyline(c: Circle): Polyline {
|
||||
let arcs = c.GetSplitCurves([0, 0.5])
|
||||
let pl = Polyline.FastCombine(arcs)
|
||||
return pl
|
||||
}
|
||||
|
||||
/** 判断多段线是否 重叠 */
|
||||
static isIntersect(pl1: Polyline, pl2: Polyline): boolean {
|
||||
let box1 = this.getBox(pl1)
|
||||
let box2 = this.getBox(pl2)
|
||||
|
||||
if (!box1.intersectsBox(box2))
|
||||
return false // 肯定不相交
|
||||
|
||||
let ipts = pl1.IntersectWith(pl2, 0)
|
||||
if (ipts.length === 0) {
|
||||
if (pl1.Area > pl2.Area)// 缓存面积
|
||||
{
|
||||
if (isTargetCurInOrOnSourceCur(pl1, pl2))
|
||||
return true // 包含
|
||||
}
|
||||
else {
|
||||
if (isTargetCurInOrOnSourceCur(pl2, pl1))
|
||||
return true // 包含
|
||||
}
|
||||
return false
|
||||
}
|
||||
else {
|
||||
return true // 有交点 一定有交集
|
||||
}
|
||||
}
|
||||
|
||||
// 多段线 圆弧合并
|
||||
static mergeCurve(pl2: Polyline): Polyline {
|
||||
const curves = pl2.Explode()
|
||||
arrayRemoveDuplicateBySort(curves, (c1, c2) => {
|
||||
return c1.Join(c2) === Status.True
|
||||
})
|
||||
|
||||
return Polyline.FastCombine(curves)
|
||||
}
|
||||
|
||||
/**
|
||||
* pl2 包含在pl1 内
|
||||
*
|
||||
*/
|
||||
// static isInside(pl1:Polyline,pl2:Polyline):boolean
|
||||
// {
|
||||
// let box1 = this.getBox(pl1);
|
||||
// let box2 = this.getBox(pl2);
|
||||
// if (!box1.intersectsBox(box2)) return false; //肯定不相交
|
||||
|
||||
// let ipts = pl1.IntersectWith(pl2, 0);
|
||||
// if (ipts.length > 0) return true; //有交点 一定有交集
|
||||
// }
|
||||
|
||||
/**
|
||||
* 两片板的干涉检查
|
||||
*
|
||||
* @param pl1
|
||||
* @param pls1_inner
|
||||
* @param pls1_model
|
||||
* @param pl2
|
||||
* @param pls2_inner
|
||||
* @param pls2_model
|
||||
* @returns
|
||||
*/
|
||||
static isOverLap(pl1: Polyline, pls1_inner: Polyline[], pls1_model: Polyline[], pl2: Polyline, pls2_inner: Polyline[], pls2_model: Polyline[]) {
|
||||
// 是否干涉, 被包含在造型洞,不算干涉
|
||||
let isOverlap = this.boxIsOverlap(pl1, pls1_inner, pl2, pls2_inner)
|
||||
|
||||
if (isOverlap)
|
||||
return true
|
||||
|
||||
// 造型 ,2v 刀路 是否干涉
|
||||
for (let pl1_model of pls1_model) {
|
||||
if (pl1_model.IntersectWith(pl2, 0).length > 0)
|
||||
return true
|
||||
for (let pl2_inner of pls2_inner) {
|
||||
if (pl1_model.IntersectWith(pl2_inner, 0).length > 0)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
for (let pl2_model of pls2_model) {
|
||||
if (pl2_model.IntersectWith(pl1, 0).length > 0)
|
||||
return true
|
||||
for (let pl1_inner of pls1_inner) {
|
||||
if (pl2_model.IntersectWith(pl1_inner, 0).length > 0)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private static boxIsOverlap(pl1: Polyline, pls1_inner: Polyline[], pl2: Polyline, pls2_inner: Polyline[]) {
|
||||
let box1 = this.getBox(pl1)
|
||||
let box2 = this.getBox(pl2)
|
||||
|
||||
if (!box1.intersectsBox(box2))
|
||||
return false // 肯定不相交
|
||||
|
||||
let ipts = pl1.IntersectWith(pl2, 0)
|
||||
if (ipts.length > 0)
|
||||
return true // 有交点 一定有交集
|
||||
|
||||
if (pl1.Area > pl2.Area)// 缓存面积
|
||||
{
|
||||
if (isTargetCurInOrOnSourceCur(pl1, pl2)) // pl1包含 pl2
|
||||
{
|
||||
for (let mpl of pls1_inner) // 如果pl1有造型洞包含pl2, 则表示不干涉,返回false
|
||||
|
||||
{
|
||||
if (isTargetCurInOrOnSourceCur(mpl, pl2) == true)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (isTargetCurInOrOnSourceCur(pl2, pl1)) // pl2包含 pl1
|
||||
{
|
||||
for (let mpl of pls2_inner) // 如果pl2有造型洞包含pl1, 则表示不干涉,返回false
|
||||
|
||||
{
|
||||
if (isTargetCurInOrOnSourceCur(mpl, pl1) == true)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/** 判断 点是否在多段线内 */
|
||||
static isPointInPolyline(pl1: Polyline, x: number, y: number): boolean {
|
||||
return pl1.PtInCurve(new Vector3(x, y, 0))
|
||||
}
|
||||
|
||||
static getBox(pl1: Polyline): Box3 {
|
||||
if (!pl1.box_tp)
|
||||
pl1.box_tp = pl1.BoundingBox
|
||||
|
||||
return pl1.box_tp as Box3
|
||||
}
|
||||
|
||||
static getArea(pl1: Polyline): number {
|
||||
if (!pl1.area_tp)
|
||||
pl1.area_tp = pl1.Area
|
||||
|
||||
return pl1.area_tp as number
|
||||
}
|
||||
|
||||
static getPath(pl: Polyline): Path2D {
|
||||
let path = new Path2D()
|
||||
let p0 = pl.LineData[0].pt
|
||||
path.moveTo(p0.x, p0.y)
|
||||
|
||||
for (let i = 0; i < pl.LineData.length; i++) {
|
||||
let p0 = pl.LineData[i].pt
|
||||
let p1 = (i == pl.LineData.length - 1) ? pl.LineData[0].pt : pl.LineData[i + 1].pt
|
||||
let bul = pl.LineData[i].bul
|
||||
if (bul == 0) {
|
||||
path.lineTo(p1.x, p1.y)
|
||||
}
|
||||
else {
|
||||
let arc = new Arc2d(new Point2d(p0.x, p0.y), new Point2d(p1.x, p1.y), bul)
|
||||
path.arc(arc.m_Center.m_X, arc.m_Center.m_Y, arc.m_Radius, arc.m_StartAngle, arc.m_EndAngle, bul < 0)
|
||||
}
|
||||
}
|
||||
path.closePath()
|
||||
return path
|
||||
}
|
||||
|
||||
static equaln(v1: number, v2: number, fuzz = 1e-5) {
|
||||
return Math.abs(v1 - v2) <= fuzz
|
||||
}
|
||||
|
||||
static toClipboard(en: Polyline | any) {
|
||||
let f = new CADFiler()
|
||||
f.Write(1)// 实体个数
|
||||
f.WriteObject(en)
|
||||
|
||||
copyTextToClipboard(f.ToString())
|
||||
}
|
||||
|
||||
static getStrPLs(ens: any[]) {
|
||||
if (ens.length == 0)
|
||||
return ''
|
||||
let f = new CADFiler()
|
||||
f.Write(ens.length)// 实体个数
|
||||
for (let en of ens)
|
||||
f.WriteObject(en)
|
||||
|
||||
return f.ToString()
|
||||
}
|
||||
}
|
85
samples/handleAbility/common/LayoutEngine/Simplify2.ts
Normal file
85
samples/handleAbility/common/LayoutEngine/Simplify2.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { Vector2 } from "../Vector2"
|
||||
|
||||
interface P {
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
|
||||
export interface IOffset {
|
||||
negativeOffset: number
|
||||
positiveOffset: number
|
||||
}
|
||||
|
||||
/** 点p到线段P1P2 的最短距离的平方,线段不延伸 */
|
||||
function GetSqSegDist(p: P, p1: P, p2: P): number {
|
||||
let x = p1.x
|
||||
let y = p1.y
|
||||
let dx = p2.x - x
|
||||
let dy = p2.y - y
|
||||
|
||||
if (dx !== 0 || dy !== 0)// 不是0长度线
|
||||
{
|
||||
const t = ((p.x - x) * dx + (p.y - y) * dy) / (dx * dx + dy * dy)
|
||||
if (t > 1) {
|
||||
x = p2.x
|
||||
y = p2.y
|
||||
}
|
||||
else if (t > 0) {
|
||||
x += dx * t
|
||||
y += dy * t
|
||||
}
|
||||
}
|
||||
dx = p.x - x
|
||||
dy = p.y - y
|
||||
return dx * dx + dy * dy
|
||||
}
|
||||
|
||||
function CrossVector2(a: P, b: P)
|
||||
{
|
||||
return a.x * b.y - a.y * b.x
|
||||
}
|
||||
|
||||
// Ramer-Douglas-Peucker algorithm
|
||||
function SimplifyDPStep(points: P[], first: number, last: number, sqTolerance: number, simplified: P[], offset: IOffset): void {
|
||||
let maxSqDist = 0
|
||||
let index: number
|
||||
const fp = points[first]
|
||||
const lp = points[last]
|
||||
|
||||
for (let i = first + 1; i < last; i++) {
|
||||
const p = points[i]
|
||||
const sqDist = GetSqSegDist(p, fp, lp)
|
||||
if (sqDist > maxSqDist) {
|
||||
index = i
|
||||
maxSqDist = sqDist
|
||||
}
|
||||
}
|
||||
|
||||
if (maxSqDist > sqTolerance) {
|
||||
if (index - first > 1)
|
||||
SimplifyDPStep(points, first, index, sqTolerance, simplified, offset)
|
||||
simplified.push(points[index])
|
||||
if (last - index > 1)
|
||||
SimplifyDPStep(points, index, last, sqTolerance, simplified, offset)
|
||||
}
|
||||
else {
|
||||
// 记录偏移
|
||||
const v = new Vector2(lp.x - fp.x, lp.y - fp.y).normalize()
|
||||
for (let i = first + 1; i < last; i++) {
|
||||
const p = points[i]
|
||||
const offsetDist = -CrossVector2(v, { x: p.x - fp.x, y: p.y - fp.y })
|
||||
offset.positiveOffset = Math.max(offset.positiveOffset, offsetDist)
|
||||
offset.negativeOffset = Math.min(offset.negativeOffset, offsetDist)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ramer-Douglas-Peucker 算法
|
||||
export function SimplifyDouglasPeucker(points: P[], sqTolerance: number): [P[], IOffset] {
|
||||
const last = points.length - 1
|
||||
const simplified: P[] = [points[0]]
|
||||
const offset: IOffset = { negativeOffset: 0, positiveOffset: 0 }
|
||||
SimplifyDPStep(points, 0, last, sqTolerance, simplified, offset)
|
||||
simplified.push(points[last])
|
||||
return [simplified, offset]
|
||||
}
|
5
samples/handleAbility/common/Point.ts
Normal file
5
samples/handleAbility/common/Point.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export interface Point
|
||||
{
|
||||
x: number
|
||||
y: number
|
||||
}
|
38
samples/handleAbility/common/Util.ts
Normal file
38
samples/handleAbility/common/Util.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
/** 判断两个值是否相等 */
|
||||
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 = (Array.isArray(arr)) ? arr.length : arr
|
||||
if (index < 0)
|
||||
return count + index
|
||||
else if (index >= count)
|
||||
return index - count
|
||||
else
|
||||
return index
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数组最大元素的索引号
|
||||
* @param compart t2大于t1返回t2
|
||||
* @returns 索引
|
||||
*/
|
||||
export function Max<T>(arr: T[], compart: (t1: T, t2: T) => boolean): number
|
||||
{
|
||||
let best: T = arr[0]
|
||||
let bestIndex = 0
|
||||
for (let i = 1; i < arr.length; i++)
|
||||
{
|
||||
let t1 = arr[i]
|
||||
if (compart(best, t1))
|
||||
{
|
||||
best = t1
|
||||
bestIndex = i
|
||||
}
|
||||
}
|
||||
return bestIndex
|
||||
}
|
335
samples/handleAbility/common/Vector2.ts
Normal file
335
samples/handleAbility/common/Vector2.ts
Normal file
@@ -0,0 +1,335 @@
|
||||
import type { Point } from './Point.js'
|
||||
|
||||
/** 二维向量 */
|
||||
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): Vector2
|
||||
{
|
||||
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 (Number.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: Point): Vector2
|
||||
{
|
||||
this.x = Math.min(this.x, v.x)
|
||||
this.y = Math.min(this.y, v.y)
|
||||
return this
|
||||
}
|
||||
|
||||
max(v: Point): 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; const 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); const 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
|
||||
}
|
||||
}
|
1625
samples/handleAbility/common/base/CAD.ts
Normal file
1625
samples/handleAbility/common/base/CAD.ts
Normal file
File diff suppressed because it is too large
Load Diff
126
samples/handleAbility/common/base/StringFormat.ts
Normal file
126
samples/handleAbility/common/base/StringFormat.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
export class StringFormat
|
||||
{
|
||||
/**
|
||||
* 对Date的扩展,将 Date 转化为指定格式的String
|
||||
* 月(M)、日(d)、12小时(h)、24小时(H)、分(m)、秒(s)、周(E)、季度(q) 可以用 1-2 个占位符
|
||||
* 年(y)可以用 1-4 个占位符,毫秒(S)只能用 1 个占位符(是 1-3 位的数字)
|
||||
* eg:
|
||||
* (new Date()).pattern("yyyy-MM-dd hh:mm:ss.S") ==> 2006-07-02 08:09:04.423
|
||||
* (new Date()).pattern("yyyy-MM-dd E HH:mm:ss") ==> 2009-03-10 二 20:09:04
|
||||
* (new Date()).pattern("yyyy-MM-dd EE hh:mm:ss") ==> 2009-03-10 周二 08:09:04
|
||||
* (new Date()).pattern("yyyy-MM-dd EEE hh:mm:ss") ==> 2009-03-10 星期二 08:09:04
|
||||
* (new Date()).pattern("yyyy-M-d h:m:s.S") ==> 2006-7-2 8:9:4.18
|
||||
*/
|
||||
static Date(date: Date, fmt): string
|
||||
{
|
||||
let o = {
|
||||
'M+': date.getMonth() + 1, // 月份
|
||||
'd+': date.getDate(), // 日
|
||||
'h+': date.getHours() % 12 == 0 ? 12 : date.getHours() % 12, // 小时
|
||||
'H+': date.getHours(), // 小时
|
||||
'm+': date.getMinutes(), // 分
|
||||
's+': date.getSeconds(), // 秒
|
||||
'q+': Math.floor((date.getMonth() + 3) / 3), // 季度
|
||||
'S': date.getMilliseconds(), // 毫秒
|
||||
}
|
||||
let week = {
|
||||
0: '/u65e5',
|
||||
1: '/u4e00',
|
||||
2: '/u4e8c',
|
||||
3: '/u4e09',
|
||||
4: '/u56db',
|
||||
5: '/u4e94',
|
||||
6: '/u516d',
|
||||
}
|
||||
if (/(y+)/.test(fmt))
|
||||
{
|
||||
fmt = fmt.replace(RegExp.$1, (`${date.getFullYear()}`).substr(4 - RegExp.$1.length))
|
||||
}
|
||||
if (/(E+)/.test(fmt))
|
||||
{
|
||||
fmt = fmt.replace(RegExp.$1, ((RegExp.$1.length > 1) ? (RegExp.$1.length > 2 ? '/u661f/u671f' : '/u5468') : '') + week[`${date.getDay()}`])
|
||||
}
|
||||
for (let k in o)
|
||||
{
|
||||
if (new RegExp(`(${k})`).test(fmt))
|
||||
{
|
||||
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : ((`00${o[k]}`).substr((`${o[k]}`).length)))
|
||||
}
|
||||
}
|
||||
return fmt
|
||||
}
|
||||
|
||||
/** 返回数字的固定小数点 123.00 */
|
||||
static number(value: number, bit: number): string
|
||||
{
|
||||
return value?.toFixed(bit).toString()
|
||||
}
|
||||
|
||||
static toFixed(value: number, bit: number = 3): number
|
||||
{
|
||||
let tt = 10 ** bit
|
||||
return Math.round(value * tt) / tt
|
||||
}
|
||||
|
||||
static filterIllegalChar(str: string): string
|
||||
{
|
||||
// var pattern = new RegExp("[/:*?'<>|\\]");
|
||||
// var rs = "";
|
||||
// for (var i = 0; i < str.length; i++)
|
||||
// {
|
||||
// rs = rs + str.substr(i, 1).replace(pattern, '');
|
||||
// }
|
||||
// return rs;
|
||||
|
||||
let rt = str.replace(/\\/g, '').replace(/\:/g, '').replace(/\*/g, '').replace(/\?/g, '').replace(/\"/g, '').replace(/\</g, '').replace(/\>/g, '').replace(/\|/g, '').replace(/\'/g, '')
|
||||
return rt
|
||||
}
|
||||
|
||||
/** 文本格式化 */
|
||||
static format(str: string, ...args: any[]): string
|
||||
{
|
||||
let data = args
|
||||
let tmpl = str
|
||||
for (const item of tmpl.matchAll(/\{(.+?)\}/g))
|
||||
{
|
||||
let parts = item[1].split(',').map(i => i.trim())
|
||||
let index = Number(parts[0])
|
||||
let arg = data[index]
|
||||
|
||||
let val = (arg || '').toString() // 默认
|
||||
|
||||
if (arg instanceof Date) // 日期
|
||||
{
|
||||
let fm = 'MM-dd HH;mm'
|
||||
if (parts.length > 1)
|
||||
{
|
||||
fm = parts[1]
|
||||
}
|
||||
val = this.Date(arg, fm)
|
||||
}
|
||||
|
||||
if (parts.length > 1 && parts[1][0] === '#')
|
||||
{
|
||||
// {2,#3} -> 数字 转成3位 001,...023,
|
||||
val = val.padStart(Number(parts[1].substring(1)), '0')
|
||||
}
|
||||
tmpl = tmpl.replace(item[0], val)
|
||||
}
|
||||
|
||||
return tmpl
|
||||
}
|
||||
|
||||
/** 实现右对齐,右边填充 (25,4,'*') => '25**' */
|
||||
static PadEnd(num: number, totalWidth: number, paddingChar: string): string
|
||||
{
|
||||
let str = num.toString()
|
||||
return str.padEnd(totalWidth, paddingChar[0])
|
||||
}
|
||||
|
||||
/** 实现右对齐,左边填充 (25,4,'0') => '0025' */
|
||||
static PadStart(num: number, totalWidth: number, paddingChar: string): string
|
||||
{
|
||||
let str = num.toString()
|
||||
return str.padStart(totalWidth, paddingChar[0])
|
||||
}
|
||||
}
|
381
samples/handleAbility/common/core/Path.ts
Normal file
381
samples/handleAbility/common/core/Path.ts
Normal file
@@ -0,0 +1,381 @@
|
||||
import { Box2 } from '../Box2.js'
|
||||
import { clipperCpp } from '../ClipperCpp.js'
|
||||
import type { NestFiler } from '../Filer.js'
|
||||
import type { Point } from '../Point.js'
|
||||
import { equaln } from '../Util.js'
|
||||
import { Vector2 } from '../Vector2.js'
|
||||
|
||||
/**
|
||||
* 轮廓路径类
|
||||
* 可以求NFP,和保存NFPCahce
|
||||
* 因为NFP结果是按照最低点移动的,所以将点旋转后,按照盒子将点移动到0点.
|
||||
*/
|
||||
export class Path
|
||||
{
|
||||
Id: number
|
||||
Points: Point[]
|
||||
OutsideNFPCache: { [key: number]: Point[][] } = {}
|
||||
InsideNFPCache: { [key: number]: Point[][] } = {}
|
||||
|
||||
constructor(public OrigionPoints?: Point[], rotation: number = 0)
|
||||
{
|
||||
if (OrigionPoints)
|
||||
this.Init(OrigionPoints, rotation)
|
||||
}
|
||||
|
||||
Origion: Path
|
||||
// 点表在旋转后的原始最小点.使用这个点将轮廓移动到0点
|
||||
OrigionMinPoint: Vector2
|
||||
Rotation: number
|
||||
|
||||
Size: Vector2// 序列化
|
||||
private 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 = clipperCpp.lib.simplifyPolygons(nfps)
|
||||
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]
|
||||
|
||||
let l = ax - bx
|
||||
let h = ay - by
|
||||
|
||||
const MinNumber = 200// 清理的数值是100,所以200是可以接受的, 200=0.020问题不大(过盈配合)
|
||||
if (l < -MinNumber || h < -MinNumber)
|
||||
return
|
||||
|
||||
if (l < MinNumber)
|
||||
l = MinNumber
|
||||
else
|
||||
l += MinNumber
|
||||
|
||||
if (h < MinNumber)
|
||||
h = MinNumber
|
||||
else
|
||||
h += MinNumber
|
||||
|
||||
nfps = [[
|
||||
{ x: 0, y: 0 },
|
||||
{ x: l, y: 0 },
|
||||
{ x: l, y: h },
|
||||
{ x: 0, y: h },
|
||||
]]
|
||||
}
|
||||
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 TranslatePath_Self(pts: Point[], mx: number, my: number): Point[]
|
||||
{
|
||||
for (let pt of pts)
|
||||
{
|
||||
pt.x += mx
|
||||
pt.y += my
|
||||
}
|
||||
return pts
|
||||
}
|
||||
|
||||
// 缩放点表,返回原始点表
|
||||
export function PathScale(pts: Point[], scale: number): Point[]
|
||||
{
|
||||
for (let p of pts)
|
||||
{
|
||||
p.x *= scale
|
||||
p.y *= scale
|
||||
}
|
||||
return pts
|
||||
}
|
0
samples/handleAbility/common/工具类.md
Normal file
0
samples/handleAbility/common/工具类.md
Normal file
Reference in New Issue
Block a user