diff --git a/.vscode/launch.json b/.vscode/launch.json index d0e52e385..a4dae132f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -15,6 +15,29 @@ "name": "Attach to Chrome", "port": 7778, "webRoot": "${workspaceRoot}" + }, + //Ref: https://github.com/Microsoft/vscode-recipes/blob/master/debugging-jest-tests/.vscode/launch.json + { + "type": "node", + "request": "launch", + "name": "Jest All", + "program": "${workspaceFolder}/node_modules/jest/bin/jest", + "args": [ + "--runInBand" + ], + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + }, + { + "type": "node", + "request": "launch", + "name": "Jest Current File", + "program": "${workspaceFolder}/node_modules/jest/bin/jest", + "args": [ + "${relativeFile}" + ], + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" } ] -} \ No newline at end of file +} diff --git a/__test__/Polyline/PointInPolyline.test.ts b/__test__/Polyline/PointInPolyline.test.ts new file mode 100644 index 000000000..39ecf5ee9 --- /dev/null +++ b/__test__/Polyline/PointInPolyline.test.ts @@ -0,0 +1,144 @@ +import { Vector2, Vector3 } from 'three'; + +import { IsPointInPolyLine } from '../../src/DatabaseServices/PointInPolyline'; +import { Polyline } from '../../src/DatabaseServices/Polyline'; + + +test('点在多段线内', () => +{ + + let pl = new Polyline([ + { + pt: new Vector2(0, 0), + bul: 0 + }, + { + pt: new Vector2(10, 0), + bul: 0 + }, + { + pt: new Vector2(10, 10), + bul: 0 + }, + { + pt: new Vector2(8, 10), + bul: 0 + }, + { + pt: new Vector2(8, 5), + bul: 0 + }, + { + pt: new Vector2(4, 5), + bul: 0 + }, + { + pt: new Vector2(4, 10), + bul: 0 + }, + { + pt: new Vector2(0, 10), + bul: 0 + }, + ]); + + pl.CloseMark = true; + + let bIn = IsPointInPolyLine(pl, new Vector3(8, 3));//? + + expect(bIn).toBeTruthy(); +}); + +describe("", () => +{ + test('退化', () => + { + let pl = new Polyline( + [ + { + pt: new Vector2(0, 0), + bul: 0 + }, + { + pt: new Vector2(10, 0), + bul: 0 + }, + { + pt: new Vector2(5, 5), + bul: 0 + }, + ]) + + pl.CloseMark = true; + + expect(IsPointInPolyLine(pl, new Vector3(5, 2))).toBeTruthy(); + }); + + test('退化2', () => + { + //右边中间 < + let pl = new Polyline( + [ + { + pt: new Vector2(0, 0), + bul: 0 + }, + { + pt: new Vector2(10, 0), + bul: 0 + }, + { + pt: new Vector2(5, 5), + bul: 0 + }, + { + pt: new Vector2(10, 10), + bul: 0 + }, + { + pt: new Vector2(0, 10), + bul: 0 + }, + ]) + + pl.CloseMark = true; + + expect(IsPointInPolyLine(pl, new Vector3(5, 2))).toBeTruthy(); + }); + + + test('左边中间', () => + { + //左边中间 + let pl = new Polyline([ + { + pt: new Vector2(0, 0), + bul: 0 + }, + { + pt: new Vector2(10, 0), + bul: 0 + }, + { + pt: new Vector2(10, 10), + bul: 0 + }, + { + pt: new Vector2(0, 10), + bul: 0 + }, + { + pt: new Vector2(0, 8), + bul: 0 + }, + + { + pt: new Vector2(5, 5), + bul: 0 + }] + ) + pl.CloseMark = true; + + expect(IsPointInPolyLine(pl, new Vector3(5, 2))).toBeTruthy(); + }); +}) diff --git a/package.json b/package.json index ad881ac89..915681db3 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,6 @@ "html-loader": "^0.5.5", "html-webpack-plugin": "^2.30.1", "jest": "^22.1.4", - "jest-environment-node-debug": "^2.0.0", "less": "^2.7.3", "less-loader": "^4.0.5", "mobx-react-devtools": "^4.2.15", diff --git a/src/Add-on/Offset.ts b/src/Add-on/Offset.ts index 0d22674fa..522ebb338 100644 --- a/src/Add-on/Offset.ts +++ b/src/Add-on/Offset.ts @@ -2,6 +2,8 @@ import { app } from '../ApplicationServices/Application'; import { Curve } from '../DatabaseServices/Curve'; import { Command } from '../Editor/CommandMachine'; import { PromptStatus } from '../Editor/PromptResult'; +import { Polyline } from '../DatabaseServices/Polyline'; +import { IsPointInPolyLine } from '../DatabaseServices/PointInPolyline'; export class Command_Offset implements Command { @@ -72,6 +74,11 @@ export class Command_TestOffset implements Command let d = cu.GetFistDeriv(cu.GetParamAtPoint(ptClose));//切线。 let c = toPtVec.cross(d); + if (cu instanceof Polyline) + { + c.z = (IsPointInPolyLine(cu, p)) ? -1 : 1; + } + lastpls = cu.GetOffsetCurves(p.distanceTo(cu.GetClosestPointTo(p, !cu.IsClose)) * Math.sign(c.z)); lastpls.forEach((offCur) => { diff --git a/src/Add-on/closetest.ts b/src/Add-on/closetest.ts index 59431e477..e67c11fcd 100644 --- a/src/Add-on/closetest.ts +++ b/src/Add-on/closetest.ts @@ -1,9 +1,14 @@ -import { Command } from "../Editor/CommandMachine"; -import { app } from "../ApplicationServices/Application"; +import { Vector3 } from 'three'; + +import { app } from '../ApplicationServices/Application'; +import { Arc } from '../DatabaseServices/Arc'; +import { Circle } from '../DatabaseServices/Circle'; +import { Curve } from '../DatabaseServices/Curve'; import { Line } from '../DatabaseServices/Line'; -import { Curve, ExtendType } from "../DatabaseServices/Curve"; -import { PromptStatus } from "../Editor/PromptResult"; -import { Polyline } from "../DatabaseServices/Polyline"; +import { IsPointInBowArc, IsPointInPolyLine } from '../DatabaseServices/PointInPolyline'; +import { Polyline } from '../DatabaseServices/Polyline'; +import { Command } from '../Editor/CommandMachine'; +import { PromptStatus } from '../Editor/PromptResult'; export class Command_ClosePt implements Command @@ -18,10 +23,16 @@ export class Command_ClosePt implements Command { let cu = cuRes.Entity as Curve; let line = new Line(); + let closeCir = new Circle(new Vector3(), 0.5); let derLine = new Line(); + app.m_Database.ModelSpace.Append(closeCir); app.m_Database.ModelSpace.Append(line); app.m_Database.ModelSpace.Append(derLine); + app.m_Editor.AddNoSnapEntity(closeCir); + app.m_Editor.AddNoSnapEntity(line); + app.m_Editor.AddNoSnapEntity(derLine); + let extend = false; while (true) { @@ -36,6 +47,17 @@ export class Command_ClosePt implements Command derLine.StartPoint = line.EndPoint; derLine.EndPoint = line.EndPoint.add(cu.GetFistDeriv(line.EndPoint)); + + if (cu instanceof Arc) + { + line.ColorIndex = IsPointInBowArc(cu, p) ? 1 : 2; + } + + if (cu instanceof Polyline) + { + closeCir.Center = p; + closeCir.ColorIndex = IsPointInPolyLine(cu, p) ? 1 : 2; + } } }) if (p.Status === PromptStatus.Keyword) diff --git a/src/Add-on/ptincu.ts b/src/Add-on/ptincu.ts new file mode 100644 index 000000000..f6e1d28b4 --- /dev/null +++ b/src/Add-on/ptincu.ts @@ -0,0 +1,39 @@ +import { Command } from "../Editor/CommandMachine"; +import { app } from '../ApplicationServices/Application'; +import { PromptStatus } from "../Editor/PromptResult"; +import { Box3, Vector3 } from "three"; +import { Circle } from '../DatabaseServices/Circle'; +import { IsPointInPolyLine } from "../DatabaseServices/PointInPolyline"; +import { Polyline } from '../DatabaseServices/Polyline'; + + +export class Command_PtInCu implements Command +{ + async exec() + { + let cuRes = await app.m_Editor.GetEntity(); + if (cuRes.Status != PromptStatus.OK) + return; + if (!(cuRes.Entity instanceof Polyline)) + { return } + + let p1Res = await app.m_Editor.GetPoint(); + if (p1Res.Status != PromptStatus.OK) return; + let p2Res = await app.m_Editor.GetPoint({ BasePoint: p1Res.Value }); + if (p2Res.Status != PromptStatus.OK) return; + + let box = new Box3().setFromPoints([p1Res.Value, p2Res.Value]); + let size = box.getSize(); + for (let i = 0; i < size.x; i++) + { + for (let j = 0; j < size.y; j++) + { + let c = new Circle(box.min.clone().add(new Vector3(i, j)), 0.5); + + c.ColorIndex = IsPointInPolyLine(cuRes.Entity as Polyline, c.Center) ? 1 : 2; + + app.m_Database.ModelSpace.Append(c); + } + } + } +} diff --git a/src/DatabaseServices/Arc.ts b/src/DatabaseServices/Arc.ts index 6f0ec4372..c92d10556 100644 --- a/src/DatabaseServices/Arc.ts +++ b/src/DatabaseServices/Arc.ts @@ -271,12 +271,6 @@ export class Arc extends Curve } return arcs; } - PtOnCurve(pt: Vector3): boolean - { - let param = this.GetParamAtPoint(pt); - return !isNaN(param) && param >= 0 && param <= 1; - } - GetOffsetCurves(offsetDist: number) { if (this.m_Clockwise) offsetDist *= -1; diff --git a/src/DatabaseServices/Curve.ts b/src/DatabaseServices/Curve.ts index 44e775233..328c75481 100644 --- a/src/DatabaseServices/Curve.ts +++ b/src/DatabaseServices/Curve.ts @@ -1,6 +1,7 @@ import { Mesh, Object3D, Vector3 } from 'three'; import { ColorMaterial } from '../Common/ColorPalette'; +import { equal } from '../Geometry/GeUtils'; import { RenderType } from '../GraphicsSystem/Enum'; import { IntersectOption } from '../GraphicsSystem/IntersectWith'; import { Factory } from './CADFactory'; @@ -88,10 +89,13 @@ export abstract class Curve extends Entity //翻转曲线.首尾调换. Reverse() { }; - //点在曲线内部 - PtOnCurve(pt: Vector3): boolean { return false; } + //点在曲线上 + PtOnCurve(pt: Vector3): boolean + { + return equal(this.StartPoint, pt) || equal(this.EndPoint, pt) || this.ParamOnCurve(this.GetParamAtPoint(pt)); + } - //参数在曲线内部 + //参数在曲线上 ParamOnCurve(param: number): boolean { return !isNaN(param) && param >= 0 && param <= this.EndParam; } GetOffsetCurves(offsetDist: number): Array { return; } diff --git a/src/DatabaseServices/Line.ts b/src/DatabaseServices/Line.ts index 7606d8ec1..0a943ad56 100644 --- a/src/DatabaseServices/Line.ts +++ b/src/DatabaseServices/Line.ts @@ -151,11 +151,6 @@ export class Line extends Curve { return this.GetDistAtParam(this.GetParamAtPoint(pt)); } - PtOnCurve(pt: Vector3): boolean - { - let param = this.GetParamAtPoint(pt); - return !isNaN(param) && param >= -1e-6 && param <= 1.0000001; - } GetSplitCurves(params: number[] | number) { let pts = new Array(); diff --git a/src/DatabaseServices/PointInPolyline.ts b/src/DatabaseServices/PointInPolyline.ts new file mode 100644 index 000000000..b9105ba9b --- /dev/null +++ b/src/DatabaseServices/PointInPolyline.ts @@ -0,0 +1,154 @@ +import { Vector2, Vector3 } from 'three'; + +import { angle, equal, equaln } from '../Geometry/GeUtils'; +import { IntersectOption } from '../GraphicsSystem/IntersectWith'; +import { Arc } from './Arc'; +import { Line } from './Line'; +import { Polyline } from './Polyline'; + +/** + * 点在扇形内部,提供一个简单实现的版本. + * 优化版本请参照:http://www.cnblogs.com/miloyip/archive/2013/04/19/3029852.html + * + * @param {Arc} arc 二维圆弧 + * @param {Vector3} pt + * @returns {boolean} 点在扇形内部. + */ +function IsPointInCircularSector(arc: Arc, pt: Vector3): boolean +{ + let center = arc.Center; + let disSq = center.distanceTo(pt); + if (disSq > arc.Radius * arc.Radius) return false; + let an = angle(pt.clone().sub(center)); + let param = arc.GetParamAtAngle(an); + return arc.ParamOnCurve(param); +} + +/** + * 点在弓型内部 + * + * @param {Arc} arc 二维圆弧 + * @param {Vector3} pt 点 + * @returns {boolean} 点在内部 + */ +export function IsPointInBowArc(arc: Arc, pt: Vector3): boolean +{ + let pv = pt.clone().sub(arc.StartPoint); + let av = arc.EndPoint.sub(arc.StartPoint); + + pv.cross(av); + + //未优化的代码 + // if (pv.z > 0 && arc.IsClockWise) + // return false; + // else if (pv.z < 0 && !arc.IsClockWise) + // return false; + // else + // return arc.Center.distanceToSquared(pt) < arc.Radius * arc.Radius; + + //简化的代码 + if ((pv.z > 0) !== arc.IsClockWise) + { + return arc.Center.distanceToSquared(pt) < arc.Radius * arc.Radius; + } + return false; +} + +/** + * 判断点在多段线内外 + * + * @export + * @param {Polyline} pl 多段线 + * @param {Vector3} pt 点 + * @returns {boolean} 点在多段线内部 + */ +export function IsPointInPolyLine(pl: Polyline, pt: Vector3): boolean +{ + let crossings = 0; + + let insLine = new Line(pt, pt.clone().add(new Vector3(0, 10, 0))); + + for (let i = 0; i < pl.EndParam; i++) + { + let cu = pl.GetCurveAtIndex(i); + let inpts = cu.IntersectWith(insLine, IntersectOption.ExtendArg); + + for (let pti of inpts) + { + if (pti.y < pt.y) + continue; + + if (equal(pti, cu.StartPoint)) + { + let der = cu.GetFistDeriv(0); + if (der.x < 1e-3) //左边+ 右边0 + crossings++; + } + else if (equal(pti, cu.EndPoint)) + { + let der = cu.GetFistDeriv(1); + if (der.x > 1e-3) //左边+ 右边0 + crossings++; + } + else + { + let der = cu.GetFistDeriv(pti); + if (!equaln(der.x, 0)) //相切. + crossings++; + } + } + } + + return (crossings % 2) === 1; +} + +/** + * 点在区域内部 + * + * @param {Vector3} pt + * @param {Vector2[]} pts + * @returns + */ +function IsPointInPolygon(pt: Vector3, pts: Vector2[]) +{ + let crossings = 0; //int + let [px, py] = [pt.x, pt.y]; + + let ptCout = pts.length; + for (let i = 0; i < ptCout; i++) + { + let pti = pts[i]; + let ptn = pts[(i + 1) % ptCout]; + + let [x1, x2] = [pti.x, ptn.x]; + + /* This is done to ensure that we get the same result when + the line goes from left to right and right to left */ + if (x1 > x2)[x1, x2] = [x2, x1]; + + /* First check if the ray is possible to cross the line */ + if (px > x1 && px <= x2 && (py < pti.y || py <= ptn.y)) + { + const eps = 0.000001; + + /* Calculate the equation of the line */ + let dx = ptn.x - pti.x; + let dy = ptn.y - pti.y; + let k; + + if (Math.abs(dx) < eps) + k = 1e300; + else + k = dy / dx; + + let m = pti.y - k * pts[i].x; + + /* Find if the ray crosses the line */ + let y2 = k * px + m; + if (py <= y2) + crossings++; + } + } + + return crossings % 2 === 1; +} diff --git a/src/Editor/CommandRegister.ts b/src/Editor/CommandRegister.ts index f5e6e7d4c..630158f6d 100644 --- a/src/Editor/CommandRegister.ts +++ b/src/Editor/CommandRegister.ts @@ -52,6 +52,7 @@ import { DrawText } from '../Add-on/DrawText'; // import { DrawFloor } from '../Add-on/DrawFloor'; // import { RevTarget, SaveTarget } from '../Add-on/RenderTarget'; import { Command_Array } from '../Add-on/Array'; +import { Command_PtInCu } from '../Add-on/ptincu'; export function registerCommand() { commandMachine.RegisterCommand("l", new DrawLine()) @@ -157,6 +158,9 @@ export function registerCommand() //阵列 commandMachine.RegisterCommand("array", new Command_Array()); + commandMachine.RegisterCommand("incu", new Command_PtInCu()); + + // commandMachine.RegisterCommand("st", new SaveTarget()) // commandMachine.RegisterCommand("rt", new RevTarget()) diff --git a/src/Geometry/GeUtils.ts b/src/Geometry/GeUtils.ts index fe238c1e5..9c28b29cd 100644 --- a/src/Geometry/GeUtils.ts +++ b/src/Geometry/GeUtils.ts @@ -14,7 +14,7 @@ export function equaln(v1: number, v2: number, fuzz = 1e-3) } export function equal(v1: THREE.Vector3, v2: THREE.Vector3) { - return v1.distanceToSquared(v2) < 1e-12; + return v1.distanceToSquared(v2) < 1e-8; } export function fixAngle(an: number, fixAngle: number, fuzz: number = 0.1)