From 7f61ffd5251cb6fb2c91eb8b0e34b4c608e2ce5a Mon Sep 17 00:00:00 2001 From: ChenX Date: Wed, 6 Dec 2023 12:38:12 +0000 Subject: [PATCH] =?UTF-8?q?!2494=20=E5=8A=9F=E8=83=BD:=E4=BD=99=E6=96=99?= =?UTF-8?q?=E5=BD=92=E6=96=B9=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Geometry/__snapshots__/lir.test.ts.snap | 171 ++++++ __test__/Geometry/lir.test.ts | 24 + .../testEntity/LargestInteriorRectangle.ts | 499 ++++++++++++++++++ src/Add-on/testEntity/TestLIR.ts | 38 ++ src/Add-on/testEntity/test.ts | 8 - src/DatabaseServices/PointInPolyline.ts | 2 +- src/Editor/CommandRegister.ts | 4 + src/api.ts | 2 + 8 files changed, 739 insertions(+), 9 deletions(-) create mode 100644 __test__/Geometry/__snapshots__/lir.test.ts.snap create mode 100644 __test__/Geometry/lir.test.ts create mode 100644 src/Add-on/testEntity/LargestInteriorRectangle.ts create mode 100644 src/Add-on/testEntity/TestLIR.ts diff --git a/__test__/Geometry/__snapshots__/lir.test.ts.snap b/__test__/Geometry/__snapshots__/lir.test.ts.snap new file mode 100644 index 000000000..3a91c3ba9 --- /dev/null +++ b/__test__/Geometry/__snapshots__/lir.test.ts.snap @@ -0,0 +1,171 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`lir 1`] = ` +Array [ + Box2 { + "max": Vector2 { + "x": 22139.12887289156, + "y": -2841.682010503894, + }, + "min": Vector2 { + "x": 21469.741102231354, + "y": -3263.948131063738, + }, + }, + Box2 { + "max": Vector2 { + "x": 21899.684788619557, + "y": -2641.585451102241, + }, + "min": Vector2 { + "x": 21688.071880475363, + "y": -2841.682010503894, + }, + }, +] +`; + +exports[`lir 2`] = ` +Array [ + Box2 { + "max": Vector2 { + "x": 23460.421387167884, + "y": -2637.4589312808325, + }, + "min": Vector2 { + "x": 23048.13690406113, + "y": -3242.62504391121, + }, + }, +] +`; + +exports[`lir 3`] = ` +Array [ + Box2 { + "max": Vector2 { + "x": 24485.042744543087, + "y": -2542.4597375987987, + }, + "min": Vector2 { + "x": 23720.604568827835, + "y": -3516.687337476508, + }, + }, + Box2 { + "max": Vector2 { + "x": 24131.56193396799, + "y": -2013.675435600278, + }, + "min": Vector2 { + "x": 23720.604568827835, + "y": -2542.4597375987987, + }, + }, +] +`; + +exports[`lir 4`] = ` +Array [ + Box2 { + "max": Vector2 { + "x": 25469.309065156027, + "y": -2798.706322535294, + }, + "min": Vector2 { + "x": 24639.22077503745, + "y": -3486.2359715248085, + }, + }, + Box2 { + "max": Vector2 { + "x": 24932, + "y": -2175, + }, + "min": Vector2 { + "x": 24639.22077503745, + "y": -2798.706322535294, + }, + }, + Box2 { + "max": Vector2 { + "x": 25072.310317708012, + "y": -2434.1892907875726, + }, + "min": Vector2 { + "x": 24932, + "y": -2798.706322535294, + }, + }, +] +`; + +exports[`lir 5`] = ` +Array [ + Box2 { + "max": Vector2 { + "x": 26840, + "y": -2267.7761534803576, + }, + "min": Vector2 { + "x": 26140, + "y": -3306, + }, + }, + Box2 { + "max": Vector2 { + "x": 26540, + "y": -3306, + }, + "min": Vector2 { + "x": 26240, + "y": -3486, + }, + }, + Box2 { + "max": Vector2 { + "x": 26140, + "y": -2246, + }, + "min": Vector2 { + "x": 26040, + "y": -2806, + }, + }, + Box2 { + "max": Vector2 { + "x": 26940, + "y": -2866, + }, + "min": Vector2 { + "x": 26840, + "y": -3266, + }, + }, +] +`; + +exports[`lir 6`] = ` +Array [ + Box2 { + "max": Vector2 { + "x": 20867.54619958679, + "y": -2462.241147060554, + }, + "min": Vector2 { + "x": 20334.371107671588, + "y": -3470.37106223355, + }, + }, + Box2 { + "max": Vector2 { + "x": 20971.36213127695, + "y": -2627.7092790342367, + }, + "min": Vector2 { + "x": 20867.54619958679, + "y": -3291.6654797019723, + }, + }, +] +`; diff --git a/__test__/Geometry/lir.test.ts b/__test__/Geometry/lir.test.ts new file mode 100644 index 000000000..75728b7b7 --- /dev/null +++ b/__test__/Geometry/lir.test.ts @@ -0,0 +1,24 @@ +import { LargestInteriorRectangle } from "../../src/Add-on/testEntity/LargestInteriorRectangle"; +import { Polyline } from "../../src/DatabaseServices/Entity/Polyline"; +import { AsVector2, equalv3 } from "../../src/Geometry/GeUtils"; +import { LoadEntityFromFileData } from "../Utils/LoadEntity.util"; + +test('lir', () => +{ + let d = + { "file": [6, "Polyline", 10, 2, 30486, 0, 1, 7, 0, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 21688.071880475363, -2641.585451102241, 0, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 10111.128053715596, -1330.0440361938563, 0, 1], 0, 0, 1, 2, 15, [0, -91.20902138697647], 0, [-99.27451926151298, -91.20902138697647], 0, [-99.27451926151298, -200.09655940165294], 0, [-218.3307782440097, -200.09655940165294], 0, [-218.3307782440097, -622.3626799614967], 0, [0, -622.3626799614967], 0, [0, -745.6835810795424], 0, [211.61290814419408, -745.6835810795424], 0, [211.61290814419408, -622.3626799614967], 0, [451.0569924161973, -622.3626799614967], 0, [451.0569924161973, -200.09655940165294], 0, [211.61290814419408, -200.09655940165294], 0, [211.61290814419408, 0], 0, [0, 0], 0, [0, -91.20902138697647], 0, false, "Polyline", 10, 2, 30487, 0, 1, 7, 0, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 11643.478317846675, -1064.2917826726493, 0, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 11643.478317846675, -1064.2917826726493, 0, 1], 0, 0, 1, 2, 8, [11404.658586214457, -2178.3332612385607], 0, [11404.658586214457, -1330.8596015191276], 0, [11544.497767619087, -1330.8596015191276], 0, [11544.497767619087, -1458.6436810785299], 0, [11673.487357363012, -1458.6436810785299], 0, [11673.487357363012, -1573.1671486081832], 0, [11816.943069321209, -1573.1671486081832], 0, [11816.943069321209, -2178.3332612385607], 0, true, "Polyline", 10, 2, 30488, 0, 1, 7, 0, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 23720.604568827835, -2013.675435600278, 0, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 7839.635766842221, -1733.9653511997813, 0, 1], 0, 0, 1, 2, 7, [764.4381757152514, -528.7843019985207], 0, [410.95736514015334, -528.7843019985207], 0, [410.95736514015334, 0], 0, [0, 0], 0, [0, -1503.01190187623], 0, [764.4381757152514, -1503.01190187623], 0, [764.4381757152514, -528.7843019985206], 0, false, "Polyline", 10, 2, 30489, 0, 1, 7, 0, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 25072.310317708012, -2434.1892907875726, 0, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 11510.346173220109, -921.6709897391447, 0, 1], 0, 0, 1, 2, 9, [396.9987474480131, -364.5170317477214], 0, [0, -364.5170317477214], 0, [0, 0], 0, [-191.2812146794986, 369.9306510311035], 0, [-220.15385085754497, 398.803287209141], 0, [-433.08954267056106, 398.803287209141], 0, [-433.08954267056106, -1052.0466807372359], 0, [396.9987474480131, -1052.0466807372359], 0, [396.9987474480131, -364.5170317477214], 0, false, "Polyline", 10, 2, 30490, 0, 1, 7, 0, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 14328.52642470492, -1135.02410656956, 0, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 14328.52642470492, -1135.02410656956, 0, 1], 0, 0, 1, 2, 9, [11652.077020504916, -1169.4279001173695], 0, [11960.765451660282, -860.7394689621026], 0, [12049.398763576115, -1132.7520469107974], 0, [12510.903249758816, -1074.6819460003912], 0, [12572.02967176977, -1554.5243587863783], 0, [12706.507800193867, -2095.493193583319], 0, [12229.721708508428, -2315.5483128227534], 0, [12067.736690179401, -2630.349386179165], 0, [11755.991937923536, -2074.0989458794857], 0, true, "Polyline", 10, 2, 30485, 0, 1, 1, 0, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 4219.473337791907, -173.97252646749985, 0, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 4219.473337791907, -173.97252646749985, 0, 1], 0, 0, 1, 2, 16, [16114.89776987968, -3305.5912097645887], 0, [16114.89776987968, -2288.268620593054], 0, [16648.072861794884, -2288.268620593054], 0, [16648.072861794884, -2453.736752566737], 0, [16707.76395829206, -2453.736752566737], 0, [16721.001408849956, -2416.1080736660547], 0, [16748.57943084557, -2416.1080736660547], 0, [16748.57943084557, -2453.6141835800895], 0, [16782.776178120075, -2419.417436305495], 0, [16782.776178120075, -2480.089084695879], 0, [16861.097760587672, -2480.089084695879], 0, [16837.932222111358, -2536.3482495669314], 0, [16884.26329906399, -2604.7417441160537], 0, [16751.888793485043, -2631.2166452318434], 0, [16751.888793485043, -3117.6929532344725], 0, [16677.979694536796, -3296.3985357660504], 0, true], "basePt": { "x": 20334.371107671588, "y": -3765.373492748725, "z": 0 }, "ucs": [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] }; + let pls = LoadEntityFromFileData(d.file) as Polyline[]; + + for (let pl of pls) + { + let pts = pl.GetStretchPoints(); + if (equalv3(pts[0], pts[pts.length - 1])) + pts.pop(); + + let lir = new LargestInteriorRectangle; + + let rects = lir.ParseLIR(pts.map(AsVector2)); + + expect(rects).toMatchSnapshot(); + } +}); diff --git a/src/Add-on/testEntity/LargestInteriorRectangle.ts b/src/Add-on/testEntity/LargestInteriorRectangle.ts new file mode 100644 index 000000000..6066e3aa2 --- /dev/null +++ b/src/Add-on/testEntity/LargestInteriorRectangle.ts @@ -0,0 +1,499 @@ +import Flatbush from 'flatbush'; +import { Box2, Vector2, Vector3 } from "three"; +import { arrayRemoveIf } from '../../Common/ArrayExt'; +import { FixIndex } from "../../Common/Utils"; +import { InsertSortedIndex, RangeUnion } from "../../DatabaseServices/Room/ParseService/RangeUtils"; +import { BoxCheckIntersect } from '../../Geometry/CheckIntersect'; +import { equaln } from "../../Geometry/GeUtils"; +import { arrayPushArray, arraySortByNumber } from "../../Nest/Common/ArrayExt"; +import { Max } from '../../Nest/Common/Util'; +import { FuzzyFactory } from '../../csg/core/FuzzyFactory'; + + +/** + * 获取面积最大的矩形 + * @param rects + * @returns + */ +function GetMaxAreaFn(rects: [number, number, number][]): number +{ + return Max(rects, (t1, t2) => t2[0] > t1[0]); +} + +function GetMaxWidthFn(rects: [number, number, number][]): number +{ + return Max(rects, (t1, t2) => t2[1] > t1[1]); +} + +function GetMaxHeightFn(rects: [number, number, number][]): number +{ + return Max(rects, (t1, t2) => t2[2] > t1[2]); +} + + +//最大内接矩形 +//1.分析盒子 +// 1.默认我们使用顶点坐标进行一维展开 +// 2.当存在斜线时, 我们精确展开(当跨度大于(100/50)时,我们精确产开另一条轴) +// (1).获取需要精确展开的区间 随后合并区间 +// (2).对区间进行展开数值,使用二分搜索,如果在已有的顶点附近,则不进行增项 + +//2.标记轮廓内的矩形 +// (1).我们收集多边形所有的斜线,与矩形盒子求交,如果相交,则矩形盒子无效化 +// (2).矩形终点在轮廓内(使用单条射线检测,因为我们始终在端点增加锚点的关系 我们似乎不会在顶点位置得到(交点? 稳妥起见 实现标准的射线检测算法) + +//3.获取最大内接矩形 +// 迭代开始 +// 动态规划 存储每个方块得到的最大矩形 +// 获取最大的(根据规则) +// 对池子里的结果进行生存选择(如果被占用了方块,则该结果已经不可用) +// 取剩下可用的最大的(因为我们已经是在合理的情况下拿最大的了,所以不需要在重新迭代) +// 重复迭代 + + +/** + * @example + * + * //1. 设置过滤条件(舍弃掉一些无用的矩形) + * let lir = new LargestInteriorRectangle; + * lir.MinWidth = 200;//设置最小可以接受的矩形宽度 + * lir.MinHeight = 200;//设置最小可以接受的矩形宽度 + * lir.MinArea = 200 * 200;//设置最小可以接受的矩形面积 + * + * //除了变量 也可以设置过滤函数 + * //例如: + * lir.FilterRectFn = !(w,h)=> (w>500 || h>500) //这样将对长或者宽不足500的矩形板过滤(舍弃) + * + * //2. 可以设置优先模式,默认提供了面积优先,你也可以使用宽度优先或者高度优先,或许默认规则可能不好用,可以自定义传入自定义函数. + * lir.GetMaxRectIndexFn = LargestInteriorRectangle.GetMaxAreaFn; //或者 LargestInteriorRectangle.GetMaxWidthFn / LargestInteriorRectangle.GetMaxHeightFn + * + * //3. 你已经设置好解析器,你现在可以进行解析了 + * let rects = lir.ParseLir([ new Vector3(0,0,0) , new Vector3(100,0,0) , new Vector3(50,100,0)]) //首尾不需要重复,首尾重复可能造成错误,程序不再校验 + * + */ +export class LargestInteriorRectangle +{ + MinWidth = 100; + MinHeight = 100; + MinArea = 200 * 200; + + /** 可以自定义过滤函数,以便过滤掉某些不需要的矩形,当返回true时,我们会过滤它 */ + FilterRectFn: (width: number, height: number) => boolean; + + GetMaxRectIndexFn: (rects: [number, number, number][]) => number = GetMaxAreaFn; + + + static GetMaxAreaFn = GetMaxAreaFn; + static GetMaxWidthFn = GetMaxWidthFn; + static GetMaxHeightFn = GetMaxHeightFn; + + constructor() + { + } + + /** + * 分析最大内接矩形 + * @param polygonPts 多边形点表,首尾不要相等 + */ + ParseLIR(polygonPts: Vector2[]): Box2[] + { + //端点列表 + let xs: number[] = []; + let ys: number[] = []; + + let xset = new Set(); + let yset = new Set(); + + //需要展开的范围 + let xranges: [number, number][] = []; + let yranges: [number, number][] = []; + + let polygonBox = new Box2().setFromPoints(polygonPts); + + let vec = new Vector2; + + let klines: [Vector2, Vector2][] = []; //所有的斜线 + // let linesP: [Vector2, Vector2][] = []; //所有的线点 + // let linesFb = new Flatbush(pts.length);//所有的线的索引 + + let fuzzX = new FuzzyFactory(1, 0.1); + let fuzzY = new FuzzyFactory(1, 0.1); + + for (let i = 0; i < polygonPts.length; i++) + { + let p = polygonPts[i]; + let p2 = polygonPts[FixIndex(i + 1, polygonPts)]; + + // linesP.push([p, p2]); + // let box = new Box2().setFromPoints([p, p2]); + // linesFb.add(box.min.x, box.min.y, box.max.x, box.max.y); + + vec.subVectors(p2, p); + + //收集端点 + let x = fuzzX.lookupOrCreate([p.x], p.x); + if (!xset.has(x)) + { + xs.push(x); + xset.add(x); + } + + let y = fuzzY.lookupOrCreate([p.y], p.y); + if (!yset.has(y)) + { + ys.push(y); + yset.add(y); + } + + //展开斜线 每20分段 + if (Math.abs(vec.x) > 20 && Math.abs(vec.y) > 20) + { + if (Math.abs(vec.x) > 20) + yranges.push(vec.y > 0 ? [p.y, p2.y] : [p2.y, p.y]); + + if (Math.abs(vec.y) > 20) + xranges.push(vec.x > 0 ? [p.x, p2.x] : [p2.x, p.x]); + } + + //收集所有的斜线 + if (!equaln(vec.x, 0, 0.1) && !equaln(vec.y, 0, 0.1)) + klines.push([p, p2]); + } + + // linesFb.finish(); + + //合并展开区间 + xranges.sort((a, b) => a[0] - b[0]); + yranges.sort((a, b) => a[0] - b[0]); + xranges = RangeUnion(xranges); + yranges = RangeUnion(yranges); + + arraySortByNumber(xs); + arraySortByNumber(ys); + + //展开 + RangesAdd(xranges, xs); + RangesAdd(yranges, ys); + + //最大化 + xs[0] = polygonBox.min.x; + xs[xs.length - 1] = polygonBox.max.x; + + ys[0] = polygonBox.min.y; + ys[ys.length - 1] = polygonBox.max.y; + + //所有网格的索引 + let gridFb = new Flatbush((xs.length - 1) * (ys.length - 1)); + + // let pls: Polyline[] = []; + + for (let i = 0; i < xs.length - 1; i++) + { + for (let j = 0; j < ys.length - 1; j++) + { + gridFb.add(xs[i], ys[j], xs[i + 1], ys[j + 1]); + + // let pl = new Polyline().RectangleFrom2Pt(new Vector3(xs[i], ys[j]), new Vector3(xs[i + 1], ys[j + 1])); + // if (false) TestDraw(pl); + // pls.push(pl); + } + } + + gridFb.finish(); + + const matrix: number[][] = new Array(xs.length - 1).fill(1).map(() => new Array(ys.length - 1).fill(1)); + + //矩形与斜线相交 + let checks: BoxCheckIntersect[] = []; + for (let line of klines) + { + let box = new Box2().setFromPoints(line); + let ids = gridFb.search(box.min.x, box.min.y, box.max.x, box.max.y); + + for (let id of ids) + { + let check = checks[id]; + + let i = Math.floor((id) / (ys.length - 1)); + let j = id - (i * (ys.length - 1)); + + if (!check) + { + check = new BoxCheckIntersect(new Box2(new Vector2(xs[i] + 0.01, ys[j] + 0.01), new Vector2(xs[i + 1] - 0.01, ys[j + 1] - 0.01))); + checks[id] = check; + + // let pl = new Polyline().RectangleFrom2Pt(new Vector3(xs[i], ys[j]), new Vector3(xs[i + 1], ys[j + 1])); + // TestDraw(pl, 2); + } + + if (check.IsIntersectLine(line[0], line[1])) + { + // pls[id].ColorIndex = 1; + // pls[id].Erase(); + matrix[i][j] = 0; + } + } + } + + //y轴扫描线(矩形在多边形外) + // if (xs.length < ys.length) + { + for (let i = 0; i < xs.length - 1; i++) + { + let x = (xs[i + 1] + xs[i]) * 0.5; + + let iPtYs = IsPointInPolygon(polygonPts, new Vector3(x, polygonBox.min.y - 0.1, 0)); + arraySortByNumber(iPtYs); + + for (let j = 0; j < ys.length - 1; j++) + { + let y = (ys[j + 1] + ys[j]) * 0.5; + while (iPtYs.length && iPtYs[0] < y) + iPtYs.shift(); + + if (iPtYs.length % 2 !== 1) + { + // pls[i * (ys.length - 1) + j].ColorIndex = 3; + // pls[i * (ys.length - 1) + j].Erase(); + matrix[i][j] = 0; + } + } + } + } + // else//x轴扫描线 + + //ref https://leetcode.cn/problems/maximal-rectangle/solutions/535672/zui-da-ju-xing-by-leetcode-solution-bjlu/ + const maximalRectangle = () => + { + const m = matrix.length;//m个竖条 xlist + if (m === 0) return 0; + const n = matrix[0].length;//n个横条 ylist + if (n === 0) return; + + const left: number[][] = new Array(m).fill(0).map(() => new Array(n).fill(0)); + + for (let i = 0; i < m; i++) + { + for (let j = 0; j < n; j++) + { + if (matrix[i][j]) + { + left[i][j] = (j === 0 ? 0 : left[i][j - 1]) + 1; + } + } + } + + //area maxXIndex maxYIndex xcount ycount + let rects: [number, number, number, number, number][] = []; + + for (let j = 0; j < n; j++) // 对于每一列,使用基于柱状图的方法 + { + const up = new Array(m).fill(0); + const down = new Array(m).fill(0); + + let stack: number[] = []; + for (let i = 0; i < m; i++) + { + while (stack.length && left[stack[stack.length - 1]][j] >= left[i][j]) + { + stack.pop(); + } + up[i] = stack.length === 0 ? -1 : stack[stack.length - 1]; + stack.push(i); + } + stack = []; + for (let i = m - 1; i >= 0; i--) + { + while (stack.length && left[stack[stack.length - 1]][j] >= left[i][j]) + { + stack.pop(); + } + down[i] = stack.length === 0 ? m : stack[stack.length - 1]; + stack.push(i); + } + + for (let i = 0; i < m; i++) + { + const xCount = down[i] - up[i] - 1; + const yCount = left[i][j]; + + if (!yCount || !xCount) continue; + + let width = xs[down[i]] - xs[down[i] - xCount]; + let height = ys[j + 1] - ys[j + 1 - yCount]; + + if (width < this.MinWidth || height < this.MinHeight) continue; + + //自定义过滤函数 + if (this.FilterRectFn && this.FilterRectFn(width, height)) continue; + + let area = width * height; + + //面积小于最小允许面积 + if (area < this.MinArea) continue; + + rects.push([area, down[i], j, xCount, yCount]); + } + } + + while (rects.length) + { + let maxIndex = Max(rects, (t1, t2) => t2[0] > t1[0]); + + let [area, maxXIndex, maxYIndex, xCount, yCount] = rects[maxIndex]; + + let xMax = xs[maxXIndex]; + let yMax = ys[maxYIndex + 1]; + + let xMin = xs[maxXIndex - xCount]; + let yMin = ys[maxYIndex + 1 - yCount]; + + maxRects.push(new Box2(new Vector2(xMin, yMin), new Vector2(xMax, yMax))); + + rects.splice(maxIndex, 1); + + //对方块进行标记 + for (let i = 0; i < xCount; i++) + { + for (let j = 0; j < yCount; j++) + { + matrix[maxXIndex - 1 - i][maxYIndex - j] = 0; + } + } + + //如果有被标记的方块,则删除它 + arrayRemoveIf(rects, rect => + { + let [area, maxX, maxY, xCount, yCount] = rect; + for (let i = 0; i < xCount; i++) + { + for (let j = 0; j < yCount; j++) + { + if (!matrix[maxX - 1 - i][maxY - j]) + return true; + } + } + return false; + }); + } + }; + + let maxRects: Box2[] = []; + while (true) + { + let count = maxRects.length; + maximalRectangle(); + if (count === maxRects.length) break; + } + + return maxRects; + } +} + +function RangesAdd(ranges: [number, number][], vList: number[]) +{ + let adds: number[] = []; + for (let range of ranges) + { + let dist = range[1] - range[0]; + let count = Math.floor(dist / 20); + let divDist = Math.floor(dist / count); + + for (let i = 1; i < count - 1; i++) + { + let d = Math.floor(range[0] + divDist * i); + + let index = InsertSortedIndex(vList, d, (a, b) => a - b); + + if (Math.abs(vList[index] - d) < 5) + continue; + + if (index !== vList.length - 1 && Math.abs(vList[index + 1] - d) < 5) + continue; + + adds.push(d); + } + } + + if (adds.length) + { + arrayPushArray(vList, adds); + arraySortByNumber(vList); + } +} + +/** + * 判断点在多段线内外 这是为了LIR实现的优化算法,返回交点的Y轴列表 + */ +function IsPointInPolygon(polyPts: Vector2[], pt: Vector3): number[] +{ + // let crossings = 0; + // let insLine = new Line(pt, p2); + + let iPtYs: number[] = []; + + for (let i = 0; i < polyPts.length; i++) + { + let sp = polyPts[i]; + let ep = polyPts[FixIndex(i + 1, polyPts)]; + + // if (equalv2(sp, pt, 1e-5) || equalv2(ep, pt, 1e-5))//在起点或者终点 + // return false; + + //点位于线上面 + if (pt.y > Math.max(sp.y, ep.y)) + continue; + //线垂直Y轴 + let derX = ep.x - sp.x; + if (equaln(derX, 0, 5e-6)) + { + // if (equaln(pt.x, ep.x, 1e-5) + // && (pt.y > Math.min(sp.y, ep.y) - 1e-5 && pt.y < Math.max(sp.y, ep.y) + 1e-5)) + // return false;//点在线上 + continue; + } + + //起点 + if (equaln(sp.x, pt.x, 5e-6)) + { + // if (sp.y > pt.y && derX < 0) + if (derX < 0) + { + // crossings++; + iPtYs.push(sp.y); + } + continue; + } + + //终点 + if (equaln(ep.x, pt.x, 5e-6)) + { + // if (ep.y > pt.y && derX > 0) + if (derX > 0) + { + // crossings++; + iPtYs.push(ep.y); + } + continue; + } + + //快速求交,只验证有没有交点 + let [x1, x2] = sp.x > ep.x ? [ep.x, sp.x] : [sp.x, ep.x]; + if (pt.x > x1 && pt.x < x2) + { + let derY = ep.y - sp.y; + let k = derY / derX; + + let iptY = (pt.x - sp.x) * k + sp.y; + // if (equaln(iptY, pt.y, 1e-5))//点在线上 返回false + // return false; + if (iptY > pt.y) + { + // crossings++; + iPtYs.push(iptY); + } + } + } + + return iPtYs; +} diff --git a/src/Add-on/testEntity/TestLIR.ts b/src/Add-on/testEntity/TestLIR.ts new file mode 100644 index 000000000..9284647c9 --- /dev/null +++ b/src/Add-on/testEntity/TestLIR.ts @@ -0,0 +1,38 @@ +import { app } from "../../ApplicationServices/Application"; +import { Polyline } from "../../DatabaseServices/Entity/Polyline"; +import { Command } from "../../Editor/CommandMachine"; +import { PromptStatus } from "../../Editor/PromptResult"; +import { AsVector2, AsVector3, equalv3 } from "../../Geometry/GeUtils"; +import { TestDraw } from "../test/TestUtil"; +import { LargestInteriorRectangle } from "./LargestInteriorRectangle"; + +export class Command_TestLIR implements Command +{ + async exec() + { + let ssRes = await app.Editor.GetSelection({ + Msg: "选择多边形进行余料归方分析:(我们会忽略圆弧),请在俯视图下选择多边形,我们忽略三维多边形,请不要绘制大于2440大小的多边形", + Filter: { filterTypes: [Polyline] } + }); + if (ssRes.Status !== PromptStatus.OK) return; + + let ents = ssRes.SelectSet.SelectEntityList as Polyline[]; + + for (let pl of ents) + { + let pts = pl.GetStretchPoints(); + if (equalv3(pts[0], pts[pts.length - 1])) + pts.pop(); + + let lir = new LargestInteriorRectangle; + + let rects = lir.ParseLIR(pts.map(AsVector2)); + + for (let rect of rects) + { + let pl = new Polyline().RectangleFrom2Pt(AsVector3(rect.min), AsVector3(rect.max)); + TestDraw(pl, 6); + } + } + } +} diff --git a/src/Add-on/testEntity/test.ts b/src/Add-on/testEntity/test.ts index f9e6976c5..9db1c4870 100644 --- a/src/Add-on/testEntity/test.ts +++ b/src/Add-on/testEntity/test.ts @@ -1,7 +1,4 @@ -import { app } from "../../ApplicationServices/Application"; -import { Entity } from "../../DatabaseServices/Entity/Entity"; import { Command } from "../../Editor/CommandMachine"; -import { PromptStatus } from "../../Editor/PromptResult"; import { HotCMD } from "../../Hot/HotCommand"; @HotCMD @@ -9,10 +6,5 @@ export class Test implements Command { async exec() { - let enRes = await app.Editor.GetEntity({ Filter: { filterTypes: [Entity] } }); - if (enRes.Status !== PromptStatus.OK) return; - let pl = enRes.Entity as Entity; - console.log(pl); - } } diff --git a/src/DatabaseServices/PointInPolyline.ts b/src/DatabaseServices/PointInPolyline.ts index 0f3048805..b60a6ffff 100644 --- a/src/DatabaseServices/PointInPolyline.ts +++ b/src/DatabaseServices/PointInPolyline.ts @@ -208,7 +208,7 @@ export function IsPointInPolygon(pt: Vec2, pts: Vec2[]) /* Calculate the equation of the line */ let dx = ptn.x - pti.x; let dy = ptn.y - pti.y; - let k; + let k: number; if (Math.abs(dx) < eps) k = 1e300; diff --git a/src/Editor/CommandRegister.ts b/src/Editor/CommandRegister.ts index 938a17a22..7440617b7 100644 --- a/src/Editor/CommandRegister.ts +++ b/src/Editor/CommandRegister.ts @@ -246,6 +246,7 @@ import { TestTargeOnCurve } from "../Add-on/testEntity/TestCurve"; import { Command_TestDrawEdgeGeometry } from "../Add-on/testEntity/TestDrawEdgeGeometry"; import { Command_TestLoadFbx } from "../Add-on/testEntity/TestFbx"; import { TestFillet } from "../Add-on/testEntity/TestFilletCode"; +import { Command_TestLIR } from "../Add-on/testEntity/TestLIR"; import { Command_SimplifyPolyline, Command_TestPolyline2PointsPolyline } from "../Add-on/testEntity/TestPolyline2PointsPolyline"; import { Command_TestRegionParse } from "../Add-on/testEntity/TestRegionParse"; import { Command_TestSweepMaxLength } from "../Add-on/testEntity/TestSweepMaxLength"; @@ -687,6 +688,9 @@ export function registerCommand() { commandMachine.RegisterCommand("DebugTemplateAssocCount", new Command_DebugTemplateAssocCount()); + commandMachine.RegisterCommand("testlir", new Command_TestLIR()); + + commandMachine.RegisterCommand("testNFP", new Command_TestNFP()); commandMachine.RegisterCommand("testSimply", new Command_TestSimply()); commandMachine.RegisterCommand("testSimply2", new Command_TestPolylin2Points()); diff --git a/src/api.ts b/src/api.ts index fe05b83e8..da5e17232 100644 --- a/src/api.ts +++ b/src/api.ts @@ -19,6 +19,8 @@ export * from "./DatabaseServices/Contour"; export * from "./DatabaseServices/Shape"; export * from "./DatabaseServices/ShapeManager"; +export * from "./Add-on/testEntity/LargestInteriorRectangle";//余料归方算法 + // export * from "./Nest/Converter/Curves2Points"; export * from "./Nest/Converter/CurveWrap";