diff --git a/__test__/Fillet/__snapshots__/polyline.test.ts.snap b/__test__/Fillet/__snapshots__/polyline.test.ts.snap new file mode 100644 index 000000000..9256a1bf1 --- /dev/null +++ b/__test__/Fillet/__snapshots__/polyline.test.ts.snap @@ -0,0 +1,29 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`双圆多段线倒角 1`] = `1208.3752684304043`; + +exports[`双圆多段线倒角 2`] = `1208.375268430404`; + +exports[`双圆多段线倒角 3`] = `1208.3752684304043`; + +exports[`双圆多段线倒角 4`] = `1208.3752684304045`; + +exports[`双圆多段线倒角 5`] = `1200.7933322000165`; + +exports[`双圆多段线倒角 6`] = `1200.7933322000165`; + +exports[`双圆多段线倒角 7`] = `1200.7933322000172`; + +exports[`双圆多段线倒角 8`] = `1200.7933322000174`; + +exports[`多段线闭合标志首尾有弧 1`] = `1357.0494514094473`; + +exports[`多段线闭合标志首尾有弧 2`] = `1357.0494514094473`; + +exports[`多段线闭合标志首尾有弧 3`] = `1357.0494514094473`; + +exports[`多段线闭合标志首尾有弧 4`] = `1357.049451409447`; + +exports[`多段线首尾倒角有圆弧 1`] = `1682.5946408278246`; + +exports[`多段线首尾倒角有圆弧 2`] = `1682.5946408278244`; diff --git a/__test__/Fillet/polyline.test.ts b/__test__/Fillet/polyline.test.ts new file mode 100644 index 000000000..6e91a63db --- /dev/null +++ b/__test__/Fillet/polyline.test.ts @@ -0,0 +1,88 @@ + +import { FilletUtils } from "../../src/Add-on/FilletUtils"; +import { Polyline } from "../../src/DatabaseServices/Polyline"; +import { LoadEntityFromFileData } from "../Utils/LoadEntity.util"; +import { PromptEntityResult } from "../../src/Editor/PromptResult"; +import { Vector3 } from "three"; + +//file.only +let fillet = new FilletUtils(); +test('多段线首尾倒角有圆弧', () => +{ + TestFilletPolyline( + [1, ["Polyline", 1, 1, 3, false, 7, -1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 375.4375364613715, 208.47861732064138, 0, 1], 2, 10, [-171.7455621301774, 180.2537770455558], -0.40012702806387124, [171.7455621301774, 180.2537770455558], 0, [221.74556213017775, 180.2537770455558], 0, [221.74556213017775, -72.32657236535573], 0, [-3.441731405810515, -260.5746253213081], 0, [-137.90517157255817, -260.5746253213081], 0, [-137.90517157255817, -37.838102302837456], 0, [-221.7455621301774, -37.838102302837456], 0, [-221.7455621301774, 180.2537770455558], 0, [-171.7455621301774, 180.2537770455558], 0, false]], + 50, + [new Vector3(178, 388), new Vector3(224, 460)] + ) +}); + +test('多段线闭合标志首尾有弧', () => +{ + TestFilletPolyline( + [1, ["Polyline", 1, 1, 30, false, 1, -1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 2175.4664840507453, 164.7609132081223, 0, 1], 2, 6, [-171.7455621301774, 180.25377704555575], 0, [-221.7455621301774, 180.2537770455558], 0, [-221.7455621301774, -37.838102302837456], 0, [221.74556213017786, -37.838102302837456], 0, [221.74556213017786, 180.2537770455558], 0, [171.7455621301774, 180.25377704555575], 0.4001270280638712, true]], + 50, + [ + new Vector3().fromArray([1978.720921920568, 345.01469025367805, 0]), + new Vector3().fromArray([2059.1464975640683, 384.8918356028163, 0]), + new Vector3().fromArray([2316.870065964417, 369.68282338998426, 0]), + new Vector3().fromArray([2372.2120461809227, 345.01469025367805, 0]) + ] + ); + + +}); + +test('双圆多段线倒角', () => +{ + //双圆 + TestFilletPolyline( + [1, ["Polyline", 1, 1, 36, false, 7, -1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 669.3126866217343, -478.9366812060718, 0, 1], 2, 3, [76.66953810162238, -86.81133085285757], -2.219258954152199, [71.33090803386779, 64.97443486580221], -3.5679817627813364, [76.66953810162238, -86.81133085285757], 0, false]], + 50, + [ + new Vector3().fromArray([655.0295275228787, -390.0373584486947, 0]), + new Vector3().fromArray([794.1254343980869, -359.44086563487383, 0]), + new Vector3().fromArray([695.0427029021831, -579.0860583389696, 0]), + new Vector3().fromArray([776.3783481250921, -599.2347834977858, 0]) + ] + ); + + //双圆 CloseMark + TestFilletPolyline( + [1, ["Polyline", 1, 1, 37, false, 1, -1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 669.3126866217344, -478.9366812060718, 0, 1], 2, 2, [70.71115893702552, -76.919569940087], -2.2862716785205572, [71.33090803386779, 64.97443486580221], -3.8599589051482246, true]], + 50, + [ + new Vector3().fromArray([655.0295275228787, -390.0373584486947, 0]), + new Vector3().fromArray([794.1254343980869, -359.44086563487383, 0]), + new Vector3().fromArray([695.0427029021831, -579.0860583389696, 0]), + new Vector3().fromArray([776.3783481250921, -599.2347834977858, 0]) + ] + ); +}); + +function TestFilletPolyline( + curveData: any, + radius: number, + filletPts: Vector3[] +) +{ + let pl = LoadEntityFromFileData(curveData)[0] as Polyline; + fillet.m_FilletRadius = radius; + + + let es1 = new PromptEntityResult(pl, undefined); + let es2 = new PromptEntityResult(pl, undefined); + + for (let i = 0; i < filletPts.length / 2; i++) + { + es1.Point = filletPts[i * 2]; + es2.Point = filletPts[i * 2 + 1]; + + let res = fillet.Fillet(es1, es2); + expect(res.cu1.Length).toMatchSnapshot(); + + pl.Reverse(); + + res = fillet.Fillet(es1, es2); + expect(res.cu1.Length).toMatchSnapshot(); + } +} diff --git a/__test__/Utils/LoadEntity.util.ts b/__test__/Utils/LoadEntity.util.ts new file mode 100644 index 000000000..72d505a1d --- /dev/null +++ b/__test__/Utils/LoadEntity.util.ts @@ -0,0 +1,18 @@ +import { CADFile } from "../../src/DatabaseServices/CADFile"; +import { Entity } from "../../src/DatabaseServices/Entity"; +import { Polyline } from "../../src/DatabaseServices/Polyline"; +import { Factory } from "../../src/DatabaseServices/CADFactory"; + +Factory(Polyline); +export function LoadEntityFromFileData(data) +{ + let file = new CADFile(); + file.Data = data; + let ens: Entity[] = []; + let count = file.Read(); + for (let i = 0; i < count; i++) + { + ens.push(file.ReadObject(undefined) as Entity); + } + return ens; +} diff --git a/src/Add-on/Fillet.ts b/src/Add-on/Fillet.ts index 0cd67fd93..45f90e4c3 100644 --- a/src/Add-on/Fillet.ts +++ b/src/Add-on/Fillet.ts @@ -1,93 +1,26 @@ -import { Matrix4, Vector3 } from 'three'; import { end } from 'xaop'; import { app } from '../ApplicationServices/Application'; -import { curveLinkGroup, GetPointAtCurveDir, Vec3DTo2D } from '../Common/CurveUtils'; import { KeyWord } from '../Common/InputState'; import { KeyBoard } from '../Common/KeyEnum'; import { FixedNotZero } from '../Common/Utils'; -import { Arc } from '../DatabaseServices/Arc'; -import { Circle } from '../DatabaseServices/Circle'; import { Curve } from '../DatabaseServices/Curve'; -import { Line } from '../DatabaseServices/Line'; import { Polyline } from '../DatabaseServices/Polyline'; import { Command } from '../Editor/CommandMachine'; import { JigUtils } from '../Editor/JigUtils'; import { PromptEntityResult, PromptStatus } from '../Editor/PromptResult'; -import { angle, equalv3, isParallelTo, midPoint } from '../Geometry/GeUtils'; -import { IntersectOption } from '../GraphicsSystem/IntersectWith'; - -enum ExtendType -{ - Start = 1, - End = 2, -} - -interface FilletRes -{ - cu1: Curve; - cu2: Curve; - arc: Arc; -} - -type CurveExtend = { Curve: Curve, ExtType: ExtendType }; +import { FilletUtils } from './FilletUtils'; const RADKEY = 'filletRadius'; -function Encode(res: PromptEntityResult, enMap: (PromptEntityResult[])[]) -{ - if (res.Entity instanceof Line) - { - enMap[0].push(res); - return 1; - } - else if (res.Entity instanceof Arc) - { - enMap[1].push(res); - return 2; - } - else if (res.Entity instanceof Polyline) - { - enMap[2].push(res); - return 4; - } -} - -//把圆转换成圆弧,避免圆参与计算. -function CircleEnResToArc(enRes: PromptEntityResult) -{ - if (enRes.Entity instanceof Circle) - { - let an = angle(enRes.Point.clone().applyMatrix4(enRes.Entity.OCSInv)) + Math.PI; - let arc = new Arc(new Vector3(), enRes.Entity.Radius, an, an + 0.1); - arc.ApplyMatrix(enRes.Entity.OCS); - arc.Center = enRes.Entity.Center; - enRes.Entity = arc; - //@ts-ignore - enRes.IsCircle = true; - } -} - -function GetFilletCurve(enRes: PromptEntityResult): any[] -{ - if (enRes.Entity instanceof Polyline) - { - let pl = enRes.Entity; - let param = pl.GetParamAtPoint(pl.GetClosestPointTo(enRes.Point, false)); - let paramF = Math.floor(param); - return [pl.GetCurveAtParam(param), paramF]; - } - else - return [enRes.Entity, NaN]; -} - export class CommandFillet implements Command { m_FilletRadius: number = 0; + private m_FilletUtils = new FilletUtils(); constructor() { let radStr = window.localStorage.getItem(RADKEY); if (radStr) - this.m_FilletRadius = parseFloat(radStr); + this.UpdateFilletRadius(parseFloat(radStr)); } async exec() { @@ -105,8 +38,6 @@ export class CommandFillet implements Command app.m_Editor.UCSMatrix = oldUcs; if (!enRes1) return; - CircleEnResToArc(enRes1); - enRes1.Entity.UpdateJigMaterial(); let lastCu: Curve; @@ -125,10 +56,9 @@ export class CommandFillet implements Command app.m_Editor.UpdateScreen(); return; } - CircleEnResToArc(res2); app.m_Editor.UCSMatrix = res2.Entity.OCS; res2.Entity.UpdateJigMaterial(); - let fres = this.Fillet(enRes1, res2); + let fres = this.m_FilletUtils.Fillet(enRes1, res2); for (let v in fres) if (fres[v]) JigUtils.Draw(fres[v]) @@ -164,7 +94,7 @@ export class CommandFillet implements Command if (enRes2.Status === PromptStatus.String) { let rad = parseFloat(enRes2.StringResult); - if (rad !== 0) + if (rad !== 0 && !isNaN(rad)) this.UpdateFilletRadius(rad); } else if (enRes2.Status !== PromptStatus.None) @@ -180,8 +110,7 @@ export class CommandFillet implements Command if (enRes2.Status === PromptStatus.OK) { - CircleEnResToArc(enRes2); - let res = this.Fillet(enRes1, enRes2); + let res = this.m_FilletUtils.Fillet(enRes1, enRes2); if (!res) return; @@ -206,743 +135,12 @@ export class CommandFillet implements Command UpdateFilletRadius(newRadius: number) { - if (newRadius < 0) + if (newRadius < 0 || isNaN(newRadius)) app.m_Editor.Prompt("半径不能为负!"); this.m_FilletRadius = Math.abs(newRadius); window.localStorage.setItem(RADKEY, this.m_FilletRadius.toString()); - } - - Fillet(enRes1: PromptEntityResult, enRes2: PromptEntityResult): FilletRes - { - let { enType, enMap } = this.EnCode(enRes1, enRes2); - if (enType === 4 && enRes1.Entity === enRes2.Entity) - return this.FilletPolyLineSelf(enRes1, enRes2); - else if (enType >= 4) - return this.FilletPolylineAndCurve(enRes1, enRes2); - - let interPts = this.GetIntersectAndSort(enRes1, enRes2, enType, enMap); - if (interPts.length === 0 - || (interPts.length === 1 && (enType & 2)))//圆弧相切 - { - if (enType === 1) - return this.FilletParallelLine(enRes1, enRes2); - else if (enType === 3) - return this.FilletLineAndArc(enMap, enRes1); - else if (enType === 2) - return this.FilletArcAndArc(enRes1, enRes2); - return; - } - - return this.FilletLineOrArc(enRes1, enRes2, interPts); - } - - FilletLineOrArc(enRes1: PromptEntityResult, enRes2: PromptEntityResult, interPts: Vector3[]): FilletRes - { - let iPt = interPts[0]; - - //裁剪延伸,使两条线组成一个尖角 - let splitedCu1 = this.SplitCurve(enRes1, iPt, interPts); - let splitedCu2 = this.SplitCurve(enRes2, iPt, interPts); - - let fRadius = this.m_FilletRadius; - //按下shift键时半径归0 - if (app.m_Editor.m_KeyCtrl.KeyIsDown(KeyBoard.Shift)) - fRadius = 0; - - if (enRes1.Entity instanceof Arc && enRes2.Entity instanceof Arc) - return this.FilletArcAndArc(enRes1, enRes2); - - let res: FilletRes = { cu1: splitedCu1.Curve, cu2: splitedCu2.Curve, arc: undefined }; - - if (fRadius > 0) - { - //角平分线向量. - let bisectorVec: Vector3 = new Vector3(); - let c1Derv = this.ComputerDerv(splitedCu1, bisectorVec); - let c2Derv = this.ComputerDerv(splitedCu2, bisectorVec); - - //方向相反 - if (equalv3(bisectorVec, new Vector3())) - return; - - //相切 - if (equalv3(c2Derv, c1Derv)) - { - bisectorVec.set(0, 0, 0); - c1Derv = this.ComputerDerv2(splitedCu1, bisectorVec); - c2Derv = this.ComputerDerv2(splitedCu2, bisectorVec); - } - let cu1RoOcsInv = new Matrix4().extractRotation(splitedCu1.Curve.OCSInv); - - [c1Derv, c2Derv, bisectorVec].forEach(v => v.applyMatrix4(cu1RoOcsInv)); - - let offCu1 = splitedCu1.Curve.GetOffsetCurves( - fRadius * -Math.sign(c1Derv.cross(bisectorVec).z))[0]; - let offCu2 = splitedCu2.Curve.GetOffsetCurves( - fRadius * -Math.sign(c2Derv.cross(bisectorVec).z))[0]; - - if (!offCu1 || !offCu2) - return; - - //测试绘制 - offCu1.ColorIndex = 6; - offCu2.ColorIndex = 6; - JigUtils.Draw(offCu1.Clone()); - JigUtils.Draw(offCu2.Clone()); - - let center = offCu1.IntersectWith(offCu2, IntersectOption.OnBothOperands) - .sort((p1, p2) => - { - return p1.distanceToSquared(iPt) - p2.distanceToSquared(iPt); - })[0]; - - if (!center) - return; - - let arcP1 = splitedCu1.Curve.GetClosestPointTo(center, true); - let arcP2 = splitedCu2.Curve.GetClosestPointTo(center, true); - if (!splitedCu1.Curve.PtOnCurve(arcP1) || !splitedCu2.Curve.PtOnCurve(arcP2)) - return; - - //时针校验 - let v1 = arcP1.clone().sub(center).applyMatrix4(cu1RoOcsInv); - let v2 = arcP2.clone().sub(center).applyMatrix4(cu1RoOcsInv); - - //绘制圆弧 - let arc = new Arc(new Vector3(), this.m_FilletRadius, angle(v1), angle(v2), v1.cross(v2).z < 0); - arc.ApplyMatrix(splitedCu1.Curve.OCS); - arc.Center = center; - res.arc = arc; - //延伸或者裁剪到圆弧点 - this.ExtendPt(splitedCu1, arcP1); - this.ExtendPt(splitedCu2, arcP2); - } - return res; - } - FilletPolyLineSelf(enRes1: PromptEntityResult, enRes2: PromptEntityResult): FilletRes - { - let pl = enRes1.Entity as Polyline; - - let param1 = pl.GetParamAtPoint(pl.GetClosestPointTo(enRes1.Point, false)); - let param2 = pl.GetParamAtPoint(pl.GetClosestPointTo(enRes2.Point, false)); - - if (param1 > param2) [param1, param2] = [param2, param1]; - - let parF1 = Math.floor(param1); - let parF2 = Math.floor(param2); - - //共线 - if (parF1 === parF2) - return; - - let c1 = pl.GetCurveAtParam(param1); - let c2 = pl.GetCurveAtParam(param2); - - if (equalv3(c1.GetFistDeriv(1).normalize(), c2.GetFistDeriv(0).normalize())) - return; - - let rem = parF2 - parF1; - if (rem === 1 || (rem + 1 === pl.EndParam))//相邻线段倒角 - { - let es1 = new PromptEntityResult(); - es1.Entity = c1; - es1.Point = enRes1.Point; - - let es2 = new PromptEntityResult(); - es2.Entity = c2; - es2.Point = enRes2.Point; - - let res = this.Fillet(es1, es2); - if (res && res.arc) - { - let pln = pl.Clone(); - //修正凸度 - if (res.cu1 instanceof Arc) - pln.SetBulgeAt(parF1, res.cu1.Bul); - if (res.cu2 instanceof Arc) - pln.SetBulgeAt(parF2, res.cu2.Bul); - - let sp2d = Vec3DTo2D(res.arc.StartPoint.applyMatrix4(pln.OCSInv)); - let ep2d = Vec3DTo2D(res.arc.EndPoint.applyMatrix4(pln.OCSInv)); - - let isNeighbor = rem === 1; - - //#IOX26 - if (isNeighbor && res.cu1 instanceof Arc && res.cu2 instanceof Arc) - { - let ins = res.cu1.IntersectWith(res.cu2, IntersectOption.OnBothOperands); - if (ins.length === 1 && !equalv3(pln.StartPoint, ins[0])) - isNeighbor = false; - } - - if (isNeighbor) - { - pln.SetPointAt(parF2, ep2d); - pln.AddVertexAt(parF2, sp2d); - pln.SetBulgeAt(parF2, res.arc.Bul); - } - else//首尾 - { - pln.SetPointAt(0, sp2d); - - let plPtCount = pln.NumberOfVertices; - if (pln.EndParam === plPtCount)//CloseMark - { - pln.AddVertexAt(plPtCount, ep2d); - pln.SetBulgeAt(plPtCount, -res.arc.Bul); - } - else - { - pln.SetPointAt(plPtCount - 1, ep2d); - - pln.SetBulgeAt(plPtCount - 1, -res.arc.Bul); - pln.AddVertexAt(plPtCount, sp2d); - } - } - - return { - cu1: pln, - cu2: undefined, - arc: undefined - }; - } - } - else//自交多段线 - { - let interPts = c1.IntersectWith(c2, IntersectOption.OnBothOperands); - if (interPts.length === 0) - return; - - if (interPts.length === 2 && c1.GetParamAtPoint(interPts[0]) > c1.GetParamAtPoint(interPts[1])) - interPts.reverse(); - - let ipt = interPts[0]; - let splitParam1 = Math.floor(param1) + c1.GetParamAtPoint(ipt); - let splitParam2 = Math.floor(param2) + c2.GetParamAtPoint(ipt); - - let cus = pl.GetSplitCurves([splitParam1, splitParam2]); - if (cus.length >= 2) - { - cus.splice(1, 1); - - let pl1 = cus[0]; - for (let i = 1; i < cus.length; i++) - pl1.Join(cus[i]); - - let es1 = new PromptEntityResult(); - es1.Entity = pl1; - es1.Point = c1.GetPointAtParam(0.1); - - let es2 = new PromptEntityResult(); - es2.Entity = pl1; - es2.Point = c2.GetPointAtParam(0.9); - - return this.FilletPolyLineSelf(es1, es2); - } - } - } - - FilletPolylineAndCurve(enRes1: PromptEntityResult, enRes2: PromptEntityResult): FilletRes - { - let arr1 = GetFilletCurve(enRes1); - let arr2 = GetFilletCurve(enRes2); - - let [cu1, paramF1] = arr1; - let [cu2, paramF2] = arr2; - - let es1 = new PromptEntityResult(); - es1.Entity = cu1; - es1.Point = enRes1.Point; - - let es2 = new PromptEntityResult(); - es2.Entity = cu2; - es2.Point = enRes2.Point; - - let fres = this.Fillet(es1, es2); - if (fres) - { - let pln: Polyline; - let isFirst = false; - let cus: Curve[] = []; - if (fres.cu1) - { - if (enRes1.Entity instanceof Polyline) - { - isFirst = true; - pln = enRes1.Entity.Clone(); - - let xcus = enRes1.Entity.Explode(); - xcus[paramF1] = fres.cu1; - cus.push(...xcus); - } - //@ts-ignore - else if (!enRes1.IsCircle) - cus.push(fres.cu1); - } - if (fres.arc) - cus.push(fres.arc); - - if (fres.cu2) - { - if (enRes2.Entity instanceof Polyline) - { - if (!pln) - pln = enRes2.Entity.Clone(); - - let xcus = enRes2.Entity.Explode(); - xcus[paramF2] = fres.cu2; - cus.push(...xcus); - } - //@ts-ignore - else if (!enRes2.IsCircle) - cus.push(fres.cu2); - } - - let groups = curveLinkGroup(cus); - for (let g of groups) - { - if (g.includes(fres.cu1) || g.includes(fres.cu2)) - { - pln.LineData = []; - pln.ApplyMatrix(pln.OCSInv); - pln.CloseMark = false; - for (let cu of g) - pln.Join(cu); - if (isFirst) - return { cu1: pln, cu2: undefined, arc: undefined }; - else - return { cu1: undefined, cu2: pln, arc: undefined }; - } - } - } - - return undefined; - } - - FilletPolyLineAllAngular(enRes1: PromptEntityResult): FilletRes - { - let pl = enRes1.Entity as Polyline; - - let cus = pl.Explode(); - let count = cus.length; - if (pl.IsClose) - cus.push(cus[0]); - - let ncus = []; - - for (let i = 0; i < count; i++) - { - let c1 = cus[i]; - let c2 = cus[i + 1]; - - ncus.push(c1); - - if (!c2) - break; - - if (equalv3(c1.GetFistDeriv(1).normalize(), c2.GetFistDeriv(0).normalize())) - continue; - - let es1 = new PromptEntityResult(); - es1.Entity = c1; - es1.Point = c1.EndPoint; - - let es2 = new PromptEntityResult(); - es2.Entity = c2; - es2.Point = c2.StartPoint; - - let fres = this.Fillet(es1, es2); - if (fres) - { - if (fres.cu1) - c1.CopyFrom(fres.cu1); - if (fres.cu2) - c2.CopyFrom(fres.cu2); - - if (fres.arc) - ncus.push(fres.arc); - } - } - - let pln = pl.Clone(); - pln.LineData = []; - pln.ApplyMatrix(pln.OCSInv); - pln.CloseMark = false; - for (let cu of ncus) - pln.Join(cu); - - pln.CloseMark = pl.CloseMark; - - return { - cu1: pln, - cu2: undefined, - arc: undefined - }; - } - - /** - * 平行线倒角 - * @param enRes1 - * @param enRes2 - * @returns parallel line - */ - FilletParallelLine(enRes1: PromptEntityResult, enRes2: PromptEntityResult): FilletRes - { - let l1 = enRes1.Entity as Line; - let l2 = enRes2.Entity as Line; - - let l1Derv = l1.GetFistDeriv(0); - if (!isParallelTo(l1Derv, l2.GetFistDeriv(0))) - return; - - let par1 = l2.GetClosestAtPoint(l1.StartPoint, true).param; - let par2 = l2.GetClosestAtPoint(l1.EndPoint, true).param; - if (!l1.ParamOnCurve(par1) && !l1.ParamOnCurve(par2)) - return; - - let lineClone1 = l1.Clone(); - let lineClone2 = l2.Clone(); - - let par = l1.GetClosestAtPoint(enRes1.Point, true).param; - - let parFix = Math.round(par); - let ptFix = lineClone1.GetPointAtParam(parFix); - let ptL2Fix = lineClone2.GetClosestAtPoint(ptFix, true).closestPt; - if ((par1 > par2) === (parFix === 1)) - lineClone2.StartPoint = ptL2Fix; - else - lineClone2.EndPoint = ptL2Fix; - - let arcCenter = midPoint(ptFix, ptL2Fix); - - let sv = ptFix.sub(arcCenter).applyMatrix4(l1.OCSInv); - let ev = ptL2Fix.sub(arcCenter).applyMatrix4(l2.OCSInv); - - if (parFix === 0) - l1Derv.negate(); - - let arc = new Arc(new Vector3(), ptFix.distanceTo(ptL2Fix) / 2, angle(sv), angle(ev), ev.cross(l1Derv.applyMatrix4(l1.OCSInv)).z > 0); - arc.ApplyMatrix(l1.OCS); - arc.Center = arcCenter; - - return { - cu1: lineClone1, - cu2: lineClone2, - arc - }; - } - - /** - * 计算圆弧与圆弧没有交点的情况下倒角结果. - * @param enRes1 - * @param enRes2 - * @returns arc and arc - */ - FilletArcAndArc(enRes1: PromptEntityResult, enRes2: PromptEntityResult): FilletRes - { - let a1 = enRes1.Entity as Arc; - let a2 = enRes2.Entity as Arc; - - let arcO1 = a1.GetOffsetCurves(this.m_FilletRadius * (a1.IsClockWise ? -1 : 1))[0]; - let arcO2 = a2.GetOffsetCurves(this.m_FilletRadius * (a2.IsClockWise ? -1 : 1))[0]; - - arcO1.ColorIndex = 6; - arcO2.ColorIndex = 6; - JigUtils.Draw(arcO1); - JigUtils.Draw(arcO2); - - //求交 - let intPts = arcO1.IntersectWith(arcO2, IntersectOption.ExtendBoth); - if (intPts.length === 0) - return;//无交点无法倒角 - - //两选择点的中点 - let clickMidp = midPoint(enRes1.Point, enRes2.Point);//用来选择合适的交点 - //选择合适的交点 - intPts.sort((p1, p2) => - { - return p1.distanceToSquared(clickMidp) - p2.distanceToSquared(clickMidp); - }); - - //圆弧圆心 - let narcCenter = intPts[0]; - let narcP1 = a1.GetClosestPointTo(narcCenter, true);//两圆弧和相切弧的交点 - let narcP2 = a2.GetClosestPointTo(narcCenter, true); - - let tempCircle = new Circle(narcCenter, this.m_FilletRadius); - let closestPt = a1.GetClosestPointTo(a2.Center, true);//两曲线距离对方圆心最近的点 - let narcMP = tempCircle.GetClosestPointTo(closestPt, false);//相切圆距离closestPt最近的点 - - //构造圆弧 - let narc = new Arc().ApplyMatrix(a1.OCS).FromThreePoint(narcP1, narcMP, narcP2); - - let a1Clone = a1.Clone(); - let a2Clone = a2.Clone(); - - let a1Param = a1.GetParamAtPoint(narcP1); - let a2Param = a2.GetParamAtPoint(narcP2); - - let a1Derv = a1.GetFistDeriv(a1Param).normalize(); - let a2Derv = a2.GetFistDeriv(a2Param).normalize(); - - let narcDerv0 = narc.GetFistDeriv(0).normalize(); - let narcDerv1 = narc.GetFistDeriv(1).normalize(); - - //裁剪圆弧 - if (equalv3(a1Derv, narcDerv0)) - a1Clone.EndPoint = narcP1; - else - a1Clone.StartPoint = narcP1 - - if (equalv3(a2Derv, narcDerv1)) - a2Clone.StartPoint = narcP2; - else - a2Clone.EndPoint = narcP2 - - return { - cu1: a1Clone, - cu2: a2Clone, - arc: narc - }; - } - - /** - * 计算直线与圆弧没有交点的情况下倒角结果. - * @param enRes1 - * @param enRes2 - * @returns line and cir - */ - FilletLineAndArc(enMap: (PromptEntityResult[])[], enRes1: PromptEntityResult): FilletRes | undefined - { - let lineRes = enMap[0][0]; - let arcRes = enMap[1][0]; - - let line = lineRes.Entity as Line; - let arc = arcRes.Entity as Arc; - - let dir = GetPointAtCurveDir(line, arc.Center) ? 1 : -1; - - let lineO = line.GetOffsetCurves(this.m_FilletRadius * dir)[0]; - let arcO = arc.GetOffsetCurves(this.m_FilletRadius * (arc.IsClockWise ? -1 : 1))[0];// tip:面积逆时针为正, 顺时针为负. - - lineO.ColorIndex = 6; - arcO.ColorIndex = 6; - JigUtils.Draw(lineO); - JigUtils.Draw(arcO); - - //求交 - let intPts = lineO.IntersectWith(arcO, IntersectOption.ExtendBoth); - if (intPts.length === 0) - return;//无交点无法倒角 - //两选择点的中点 - let clickMidp = midPoint(lineRes.Point, arcRes.Point); - //选择适合的交点。 - intPts.sort((p1, p2) => - { - return p1.distanceToSquared(clickMidp) - p2.distanceToSquared(clickMidp); - }); - //圆弧圆心 - let arcCenter = intPts[0]; - - let arcP1 = line.GetClosestPointTo(arcCenter, true);//直线与相切圆的交点 - let arcP2 = arc.GetClosestPointTo(arcCenter, true);//圆弧与相切圆的交点 - - let tempCircle = new Circle(arcCenter, this.m_FilletRadius); - let { closestPt, param } = line.GetClosestAtPoint(arc.Center, true); - let arcMP = tempCircle.GetClosestPointTo(closestPt, false); - - //构造圆弧 - let narc = new Arc().ApplyMatrix(arc.OCS).FromThreePoint(arcP1, arcMP, arcP2); - - //裁剪线 - let lineClone = line.Clone(); - let arcClone = arc.Clone(); - - let p1Param = line.GetParamAtPoint(arcP1); - if (p1Param > param) - lineClone.StartPoint = arcP1; - else - lineClone.EndPoint = arcP1; - - //裁剪圆弧 - let arcParam = arc.GetParamAtPoint(arcP2); - let arcDerv = arc.GetFistDeriv(arcParam).normalize(); - let narcDerv = narc.GetFistDeriv(1).normalize(); - - if (equalv3(arcDerv, narcDerv)) - arcClone.StartPoint = arcP2; - else - arcClone.EndPoint = arcP2; - - //先选直线为真 - if (enRes1.Entity === line) - return { - cu1: lineClone, - cu2: arcClone, - arc: narc - } - else - return { - cu1: arcClone, - cu2: lineClone, - arc: narc - } - } - - //获得两曲线的交点,并且排序交点. - private GetIntersectAndSort(enRes: PromptEntityResult, enRes2: PromptEntityResult, enType: number, enMap: PromptEntityResult[][]) - { - let interPts = enRes.Entity.IntersectWith(enRes2.Entity, IntersectOption.ExtendBoth); - if (interPts.length > 1) - { - let baseP: Vector3; - if (enType & 1) //如果有直线,那么用直线 - baseP = enMap[0][0].Point; - else if (enType === 2) //如果都是圆弧,那么取中点 - baseP = midPoint(enMap[1][0].Point, enMap[1][1].Point); - interPts.sort((p1, p2) => p1.distanceToSquared(baseP) - p2.distanceToSquared(baseP)); - } - return interPts; - } - - /** - * 对图元列表进行按位编码,类型映射如下: - * # 1:line 2:arc 4:polyline - * @param enRes - * @param enRes2 - * @returns - */ - private EnCode(enRes: PromptEntityResult, enRes2: PromptEntityResult) - { - let enMap: (PromptEntityResult[])[] = [[], [], []]; - let enType = 0; - enType |= Encode(enRes, enMap); - enType |= Encode(enRes2, enMap); - return { enType, enMap }; - } - - //计算曲线在相交处的切线,取真实的切线 - private ComputerDerv(cuRes: CurveExtend, dervSum: Vector3) - { - let derv: Vector3; - let cu = cuRes.Curve; - if (cuRes.ExtType === ExtendType.Start) - { - derv = cu.GetFistDeriv(0).normalize(); - dervSum.add(derv); - } - else - { - derv = cu.GetFistDeriv(cu.EndParam).normalize(); - dervSum.add(derv.clone().negate()); - } - return derv; - } - - // 计算曲线在相交处的切线,取起点到终点的切线.(当曲线相切时调用此方法.) - private ComputerDerv2(cuRes: CurveExtend, dervSum: Vector3) - { - let cu = cuRes.Curve; - let derv = cu.EndPoint.sub(cu.StartPoint); - if (cuRes.ExtType === ExtendType.Start) - dervSum.add(derv); - else - dervSum.add(derv.clone().negate()); - return derv; - } - - // 延伸或者裁剪到指定的圆弧的点. - ExtendPt(cu: CurveExtend, newP: Vector3) - { - if (cu.ExtType === ExtendType.Start) - cu.Curve.StartPoint = newP; - else - cu.Curve.EndPoint = newP; - } - - /** - * 切割或者延伸曲线,尖角化 - * - * @param cu 处理的曲线 - * @param interPt 原先的相交点 - * @param pickPoint 鼠标点击点 - * @returns 返回新的曲线 - */ - SplitCurve(enRes: PromptEntityResult, interPt: Vector3, interPts: Vector3[]): CurveExtend - { - let cu = enRes.Entity as Curve; - let pickPoint = enRes.Point; - - let cp = cu.GetClosestPointTo(pickPoint, false); - let cus = cu.GetSplitCurvesByPts([interPt]); - if (cus.length === 0) - cus.push(cu.Clone() as Curve); - else if (cus.length === 2) - cus.sort((c1: Curve, c2: Curve) => - { - return c1.GetClosestPointTo(cp, false).distanceTo(cp) - < c2.GetClosestPointTo(cp, false).distanceTo(cp) ? -1 : 1; - }); - - let exType = undefined; - - let newCu = cus[0]; - if (newCu instanceof Line) - newCu.Extend(newCu.GetParamAtPoint(interPt));//延伸到需要的长度 - else if (newCu instanceof Arc) - { - let arc = newCu as Arc; - if (cus.length === 1) - if (!cu.PtOnCurve(interPt)) - { - if (cu.PtOnCurve(interPts[1])) - { - //交点参数 - let iparam = arc.GetParamAtPoint(interPts[1]); - let pickParam = arc.GetParamAtAngle(arc.GetAngleAtPoint(pickPoint)); - - if (pickParam > iparam) - { - arc.EndAngle = arc.GetAngleAtPoint(interPt); - exType = ExtendType.End; - } - else - { - arc.StartAngle = arc.GetAngleAtPoint(interPt); - exType = ExtendType.Start; - } - } - else - { - //终点,起点 - interPts = interPts.sort((p1, p2) => - { - return arc.ComputeAnlge(arc.GetAngleAtPoint(p1)) - arc.ComputeAnlge(arc.GetAngleAtPoint(p2)); - }); - if (interPt === interPts[0]) - { - arc.EndAngle = arc.GetAngleAtPoint(interPt); - exType = ExtendType.End; - } - else - { - arc.StartAngle = arc.GetAngleAtPoint(interPt); - exType = ExtendType.Start; - } - } - } - } - - if (exType === undefined) - { - //使用equalv3时由于精度误差导致的判断错误 - if (interPt.manhattanDistanceTo(newCu.StartPoint) < interPt.manhattanDistanceTo(newCu.EndPoint)) - exType = ExtendType.Start; - else - exType = ExtendType.End; - } - return { Curve: newCu, ExtType: exType }; + this.m_FilletUtils.m_FilletRadius = this.m_FilletRadius; } async SelectCurve(keyword: KeyWord[]): Promise @@ -1010,7 +208,7 @@ export class CommandFillet implements Command return; } - let fres = this.FilletPolyLineAllAngular(e); + let fres = this.m_FilletUtils.FilletPolyLineAllAngular(e); if (fres && fres.cu1) JigUtils.Draw(fres.cu1); @@ -1041,7 +239,7 @@ export class CommandFillet implements Command lastPl.RestoreJigMaterial(); if (enRes.Status === PromptStatus.OK) { - let fres = this.FilletPolyLineAllAngular(enRes); + let fres = this.m_FilletUtils.FilletPolyLineAllAngular(enRes); if (fres && fres.cu1) enRes.Entity.CopyFrom(fres.cu1); } diff --git a/src/Add-on/FilletUtils.ts b/src/Add-on/FilletUtils.ts new file mode 100644 index 000000000..ccdd2c875 --- /dev/null +++ b/src/Add-on/FilletUtils.ts @@ -0,0 +1,811 @@ +import { Matrix4, Vector3 } from "three"; +import { curveLinkGroup, GetPointAtCurveDir, Vec3DTo2D } from "../Common/CurveUtils"; +import { Arc } from "../DatabaseServices/Arc"; +import { Circle } from "../DatabaseServices/Circle"; +import { Curve } from "../DatabaseServices/Curve"; +import { Line } from "../DatabaseServices/Line"; +import { Polyline } from "../DatabaseServices/Polyline"; +// import { JigUtils } from "../Editor/JigUtils"; +import { PromptEntityResult } from "../Editor/PromptResult"; +import { angle, equalv3, isParallelTo, midPoint } from "../Geometry/GeUtils"; +import { IntersectOption } from "../GraphicsSystem/IntersectWith"; + +function Encode(res: PromptEntityResult, enMap: (PromptEntityResult[])[]) +{ + if (res.Entity instanceof Line) + { + enMap[0].push(res); + return 1; + } + else if (res.Entity instanceof Arc) + { + enMap[1].push(res); + return 2; + } + else if (res.Entity instanceof Polyline) + { + enMap[2].push(res); + return 4; + } +} + +//把圆转换成圆弧,避免圆参与计算. +function CircleEnResToArc(enRes: PromptEntityResult) +{ + if (enRes.Entity instanceof Circle) + { + let an = angle(enRes.Point.clone().applyMatrix4(enRes.Entity.OCSInv)) + Math.PI; + let arc = new Arc(new Vector3(), enRes.Entity.Radius, an, an + 0.1); + arc.ApplyMatrix(enRes.Entity.OCS); + arc.Center = enRes.Entity.Center; + enRes.Entity = arc; + //@ts-ignore + enRes.IsCircle = true; + } +} + +function GetFilletCurve(enRes: PromptEntityResult): any[] +{ + if (enRes.Entity instanceof Polyline) + { + let pl = enRes.Entity; + let param = pl.GetParamAtPoint(pl.GetClosestPointTo(enRes.Point, false)); + let paramF = Math.floor(param); + return [pl.GetCurveAtParam(param), paramF]; + } + else + return [enRes.Entity, NaN]; +} + +enum ExtendType +{ + Start = 1, + End = 2, +} + +interface FilletRes +{ + cu1: Curve; + cu2: Curve; + arc: Arc; +} + +type CurveExtend = { Curve: Curve, ExtType: ExtendType }; + +export class FilletUtils +{ + m_FilletRadius: number; + Fillet(enRes1: PromptEntityResult, enRes2: PromptEntityResult): FilletRes + { + CircleEnResToArc(enRes1); + CircleEnResToArc(enRes2); + + let { enType, enMap } = this.EnCode(enRes1, enRes2); + if (enType === 4 && enRes1.Entity === enRes2.Entity) + return this.FilletPolyLineSelf(enRes1, enRes2); + else if (enType >= 4) + return this.FilletPolylineAndCurve(enRes1, enRes2); + + let interPts = this.GetIntersectAndSort(enRes1, enRes2, enType, enMap); + if (interPts.length === 0 + || (interPts.length === 1 && (enType & 2)))//圆弧相切 + { + if (enType === 1) + return this.FilletParallelLine(enRes1, enRes2); + else if (enType === 3) + return this.FilletLineAndArc(enMap, enRes1); + else if (enType === 2) + return this.FilletArcAndArc(enRes1, enRes2); + return; + } + + return this.FilletLineOrArc(enRes1, enRes2, interPts); + } + + private FilletLineOrArc(enRes1: PromptEntityResult, enRes2: PromptEntityResult, interPts: Vector3[]): FilletRes + { + let iPt = interPts[0]; + + //裁剪延伸,使两条线组成一个尖角 + let splitedCu1 = this.SplitCurve(enRes1, iPt, interPts); + let splitedCu2 = this.SplitCurve(enRes2, iPt, interPts); + + let fRadius = this.m_FilletRadius; + + if (enRes1.Entity instanceof Arc && enRes2.Entity instanceof Arc) + return this.FilletArcAndArc(enRes1, enRes2); + + let res: FilletRes = { cu1: splitedCu1.Curve, cu2: splitedCu2.Curve, arc: undefined }; + + if (fRadius > 0) + { + //角平分线向量. + let bisectorVec: Vector3 = new Vector3(); + let c1Derv = this.ComputerDerv(splitedCu1, bisectorVec); + let c2Derv = this.ComputerDerv(splitedCu2, bisectorVec); + + //方向相反 + if (equalv3(bisectorVec, new Vector3())) + return; + + //相切 + if (equalv3(c2Derv, c1Derv)) + { + bisectorVec.set(0, 0, 0); + c1Derv = this.ComputerDerv2(splitedCu1, bisectorVec); + c2Derv = this.ComputerDerv2(splitedCu2, bisectorVec); + } + let cu1RoOcsInv = new Matrix4().extractRotation(splitedCu1.Curve.OCSInv); + + [c1Derv, c2Derv, bisectorVec].forEach(v => v.applyMatrix4(cu1RoOcsInv)); + + let offCu1 = splitedCu1.Curve.GetOffsetCurves( + fRadius * -Math.sign(c1Derv.cross(bisectorVec).z))[0]; + let offCu2 = splitedCu2.Curve.GetOffsetCurves( + fRadius * -Math.sign(c2Derv.cross(bisectorVec).z))[0]; + + if (!offCu1 || !offCu2) + return; + + // //测试绘制 + // offCu1.ColorIndex = 6; + // offCu2.ColorIndex = 6; + // JigUtils.Draw(offCu1.Clone()); + // JigUtils.Draw(offCu2.Clone()); + + let center = offCu1.IntersectWith(offCu2, IntersectOption.OnBothOperands) + .sort((p1, p2) => + { + return p1.distanceToSquared(iPt) - p2.distanceToSquared(iPt); + })[0]; + + if (!center) + return; + + let arcP1 = splitedCu1.Curve.GetClosestPointTo(center, true); + let arcP2 = splitedCu2.Curve.GetClosestPointTo(center, true); + if (!splitedCu1.Curve.PtOnCurve(arcP1) || !splitedCu2.Curve.PtOnCurve(arcP2)) + return; + + //时针校验 + let v1 = arcP1.clone().sub(center).applyMatrix4(cu1RoOcsInv); + let v2 = arcP2.clone().sub(center).applyMatrix4(cu1RoOcsInv); + + //绘制圆弧 + let arc = new Arc(new Vector3(), this.m_FilletRadius, angle(v1), angle(v2), v1.cross(v2).z < 0); + arc.ApplyMatrix(splitedCu1.Curve.OCS); + arc.Center = center; + res.arc = arc; + //延伸或者裁剪到圆弧点 + this.ExtendPt(splitedCu1, arcP1); + this.ExtendPt(splitedCu2, arcP2); + } + return res; + } + private FilletPolyLineSelf(enRes1: PromptEntityResult, enRes2: PromptEntityResult): FilletRes + { + let pl = enRes1.Entity as Polyline; + + let param1 = pl.GetParamAtPoint(pl.GetClosestPointTo(enRes1.Point, false)); + let param2 = pl.GetParamAtPoint(pl.GetClosestPointTo(enRes2.Point, false)); + + if (param1 > param2) [param1, param2] = [param2, param1]; + + let parF1 = Math.floor(param1); + let parF2 = Math.floor(param2); + + //共线 + if (parF1 === parF2) + return; + + let c1 = pl.GetCurveAtParam(param1); + let c2 = pl.GetCurveAtParam(param2); + + if (equalv3(c1.GetFistDeriv(1).normalize(), c2.GetFistDeriv(0).normalize())) + return; + + let rem = parF2 - parF1; + if (rem === 1 || (rem + 1 === pl.EndParam))//相邻线段倒角 + { + let es1 = new PromptEntityResult(); + es1.Entity = c1; + es1.Point = enRes1.Point; + + let es2 = new PromptEntityResult(); + es2.Entity = c2; + es2.Point = enRes2.Point; + + let res = this.Fillet(es1, es2); + if (res && res.arc) + { + let pln = pl.Clone(); + //修正凸度 + if (res.cu1 instanceof Arc) + pln.SetBulgeAt(parF1, res.cu1.Bul); + if (res.cu2 instanceof Arc) + pln.SetBulgeAt(parF2, res.cu2.Bul); + + let sp2d = Vec3DTo2D(res.arc.StartPoint.applyMatrix4(pln.OCSInv)); + let ep2d = Vec3DTo2D(res.arc.EndPoint.applyMatrix4(pln.OCSInv)); + + let isNeighbor = rem === 1; + + //#IOX26 + if (isNeighbor && res.cu1 instanceof Arc && res.cu2 instanceof Arc) + { + let ins = res.cu1.IntersectWith(res.cu2, IntersectOption.OnBothOperands); + if (ins.length === 1 && !equalv3(pln.StartPoint, ins[0])) + isNeighbor = false; + } + + if (isNeighbor) + { + pln.SetPointAt(parF2, ep2d); + pln.AddVertexAt(parF2, sp2d); + pln.SetBulgeAt(parF2, res.arc.Bul); + } + else//首尾 + { + pln.SetPointAt(0, sp2d); + + let plPtCount = pln.NumberOfVertices; + if (pln.EndParam === plPtCount)//CloseMark + { + pln.AddVertexAt(plPtCount, ep2d); + pln.SetBulgeAt(plPtCount, -res.arc.Bul); + } + else + { + pln.SetPointAt(plPtCount - 1, ep2d); + + pln.SetBulgeAt(plPtCount - 1, -res.arc.Bul); + pln.AddVertexAt(plPtCount, sp2d); + } + } + + return { + cu1: pln, + cu2: undefined, + arc: undefined + }; + } + } + else//自交多段线 + { + let interPts = c1.IntersectWith(c2, IntersectOption.OnBothOperands); + if (interPts.length === 0) + return; + + if (interPts.length === 2 && c1.GetParamAtPoint(interPts[0]) > c1.GetParamAtPoint(interPts[1])) + interPts.reverse(); + + let ipt = interPts[0]; + let splitParam1 = Math.floor(param1) + c1.GetParamAtPoint(ipt); + let splitParam2 = Math.floor(param2) + c2.GetParamAtPoint(ipt); + + let cus = pl.GetSplitCurves([splitParam1, splitParam2]); + if (cus.length >= 2) + { + cus.splice(1, 1); + + let pl1 = cus[0]; + for (let i = 1; i < cus.length; i++) + pl1.Join(cus[i]); + + let es1 = new PromptEntityResult(); + es1.Entity = pl1; + es1.Point = c1.GetPointAtParam(0.1); + + let es2 = new PromptEntityResult(); + es2.Entity = pl1; + es2.Point = c2.GetPointAtParam(0.9); + + return this.FilletPolyLineSelf(es1, es2); + } + } + } + + private FilletPolylineAndCurve(enRes1: PromptEntityResult, enRes2: PromptEntityResult): FilletRes + { + let arr1 = GetFilletCurve(enRes1); + let arr2 = GetFilletCurve(enRes2); + + let [cu1, paramF1] = arr1; + let [cu2, paramF2] = arr2; + + let es1 = new PromptEntityResult(); + es1.Entity = cu1; + es1.Point = enRes1.Point; + + let es2 = new PromptEntityResult(); + es2.Entity = cu2; + es2.Point = enRes2.Point; + + let fres = this.Fillet(es1, es2); + if (fres) + { + let pln: Polyline; + let isFirst = false; + let cus: Curve[] = []; + if (fres.cu1) + { + if (enRes1.Entity instanceof Polyline) + { + isFirst = true; + pln = enRes1.Entity.Clone(); + + let xcus = enRes1.Entity.Explode(); + xcus[paramF1] = fres.cu1; + cus.push(...xcus); + } + //@ts-ignore + else if (!enRes1.IsCircle) + cus.push(fres.cu1); + } + if (fres.arc) + cus.push(fres.arc); + + if (fres.cu2) + { + if (enRes2.Entity instanceof Polyline) + { + if (!pln) + pln = enRes2.Entity.Clone(); + + let xcus = enRes2.Entity.Explode(); + xcus[paramF2] = fres.cu2; + cus.push(...xcus); + } + //@ts-ignore + else if (!enRes2.IsCircle) + cus.push(fres.cu2); + } + + let groups = curveLinkGroup(cus); + for (let g of groups) + { + if (g.includes(fres.cu1) || g.includes(fres.cu2)) + { + pln.LineData = []; + pln.ApplyMatrix(pln.OCSInv); + pln.CloseMark = false; + for (let cu of g) + pln.Join(cu); + if (isFirst) + return { cu1: pln, cu2: undefined, arc: undefined }; + else + return { cu1: undefined, cu2: pln, arc: undefined }; + } + } + } + + return undefined; + } + + FilletPolyLineAllAngular(enRes1: PromptEntityResult): FilletRes + { + let pl = enRes1.Entity as Polyline; + + let cus = pl.Explode(); + let count = cus.length; + if (pl.IsClose) + cus.push(cus[0]); + + let ncus = []; + + for (let i = 0; i < count; i++) + { + let c1 = cus[i]; + let c2 = cus[i + 1]; + + ncus.push(c1); + + if (!c2) + break; + + if (equalv3(c1.GetFistDeriv(1).normalize(), c2.GetFistDeriv(0).normalize())) + continue; + + let es1 = new PromptEntityResult(); + es1.Entity = c1; + es1.Point = c1.EndPoint; + + let es2 = new PromptEntityResult(); + es2.Entity = c2; + es2.Point = c2.StartPoint; + + let fres = this.Fillet(es1, es2); + if (fres) + { + if (fres.cu1) + c1.CopyFrom(fres.cu1); + if (fres.cu2) + c2.CopyFrom(fres.cu2); + + if (fres.arc) + ncus.push(fres.arc); + } + } + + let pln = pl.Clone(); + pln.LineData = []; + pln.ApplyMatrix(pln.OCSInv); + pln.CloseMark = false; + for (let cu of ncus) + pln.Join(cu); + + pln.CloseMark = pl.CloseMark; + + return { + cu1: pln, + cu2: undefined, + arc: undefined + }; + } + + /** + * 平行线倒角 + * @param enRes1 + * @param enRes2 + * @returns parallel line + */ + private FilletParallelLine(enRes1: PromptEntityResult, enRes2: PromptEntityResult): FilletRes + { + let l1 = enRes1.Entity as Line; + let l2 = enRes2.Entity as Line; + + let l1Derv = l1.GetFistDeriv(0); + if (!isParallelTo(l1Derv, l2.GetFistDeriv(0))) + return; + + let par1 = l2.GetClosestAtPoint(l1.StartPoint, true).param; + let par2 = l2.GetClosestAtPoint(l1.EndPoint, true).param; + if (!l1.ParamOnCurve(par1) && !l1.ParamOnCurve(par2)) + return; + + let lineClone1 = l1.Clone(); + let lineClone2 = l2.Clone(); + + let par = l1.GetClosestAtPoint(enRes1.Point, true).param; + + let parFix = Math.round(par); + let ptFix = lineClone1.GetPointAtParam(parFix); + let ptL2Fix = lineClone2.GetClosestAtPoint(ptFix, true).closestPt; + if ((par1 > par2) === (parFix === 1)) + lineClone2.StartPoint = ptL2Fix; + else + lineClone2.EndPoint = ptL2Fix; + + let arcCenter = midPoint(ptFix, ptL2Fix); + + let sv = ptFix.sub(arcCenter).applyMatrix4(l1.OCSInv); + let ev = ptL2Fix.sub(arcCenter).applyMatrix4(l2.OCSInv); + + if (parFix === 0) + l1Derv.negate(); + + let arc = new Arc(new Vector3(), ptFix.distanceTo(ptL2Fix) / 2, angle(sv), angle(ev), ev.cross(l1Derv.applyMatrix4(l1.OCSInv)).z > 0); + arc.ApplyMatrix(l1.OCS); + arc.Center = arcCenter; + + return { + cu1: lineClone1, + cu2: lineClone2, + arc + }; + } + + /** + * 计算圆弧与圆弧没有交点的情况下倒角结果. + * @param enRes1 + * @param enRes2 + * @returns arc and arc + */ + private FilletArcAndArc(enRes1: PromptEntityResult, enRes2: PromptEntityResult): FilletRes + { + let a1 = enRes1.Entity as Arc; + let a2 = enRes2.Entity as Arc; + + let arcO1 = a1.GetOffsetCurves(this.m_FilletRadius * (a1.IsClockWise ? -1 : 1))[0]; + let arcO2 = a2.GetOffsetCurves(this.m_FilletRadius * (a2.IsClockWise ? -1 : 1))[0]; + + // arcO1.ColorIndex = 6; + // arcO2.ColorIndex = 6; + // JigUtils.Draw(arcO1); + // JigUtils.Draw(arcO2); + + //求交 + let intPts = arcO1.IntersectWith(arcO2, IntersectOption.ExtendBoth); + if (intPts.length === 0) + return;//无交点无法倒角 + + //两选择点的中点 + let clickMidp = midPoint(enRes1.Point, enRes2.Point);//用来选择合适的交点 + //选择合适的交点 + intPts.sort((p1, p2) => + { + return p1.distanceToSquared(clickMidp) - p2.distanceToSquared(clickMidp); + }); + + //圆弧圆心 + let narcCenter = intPts[0]; + let narcP1 = a1.GetClosestPointTo(narcCenter, true);//两圆弧和相切弧的交点 + let narcP2 = a2.GetClosestPointTo(narcCenter, true); + + let tempCircle = new Circle(narcCenter, this.m_FilletRadius); + let closestPt = a1.GetClosestPointTo(a2.Center, true);//两曲线距离对方圆心最近的点 + let narcMP = tempCircle.GetClosestPointTo(closestPt, false);//相切圆距离closestPt最近的点 + + //构造圆弧 + let narc = new Arc().ApplyMatrix(a1.OCS).FromThreePoint(narcP1, narcMP, narcP2); + + let a1Clone = a1.Clone(); + let a2Clone = a2.Clone(); + + let a1Param = a1.GetParamAtPoint(narcP1); + let a2Param = a2.GetParamAtPoint(narcP2); + + let a1Derv = a1.GetFistDeriv(a1Param).normalize(); + let a2Derv = a2.GetFistDeriv(a2Param).normalize(); + + let narcDerv0 = narc.GetFistDeriv(0).normalize(); + let narcDerv1 = narc.GetFistDeriv(1).normalize(); + + //裁剪圆弧 + if (equalv3(a1Derv, narcDerv0)) + a1Clone.EndPoint = narcP1; + else + a1Clone.StartPoint = narcP1 + + if (equalv3(a2Derv, narcDerv1)) + a2Clone.StartPoint = narcP2; + else + a2Clone.EndPoint = narcP2 + + return { + cu1: a1Clone, + cu2: a2Clone, + arc: narc + }; + } + + /** + * 计算直线与圆弧没有交点的情况下倒角结果. + * @param enRes1 + * @param enRes2 + * @returns line and cir + */ + private FilletLineAndArc(enMap: (PromptEntityResult[])[], enRes1: PromptEntityResult): FilletRes | undefined + { + let lineRes = enMap[0][0]; + let arcRes = enMap[1][0]; + + let line = lineRes.Entity as Line; + let arc = arcRes.Entity as Arc; + + let dir = GetPointAtCurveDir(line, arc.Center) ? 1 : -1; + + let lineO = line.GetOffsetCurves(this.m_FilletRadius * dir)[0]; + let arcO = arc.GetOffsetCurves(this.m_FilletRadius * (arc.IsClockWise ? -1 : 1))[0];// tip:面积逆时针为正, 顺时针为负. + + // lineO.ColorIndex = 6; + // arcO.ColorIndex = 6; + // JigUtils.Draw(lineO); + // JigUtils.Draw(arcO); + + //求交 + let intPts = lineO.IntersectWith(arcO, IntersectOption.ExtendBoth); + if (intPts.length === 0) + return;//无交点无法倒角 + + //两选择点的中点 + let clickMidp = midPoint(lineRes.Point, arcRes.Point); + //选择适合的交点。 + intPts.sort((p1, p2) => + { + return p1.distanceToSquared(clickMidp) - p2.distanceToSquared(clickMidp); + }); + //圆弧圆心 + let arcCenter = intPts[0]; + + let arcP1 = line.GetClosestPointTo(arcCenter, true);//直线与相切圆的交点 + let arcP2 = arc.GetClosestPointTo(arcCenter, true);//圆弧与相切圆的交点 + + let tempCircle = new Circle(arcCenter, this.m_FilletRadius); + let { closestPt, param } = line.GetClosestAtPoint(arc.Center, true); + let arcMP = tempCircle.GetClosestPointTo(closestPt, false); + + //构造圆弧 + let narc = new Arc().ApplyMatrix(arc.OCS).FromThreePoint(arcP1, arcMP, arcP2); + + //裁剪线 + let lineClone = line.Clone(); + let arcClone = arc.Clone(); + + let p1Param = line.GetParamAtPoint(arcP1); + if (p1Param > param) + lineClone.StartPoint = arcP1; + else + lineClone.EndPoint = arcP1; + + //裁剪圆弧 + let arcParam = arc.GetParamAtPoint(arcP2); + let arcDerv = arc.GetFistDeriv(arcParam).normalize(); + let narcDerv = narc.GetFistDeriv(1).normalize(); + + if (equalv3(arcDerv, narcDerv)) + arcClone.StartPoint = arcP2; + else + arcClone.EndPoint = arcP2; + + //先选直线为真 + if (enRes1.Entity === line) + return { + cu1: lineClone, + cu2: arcClone, + arc: narc + } + else + return { + cu1: arcClone, + cu2: lineClone, + arc: narc + } + } + + //获得两曲线的交点,并且排序交点. + private GetIntersectAndSort(enRes: PromptEntityResult, enRes2: PromptEntityResult, enType: number, enMap: PromptEntityResult[][]) + { + let interPts = enRes.Entity.IntersectWith(enRes2.Entity, IntersectOption.ExtendBoth); + if (interPts.length > 1) + { + let baseP: Vector3; + if (enType & 1) //如果有直线,那么用直线 + baseP = enMap[0][0].Point; + else if (enType === 2) //如果都是圆弧,那么取中点 + baseP = midPoint(enMap[1][0].Point, enMap[1][1].Point); + interPts.sort((p1, p2) => p1.distanceToSquared(baseP) - p2.distanceToSquared(baseP)); + } + return interPts; + } + + /** + * 对图元列表进行按位编码,类型映射如下: + * # 1:line 2:arc 4:polyline + * @param enRes + * @param enRes2 + * @returns + */ + private EnCode(enRes: PromptEntityResult, enRes2: PromptEntityResult) + { + let enMap: (PromptEntityResult[])[] = [[], [], []]; + let enType = 0; + enType |= Encode(enRes, enMap); + enType |= Encode(enRes2, enMap); + return { enType, enMap }; + } + + //计算曲线在相交处的切线,取真实的切线 + private ComputerDerv(cuRes: CurveExtend, dervSum: Vector3) + { + let derv: Vector3; + let cu = cuRes.Curve; + if (cuRes.ExtType === ExtendType.Start) + { + derv = cu.GetFistDeriv(0).normalize(); + dervSum.add(derv); + } + else + { + derv = cu.GetFistDeriv(cu.EndParam).normalize(); + dervSum.add(derv.clone().negate()); + } + return derv; + } + + // 计算曲线在相交处的切线,取起点到终点的切线.(当曲线相切时调用此方法.) + private ComputerDerv2(cuRes: CurveExtend, dervSum: Vector3) + { + let cu = cuRes.Curve; + let derv = cu.EndPoint.sub(cu.StartPoint); + if (cuRes.ExtType === ExtendType.Start) + dervSum.add(derv); + else + dervSum.add(derv.clone().negate()); + return derv; + } + + // 延伸或者裁剪到指定的圆弧的点. + private ExtendPt(cu: CurveExtend, newP: Vector3) + { + if (cu.ExtType === ExtendType.Start) + cu.Curve.StartPoint = newP; + else + cu.Curve.EndPoint = newP; + } + + /** + * 切割或者延伸曲线,尖角化 + * + * @param cu 处理的曲线 + * @param interPt 原先的相交点 + * @param pickPoint 鼠标点击点 + * @returns 返回新的曲线 + */ + private SplitCurve(enRes: PromptEntityResult, interPt: Vector3, interPts: Vector3[]): CurveExtend + { + let cu = enRes.Entity as Curve; + let pickPoint = enRes.Point; + + let cp = cu.GetClosestPointTo(pickPoint, false); + let cus = cu.GetSplitCurvesByPts([interPt]); + if (cus.length === 0) + cus.push(cu.Clone() as Curve); + else if (cus.length === 2) + cus.sort((c1: Curve, c2: Curve) => + { + return c1.GetClosestPointTo(cp, false).distanceTo(cp) + < c2.GetClosestPointTo(cp, false).distanceTo(cp) ? -1 : 1; + }); + + let exType = undefined; + + let newCu = cus[0]; + if (newCu instanceof Line) + newCu.Extend(newCu.GetParamAtPoint(interPt));//延伸到需要的长度 + else if (newCu instanceof Arc) + { + let arc = newCu as Arc; + if (cus.length === 1) + if (!cu.PtOnCurve(interPt)) + { + if (cu.PtOnCurve(interPts[1])) + { + //交点参数 + let iparam = arc.GetParamAtPoint(interPts[1]); + let pickParam = arc.GetParamAtAngle(arc.GetAngleAtPoint(pickPoint)); + + if (pickParam > iparam) + { + arc.EndAngle = arc.GetAngleAtPoint(interPt); + exType = ExtendType.End; + } + else + { + arc.StartAngle = arc.GetAngleAtPoint(interPt); + exType = ExtendType.Start; + } + } + else + { + //终点,起点 + interPts = interPts.sort((p1, p2) => + { + return arc.ComputeAnlge(arc.GetAngleAtPoint(p1)) - arc.ComputeAnlge(arc.GetAngleAtPoint(p2)); + }); + if (interPt === interPts[0]) + { + arc.EndAngle = arc.GetAngleAtPoint(interPt); + exType = ExtendType.End; + } + else + { + arc.StartAngle = arc.GetAngleAtPoint(interPt); + exType = ExtendType.Start; + } + } + } + } + + if (exType === undefined) + { + //使用equalv3时由于精度误差导致的判断错误 + if (interPt.manhattanDistanceTo(newCu.StartPoint) < interPt.manhattanDistanceTo(newCu.EndPoint)) + exType = ExtendType.Start; + else + exType = ExtendType.End; + } + return { Curve: newCu, ExtType: exType }; + } + +} diff --git a/src/Editor/CommandRegister.ts b/src/Editor/CommandRegister.ts index 6a7ff463d..86896a69a 100644 --- a/src/Editor/CommandRegister.ts +++ b/src/Editor/CommandRegister.ts @@ -183,7 +183,7 @@ export function registerCommand() commandMachine.RegisterCommand("ptl", new TestDrawPointLight()); - commandMachine.RegisterCommand("pt", new Command_CopyPoint()); + commandMachine.RegisterCommand("ptcopy", new Command_CopyPoint()); commandMachine.RegisterCommand("dl", new TestDrawDirectionalLight()); diff --git a/src/Editor/PromptResult.ts b/src/Editor/PromptResult.ts index 009cbf0b8..65aaf8237 100644 --- a/src/Editor/PromptResult.ts +++ b/src/Editor/PromptResult.ts @@ -73,10 +73,15 @@ export class PromptDistendResult extends PromptResult export class PromptEntityResult extends PromptResult { - //选择到的图形 - Entity?: Entity; - //点取的点 - Point?: Vector3; + constructor( + //选择到的图形 + public Entity?: Entity, + //点取的点 + public Point?: Vector3 + ) + { + super(); + } } export class PromptSsgetResult extends PromptResult diff --git a/wallaby.conf.js b/wallaby.conf.js index f83d82ece..0a9f0d406 100644 --- a/wallaby.conf.js +++ b/wallaby.conf.js @@ -2,10 +2,11 @@ module.exports = function (wallaby) { return { files: [ - 'tsconfig.json', // <-- + 'tsconfig.json', 'src/**/*.ts*', 'src/**/*.tsx*', - 'src/**/*.js*' + 'src/**/*.js*', + '__test__/**/*.util.ts', ], tests: ['__test__/**/*.test.ts*'],