diff --git a/__test__/Geometry/__snapshots__/ellipse.test.ts.snap b/__test__/Geometry/__snapshots__/ellipse.test.ts.snap new file mode 100644 index 000000000..4d772dc3b --- /dev/null +++ b/__test__/Geometry/__snapshots__/ellipse.test.ts.snap @@ -0,0 +1,316 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`完整椭圆 GetClosestPointTo 1`] = ` +Vector3 { + "x": 161.75177046648375, + "y": 76.24839906616579, + "z": 0, +} +`; + +exports[`完整椭圆 GetClosestPointTo 2`] = ` +Vector3 { + "x": -133.46958799601072, + "y": -84.55612959938519, + "z": 0, +} +`; + +exports[`完整椭圆 offset 1`] = ` +Array [ + Vector3 { + "x": 250, + "y": 0, + "z": 0, + }, + Vector3 { + "x": -250, + "y": 0, + "z": 0, + }, + Vector3 { + "x": 0, + "y": 100, + "z": 0, + }, + Vector3 { + "x": 0, + "y": -100, + "z": 0, + }, + Vector3 { + "x": 250, + "y": 0, + "z": 0, + }, + Vector3 { + "x": 0, + "y": 0, + "z": 0, + }, +] +`; + +exports[`完整椭圆 offset 2`] = ` +Array [ + Vector3 { + "x": 200, + "y": 0, + "z": 0, + }, + Vector3 { + "x": -200, + "y": 0, + "z": 0, + }, + Vector3 { + "x": 0, + "y": 50, + "z": 0, + }, + Vector3 { + "x": 0, + "y": -50, + "z": 0, + }, + Vector3 { + "x": 200, + "y": 0, + "z": 0, + }, + Vector3 { + "x": 0, + "y": 0, + "z": 0, + }, +] +`; + +exports[`完整椭圆 offset 3`] = ` +Array [ + Vector3 { + "x": 450, + "y": 0, + "z": 0, + }, + Vector3 { + "x": -450, + "y": 0, + "z": 0, + }, + Vector3 { + "x": 0, + "y": 300, + "z": 0, + }, + Vector3 { + "x": 0, + "y": -300, + "z": 0, + }, + Vector3 { + "x": 450, + "y": 0, + "z": 0, + }, + Vector3 { + "x": 0, + "y": 0, + "z": 0, + }, +] +`; + +exports[`完整椭圆 几何数据 1`] = `1150.6559640322623`; + +exports[`完整椭圆 几何数据 2`] = `78539.81633974482`; + +exports[`非完整椭圆 GetParamAtDist 1`] = `242.30725311615984`; + +exports[`非完整椭圆 GetParamAtDist 2`] = `605.7681327903996`; + +exports[`非完整椭圆 GetParamAtDist 3`] = `969.2290124646394`; + +exports[`非完整椭圆 GetPointAtParam 1`] = ` +Vector3 { + "x": -128.24678398779253, + "y": 479.9479365060514, + "z": 0, +} +`; + +exports[`非完整椭圆 GetPointAtParam 2`] = ` +Vector3 { + "x": 239.42920866477306, + "y": 323.0232500594477, + "z": 0, +} +`; + +exports[`非完整椭圆 offset 1`] = ` +Array [ + Vector3 { + "x": 350.2058941499969, + "y": 373.22534255869425, + "z": 0, + }, + Vector3 { + "x": -290.9818601387027, + "y": 910.1549254183373, + "z": 0, + }, + Vector3 { + "x": 174.41678315228742, + "y": 814.612330454892, + "z": 0, + }, + Vector3 { + "x": -115.19274914099324, + "y": 468.7679375221395, + "z": 0, + }, + Vector3 { + "x": -298.4690575735684, + "y": 706.7486518071757, + "z": 0, + }, + Vector3 { + "x": 245.79986047889065, + "y": 746.5732067866242, + "z": 0, + }, + Vector3 { + "x": 29.61201700564709, + "y": 641.6901339885158, + "z": 0, + }, +] +`; + +exports[`非完整椭圆 offset 2`] = ` +Array [ + Vector3 { + "x": 311.87157824105714, + "y": 405.3264363686194, + "z": 0, + }, + Vector3 { + "x": -252.64754422976296, + "y": 878.0538316084121, + "z": 0, + }, + Vector3 { + "x": 142.3156893423623, + "y": 776.2780145459523, + "z": 0, + }, + Vector3 { + "x": -83.09165533106811, + "y": 507.10225343107925, + "z": 0, + }, + Vector3 { + "x": -248.70057555417316, + "y": 711.5547102218358, + "z": 0, + }, + Vector3 { + "x": 205.57861428580685, + "y": 716.8704905021605, + "z": 0, + }, + Vector3 { + "x": 29.61201700564709, + "y": 641.6901339885158, + "z": 0, + }, +] +`; + +exports[`非完整椭圆 offset 3`] = ` +Array [ + Vector3 { + "x": 273.5372623321175, + "y": 437.42753017854454, + "z": 0, + }, + Vector3 { + "x": -214.3132283208233, + "y": 845.952737798487, + "z": 0, + }, + Vector3 { + "x": 110.21459553243717, + "y": 737.9436986370126, + "z": 0, + }, + Vector3 { + "x": -50.99056152114299, + "y": 545.4365693400189, + "z": 0, + }, + Vector3 { + "x": -198.93209353477783, + "y": 716.3607686364959, + "z": 0, + }, + Vector3 { + "x": 165.35736809272308, + "y": 687.1677742176968, + "z": 0, + }, + Vector3 { + "x": 29.61201700564709, + "y": 641.6901339885158, + "z": 0, + }, +] +`; + +exports[`非完整椭圆 offset 4`] = ` +Array [ + Vector3 { + "x": 503.54315778575557, + "y": 244.82096731899384, + "z": 0, + }, + Vector3 { + "x": -444.3191237744614, + "y": 1038.5593006580377, + "z": 0, + }, + Vector3 { + "x": 302.8211583919879, + "y": 967.9495940906507, + "z": 0, + }, + Vector3 { + "x": -243.59712438069374, + "y": 315.43067388638076, + "z": 0, + }, + Vector3 { + "x": -497.5429856511497, + "y": 687.5244181485352, + "z": 0, + }, + Vector3 { + "x": 406.6848452512257, + "y": 865.3840719244788, + "z": 0, + }, + Vector3 { + "x": 29.61201700564709, + "y": 641.6901339885158, + "z": 0, + }, +] +`; + +exports[`非完整椭圆 几何参数 1`] = `885.1880105792319`; + +exports[`非完整椭圆 几何参数 2`] = `72828.18768552072`; + +exports[`非完整椭圆 几何参数 3`] = `1211.5362655807992`; + +exports[`非完整椭圆 几何参数 4`] = `72828.18768552072`; diff --git a/__test__/Geometry/ellipse.test.ts b/__test__/Geometry/ellipse.test.ts new file mode 100644 index 000000000..3ac9e262c --- /dev/null +++ b/__test__/Geometry/ellipse.test.ts @@ -0,0 +1,358 @@ +import { Vector3 } from "three"; +import { Ellipse } from "../../src/DatabaseServices/Ellipse"; +import { equalv3 } from "../../src/Geometry/GeUtils"; +import { Status } from "../../src/Common/Status"; +import { Line } from "../../src/DatabaseServices/Line"; +import { Arc } from "../../src/DatabaseServices/Arc"; +import { Circle } from "../../src/DatabaseServices/Circle"; +import { LoadCurvesFromFileData } from "../Utils/LoadEntity.util"; +import { Polyline } from "../../src/DatabaseServices/Polyline"; +import { Curve } from "../../src/DatabaseServices/Curve"; + +describe('完整椭圆', () => +{ + let el = new Ellipse(new Vector3(), 250, 100, 0); + let p1 = el.GetPointAtParam(0); + let p2 = el.GetPointAtParam(0.1); + let p3 = el.GetPointAtParam(0.25); + let p4 = el.GetPointAtParam(0.5); + let p5 = el.GetPointAtParam(0.6); + let p6 = el.GetPointAtParam(0.75); + let p7 = el.GetPointAtParam(0.8); + + test("几何数据", () => + { + expect(el.Length).toMatchSnapshot(); + expect(el.Length).toBeCloseTo(1150.66); + expect(el.Area).toMatchSnapshot(); + expect(el.Area).toBeCloseTo(78539.82); + }) + test("PtOnCurve", () => + { + let pt = new Vector3(160.20983328761838, 76.76750283077186); + [pt, new Vector3(250), new Vector3(0, -100)].forEach(p => + { + expect(el.PtOnCurve(p)).toBeTruthy(); + }) + }); + test("GetPointAtParam", () => + { + expect(equalv3(p1, new Vector3(250))).toBeTruthy(); + expect(equalv3(p2, new Vector3(202.25424859373686, 58.778525229247315))).toBeTruthy(); + expect(equalv3(p3, new Vector3(0, 100))).toBeTruthy(); + expect(equalv3(p4, new Vector3(-250))).toBeTruthy(); + }); + test("GetParamAtPoint", () => + { + expect(el.GetParamAtPoint(p1)).toBeCloseTo(0); + expect(el.GetParamAtPoint(p2)).toBeCloseTo(0.1); + expect(el.GetParamAtPoint(p3)).toBeCloseTo(0.25); + expect(el.GetParamAtPoint(p5)).toBeCloseTo(0.6); + expect(el.GetParamAtPoint(p7)).toBeCloseTo(0.8); + }); + test("GetDistAtParam", () => + { + expect(el.GetDistAtParam(0.5)).toBeCloseTo(el.Length * 0.5); + }); + test("GetParamAtDist", () => + { + expect(el.GetParamAtDist(el.Length * 0.5)).toBeCloseTo(0.5); + }); + test("GetFistDeriv", () => + { + expect(equalv3(el.GetFistDeriv(0), new Vector3(0, 1))).toBeTruthy(); + expect(equalv3(el.GetFistDeriv(0.5), new Vector3(0, -1))).toBeTruthy(); + expect(equalv3(el.GetFistDeriv(0.25), new Vector3(-1))).toBeTruthy(); + expect(equalv3(el.GetFistDeriv(0.75), new Vector3(1))).toBeTruthy(); + expect(equalv3(el.GetFistDeriv(0.1), new Vector3(-1, 0.5505527681884694))).toBeTruthy(); + expect(equalv3(el.GetFistDeriv(0.3), new Vector3(-1, -0.1299678784931625))).toBeTruthy(); + expect(equalv3(el.GetFistDeriv(0.6), new Vector3(1, -0.5505527681884692))).toBeTruthy(); + expect(equalv3(el.GetFistDeriv(0.8), new Vector3(1, 0.12996787849316255))).toBeTruthy(); + }); + test("GetClosestPointTo", () => + { + expect(el.GetClosestPointTo(new Vector3(180, 130), true)).toMatchSnapshot(); + expect(el.GetClosestPointTo(new Vector3(-150, -150), true)).toMatchSnapshot(); + expect(equalv3(el.GetClosestPointTo(new Vector3(), true), new Vector3(250))).toBeTruthy(); + }); + test('offset', () => + { + for (let d of [0, -50, -100, 200]) + { + let els = el.GetOffsetCurves(d); + for (let e of els) + { + expect(e.GetGripPoints()).toMatchSnapshot(); + } + } + }); + test("join", () => + { + let el1 = el.Clone(); + el1.StartAngle = Math.PI * 0.2; + el1.EndAngle = Math.PI * 0.5; + let el2 = el.Clone(); + el2.StartAngle = Math.PI * 0.5; + el2.EndAngle = Math.PI * 0.2; + expect(el1.Join(el2)).toBe(Status.True); + expect(el1.IsClose).toBeTruthy(); + }); + test("GetSplitCurves", () => + { + let els = el.GetSplitCurves([0, 0.1, 0.5, 0.75]); + expect(els.length).toBe(4); + let len = 0; + els.forEach(l => len += l.Length); + expect(len).toBeCloseTo(el.Length); + }); + test("椭圆和直线相交", () => + { + let l = new Line(new Vector3(250), new Vector3(250, 300)); + let pts = el.IntersectWith(l, 0); + expect(pts.length).toBe(1); + l = new Line(new Vector3(250), new Vector3(-200, 200)); + pts = el.IntersectWith(l, 0); + expect(pts.length).toBe(2); + l = new Line(new Vector3(130, 50), new Vector3(-200, 200)); + pts = el.IntersectWith(l, 0); + expect(pts.length).toBe(1); + l = new Line(new Vector3(200, -300), new Vector3(200, 300)); + pts = el.IntersectWith(l, 0); + expect(pts.length).toBe(2); + }); + test("椭圆和圆弧相交", () => + { + let l = new Arc(new Vector3(200, 90), 120, 1.2667, 4.204); + let pts = el.IntersectWith(l, 0); + expect(pts.length).toBe(1); + pts = el.IntersectWith(l, 3); + expect(pts.length).toBe(2); + let cir = new Circle(new Vector3(), 100); + pts = el.IntersectWith(cir, 0); + expect(pts.length).toBe(2); + cir = new Circle(new Vector3(), 250); + pts = el.IntersectWith(cir, 0); + expect(pts.length).toBe(2); + cir = new Circle(new Vector3(), 200); + pts = el.IntersectWith(cir, 0); + expect(pts.length).toBe(4); + cir = new Circle(new Vector3(0, 100), 100); + pts = el.IntersectWith(cir, 0); + expect(pts.length).toBe(2); + cir = new Circle(new Vector3(400), 100); + pts = el.IntersectWith(cir, 0); + expect(pts.length).toBe(0); + + let data = + { "file": [2, "Ellipse", 3, 2, 833, false, 0, 7, 0, [1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 0, 11102.861204896697, 0, 2032.2646535460103, 1], 1, 3731.0773801284895, 1822.1540693650747, -0.7853981633972875, 0, 6.283185307179586, "Circle", 3, 2, 842, false, 1, 7, 0, [1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 0, 11102.861204896697, 0, 2032.2646535460103, 1], 1, 3001.2505191312093], "basePt": { "x": 20324.11883779178, "y": 0, "z": 2875.7945530299553 } } + let cus = LoadCurvesFromFileData(data) as Curve[]; + pts = cus[0].IntersectWith(cus[1], 0); + expect(pts.length).toBe(4); + }); + test("椭圆和多段线相交", () => + { + let data = { "file": [1, "Polyline", 3, 2, 112, false, 1, 7, 0, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 2, 4, [-335.6038995638465, -82.43257813770273], 0, [-106.1153157638372, -6.7633694252672285], 0, [12.970652045897396, -45.21821319716078], 0, [117.1708738794153, 118.52499254122434], 0, false], "basePt": { "x": 55.14693231184518, "y": 32.93195317797762, "z": 0 } }; + let pl = LoadCurvesFromFileData(data)[0]; + let pts = el.IntersectWith(pl, 0); + expect(pts.length).toBe(2); + data = { "file": [1, "Polyline", 3, 2, 113, false, 1, 7, 0, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 2, 5, [-410.1219439207879, -125.67070828328724], 0, [-173.79584080236958, -1.6724443014010655], 0.2825944770646096, [-68.76201719418361, 174.84273148457797], -2.140757839596393, [63.989065421717896, 66.8913016650536], 0, [441.8190697900534, -102.32985859257917], 0, false], "basePt": { "x": 435.98385736737646, "y": -78.98900890187124, "z": 0 } } + pl = LoadCurvesFromFileData(data)[0]; + pts = el.IntersectWith(pl, 0); + expect(pts.length).toBe(4); + }); + test("椭圆椭圆相交", () => + { + let pts = el.IntersectWith(el.Clone(), 0); + expect(pts.length).toBe(0); + let el2 = el.Clone(); + el2.Center = new Vector3(250); + pts = el.IntersectWith(el2, 0); + expect(pts.length).toBe(2); + let data = { "file": [1, "Ellipse", 3, 2, 115, false, 1, 7, 0, [-0.4190581774617472, -0.9079593845004515, 0, 0, 0.9079593845004515, -0.4190581774617472, 0, 0, 0, 0, 1, 0, 20.59763950762874, 46.6612008780327, 0, 1], 1, 250, 100, 0, 0, 6.283185307179586], "basePt": { "x": 735.3489273304585, "y": 121.3525062483825, "z": 0 } }; + let el3 = LoadCurvesFromFileData(data)[0]; + pts = el.IntersectWith(el3, 0); + expect(pts.length).toBe(4); + data = { "file": [1, "Ellipse", 3, 2, 115, false, 1, 7, 0, [-0.4190581774617472, -0.9079593845004515, 0, 0, 0.9079593845004515, -0.4190581774617472, 0, 0, 0, 0, 1, 0, 187.7445845668207, 364.27473348939463, 0, 1], 1, 250, 100, 0, 0, 6.283185307179586], "basePt": { "x": 317.9344290644616, "y": 379.38531948896275, "z": 0 } } + el3 = LoadCurvesFromFileData(data)[0]; + pts = el.IntersectWith(el3, 0); + expect(pts.length).toBe(0); + }) +}); + +describe("非完整椭圆", () => +{ + let data = { "file": [1, "Ellipse", 3, 2, 121, false, 1, 7, 0, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 1, 250, 100, 0, 3.2624068331327574, 1.8128125175408325], "basePt": { "x": 948.626520974941, "y": -75.40395225389025, "z": 0 } }; + let el = LoadCurvesFromFileData(data)[0] as Ellipse; + data = { "file": [1, "Ellipse", 3, 2, 124, false, 1, 7, 0, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 29.61201700564709, 641.6901339885158, 0, 1], 1, 418.15520838547974, 225.54490978414745, -0.69713252541264, 3.934994981148553, 1.3332220725861395], "basePt": { "x": 334.56716843563856, "y": 365.4059797015151, "z": 0 } } + let el2 = LoadCurvesFromFileData(data)[0] as Ellipse; + test("几何参数", () => + { + expect(el.Length).toMatchSnapshot(); + expect(el.Area).toMatchSnapshot(); + expect(el2.Length).toMatchSnapshot(); + expect(el.Area).toMatchSnapshot(); + }); + test("PtOnCurve", () => + { + let p = new Vector3(-283.26124450979813, 673.4306820783466); + let data = { "file": [1, "Ellipse", 3, 2, 124, false, 1, 7, 0, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 29.61201700564709, 641.6901339885158, 0, 1], 1, 418.15520838547974, 225.54490978414745, -0.69713252541264, 3.934994981148553, 1.3332220725861395], "basePt": { "x": 2681.2570394768845, "y": -1593.4506532289088, "z": 0 } } + let l = LoadCurvesFromFileData(data)[0] as Ellipse; + expect(l.PtOnCurve(p)).toBeTruthy(); + + p = new Vector3(2397.1610323469677, 85.35748381925586); + data = { "file": [1, "Ellipse", 3, 2, 444, false, 1, 7, 0, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 2079.1139796352218, 127.64566779152159, 0, 1], 1, 225.54490978414742, 418.1552083854798, -2.2679288522075365, 5.505791307943451, 2.904018399381034], "basePt": { "x": 3700.685116502911, "y": -68.9791965641534, "z": 0 } } + l = LoadCurvesFromFileData(data)[0] as Ellipse; + expect(l.PtOnCurve(p)).toBeTruthy(); + }); + test("GetPointAtParam", () => + { + expect(el2.GetPointAtParam(0.2)).toMatchSnapshot(); + expect(el2.GetPointAtParam(0.5)).toMatchSnapshot(); + }); + test("GetParamAtPoint", () => + { + let data = { "file": [1, "Ellipse", 3, 2, 124, false, 1, 7, 0, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 29.61201700564709, 641.6901339885158, 0, 1], 1, 418.15520838547974, 225.54490978414745, -0.69713252541264, 3.934994981148553, 1.3332220725861395], "basePt": { "x": 2681.2570394768845, "y": -1593.4506532289088, "z": 0 } } + let l = LoadCurvesFromFileData(data)[0] as Ellipse; + let p = new Vector3(246.01582144145584, 746.3383795723034); + expect(l.GetParamAtPoint(p)).toBeCloseTo(1); + p = new Vector3(73.23667922398035, 886.2085173816399); + expect(l.GetParamAtPoint(p)).toBeGreaterThan(1); + p = new Vector3(-317.46308327988817, 769.1547312689645); + expect(l.GetParamAtPoint(p)).toBeLessThan(1); + + expect(l.GetParamAtPoint(l.GetPointAtParam(0.2))).toBeCloseTo(0.2); + expect(l.GetParamAtPoint(l.GetPointAtParam(0.5))).toBeCloseTo(0.5); + expect(l.GetParamAtPoint(l.GetPointAtParam(0.8))).toBeCloseTo(0.8); + }); + test("GetDistAtParam", () => + { + expect(el2.GetParamAtDist(el2.GetDistAtParam(0.2))).toBeCloseTo(0.2); + expect(el2.GetParamAtDist(el2.GetDistAtParam(0.5))).toBeCloseTo(0.5); + expect(el2.GetParamAtDist(el2.GetDistAtParam(0.8))).toBeCloseTo(0.8); + }); + test("GetParamAtDist", () => + { + expect(el2.GetDistAtParam(0.2)).toMatchSnapshot(); + expect(el2.GetDistAtParam(0.5)).toMatchSnapshot(); + expect(el2.GetDistAtParam(0.8)).toMatchSnapshot(); + }); + test("GetFistDeriv", () => + { + let p = new Vector3(3245.070906808509, -20.406490193093504); + let data = { "file": [1, "Ellipse", 3, 2, 131, false, 1, 7, 0, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 2910.330625096328, 62.245373351688414, 0, 1], 1, 418.15520838547974, 225.54490978414745, -0.69713252541264, 3.934994981148553, 1.3332220725861395], "basePt": { "x": 3893.631897322025, "y": -235.90267753572692, "z": 0 } } + let el = LoadCurvesFromFileData(data)[0] as Ellipse; + + let derv = el.GetFistDeriv(p); + expect(derv.x < 0).toBeTruthy(); + expect(derv.y > 0).toBeTruthy(); + + derv = el.GetFistDeriv(0.2) + expect(derv.x > 0).toBeTruthy(); + expect(derv.y < 0).toBeTruthy(); + derv = el.GetFistDeriv(0.5) + expect(derv.x > 0).toBeTruthy(); + expect(derv.y > 0).toBeTruthy(); + + }); + + test("GetClosestPointTo", () => + { + let p = new Vector3(-485.6180207115862, 791.5205029837365); + let data = { "file": [1, "Ellipse", 3, 2, 124, false, 1, 7, 0, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 29.61201700564709, 641.6901339885158, 0, 1], 1, 418.15520838547974, 225.54490978414745, -0.69713252541264, 3.934994981148553, 1.3332220725861395], "basePt": { "x": 1514.970345140928, "y": 460.6123732805639, "z": 0 } }; + let l = LoadCurvesFromFileData(data)[0] as Ellipse; + let closePt = l.GetClosestPointTo(p, false); + expect(equalv3(closePt, l.StartPoint)).toBeTruthy(); + + p = new Vector3(255.02021875477413, 873.0660067450125); + closePt = l.GetClosestPointTo(p, false); + expect(equalv3(closePt, l.EndPoint)).toBeTruthy(); + + p = new Vector3(-301.20629379808594, 636.6952439507315); + closePt = l.GetClosestPointTo(p, false); + expect(l.PtOnCurve(closePt)).toBeTruthy(); + }); + test('offset', () => + { + for (let d of [0, -50, -100, 200]) + { + let els = el2.GetOffsetCurves(d); + for (let e of els) + { + expect(e.GetGripPoints()).toMatchSnapshot(); + } + } + + }); + test("join", () => + { + expect(el.Join(el2)).toBeFalsy(); + let data = { "file": [2, "Ellipse", 3, 2, 834, false, 1, 7, 0, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 2910.330625096328, 62.245373351688414, 0, 1], 1, 418.15520838547974, 225.54490978414745, -0.69713252541264, 6.343188564109003, 7.616407379765725, "Ellipse", 3, 2, 833, false, 1, 7, 0, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 2910.330625096327, 62.24537335168705, 0, 1], 1, 418.15520838547974, 225.54490978414745, -0.69713252541264, 3.934994981148553, 6.34318856410901], "basePt": { "x": 3070.6075022402556, "y": -251.35268998703185, "z": 0 } } + let els = LoadCurvesFromFileData(data) as Ellipse[]; + expect(els[0].Join(els[1])).toBeTruthy(); + expect(els[0].StartAngle).toBeCloseTo(el2.StartAngle); + expect(els[0].EndAngle).toBeCloseTo(el2.EndAngle); + }); + test("GetSplitCurves", () => + { + let cus = el2.GetSplitCurves(0.2); + expect(cus.length).toBe(2); + cus = el2.GetSplitCurves([0, 1]); + expect(cus.length).toBe(1); + expect(cus[0].Length).toBeCloseTo(el2.Length); + }); + test("椭圆和直线相交", () => + { + let data = { "file": [1, "Line", 3, 2, 835, false, 1, 7, 0, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 1, [40.99635967060419, 828.344303500637, 0], [419.2963252629038, 79.65300513942876, 0]], "basePt": { "x": 1547.6371722986987, "y": 505.9487058831845, "z": 0 } } + let l = LoadCurvesFromFileData(data)[0] as Line; + let intPts = el2.IntersectWith(l, 0); + expect(intPts.length).toBe(1); + data = { "file": [1, "Line", 3, 2, 836, false, 1, 7, 0, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 1, [-508.03376696505734, 378.26728108419354, 0], [1506.1407092390264, 857.0726240804099, 0]], "basePt": { "x": 819.8530509444495, "y": 697.4708430816711, "z": 0 } } + l = LoadCurvesFromFileData(data)[0] as Line; + intPts = el2.IntersectWith(l, 0); + expect(intPts.length).toBe(2); + }); + test("椭圆和圆弧相交", () => + { + let data = { "file": [1, "Circle", 3, 2, 837, false, 1, 7, 0, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 29.61201700564709, 641.6901339885158, 0, 1], 1, 240.28658436372382], "basePt": { "x": 1747.976477968104, "y": 478.1948499374774, "z": 0 } } + let cir = LoadCurvesFromFileData(data)[0] as Circle; + let intPts = el2.IntersectWith(cir, 0); + expect(intPts.length).toBe(3); + + let cus = el2.GetSplitCurvesByPts(intPts); + expect(cus.length).toBe(3); + + data = { "file": [1, "Circle", 3, 2, 829, false, 1, 7, 0, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 187.80449653238327, 687.2252854417625, 0, 1], 1, 142.38840109430407], "basePt": { "x": 230.04137781057622, "y": 568.9620178628227, "z": 0 } } + cir = LoadCurvesFromFileData(data)[0] as Circle; + intPts = el2.IntersectWith(cir, 0); + expect(intPts.length).toBe(1); + + data = { "file": [1, "Arc", 3, 2, 830, false, 1, 7, 0, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 23.981256652944406, 734.2425700250855, 0, 1], 2, 380.9050292698399, 3.8973798333988365, 0.296805111427033, false], "basePt": { "x": 676.8826456844006, "y": 686.8100446096855, "z": 0 } } + + + let arc = LoadCurvesFromFileData(data)[0] as Arc; + intPts = el2.IntersectWith(arc, 0); + expect(intPts.length).toBe(2); + + }); + test("椭圆和多段线相交", () => + { + let data = + { "file": [1, "Polyline", 3, 2, 831, false, 1, 7, 0, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 2, 4, [-199.7615585356133, 411.90419659644084], 0, [67.50801592170811, 509.64849811226117], 0, [316.4505338448129, 443.97654553131935], 0, [316.4505338448129, 828.844732749862], 0, false], "basePt": { "x": 1289.3117848694626, "y": 358.45028170497653, "z": 0 } } + + let l = LoadCurvesFromFileData(data)[0] as Polyline; + let pts = el2.IntersectWith(l, 0); + expect(pts.length).toBe(2); + + }); + test("椭圆椭圆相交", () => + { + let data = + { "file": [1, "Ellipse", 3, 2, 121, false, 1, 7, 0, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 280.916829323563, 321.0478049412151, 0, 1], 1, 250, 100, 0, 3.2624068331327574, 1.8128125175408325], "basePt": { "x": 217.06597636418695, "y": 243.35797242008385, "z": 0 } } + + let l = LoadCurvesFromFileData(data)[0] as Ellipse; + + let pts = el2.IntersectWith(l, 0); + + expect(pts.length).toBe(1); + }) +}) diff --git a/__test__/Utils/LoadEntity.util.ts b/__test__/Utils/LoadEntity.util.ts index 83e5d201f..acf36523a 100644 --- a/__test__/Utils/LoadEntity.util.ts +++ b/__test__/Utils/LoadEntity.util.ts @@ -12,6 +12,9 @@ Factory(Board); export function LoadEntityFromFileData(data) { + if (!Array.isArray(data)) + data = data.file; + let file = new CADFiler(); file.Data = data; let ens: Entity[] = []; diff --git a/src/Add-on/Trim.ts b/src/Add-on/Trim.ts index 65aafb353..1580ef137 100644 --- a/src/Add-on/Trim.ts +++ b/src/Add-on/Trim.ts @@ -3,7 +3,6 @@ import { app } from '../ApplicationServices/Application'; import { arrayLast, arrayRemove } from '../Common/ArrayExt'; import { Status } from '../Common/Status'; 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'; @@ -110,7 +109,7 @@ export class Command_Trim implements Command let newCus: Curve[] = []; if (cu instanceof Polyline && (kniefCus.includes(cu) || kniefCus.length === 0)) newCus = this.TrimPolyline(cu, kniefCus, s); - else if (cu instanceof Circle || cu instanceof Arc || cu instanceof Line || cu instanceof Polyline) + else newCus = this.TrimCurve(cu, kniefCus, s); resCus.set(cu, newCus); @@ -141,7 +140,7 @@ export class Command_Trim implements Command * @param selBox * @param [isSelect] 是否已经被选中 */ - TrimCurve(curve: Line | Circle | Arc | Polyline, kniefCus: Curve[], selBox: SelectBox, thisCurve: Curve = curve, insSelfPts: Vector3[] = []): Curve[] + TrimCurve(curve: Curve, kniefCus: Curve[], selBox: SelectBox, thisCurve: Curve = curve, insSelfPts: Vector3[] = []): Curve[] { //求交 let intPts = this.GetIntersetPoints(kniefCus, curve, thisCurve); diff --git a/src/Add-on/closetest.ts b/src/Add-on/closetest.ts index 7f6db1be5..b777a2122 100644 --- a/src/Add-on/closetest.ts +++ b/src/Add-on/closetest.ts @@ -46,7 +46,7 @@ export class Command_ClosePt implements Command line.EndPoint = cu.GetClosestPointTo(p, extend); derLine.StartPoint = line.EndPoint; - derLine.EndPoint = line.EndPoint.add(cu.GetFistDeriv(line.EndPoint)); + derLine.EndPoint = line.EndPoint.add(cu.GetFistDeriv(line.EndPoint).multiplyScalar(100)); if (cu instanceof Arc) { diff --git a/src/Common/CurveUtils.ts b/src/Common/CurveUtils.ts index c1a2fe83f..79e291e5f 100644 --- a/src/Common/CurveUtils.ts +++ b/src/Common/CurveUtils.ts @@ -13,6 +13,7 @@ import { FixIndex } from './Utils'; import { PlaneExt } from '../Geometry/Plane'; import { IntersectOption } from '../GraphicsSystem/IntersectWith'; import { arrayLast, changeArrayStartIndex, equalArray } from './ArrayExt'; +import { Ellipse } from '../DatabaseServices/Ellipse'; //3点获取圆心 export function getCircleCenter(pt1: Vector3, pt2: Vector3, pt3: Vector3) @@ -256,6 +257,13 @@ export function equalCurve(cu1: Curve, cu2: Curve) && equaln(cu1.StartAngle, cu2.StartAngle) && equaln(cu1.EndAngle, cu2.EndAngle); } + else if (cu1 instanceof Ellipse && cu2 instanceof Ellipse) + { + return equalv3(cu1.Center, cu2.Center) + && equaln(cu1.RadX, cu2.RadX) + && equaln(cu1.RadY, cu2.RadY) + && equalv3(cu1.StartPoint, cu2.StartPoint); + } return false; } @@ -546,7 +554,7 @@ export function CircleOuterTangentLines(circle0: Circle, circle1: Circle): Line[ } } -export function getArcOrCirNearPts(cu: Circle | Arc, pickPoint: Vector3, viewXform: Matrix3) +export function getArcOrCirNearPts(cu: Circle | Arc | Ellipse, pickPoint: Vector3, viewXform: Matrix3) { //@ts-ignore let viewNormal = new Vector3().fromArray(viewXform.elements, 2 * 3); @@ -579,3 +587,8 @@ export function getArcOrCirNearPts(cu: Circle | Arc, pickPoint: Vector3, viewXfo return cu.IntersectWith(lz, IntersectOption.ExtendBoth); } } + +export function getTanPtsOnEllipse(cu: Ellipse, lastPoint: Vector3) +{ + return []; +} diff --git a/src/DatabaseServices/Arc.ts b/src/DatabaseServices/Arc.ts index 54437851e..47d286824 100644 --- a/src/DatabaseServices/Arc.ts +++ b/src/DatabaseServices/Arc.ts @@ -8,13 +8,14 @@ import { ObjectSnapMode } from '../Editor/ObjectSnapMode'; import { BufferGeometryUtils } from '../Geometry/BufferGeometryUtils'; import { angle, angleTo2Pi, equaln, equalv3, midPoint, MoveMatrix, polar } from '../Geometry/GeUtils'; import { RenderType } from '../GraphicsSystem/RenderType'; -import { IntersectArcAndArc, IntersectCircleAndArc, IntersectLineAndArc, IntersectOption, IntersectPolylineAndCurve, reverseIntersectOption } from '../GraphicsSystem/IntersectWith'; +import { IntersectArcAndArc, IntersectCircleAndArc, IntersectLineAndArc, IntersectOption, IntersectPolylineAndCurve, reverseIntersectOption, IntersectEllipseAndCircleOrArc } from '../GraphicsSystem/IntersectWith'; import { Factory } from './CADFactory'; import { CADFiler } from './CADFiler'; import { Circle } from './Circle'; import { Curve } from './Curve'; import { Line } from './Line'; import { Polyline } from './Polyline'; +import { Ellipse } from './Ellipse'; /** * 圆弧实体类 * 与ACAD不同,这个类加入了时针变量,并且默认构造的圆弧为顺时针圆弧. @@ -456,7 +457,10 @@ export class Arc extends Curve return IntersectCircleAndArc(curve, this, reverseIntersectOption(intType)); } if (curve instanceof Polyline) - return IntersectPolylineAndCurve(curve, this, reverseIntersectOption(intType)) + return IntersectPolylineAndCurve(curve, this, reverseIntersectOption(intType)); + + if (curve instanceof Ellipse) + return IntersectEllipseAndCircleOrArc(curve, this, intType); return []; } diff --git a/src/DatabaseServices/Circle.ts b/src/DatabaseServices/Circle.ts index 705cd206f..ba6fbc5f1 100644 --- a/src/DatabaseServices/Circle.ts +++ b/src/DatabaseServices/Circle.ts @@ -8,7 +8,7 @@ import { clamp } from '../Common/Utils'; import { ObjectSnapMode } from '../Editor/ObjectSnapMode'; import { BufferGeometryUtils } from '../Geometry/BufferGeometryUtils'; import { angle, equaln, MoveMatrix, polar } from '../Geometry/GeUtils'; -import { IntersectCircleAndArc, IntersectCircleAndCircle, IntersectLineAndCircle, IntersectOption, IntersectPolylineAndCurve, reverseIntersectOption } from '../GraphicsSystem/IntersectWith'; +import { IntersectCircleAndArc, IntersectCircleAndCircle, IntersectLineAndCircle, IntersectOption, IntersectPolylineAndCurve, reverseIntersectOption, IntersectEllipseAndCircleOrArc } from '../GraphicsSystem/IntersectWith'; import { RenderType } from '../GraphicsSystem/RenderType'; import { Arc } from './Arc'; import { Factory } from './CADFactory'; @@ -16,6 +16,7 @@ import { CADFiler } from './CADFiler'; import { Curve } from './Curve'; import { Line } from './Line'; import { Polyline } from './Polyline'; +import { Ellipse } from './Ellipse'; let circleGeometry: BufferGeometry; function GetCircleGeometry() @@ -216,6 +217,10 @@ export class Circle extends Curve { return IntersectCircleAndCircle(this, curve); } + if (curve instanceof Ellipse) + { + return IntersectEllipseAndCircleOrArc(curve, this, intType); + } if (curve instanceof Polyline) return IntersectPolylineAndCurve(curve, this, reverseIntersectOption(intType)) return []; diff --git a/src/DatabaseServices/Curve.ts b/src/DatabaseServices/Curve.ts index e52ae6662..527bc1a9a 100644 --- a/src/DatabaseServices/Curve.ts +++ b/src/DatabaseServices/Curve.ts @@ -155,7 +155,7 @@ export abstract class Curve extends Entity * @returns {Vector3[]} * @memberof Curve */ - IntersectWith(curve: Curve, intType: IntersectOption): Vector3[] { return; } + IntersectWith(curve: Curve, intType: IntersectOption): Vector3[] { return []; } //------------------绘制相关------------------ GetDrawObjectFromRenderType(renderType: RenderType = RenderType.Wireframe): Object3D diff --git a/src/DatabaseServices/Ellipse.ts b/src/DatabaseServices/Ellipse.ts index 5a233159a..f5296088a 100644 --- a/src/DatabaseServices/Ellipse.ts +++ b/src/DatabaseServices/Ellipse.ts @@ -1,18 +1,29 @@ import * as THREE from 'three'; -import { Geometry, Object3D, Shape, Vector3 } from 'three'; +import { Box3, BufferGeometry, Matrix3, Matrix4, Object3D, Shape, Vector3 } from 'three'; +import { arrayLast, arrayRemoveDuplicateBySort } from '../Common/ArrayExt'; import { ColorMaterial } from '../Common/ColorPalette'; -import { MoveMatrix, rotatePoint } from '../Geometry/GeUtils'; +import { getDeterminantFor2V, Vec3DTo2D, getArcOrCirNearPts, getTanPtsOnEllipse } from '../Common/CurveUtils'; +import { Status } from '../Common/Status'; +import { ObjectSnapMode } from '../Editor/ObjectSnapMode'; +import { angle, equaln, equalv3, MoveMatrix, rotatePoint, angleTo } from '../Geometry/GeUtils'; +import { IntersectEllipse, IntersectEllipseAndCircleOrArc, IntersectEllipseAndLine, IntersectOption, IntersectPolylineAndCurve, reverseIntersectOption } from '../GraphicsSystem/IntersectWith'; import { RenderType } from '../GraphicsSystem/RenderType'; +import { Arc } from './Arc'; import { Factory } from './CADFactory'; import { CADFiler } from './CADFiler'; +import { Circle } from './Circle'; import { Curve } from './Curve'; +import { Line } from './Line'; +import { Polyline } from './Polyline'; @Factory export class Ellipse extends Curve { - private m_RadX: number; - private m_RadY: number; - private m_Angle: number; + private _radX: number; + private _radY: number; + private _rotate: number; + private _startAngle = 0; + private _endAngle = Math.PI * 2; constructor( center?: Vector3, radX: number = 1e-3, @@ -21,20 +32,35 @@ export class Ellipse extends Curve { super(); center && this.m_Matrix.setPosition(center); - this.m_RadX = radX; - this.m_RadY = radY; - this.m_Angle = angle; + this._radX = radX; + this._radY = radY; + this._rotate = angle; + } + get StartParam(): number + { + return 0; + } + get EndParam(): number + { + return 1; + } + get StartPoint() + { + return this.GetPointAtParam(0); + } + get EndPoint() + { + return this.GetPointAtParam(1); } - get Shape(): Shape { let sp = new Shape(); - sp.ellipse(0, 0, this.m_RadX, this.m_RadY, 0, 2 * Math.PI, false, this.m_Angle); + sp.ellipse(0, 0, this._radX, this._radY, this._startAngle, this._endAngle, false, this._rotate); return sp; } - get IsClose(): boolean + get IsClose(): boolean { - return true; + return equaln(this.TotalAngle, Math.PI * 2); } get Center() { @@ -48,58 +74,423 @@ export class Ellipse extends Curve } get RadX() { - return this.m_RadX; + return this._radX; } set RadX(v: number) { this.WriteAllObjectRecord(); - this.m_RadX = v; + this._radX = v; this.Update(); } get RadY() { - return this.m_RadY; + return this._radY; } set RadY(v: number) { this.WriteAllObjectRecord(); - this.m_RadY = v; + this._radY = v; + this.Update(); + } + get Rotation() + { + return this._rotate; + } + set Rotation(v: number) + { + this.WriteAllObjectRecord(); + this._rotate = v; this.Update(); } - get Angle() + get StartAngle() + { + return this._startAngle; + } + get EndAngle() + { + return this._startAngle; + } + set StartAngle(v: number) { - return this.m_Angle; + this.WriteAllObjectRecord(); + this._startAngle = v; + this.Update(); } - set Angle(v: number) + set EndAngle(v: number) { this.WriteAllObjectRecord(); - this.m_Angle = v; + this._endAngle = v; this.Update(); } + get Length() + { + let q = this._radX + this._radY; + let h = ((this._radX - this._radY) / (this._radX + this._radY)) ** 2; + let m = 22 / 7 * Math.PI - 1; + let n = Math.pow(((this._radX - this._radY) / this._radX), 33.697); + let len = Math.PI * q * (1 + 3 * h / (10 + Math.sqrt(4 - 3 * h))) * (1 + m * n); + return len * this.TotalAngle / Math.PI * 0.5; + } + get Area() + { + let area = Math.PI * this._radX * this._radY; + let an = this._endAngle - this._startAngle; + if (an < 0) + an = Math.PI * 2 + an; + area *= an / Math.PI * 0.5; + let area2 = Math.abs( + getDeterminantFor2V( + Vec3DTo2D(this.StartPoint.sub(this.Center)), + Vec3DTo2D(this.EndPoint.sub(this.Center))) + ) / 2; + if (an < Math.PI) + area -= area2; + else + area += area2; + return area; + } + get TotalAngle() + { + let totolAngle = this._endAngle - this._startAngle; + if (totolAngle < 0) + totolAngle = Math.PI * 2 + totolAngle; + return totolAngle; + } + get BoundingBox(): Box3 + { + return new Box3().setFromPoints(this.GetGripPoints()); + } PtInCurve(pt: Vector3) { - let p = rotatePoint(pt.clone().sub(this.Center), -this.Angle); + let p = rotatePoint(pt.clone().sub(this.Center), -this.Rotation); return p.x ** 2 / this.RadX ** 2 + p.y ** 2 / this.RadY ** 2 < 1; } + PtOnCurve(pt: Vector3) + { + if (this.PtOnEllipse(pt)) + { + let a = this.GetCircleAngleAtPoint(pt); + return a <= this.TotalAngle + 1e-6; + } + return false; + } + PtOnEllipse(pt: Vector3) + { + let p = rotatePoint(pt.clone().applyMatrix4(this.OCSInv), -this.Rotation); + return equaln(p.x ** 2 / this.RadX ** 2 + p.y ** 2 / this.RadY ** 2, 1, 1e-3); + } + GetPointAtParam(param: number) + { + let an = this.TotalAngle * param + this._startAngle; + + if (an > Math.PI) + an -= 2 * Math.PI; + + let a = this.RadX; + let b = this.RadY; + let pt = new Vector3(a * Math.cos(an), b * Math.sin(an), 0); + + pt.applyMatrix4(new Matrix4().makeRotationZ(this._rotate)); + + return pt.applyMatrix4(this.OCS); + } + GetParamAtPoint(pt?: Vector3) + { + if (!this.PtOnEllipse(pt)) + { + return NaN; + } + let an = this.GetCircleAngleAtPoint(pt); + let par = an / this.TotalAngle; + + if (this.IsClose || par < 1 + 1e-6) + return par; + else + { + let diffPar = Math.PI * 2 / this.TotalAngle - 1; + if (par - 1 < diffPar / 2) + return par; + else + return par - 1 - diffPar; + } + } + GetPointAtDistance(distance: number) + { + let param = distance / this.Length; + return this.GetPointAtParam(param); + } + GetDistAtParam(param: number) + { + return this.Length * param; + } + GetDistAtPoint(pt: Vector3) + { + let param = this.GetParamAtPoint(pt); + return this.GetDistAtParam(param); + } + GetParamAtDist(d: number) + { + return d / this.Length; + } + GetAngleAtParam(par: number) + { + let pt = this.GetPointAtParam(par).applyMatrix4(this.OCSInv).applyMatrix4(new Matrix4().makeRotationZ(-this.Rotation)); + return angle(pt) + this._startAngle; + } + GetCircleAngleAtPoint(pt: Vector3) + { + pt = pt.clone().applyMatrix4(this.OCSInv); + let an = angle(pt) - this._rotate; + if (an < 0) + an = Math.PI * 2 - an; + if (an > Math.PI * 2) + an -= Math.PI * 2; + let dist = pt.length(); + let k = dist * Math.cos(an) / this._radX; + if (Math.abs(k) > 1) + k = Math.floor(Math.abs(k)) * Math.sign(k); + if (Math.abs(an) <= Math.PI) + an = Math.acos(k); + else + an = Math.PI * 2 - Math.acos(k); + + an -= this._startAngle; + + if (an < 0) + an = Math.PI * 2 + an; + return an; + } + + GetFistDeriv(pt: number | Vector3) + { + if (typeof pt === "number") + pt = this.GetPointAtParam(pt); + else + pt = pt.clone(); + + let refPts = this.GetGripPoints(); + + let p = pt.clone().applyMatrix4(this.OCSInv).applyMatrix4(new Matrix4().makeRotationZ(-this._rotate)); + let vec = new Vector3(); + if (equalv3(pt, refPts[0])) + vec.set(0, 1, 0); + else if (equalv3(pt, refPts[1])) + { + vec.set(0, -1, 0); + } + else if (p.y > 0) + { + let k = -(this._radY ** 2 * p.x) / (this._radX ** 2 * p.y); + vec.set(-1, -k, 0); + } + else + { + let k = -(this._radY ** 2 * p.x) / (this._radX ** 2 * p.y); + vec.set(1, k, 0); + } + vec.applyMatrix4(new Matrix4().makeRotationZ(this._rotate)); + + return vec.applyMatrix4(new Matrix4().extractRotation(this.OCS)); + } + GetClosestPointTo(p: Vector3, extend: boolean): Vector3 + { + //参考:https://wet-robots.ghost.io/simple-method-for-distance-to-ellipse/ + let ro = new Matrix4().makeRotationZ(this._rotate); + let roInv = new Matrix4().getInverse(ro); + let pt = p.clone().applyMatrix4(this.OCSInv).setZ(0).applyMatrix4(roInv); + let px = pt.x; + let py = pt.y; + let t = angle(pt); + let a = this._radX; + let b = this._radY; + let x: number, y: number; + for (let i = 0; i < 3; i++) + { + x = a * Math.cos(t); + y = b * Math.sin(t); + let ex = (a ** 2 - b ** 2) * Math.cos(t) ** 3 / a; + let ey = (b * b - a * a) * Math.sin(t) ** 3 / b; + let rx = x - ex; + let ry = y - ey; + let qx = px - ex; + let qy = py - ey; + + let r = Math.sqrt(ry ** 2 + rx ** 2); + let q = Math.sqrt(qy ** 2 + qx ** 2); + + let dc = r * Math.asin((rx * qy - ry * qx) / (r * q)); + let dt = dc / Math.sqrt(a * a + b * b - x * x - y * y); + + t += dt; + } + let retPt = new Vector3(x, y).applyMatrix4(ro).applyMatrix4(this.OCS); + if (this.IsClose || extend) + { + return retPt; + } + else if (this.PtOnCurve(retPt)) + { + return retPt + } + else + { + let d1 = p.distanceToSquared(this.StartPoint); + let d2 = p.distanceToSquared(this.EndPoint); + return d1 < d2 ? this.StartPoint : this.EndPoint; + } + } + GetOffsetCurves(offsetDist: number) + { + if ((offsetDist + Math.min(this._radX, this._radY)) > 0) + { + let el = this.Clone(); + el.RadX = this._radX + offsetDist; + el.RadY = this._radY + offsetDist; + return [el]; + } + return [] + } + GetSplitCurves(param: number[] | number) + { + let params: number[]; + if (param instanceof Array) + { + params = param.filter(p => this.ParamOnCurve(p)); + params.sort((a1, a2) => a2 - a1);//从大到小 + } + else + params = [param]; + + //补上最后一个到第一个的弧 + if (this.IsClose) + params.unshift(arrayLast(params)); + else + { + params.unshift(1); + params.push(0); + } + arrayRemoveDuplicateBySort(params); + + let anglelist = params.map(param => this.TotalAngle * param + this._startAngle); + let elllist: this[] = []; + for (let i = 0; i < anglelist.length - 1; i++) + { + let sa = anglelist[i]; + let ea = anglelist[i + 1]; + let el = this.Clone(); + if (!equaln(sa, ea, 1e-6)) + { + el.StartAngle = ea; + el.EndAngle = equaln(sa, 0) ? Math.PI * 2 : sa; + elllist.push(el); + } + } + return elllist; + } + Join(el: Ellipse) + { + if (this.IsClose || el.IsClose || !this.IsCoplaneTo(el) || !equalv3(el.Center, this.Center)) + return Status.False; + + let status = Status.False; + if (equaln(this._endAngle, this._startAngle)) + { + this.EndAngle = this._endAngle; + status = Status.True; + } + else if (equaln(this._startAngle, el._endAngle)) + { + this.StartAngle = el._startAngle; + status = Status.True; + } + if (status === Status.True && !this.IsClose && equaln(this._startAngle, this._endAngle)) + { + this.StartAngle = 0; + this.EndAngle = Math.PI * 2; + } + return status; + } + GetObjectSnapPoints( + snapMode: ObjectSnapMode, + pickPoint: Vector3, + lastPoint: Vector3, + viewXform?: Matrix3 + ): Vector3[] + { + switch (snapMode) + { + case ObjectSnapMode.End: + { + let pts = this.GetGripPoints(); + return pts; + } + case ObjectSnapMode.Cen: + return [this.Center]; + case ObjectSnapMode.Nea: + { + return getArcOrCirNearPts(this, pickPoint, viewXform); + } + case ObjectSnapMode.Per: + if (lastPoint) + { + if (equaln(lastPoint.distanceToSquared(this.Center), 0, 1e-10)) + return []; + return [this.GetClosestPointTo(lastPoint, false)]; + } + case ObjectSnapMode.Tan: + { + //TODO:过某点获取椭圆全部切点 + if (lastPoint) + { + return getTanPtsOnEllipse(this, lastPoint); + } + } + default: + return []; + } + } + IntersectWith(curve: Curve, intType: IntersectOption): Vector3[] + { + //TODO:优化椭圆和椭圆,椭圆和圆相交 + if (curve instanceof Line) + { + return IntersectEllipseAndLine(curve, this, reverseIntersectOption(intType)); + } + else if (curve instanceof Circle || curve instanceof Arc) + { + return IntersectEllipseAndCircleOrArc(this, curve, intType); + } + else if (curve instanceof Polyline) + { + return IntersectPolylineAndCurve(curve, this, intType); + } + else if (curve instanceof Ellipse) + { + return IntersectEllipse(this, curve, intType); + } + else + return []; + } InitDrawObject(renderType: RenderType = RenderType.Wireframe): Object3D { - let geometry = new Geometry(); - this.UpdateGeometry(geometry); - let ellipseObj = new THREE.Line(geometry, ColorMaterial.GetLineMaterial(this.m_Color)); - return ellipseObj; + let line = new THREE.Line(this.UpdateGeometry(), ColorMaterial.GetLineMaterial(this.m_Color)); + this.UpdateDrawObject(renderType, line); + return line; } - UpdateDrawObject(type: RenderType, en: Object3D) + UpdateDrawObject(type: RenderType, obj: Object3D) { - let geo = (en as THREE.Line).geometry as Geometry; + let geo = (obj as THREE.Line).geometry as BufferGeometry; this.UpdateGeometry(geo); } //更新Geometry - private UpdateGeometry(geo: THREE.Geometry) + private UpdateGeometry(geo?: BufferGeometry) { + if (!geo) + geo = new BufferGeometry(); let curve = this.Shape; geo.setFromPoints(curve.getPoints(60)); - geo.verticesNeedUpdate = true; + return geo; } GetStretchPoints(): Array { @@ -107,15 +498,21 @@ export class Ellipse extends Curve } GetGripPoints(): Array { - let tmpMat4 = new THREE.Matrix4().makeRotationZ(this.Angle); + let tmpMat4 = new THREE.Matrix4().makeRotationZ(this.Rotation); let pts = [ - new Vector3(this.m_RadX, 0), - new Vector3(-this.m_RadX, 0), - new Vector3(0, -this.m_RadY), - new Vector3(0, this.m_RadY), - new Vector3(), - ] - return pts.map(p => p.applyMatrix4(tmpMat4).applyMatrix4(this.OCS)); + new Vector3(this._radX, 0), + new Vector3(-this._radX, 0), + new Vector3(0, this._radY), + new Vector3(0, -this._radY) + ].map(p => p.applyMatrix4(tmpMat4).applyMatrix4(this.OCS)) + + if (!equaln(0, this._startAngle)) + pts.push(this.StartPoint); + if (!equaln(0, this._endAngle)) + pts.push(this.EndPoint); + + pts.push(this.Center); + return pts; } MoveStretchPoints(indexList: Array, vec: Vector3) { @@ -127,24 +524,46 @@ export class Ellipse extends Curve if (indexList.length > 0) { - let p = pts[indexList[0]]; + let p = pts[indexList[0]].clone(); p.add(vec); - if (indexList[0] === 0 || indexList[0] === 1) + if (indexList[0] <= 1) this.RadX = p.distanceTo(this.Center); - else if (indexList[0] === 2 || indexList[0] === 3) + else if (indexList[0] <= 3) this.RadY = p.distanceTo(this.Center); else - this.Center = p; + { + let p1 = pts[indexList[0]]; + //TODO:跟cad不一致待优化 + if (equalv3(p1, this.StartPoint)) + { + let v1 = p1.clone().sub(this.Center); + let v2 = p.clone().sub(this.Center); + let an = angleTo(v1, v2); + this.StartAngle = this.StartAngle + an; + } + else if (equalv3(p1, this.EndPoint)) + { + let v1 = p1.clone().sub(this.Center); + let v2 = p.clone().sub(this.Center); + let an = angleTo(v2, v1); + this.EndAngle = this.EndAngle + an; + } + else + this.Center = p; + } } } protected _ReadFile(file: CADFiler) { super._ReadFile(file); let ver = file.Read(); - this.RadX = file.Read(); - this.RadY = file.Read(); - this.Angle = file.Read(); + this._radX = file.Read(); + this._radY = file.Read(); + this._rotate = file.Read(); + this._startAngle = file.Read(); + this._endAngle = file.Read(); + this.Update(); } //对象将自身数据写入到文件. WriteFile(file: CADFiler) @@ -153,7 +572,9 @@ export class Ellipse extends Curve file.Write(1); file.Write(this.RadX); file.Write(this.RadY); - file.Write(this.Angle); + file.Write(this.Rotation); + file.Write(this._startAngle); + file.Write(this._endAngle); } } diff --git a/src/DatabaseServices/Line.ts b/src/DatabaseServices/Line.ts index 6709b277d..c9e1d7654 100644 --- a/src/DatabaseServices/Line.ts +++ b/src/DatabaseServices/Line.ts @@ -7,7 +7,7 @@ import { ObjectSnapMode } from '../Editor/ObjectSnapMode'; import { BufferGeometryUtils } from '../Geometry/BufferGeometryUtils'; import { equaln, equalv3, isParallelTo, MoveMatrix } from '../Geometry/GeUtils'; import { PlaneExt } from '../Geometry/Plane'; -import { IntersectLineAndArc, IntersectLineAndCircle, IntersectLineAndLine, IntersectOption, IntersectPolylineAndCurve, reverseIntersectOption } from '../GraphicsSystem/IntersectWith'; +import { IntersectLineAndArc, IntersectLineAndCircle, IntersectLineAndLine, IntersectOption, IntersectPolylineAndCurve, reverseIntersectOption, IntersectEllipseAndLine } from '../GraphicsSystem/IntersectWith'; import { RenderType } from '../GraphicsSystem/RenderType'; import { Arc } from './Arc'; import { Factory } from './CADFactory'; @@ -15,6 +15,7 @@ import { CADFiler } from './CADFiler'; import { Circle } from './Circle'; import { Curve } from './Curve'; import { Polyline } from './Polyline'; +import { Ellipse } from './Ellipse'; @Factory export class Line extends Curve @@ -174,6 +175,10 @@ export class Line extends Curve { return IntersectPolylineAndCurve(curve, this, reverseIntersectOption(intType)); } + + if (curve instanceof Ellipse) + return IntersectEllipseAndLine(this, curve, intType); + //其他的尚未实现. return []; } diff --git a/src/Editor/CommandRegister.ts b/src/Editor/CommandRegister.ts index bdc302b53..12e12e555 100644 --- a/src/Editor/CommandRegister.ts +++ b/src/Editor/CommandRegister.ts @@ -110,6 +110,7 @@ import { DrawLattice } from '../Add-on/LatticeDrawer/DrawLatticeDrawer'; import { TestFb } from '../Add-on/TestFb'; import { DeleteDrill } from '../Add-on/DrawDrilling/DeleteDrill'; import { DownLoadDrillConfig, UpLoadDrillConfig } from '../Add-on/LoadConfig'; +import { DrawEllipse } from '../Add-on/DrawEllipse'; export function registerCommand() @@ -124,7 +125,7 @@ export function registerCommand() commandMachine.RegisterCommand("ze", new ZoomE()) commandMachine.RegisterCommand("RECTANG", new DrawRect()) commandMachine.RegisterCommand("c", new DrawCircle()) - // commandMachine.RegisterCommand("el", new DrawEllipse()) + commandMachine.RegisterCommand("el", new DrawEllipse()) commandMachine.RegisterCommand("spl", new DrawSpline()) commandMachine.RegisterCommand("pl", new DrawPolyline()) commandMachine.RegisterCommand("sc", new Command_Scale()) diff --git a/src/GraphicsSystem/IntersectWith.ts b/src/GraphicsSystem/IntersectWith.ts index 1601a5e55..e3a457ee8 100644 --- a/src/GraphicsSystem/IntersectWith.ts +++ b/src/GraphicsSystem/IntersectWith.ts @@ -1,8 +1,9 @@ -import { Vector3 } from 'three'; +import { Matrix4, Vector3 } from 'three'; import { arrayRemoveDuplicateBySort } from '../Common/ArrayExt'; import { Arc } from '../DatabaseServices/Arc'; import { Circle } from '../DatabaseServices/Circle'; import { Curve } from '../DatabaseServices/Curve'; +import { Ellipse } from '../DatabaseServices/Ellipse'; import { Line } from '../DatabaseServices/Line'; import { Polyline } from '../DatabaseServices/Polyline'; import { comparePoint, equaln, equalv3 } from '../Geometry/GeUtils'; @@ -139,6 +140,12 @@ export function IntersectArcAndArc(arc1: Arc, arc2: Arc, extType: IntersectOptio return CheckPointOnCurve(pts, arc1, arc2, extType); } +export function IntersectEllipseAndLine(l: Line, el: Ellipse, extType: IntersectOption) +{ + let pts = IntersectLineAndEllipseFor2D(l, el); + return CheckPointOnCurve(pts, l, el, extType); +} + /** * 通用方法:计算直线与圆的交点,默认延伸全部 * @@ -383,3 +390,177 @@ export function IntersectPolylineAndCurve(pl: Polyline, cu: Curve, extType: Inte arrayRemoveDuplicateBySort(pts, equalv3); return pts; } + +export function IntersectLineAndEllipseFor2D(l: Line, el: Ellipse) +{ + if (!l.IsCoplaneTo(el)) return []; + + let mat = new Matrix4().makeRotationZ(-el.Rotation).multiply(el.OCSInv); + let a = el.RadX; + let b = el.RadY; + let sp = l.StartPoint.applyMatrix4(mat); + let ep = l.EndPoint.applyMatrix4(mat); + let pts: Vector3[] = []; + if (equaln(sp.x, ep.x)) + { + let c = sp.x; + let j = (b ** 2) * (1 - (c ** 2) / (a ** 2)); + if (equaln(j, 0)) + { + pts = [new Vector3(sp.x, 0)]; + } + else if (j < 0) + return []; + else + { + let y1 = Math.sqrt(j); + let y2 = -Math.sqrt(j); + pts = [ + new Vector3(c, y1), + new Vector3(c, y2) + ] + } + } + else + { + let k = (sp.y - ep.y) / (sp.x - ep.x); + let c = sp.y - sp.x * k; + let j = (2 * a * a * k * c) * (2 * a * a * k * c) - 4 * (b * b + a * a * k * k) * a * a * (c * c - b * b); + if (equaln(j, 0)) + { + let x1 = -2 * k * c * a * a / (2 * (b * b + a * a * k * k)); + let y1 = k * x1 + c; + pts = [new Vector3(x1, y1)]; + } + else if (j < 0) + return []; + else + { + let x1 = (-2 * k * c * a * a + Math.sqrt(j)) / (2 * (b * b + a * a * k * k)); + let y1 = k * x1 + c; + let x2 = (-2 * k * c * a * a - Math.sqrt(j)) / (2 * (b * b + a * a * k * k)); + let y2 = k * x2 + c; + pts = [ + new Vector3(x1, y1), + new Vector3(x2, y2) + ] + } + } + + let matInv = new Matrix4().getInverse(mat); + return pts.map(p => p.applyMatrix4(matInv)); +} +export function IntersectEllipseAndCircleOrArc(el: Ellipse, cir: Circle | Arc, type: IntersectOption) +{ + if (!el.IsCoplaneTo(cir)) return []; + + let a = Math.max(el.RadX, el.RadY); + let dist = el.Center.distanceTo(cir.Center); + + let disVail = dist > (a + cir.Radius) + + if (disVail) + return []; + + if (equalv3(el.Center, cir.Center)) + { + let a = el.RadX; + let b = el.RadY; + let r = cir.Radius; + let j = ((a * b) ** 2 - (b * r) ** 2) / (a ** 2 - b ** 2); + let pts: Vector3[] = []; + if (equaln(j, 0) || equaln(j, r ** 2)) + { + if (equaln(j, 0)) + pts = [ + new Vector3(a, 0), + new Vector3(-a, 0) + ] + else + pts = [ + new Vector3(0, r), + new Vector3(0, -r) + ] + } + else if (j < 0) + return []; + else + { + let y1 = Math.sqrt(j); + let y2 = - Math.sqrt(j); + let n = r ** 2 - j; + let x1 = Math.sqrt(n); + let x2 = - Math.sqrt(n); + pts = [ + new Vector3(x1, y1), + new Vector3(x1, y2), + new Vector3(x2, y1), + new Vector3(x2, y2), + ] + } + let ro = new Matrix4().makeRotationZ(el.Rotation); + pts = pts.map(p => p.applyMatrix4(ro).applyMatrix4(el.OCS)); + return CheckPointOnCurve(pts, el, cir, type); + } + else + { + let pts = el.Shape.getPoints(60); + let lineData = pts.map(p => + { + return { pt: p, bul: 0 } + }); + let pl = new Polyline(lineData); + let cirClone = cir.Clone().ApplyMatrix(el.OCSInv); + + if (type === IntersectOption.ExtendBoth) + type = IntersectOption.ExtendArg; + else if (type !== IntersectOption.ExtendArg) + type = IntersectOption.OnBothOperands; + + let intPts = IntersectPolylineAndCurve(pl, cirClone, type); + return intPts.map(p => p.applyMatrix4(el.OCS)); + } +} +export function IntersectEllipse(el1: Ellipse, el2: Ellipse, type: IntersectOption) +{ + if (!el1.IsCoplaneTo(el2)) return []; + + let isEqul = equalv3(el1.Center, el2.Center) + && equaln(el1.RadX, el2.RadX) + && equaln(el1.RadY, el2.RadY) + && equalv3(el1.StartPoint, el2.StartPoint); + + if (isEqul) + return []; + + let a1 = Math.max(el1.RadX, el1.RadY); + let a2 = Math.max(el2.RadX, el2.RadY); + + let dist = el1.Center.distanceToSquared(el2.Center); + if (dist > (a1 + a2) ** 2) + { + return []; + } + + if (!el1.BoundingBox.intersectsBox(el2.BoundingBox)) + return []; + + let diffMat = el1.OCSInv.multiply(el2.OCS); + let pts1 = el1.Shape.getPoints(60); + let pts2 = el2.Shape.getPoints(60); + + let lineData1 = pts1.map(p => + { + return { pt: p, bul: 0 } + }); + let lineData2 = pts2.map(p => + { + return { pt: p, bul: 0 } + }); + + let pl1 = new Polyline(lineData1); + let pl2 = new Polyline(lineData2).ApplyMatrix(diffMat); + + let intPts = pl1.IntersectWith(pl2, 0); + return intPts.map(p => p.applyMatrix4(el1.OCS)); +} diff --git a/src/UI/Components/EntityModal/EntityModal.tsx b/src/UI/Components/EntityModal/EntityModal.tsx index aa5b82a99..d9e02c9aa 100644 --- a/src/UI/Components/EntityModal/EntityModal.tsx +++ b/src/UI/Components/EntityModal/EntityModal.tsx @@ -21,7 +21,7 @@ export class EntityModal extends React.Component<{ store?: EntityStore }, {}> { { if (e.keyCode === KeyBoard.Escape) app.m_Editor.m_ModalManage.Clear(); - })) + })); } componentWillUnmount() { @@ -45,6 +45,7 @@ export class EntityModal extends React.Component<{ store?: EntityStore }, {}> { { options.unshift({ label: "全部" + count.toString(), value: "all" }); } + let ents = this.props.store.GetEntitys(); return (
@@ -62,6 +63,18 @@ export class EntityModal extends React.Component<{ store?: EntityStore }, {}> {
  • + { + ents.length > 0 && this.props.store.GetEntitys()[0]['Length'] !== undefined && + <> +
  • + 长度: {this.props.store.GetEntitys()[0]['Length'].toFixed(2)} +
  • +
  • + 面积: {this.props.store.GetEntitys()[0]['Area'].toFixed(2)} +
  • + + } +
    ); diff --git a/src/UI/Store/EntityStore.ts b/src/UI/Store/EntityStore.ts index 6f6ec51ce..413d13853 100644 --- a/src/UI/Store/EntityStore.ts +++ b/src/UI/Store/EntityStore.ts @@ -6,6 +6,7 @@ import { Entity } from "../../DatabaseServices/Entity"; import { Line } from "../../DatabaseServices/Line"; import { Polyline } from "../../DatabaseServices/Polyline"; import { Region } from "../../DatabaseServices/Region"; +import { Ellipse } from "../../DatabaseServices/Ellipse"; export class EntityStore extends Singleton { @@ -43,6 +44,8 @@ export class EntityStore extends Singleton addEnToMap("圆", en); else if (en instanceof Arc) addEnToMap("圆弧", en); + else if (en instanceof Ellipse) + addEnToMap("椭圆", en); else addEnToMap("三维实体", en);