diff --git a/__test__/Polyline/__snapshots__/split.test.ts.snap b/__test__/Polyline/__snapshots__/split.test.ts.snap index d8c0a23b1..cdf059072 100644 --- a/__test__/Polyline/__snapshots__/split.test.ts.snap +++ b/__test__/Polyline/__snapshots__/split.test.ts.snap @@ -277,131 +277,6 @@ Array [ ] `; -exports[`单刀 6`] = ` -Array [ - Object { - "bul": 0, - "pt": Vector2 { - "x": 0, - "y": 0, - }, - }, - Object { - "bul": 1, - "pt": Vector2 { - "x": 5, - "y": 0, - }, - }, - Object { - "bul": -0.41421356237309503, - "pt": Vector2 { - "x": 5, - "y": 5, - }, - }, - Object { - "bul": 0, - "pt": Vector2 { - "x": 2.5, - "y": 7.5, - }, - }, -] -`; - -exports[`单刀 7`] = ` -Array [ - Object { - "bul": -0.41421356237309503, - "pt": Vector2 { - "x": 2.5, - "y": 7.5, - }, - }, - Object { - "bul": 0, - "pt": Vector2 { - "x": 5, - "y": 10, - }, - }, - Object { - "bul": 0, - "pt": Vector2 { - "x": 0, - "y": 10, - }, - }, - Object { - "bul": 0, - "pt": Vector2 { - "x": 0, - "y": 0, - }, - }, -] -`; - -exports[`单刀 8`] = ` -Array [ - Object { - "bul": 0, - "pt": Vector2 { - "x": 0, - "y": 0, - }, - }, - Object { - "bul": 1, - "pt": Vector2 { - "x": 5, - "y": 0, - }, - }, - Object { - "bul": -1, - "pt": Vector2 { - "x": 5, - "y": 5, - }, - }, - Object { - "bul": 0, - "pt": Vector2 { - "x": 5, - "y": 10, - }, - }, -] -`; - -exports[`单刀 9`] = ` -Array [ - Object { - "bul": 0, - "pt": Vector2 { - "x": 5, - "y": 10, - }, - }, - Object { - "bul": 0, - "pt": Vector2 { - "x": 0, - "y": 10, - }, - }, - Object { - "bul": 0, - "pt": Vector2 { - "x": 0, - "y": 0, - }, - }, -] -`; - exports[`单刀闭合 1`] = ` Array [ Object { diff --git a/src/Add-on/DrawPolyline.ts b/src/Add-on/DrawPolyline.ts index e0308618d..177302e56 100644 --- a/src/Add-on/DrawPolyline.ts +++ b/src/Add-on/DrawPolyline.ts @@ -49,8 +49,9 @@ export class DrawPolyline implements Command let dir: Vector3; let KeyWordList = [{ msg: "圆弧", key: "A" }, { msg: "直线", key: "L" }, { msg: "放弃", key: "U" }]; - //返回一步 - let restore = () => + + //弹出循环开始就添加的线段点 + let popLineProps = () => { lineProps.pop(); polyline.LineData = lineProps; @@ -109,7 +110,6 @@ export class DrawPolyline implements Command { updateBul(ptRes.Value, basePt); tangentLine = rotateLine(tangentLine, cirAng) - } polyline.LineData = lineProps; BasePts.push(basePt); @@ -155,11 +155,11 @@ export class DrawPolyline implements Command lineProps[lineProps.length - 2].bul = 0; this.m_Model = PolylineModel.Line; } - restore(); + popLineProps(); } else { - restore(); + popLineProps(); break; } } diff --git a/src/GraphicsSystem/OffestPolyline.ts b/src/GraphicsSystem/OffestPolyline.ts index 211c4eebf..08a36f7ef 100644 --- a/src/GraphicsSystem/OffestPolyline.ts +++ b/src/GraphicsSystem/OffestPolyline.ts @@ -1,5 +1,5 @@ import { Vector2, Vector3 } from "three"; -import { Vec2DTo3D, Vec3DTo2D, isTargetCurInSourceCur, rotateLine } from "../Common/CurveUtils"; +import { Vec2DTo3D, Vec3DTo2D, getCirAngleByChordAndTangent, rotateLine } from "../Common/CurveUtils"; import { sliceDeep } from "../Common/Utils"; import { Arc } from "../DatabaseServices/Arc"; import { Circle } from "../DatabaseServices/Circle"; @@ -7,8 +7,8 @@ import { Curve } from "../DatabaseServices/Curve"; import { Line } from "../DatabaseServices/Line"; import { Polyline, PolylineProps } from "../DatabaseServices/Polyline"; import { MoveMatrix, angleTo, equal, equaln } from "../Geometry/GeUtils"; +import { isTargetCurInSourceCur } from "./BoolOperateUtils"; import { IntersectOption } from "./IntersectWith"; -import { app } from "../ApplicationServices/Application"; interface offestRes { index: number, @@ -22,6 +22,8 @@ interface offestRes * 存在问题为仍有一些无效线段无法正确删除 * 移除自交线段时部分情况不正确 * 存在多封闭区域的时候无法都识别出来 + * 分离自交曲线 存在问题 + * //TODO:尝试先通过自交点把偏移后曲线数组打散,然后进行曲线切割(用源线段点和线段到源线段的最近点) * @export * @class PolyOffestUtil */ @@ -40,16 +42,16 @@ export class PolyOffestUtil let plList: offestRes[] = this.offestCurve(this.m_Polyline.Explode()); //连接全部连接点 let newPlList: Array = this.trimAndJointOffestPolyline(plList); - // //分离出自交的线 + // // //分离出自交的线 let outputPls = this.removeSelfIntersect(newPlList); this.linkPLine(newPlList); this.cuttingPolyLine(newPlList); - //如果曲线闭合并且向内偏移,曲线应都在闭合曲线内 - if (this.m_Polyline.IsClose) - { - //FIXME:没想好确定偏移方向 - } + // //如果曲线闭合并且向内偏移,曲线应都在闭合曲线内 + // if (this.m_Polyline.IsClose) + // { + // } + outputPls.push(...this.arrangePlineList(newPlList)); return outputPls; } @@ -158,21 +160,18 @@ export class PolyOffestUtil //如果没有交点,则用圆弧连接 if (interPts.length === 0) { - let srcLine = offResList[i + 1] ? offResList[i + 1] : offResList[0]; - let centerPt = this.m_Polyline.GetCurveAtIndex(srcLine.index).StartPoint; - // 圆心到前面线end点向量 - let startLine = frontLine.EndPoint.clone().sub(centerPt); - // 圆心到后面线start点向量 - let endLine = laterLine.StartPoint.clone().sub(centerPt); - let circleAngle; + //偏移线段对应的源线段,取其起始点作为圆心 + let srcOffestRes = offResList[i + 1] ? offResList[i + 1] : offResList[0]; + let centerPt = this.m_Polyline.GetCurveAtIndex(srcOffestRes.index).StartPoint; + + // 圆心到前面线end点距离 + let startDist = frontLine.EndPoint.distanceToSquared(centerPt); + // 圆心到后面线start点距离 + let endDist = laterLine.StartPoint.distanceToSquared(centerPt); //补圆弧的起始交点 let intPt = Vec2DTo3D(frontLine.LineData[1].pt); - // 如果起始向量和终止向量相等,则都为半径 - if (equaln(startLine.lengthSq(), endLine.lengthSq())) - { - circleAngle = angleTo(startLine, endLine); - } - else + + if (!equaln(startDist, endDist)) { // 以centerPt为圆心,偏移距离为为半径画圆 let tmpCir = new Circle(centerPt, Math.abs(this.m_OffestDist)); @@ -183,12 +182,11 @@ export class PolyOffestUtil //选取交点,如果有多个选离前面线start点近的 intPt = this.selectPlInterPt(pts, frontLine.StartPoint); let insLine = intPt.clone().sub(centerPt); - circleAngle = angleTo(insLine, endLine); this.adjustFrontLine(startV, frontLine.LineData[1], intPt); } else { - // 与补线圆无交点,放弃补圆弧 + //TODO: 与补线圆无交点,放弃补圆弧,若按CAD可以补 newPlList.push(new Polyline([ startV, this.clonePlData(frontLine.LineData[1]) ])) @@ -196,6 +194,11 @@ export class PolyOffestUtil continue; } } + //改用根据laterline切线和弦判断圆心角及方向 + let chord = intPt.clone().sub(laterLine.StartPoint); + let tangent = laterLine.GetFistDeriv(0).negate().normalize(); + let circleAngle = -getCirAngleByChordAndTangent(chord, tangent); + let tmpPlData = { pt: Vec3DTo2D(intPt), bul: Math.tan(circleAngle / 4) @@ -343,6 +346,23 @@ export class PolyOffestUtil } return plLists; } + //根据自交点分离多段线 + private spliteOffestPolyline(plList: Polyline[]) + { + for (let i = 0; i < plList.length - 1; i++) + { + //前一根线 + let frontLine = plList[i]; + let insertPt = new Vector3(); + for (let j = i + 1; j < plList.length; j++) + { + // 后一根线 + let laterLine = plList[j]; + let intPts = frontLine.IntersectWith(laterLine, IntersectOption.OnBothOperands); + + } + } + } //处理分离出的多段线,把相交的部分提取出来 private handleRemoveLine(plList: Polyline[]) { @@ -1060,449 +1080,3 @@ export class PolyOffestUtil2 } } } - -//合并前2个偏移算法,未完善 -export class PolyOffestUtil3 -{ - private m_Polyline: Polyline; - private m_OffestDist: number - constructor(pl: Polyline, offest: number) - { - this.m_Polyline = pl; - this.m_OffestDist = offest; - } - - //偏移 - GetOffsetCurves(): Curve[] - { - - //预处理源线段自交线段 - let handledPl = this.handleIntersectToSelf(); - let pls = handledPl.Explode(); - let plList1: offestRes[] = this.offestCurve(pls, this.m_OffestDist); - let plList2: offestRes[] = this.offestCurve(pls, -this.m_OffestDist); - - let l = plList1.map(l => l.pl).filter(l => l !== undefined); - let newPls = this.trimAndJointOffestPolyline(plList1, handledPl); - let refPls = this.trimAndJointOffestPolyline(plList2, handledPl); - newPls = this.arrangePlineList(newPls); - // console.log('newPls: ', newPls); - refPls = this.arrangePlineList(refPls); - return [...newPls, ...refPls] - // console.log('refPls: ', refPls); - // let outputPls: Polyline[] = []; - // for (let newPl of newPls) - // { - // for (let refPl of refPls) - // { - // outputPls.push(...this.clipOffestPolyline(handledPl, newPl, refPl)); - // } - // } - // return outputPls; - // return newPls; - } - //偏移曲线 - private offestCurve(pls: Curve[], dist: number): offestRes[] - { - let plList: offestRes[] = []; - pls.forEach((pl, index) => - { - let l1 = pl.GetOffsetCurves(dist)[0]; - let offPl: Polyline; - if (l1 instanceof Line) - { - offPl = new Polyline([{ - pt: Vec3DTo2D(l1.StartPoint), - bul: 0 - }, { - pt: Vec3DTo2D(l1.EndPoint), - bul: 0 - }]); - } - else if (l1 instanceof Arc) - { - offPl = new Polyline([{ - pt: Vec3DTo2D(l1.StartPoint), - bul: Math.tan(l1.AllAngle / 4) * (l1.IsClockWise ? -1 : 1) - }, { - pt: Vec3DTo2D(l1.EndPoint), - bul: 0 - }]); - } - offPl && plList.push({ - index, pl: offPl - }) - - }) - return plList; - } - /** - * 预处理源线段相邻线段自交 - * @private - * @memberof PolyOffestUtil2 - */ - private handleIntersectToSelf() - { - let newPlProps = sliceDeep(this.m_Polyline.LineData) as PolylineProps[]; - //处理相邻线段自交 - for (let i = 0; i < this.m_Polyline.EndParam - 1; i++) - { - let frontLine = this.m_Polyline.GetCurveAtIndex(i); - let laterLine = this.m_Polyline.GetCurveAtIndex(i + 1); - let interPts = frontLine.IntersectWith(laterLine, IntersectOption.OnBothOperands).filter(p => - { - return !equal(p, frontLine.EndPoint); - }); - - let newProp: PolylineProps; - if (interPts.length > 0) - { - newProp = { - pt: Vec3DTo2D(interPts[0]), - bul: 0 - } - if (frontLine instanceof Arc) - { - let startV = this.m_Polyline.LineData[i]; - this.adjustFrontLine(startV, newPlProps[i + 1], interPts[0]); - frontLine.StartPoint = interPts[0]; - newProp.bul = Math.tan(frontLine.AllAngle * 0.25) * Math.sign(startV.bul); - } - newPlProps.splice(i + 1, 0, newProp); - i++; - } - } - return new Polyline(newPlProps) - } - private getSelfInterPts(pl: Polyline) - { - let interPts: Vector3[] = []; - for (let i = 0; i < pl.EndParam; i++) - { - let frontLine = pl.GetCurveAtIndex(i); - for (let j = i + 1; j < pl.EndParam; j++) - { - let laterLine = pl.GetCurveAtIndex(j); - let tmpPts = frontLine.IntersectWith(laterLine, IntersectOption.OnBothOperands).filter(p => - { - return !equal(p, frontLine.EndPoint); - }); - interPts.push(...tmpPts); - } - } - return interPts; - } - // 修剪连接相邻曲线 - private trimAndJointOffestPolyline(offsetRes: offestRes[], originLine: Polyline) - { - if (offsetRes.length <= 1) - { - return offsetRes[0] ? [offsetRes[0].pl] : []; - } - let newPlList: Array = []; - - let startV = this.clonePlData(offsetRes[0].pl.LineData[0]); - let isStillClose = this.m_Polyline.IsClose; - for (let i = 0; i < offsetRes.length; i++) - { - //前面线 - let frontLine = offsetRes[i].pl; - - let laterLine = i === offsetRes.length - 1 ? undefined : offsetRes[i + 1].pl; - - if (i === offsetRes.length - 1) - { - if (this.m_Polyline.IsClose && isStillClose) - laterLine = newPlList[0]; - else break; - } - if (!laterLine) return []; //终点data - let endV = i === offsetRes.length - 1 ? laterLine.LineData[0] : this.clonePlData(laterLine.LineData[0]); - //交点 - let interPt = Vec2DTo3D(endV.pt); - // 如果两线结合点不相等 - if (!equal(frontLine.EndPoint, laterLine.StartPoint)) - { - // 多段线交点数组 - let interPts = frontLine.IntersectWith(laterLine, IntersectOption.ExtendBoth); - - //如果没有交点,则用圆弧连接 - if (interPts.length === 0) - { - let centerPt = this.m_Polyline.GetCurveAtIndex(offsetRes[i + 1].index).StartPoint; - // 圆心到前面线end点向量 - let startLine = frontLine.EndPoint.clone().sub(centerPt); - // 圆心到后面线start点向量 - let endLine = laterLine.StartPoint.clone().sub(centerPt); - let circleAngle; - //补圆弧的起始交点 - let intPt = Vec2DTo3D(frontLine.LineData[1].pt); - // 如果起始向量和终止向量相等,则都为半径 - if (equaln(startLine.lengthSq(), endLine.lengthSq())) - { - circleAngle = angleTo(startLine, endLine); - } - else - { - // 以centerPt为圆心,偏移距离为为半径画圆 - let tmpCir = new Circle(centerPt, Math.abs(this.m_OffestDist)); - //只会和前面线相交,因为圆心选取是根据后面线startPoint偏移距离处的点,只会与后面线相切,允许前面线延伸 - let pts = tmpCir.IntersectWith(frontLine, IntersectOption.OnBothOperands); - if (pts.length > 0) - { - //选取交点,如果有多个选离前面线start点近的 - intPt = this.selectFitInterPt(pts, frontLine.StartPoint); - let insLine = intPt.clone().sub(centerPt); - circleAngle = angleTo(insLine, endLine); - this.adjustFrontLine(startV, frontLine.LineData[1], intPt); - } - else - { - // 与补线圆无交点,放弃补圆弧 - newPlList.push(new Polyline([ - startV, this.clonePlData(frontLine.LineData[1]) - ])) - startV = this.clonePlData(laterLine.LineData[0]); - continue; - } - } - let tmpPlData = { - pt: Vec3DTo2D(intPt), - bul: Math.tan(circleAngle / 4) - }; - newPlList.push(new Polyline([ - startV, tmpPlData - ])) - startV = this.clonePlData(tmpPlData); - } - else - { - let pts = frontLine.IntersectWith(laterLine, IntersectOption.OnBothOperands); - interPt = this.selectFitInterPt(pts.length ? pts : interPts, laterLine.StartPoint) - this.adjustFrontLine(startV, frontLine.LineData[1], interPt); - if (i === offsetRes.length - 1) - { - let tmpPar = laterLine.GetParamAtPoint(interPt); - tmpPar > 1 && newPlList.splice(0, 1); - } - this.adjustLaterLine(endV, laterLine.LineData[1], interPt); - } - } - - let pl = new Polyline([startV, endV]); - let tmpPar = frontLine.GetParamAtPoint(Vec2DTo3D(startV.pt)); - let tmpCur = frontLine.GetCurveAtIndex(0) as Line | Arc; - if (tmpPar > 0) tmpCur.EndPoint = Vec2DTo3D(startV.pt); - else tmpCur.StartPoint = Vec2DTo3D(startV.pt); - let par = tmpCur.GetParamAtPoint(interPt); - // newSlope = pl.GetFistDeriv(0); - startV = this.clonePlData(endV); - - if (equal(pl.StartPoint, pl.EndPoint) || par < 0) - { - if (isStillClose) - { - isStillClose = !(offsetRes[i].index === 0 || offsetRes[i].index === originLine.EndParam); - } - continue; - } - newPlList.push(pl); - } - if (!this.m_Polyline.IsClose) - { - let frontLine = offsetRes[offsetRes.length - 1].pl; - let endV = this.clonePlData(frontLine.LineData[1]); - let tmpPar = frontLine.GetParamAtPoint(Vec2DTo3D(startV.pt)); - if (tmpPar < 1) - { - if (offsetRes.length >= 3) - { - let laterLine = newPlList[0]; - let pts = frontLine.IntersectWith(laterLine, IntersectOption.OnBothOperands); - if (pts.length === 1) - { - this.adjustFrontLine(startV, endV, pts[0]); - endV.pt = Vec3DTo2D(pts[0]); - if (newPlList.length > 0) - { - this.adjustLaterLine(laterLine.LineData[0], laterLine.LineData[1], pts[0]); - } - } - } - let pl = new Polyline([startV, endV]); - let par = frontLine.GetParamAtPoint(Vec2DTo3D(endV.pt)); - par > 0 && newPlList.push(pl); - } - - } - return newPlList; - } - /** - * 裁剪 偏移后的未修剪线段 - * 在2测偏移线段之间的线段,剔除掉 - * @private - * @param {Polyline} origin - * @param {Polyline} newPl - * @param {Polyline} refPl - * @memberof PolyOffestUtil2 - */ - private clipOffestPolyline(origin: Polyline, newPl: Polyline, refPl: Polyline) - { - //结果曲线与参考曲线的交点 - let interPts = newPl.IntersectWith(refPl, IntersectOption.OnBothOperands); - // 结果曲线自交点 - let selfInterPts = this.getSelfInterPts(newPl); - //临时多段线数组 - let tmpPlList: Polyline[] = []; - if (!interPts.length && !selfInterPts.length) - { - tmpPlList.push(newPl); - } - else - { - //根据交点顺序分割偏移线段 - let params = [...interPts].map(pt => newPl.GetParamAtPoint(pt)); - [...selfInterPts].forEach(pt => - { - params.push(...this.getParsByPt(newPl, pt)); - }) - //分割的线段 - let tmpPls = newPl.GetSplitCurves(params); - tmpPls.forEach(pl => - { - let tmpInterPts = pl.IntersectWith(origin, IntersectOption.OnBothOperands); - if (tmpInterPts.length === 0) - tmpPlList.push(pl) - else - { - let tmpPt = tmpInterPts.filter(pt => equal(pt, origin.StartPoint) || equal(pt, origin.EndPoint)); - if (tmpPt.length > 0) - { - //切割该分段 - let tmpCir = new Circle(tmpPt[0], Math.abs(this.m_OffestDist)); - tmpPlList.push(...this.cuttingPolyline(pl, tmpPt[0])) - } - } - }) - } - //用小于偏移距离最近点对结果多段线数组进行切割 - // this.cuttingOffestPlByClosePt(origin, tmpPlList); - return tmpPlList.filter(pl => !equaln(pl.Length, 0)); - } - private getParsByPt(pl: Polyline, pt: Vector3) - { - let pars: number[] = []; - let cus = pl.Explode(); - for (let i = 0; i < cus.length; i++) - { - let cu = cus[i]; - let param = cu.GetParamAtPoint(pt); - if (cu.ParamOnCurve(param)) - pars.push(i + param); //返回点在曲线内部的参数 - } - return pars; - } - private cuttingOffestPlByClosePt(origin: Polyline, pllist: Polyline[]) - { - - for (let i = 0; i < pllist.length; i++) - { - let pl = pllist[i]; - for (let j = 0; j < pl.EndParam + 1; j++) - { - let pt = pl.GetPointAtParam(j); - let center = origin.GetClosestPointTo(pt, false); - let dist = center.distanceToSquared(pt); - if (dist - Math.pow(this.m_OffestDist, 2) < -1e-4) - { - let cutedPls = this.cuttingPolyline(pl, center).filter(l => !equaln(l.Length, 0, 0.1)); - pllist.splice(i, 1, ...cutedPls); - i -= (cutedPls.length ? cutedPls.length : 1); - break; - } - } - } - - } - private cuttingPolyline(pl: Polyline, centerPt: Vector3) - { - let rad = Math.abs(this.m_OffestDist); - let circle = new Circle(centerPt, rad); - let pts = pl.IntersectWith(circle, IntersectOption.OnBothOperands); - let params = pts.map(p => pl.GetParamAtPoint(p)); - let cutedPls = pl.GetSplitCurves(params).filter(l => !isTargetCurInSourceCur(circle, l)); - return cutedPls; - } - // 根据交点调整前面线段的凸度 - private adjustFrontLine(startV: PolylineProps, endV: PolylineProps, insertPt: Vector3) - { - if (startV.bul !== 0) - { - let arc = new Arc().ParseFromBul(startV.pt, endV.pt, startV.bul); - arc.EndPoint = insertPt; - //前面线的凸度调整 - startV.bul = Math.tan(arc.AllAngle / 4) * Math.sign(startV.bul); - } - } - // 根据交点调整后面线段的凸度和起始点 - private adjustLaterLine(startV: PolylineProps, endV: PolylineProps, interPt: Vector3) - { - let bul = 0; - if (startV.bul !== 0) - { - let arc = new Arc().ParseFromBul(startV.pt, endV.pt, startV.bul); - arc.StartPoint = interPt; - bul = Math.tan(arc.AllAngle * 0.25) * Math.sign(startV.bul); - } - // 调整后面线起始点和凸度 - startV.pt = Vec3DTo2D(interPt); - startV.bul = bul; - } - // 选择合适的交点 - private selectFitInterPt(pts: Vector3[], refPt: Vector3) - { - let pt = pts[0]; - if (pts.length > 1) - { - let dist1 = refPt.distanceToSquared(pts[0]); - let dist2 = refPt.distanceToSquared(pts[1]); - pt = dist1 <= dist2 ? pts[0] : pts[1]; - } - return pt; - } - private clonePlData(pl: PolylineProps) - { - return { - pt: pl.pt, - bul: pl.bul - } - } - - //整理多段线数组,把相连的组合成多段线,返回多段线数组 - private arrangePlineList(pls: Array) - { - if (pls.length === 0) return []; - let plDataList: PolylineProps[] = []; - let plList: Polyline[] = []; - plDataList.push(pls[0].LineData[0]); - for (let i = 0; i < pls.length - 1; i++) - { - let frontLine = pls[i]; - let laterLine = pls[i + 1]; - if (equal(frontLine.EndPoint, laterLine.StartPoint)) - { - plDataList.push(laterLine.LineData[0]); - } - else - { - plDataList.push(frontLine.LineData[1]); - plList.push(new Polyline(plDataList)); - plDataList = [laterLine.LineData[0]]; - } - } - plDataList.push(pls[pls.length - 1].LineData[1]); - plList.push(new Polyline(plDataList)); - - return plList; - } -}