export class Vector2d { m_X = 0 m_Y = 0 /** 长度 */ Length = 0 /** 角度 */ Angle = 0 constructor(x: number, y: number) { this.m_X = x this.m_Y = y this.GetLength() this.GetAngle() } private GetLength() { this.Length = Math.sqrt(this.m_X * this.m_X + this.m_Y * this.m_Y) } // 角度 private GetAngle() { let a = Math.atan2(this.m_Y, this.m_X) if (a < 0) a = Math.PI * 2 + a this.Angle = a } Normal(): Vector2d { if (this.Length != 0) { let k = 1 / this.Length this.m_X *= k this.m_Y *= k this.Length = 1 } return this } static OpDivide(v: Vector2d, c: number): Vector2d { return new Vector2d(v.m_X / c, v.m_Y / c) } static OpMultiply(v: Vector2d, c: number): Vector2d { return new Vector2d(v.m_X * c, v.m_Y * c) } static OpAdd(v1: Vector2d, v2: Vector2d): Vector2d { return new Vector2d(v1.m_X + v2.m_X, v1.m_Y + v2.m_Y) } static OpMultiplyValue(v1: Vector2d, v2: Vector2d): number { return v1.m_X * v2.m_X + v1.m_Y * v2.m_Y } /** 点积 */ DotProduct(v1: Vector2d): number { return Vector2d.OpMultiplyValue(this, v1) } /** 叉积 */ CrossProduct(v: Vector2d): number { return this.m_X * v.m_Y - this.m_Y * v.m_X } IsEqual(v: Vector2d, fuzz = 1e-3): boolean { return DoubleUtil.EqualX(this.m_X, v.m_X, fuzz) && DoubleUtil.EqualX(this.m_Y, v.m_Y, fuzz) } /** 返回和另外一个向量的夹角 */ AngleTo(v: Vector2d): number { let an = Math.abs(this.Angle - v.Angle) if (DoubleUtil.EqualX(v.Length, 0, 1e-5) || DoubleUtil.EqualX(this.Length, 0, 1e-5)) { return -Math.PI// 随缘给 } else if (Math.abs(Math.sin(an)) < 0) { return this.Normal().IsEqual(v.Normal()) ? 0 : Math.PI } else { let p0 = new Point2d(0, 0) let p1 = Point2d.OpAdd(new Point2d(0, 0), this) let p2 = Point2d.OpAdd(new Point2d(0, 0), v) let s = this.sign(Utils.Det(p1, p0, p2)) if (an > Math.PI) { return ((2 * Math.PI) - an) * s } else { return an * s } } } private sign(x: number): number { if (x == 0) { return 0 } else if (x > 0) { return -1 } else { return 1 } } } /** CAD 点 */ export class Point2d { m_X = 0 m_Y = 0 constructor(x: number, y: number) { this.m_X = x this.m_Y = y } DistensTo(pt: Point2d): number { return Point2d.OpSubtract(pt, this).Length } Polar(angle: number, distens: number): Point2d { let x1 = this.m_X + Math.cos(angle) * distens let y1 = this.m_Y + Math.sin(angle) * distens return new Point2d(x1, y1) } Mid(pt: Point2d): Point2d { return new Point2d((this.m_X + pt.m_X) * 0.5, (this.m_Y + pt.m_Y) * 0.5) } AsVector(): Vector2d { return new Vector2d(this.m_X, this.m_Y) } /** 减以 - */ static OpSubtract(p1: Point2d, p2: Point2d): Vector2d { return new Vector2d(p1.m_X - p2.m_X, p1.m_Y - p2.m_Y) } static OpAdd(p1: Point2d, p2: Vector2d): Point2d { return new Point2d(p1.m_X + p2.m_X, p1.m_Y + p2.m_Y) } } function equaln(v1: number, v2: number, fuzz = 1e-5) { return Math.abs(v1 - v2) <= fuzz } function equalp(v1: Point2d, v2: Point2d, fuzz = 1e-5) { return equaln(v1.m_X, v2.m_X, fuzz) && equaln(v1.m_Y, v2.m_Y, fuzz) } /** 二维曲线:直线,圆弧 */ export abstract class Curve2d { // 求交点 abstract IntersectWith(cu: Curve2d, retIns: Point2d[]): void abstract Offset(distens: number): Curve2d // 点在线内部 abstract PtInCurve(pt: Point2d): boolean abstract ClosePointTo(pt: Point2d): Point2d // 获得参数 返回0或1 abstract GetParametersAt(pt: Point2d): number abstract Parse() PtOnCurve(p: Point2d): boolean { return equalp(p, this.m_StartPoint) || equalp(p, this.m_EndPoint) || this.ParamOnCurve(this.GetParametersAt(p)) } ParamOnCurve(param: number, fuzz = 1e-6): boolean { return !Number.isNaN(param) && param >= -fuzz && param <= 1 + fuzz } get EndPoint(): Point2d { return this.m_EndPoint } set EndPoint(p: Point2d) { this.m_EndPoint = p try { this.Parse() } catch (error) { } } get StartPoint(): Point2d { return this.m_StartPoint } set StartPoint(p: Point2d) { this.m_StartPoint = p try { this.Parse() } catch (error) { } } protected m_StartPoint: Point2d protected m_EndPoint: Point2d tagData: number // 特殊数据 异形边 封边值 tagData2: number // 特殊数据 侧孔数 } /** 直线 */ export class Line2d extends Curve2d { get toString() { return `[${this.m_StartPoint.m_X.toFixed(1)},${this.m_StartPoint.m_Y.toFixed(1)}] [${this.m_EndPoint.m_X.toFixed(1)},${this.m_EndPoint.m_Y.toFixed(1)}]` } constructor(p1: Point2d, p2: Point2d) { super() this.m_StartPoint = p1 this.m_EndPoint = p2 this.Parse() } /** 求交点 */ IntersectWith(cu: Curve2d, retIns: Point2d[]) { if (cu instanceof Line2d) { let l = cu as Line2d let dx1 = this.StartPoint.m_X - this.EndPoint.m_X let dx2 = l.StartPt.m_X - l.EndPt.m_X let dx3 = l.EndPt.m_X - this.EndPoint.m_X let dy1 = this.StartPoint.m_Y - this.EndPoint.m_Y let dy2 = l.StartPt.m_Y - l.EndPt.m_Y let dy3 = l.EndPt.m_Y - this.EndPoint.m_Y let det = (dx2 * dy1) - (dy2 * dx1) let pt = new Point2d(0, 0) if (DoubleUtil.EqualX(det, 0.0, 1e-5)) { if (DoubleUtil.EqualX(dx2 * dy3, dy2 * dx3, 1e-5)) { if (l.StartPoint.DistensTo(this.EndPoint) < 1e-3) { retIns.push(this.EndPoint) } } return } let ratio = ((dx1 * dy3) - (dy1 * dx3)) / det pt.m_X = (ratio * dx2) + l.EndPt.m_X pt.m_Y = (ratio * dy2) + l.EndPt.m_Y retIns.push(pt) } else { let arc = cu as Arc2d arc.IntersectWith(this, retIns) } } /** 偏移 */ Offset(distens: number): Curve2d { let an = Point2d.OpSubtract(this.EndPt, this.StartPt).Angle - Math.PI * 0.5 return new Line2d(this.StartPt.Polar(an, distens), this.EndPt.Polar(an, distens)) } /** 判断点在线内 */ PtInCurve(pt: Point2d): boolean { // 首先判断平行 let minX = Math.min(this.m_StartPoint.m_X, this.m_EndPoint.m_X) let maxX = Math.max(this.m_StartPoint.m_X, this.m_EndPoint.m_X) if (DoubleUtil.EqualX(minX, maxX, 1e-3)) { let minY = Math.min(this.m_StartPoint.m_Y, this.m_EndPoint.m_Y) let maxY = Math.max(this.m_StartPoint.m_Y, this.m_EndPoint.m_Y) return (pt.m_Y >= minY - 1e-4 && pt.m_Y <= maxY + 1e-4) } if (pt.m_X >= minX - 0.0001 && pt.m_X <= maxX + 0.0001) { let vec = Point2d.OpSubtract(this.m_EndPoint, this.m_StartPoint)// 标准向量 let k = vec.m_Y / vec.m_X return DoubleUtil.EqualX(this.m_StartPoint.m_Y + k * (pt.m_X - this.m_StartPoint.m_X), pt.m_Y, 0.01) } else { return false } } ClosePointTo(pt: Point2d): Point2d { let an = Point2d.OpSubtract(this.m_EndPoint, this.m_StartPoint).Angle + Math.PI / 2 let vec = new Point2d(0, 0).Polar(an, 1).AsVector() let p2 = Point2d.OpAdd(pt, vec) let line = new Line2d(pt, p2) let ptLst: Point2d[] = [] this.IntersectWith(line, ptLst) return ptLst[0] } Parse() { if (this.EndPoint != null) { this.m_Length = this.StartPoint.DistensTo(this.EndPoint) } } GetParametersAt(pt: Point2d): number { let nearPt = this.ClosePointTo(pt) let vec = Point2d.OpSubtract(nearPt, this.m_StartPoint) let vec2 = Point2d.OpSubtract(this.EndPoint, this.StartPoint) let an = vec.DotProduct(vec2) return Math.sign(an) * vec.Length / this.m_Length } get StartPt(): Point2d { return this.StartPoint } get EndPt(): Point2d { return this.EndPoint } m_Length: number static New(x0: number, y0: number, x1: number, y1: number): Line2d { let p0 = new Point2d(x0, y0) let p1 = new Point2d(x1, y1) return new Line2d(p0, p1) } } /** 圆弧 */ export class Arc2d extends Curve2d { constructor(p1: Point2d, p2: Point2d, bul: number) { super() this.m_StartPoint = p1 this.m_EndPoint = p2 this.m_Bul = bul let vec = Point2d.OpSubtract(p2, p1) let an = vec.Angle let length = vec.Length this.m_Radius = length / Math.sin(2 * Math.atan(bul)) / 2 this.m_AllAngle = Math.atan(bul) * 4 let delDis = bul * length / 2 let toDis = this.m_Radius - delDis an += Math.PI * 0.5 this.m_Center = p1.Mid(p2) this.m_Center = this.m_Center.Polar(an, toDis) this.m_StartAngle = Point2d.OpSubtract(this.StartPoint, this.m_Center).Angle this.m_EndAngle = Point2d.OpSubtract(this.EndPoint, this.m_Center).Angle if (this.m_Bul < 0) this.m_Radius = Math.abs(this.m_Radius) } /** 圆心 */ m_Center: Point2d /** 半径 */ m_Radius: number /** 起始弧度 */ m_StartAngle: number /** 结束弧度 */ m_EndAngle: number /** 所有的弧度 */ m_AllAngle: number IntersectWith(cu: Curve2d, retIns: Point2d[]) { if (cu instanceof Line2d) { let l = cu as Line2d let a = DoubleUtil.Sqr(l.EndPt.m_X - l.StartPt.m_X) + DoubleUtil.Sqr(l.EndPt.m_Y - l.StartPt.m_Y) let b = (2.0) * ((l.EndPt.m_X - l.StartPt.m_X) * (l.StartPt.m_X - this.m_Center.m_X) + (l.EndPt.m_Y - l.StartPt.m_Y) * (l.StartPt.m_Y - this.m_Center.m_Y)) let c = DoubleUtil.Sqr(this.m_Center.m_X) + DoubleUtil.Sqr(this.m_Center.m_Y) + DoubleUtil.Sqr(l.StartPt.m_X) + DoubleUtil.Sqr(l.StartPt.m_Y) - (2.0) * (this.m_Center.m_X * l.StartPt.m_X + this.m_Center.m_Y * l.StartPt.m_Y) - DoubleUtil.Sqr(this.m_Radius) let det = b * b - (4.0) * a * c if (DoubleUtil.EqualX(det, 0.0, 0.1)) { let delta = -b / ((2.0) * a) let pt = new Point2d(l.StartPt.m_X + delta * (l.EndPt.m_X - l.StartPt.m_X), l.StartPt.m_Y + delta * (l.EndPt.m_Y - l.StartPt.m_Y)) retIns.push(pt) return } else if (det > (0.0)) { let sqrt_det = Math.sqrt(det) let delta = (-b + sqrt_det) / ((2.0) * a) let p2 = new Point2d(l.StartPt.m_X + delta * (l.EndPt.m_X - l.StartPt.m_X), l.StartPt.m_Y + delta * (l.EndPt.m_Y - l.StartPt.m_Y)) delta = (-b - sqrt_det) / ((2.0) * a) let p3 = new Point2d(l.StartPt.m_X + delta * (l.EndPt.m_X - l.StartPt.m_X), l.StartPt.m_Y + delta * (l.EndPt.m_Y - l.StartPt.m_Y)) retIns.push(p2) retIns.push(p3) return } } else if (cu instanceof Arc2d) { let arc = cu as (Arc2d) let dist = arc.m_Center.DistensTo(this.m_Center) if (dist < Math.abs(this.m_Radius - cu.m_Radius) - 1e-3 || dist > (this.m_Radius + cu.m_Radius + 1e-3)) return let dstsqr = dist * dist let r1sqr = this.m_Radius * this.m_Radius let r2sqr = arc.m_Radius * arc.m_Radius let a = (dstsqr - r2sqr + r1sqr) / (2 * dist) let h = Math.sqrt(Math.abs(r1sqr - (a * a))) let ratio_a = a / dist let ratio_h = h / dist let dx = arc.m_Center.m_X - this.m_Center.m_X let dy = arc.m_Center.m_Y - this.m_Center.m_Y let phix = this.m_Center.m_X + (ratio_a * dx) let phiy = this.m_Center.m_Y + (ratio_a * dy) dx = dx * ratio_h dy = dy * ratio_h let pt = new Point2d(phix + dy, phiy - dx) let p2 = new Point2d(phix - dy, phiy + dx) retIns.push(pt) retIns.push(p2) } } Offset(distens: number): Curve2d { let rn = new Arc2d(new Point2d(0, 0), new Point2d(0, 0), 0) rn.m_Center = this.m_Center rn.m_Radius = this.m_Radius rn.m_StartAngle = this.m_StartAngle rn.m_EndAngle = this.m_EndAngle if (this.m_Bul > 0) rn.m_Radius += distens else rn.m_Radius -= distens rn.Bul = this.m_Bul rn.ParsePointFormAngle() return rn } Parse() { // 解析角度 this.ParseAngleFormPoint() this.ParseAllAngle() this.ParseBul() } ParseAllAngle() { this.m_AllAngle = this.ComputeAnlge(this.m_EndAngle) } ComputeAnlge(endAngle: number) { // 顺时针 if (this.m_Bul < 0) { if (this.m_StartAngle > endAngle) return this.m_StartAngle - endAngle else // 越过0点绘制圆弧 return (Math.PI * 2) - (endAngle - this.m_StartAngle) } else { if (endAngle > this.m_StartAngle) return endAngle - this.m_StartAngle else return (Math.PI * 2) - (this.m_StartAngle - endAngle) } } ParseBul() { this.m_Bul = Math.tan(this.m_AllAngle * 0.25) * (this.m_Bul < 0 ? -1 : 1) } ParsePointFormAngle() { this.m_StartPoint = this.m_Center.Polar(this.m_StartAngle, this.m_Radius) this.m_EndPoint = this.m_Center.Polar(this.m_EndAngle, this.m_Radius) } ParseAngleFormPoint() { this.m_StartAngle = Point2d.OpSubtract(this.StartPoint, this.m_Center).Angle this.m_EndAngle = Point2d.OpSubtract(this.EndPoint, this.m_Center).Angle } PtInCurve(pt: Point2d): boolean { let param = this.GetParametersAt(pt) return param > -1e-3 && param < 1.0001 } ClosePointTo(pt: Point2d): Point2d { return null } GetParametersAt(pt: Point2d): number { let vec = Point2d.OpSubtract(pt, this.m_Center) let an = vec.Angle this.ParseAllAngle() // 如果以pt为终点,那么所有的角度为 let ptAllAn = this.ComputeAnlge(an) let allAn = this.m_AllAngle // 减去圆弧角度,剩余角度的一半 let surplusAngleHalf = Math.PI - allAn / 2 if (ptAllAn > allAn + surplusAngleHalf)// 返回负数 return ((ptAllAn - allAn) - (surplusAngleHalf * 2)) / allAn else// 返回正数 return ptAllAn / allAn } GetAngleAtParam(param: number) { return this.clampRad(this.m_StartAngle + param * this.m_AllAngle) } private clampRad(an: number) { an = an % (Math.PI * 2) if (an < 0) an += Math.PI * 2 return an } /** 凸度. */ m_Bul: number get Bul() { return this.m_Bul } set Bul(v: number) { this.m_Bul = v } static New(x0: number, y0: number, x1: number, y1: number, bul: number): Arc2d { return new Arc2d(new Point2d(x0, y0), new Point2d(x1, y1), bul) } } /** 圆弧 */ export class Arc2d_new extends Curve2d { constructor(p1: Point2d, p2: Point2d, bul: number) { super() this.m_StartPoint = p1 this.m_EndPoint = p2 this.m_Bul = bul let vec = Point2d.OpSubtract(p2, p1) let an = vec.Angle let length = vec.Length this.m_Radius = length / Math.sin(2 * Math.atan(bul)) / 2 this.m_AllAngle = Math.atan(bul) * 4 let delDis = bul * length / 2 let toDis = this.m_Radius - delDis an += Math.PI * 0.5 this.m_Center = p1.Mid(p2) this.m_Center = this.m_Center.Polar(an, toDis) this.m_StartAngle = Point2d.OpSubtract(this.StartPoint, this.m_Center).Angle this.m_EndAngle = Point2d.OpSubtract(this.EndPoint, this.m_Center).Angle if (this.m_Bul < 0) this.m_Radius = Math.abs(this.m_Radius) } /** 圆心 */ m_Center: Point2d /** 半径 */ m_Radius: number /** 起始弧度 */ m_StartAngle: number /** 结束弧度 */ m_EndAngle: number /** 所有的弧度 */ m_AllAngle: number IntersectWith(cu: Curve2d, retIns: Point2d[]) { if (cu instanceof Line2d) { let l = cu as Line2d let a = DoubleUtil.Sqr(l.EndPt.m_X - l.StartPt.m_X) + DoubleUtil.Sqr(l.EndPt.m_Y - l.StartPt.m_Y) let b = (2.0) * ((l.EndPt.m_X - l.StartPt.m_X) * (l.StartPt.m_X - this.m_Center.m_X) + (l.EndPt.m_Y - l.StartPt.m_Y) * (l.StartPt.m_Y - this.m_Center.m_Y)) let c = DoubleUtil.Sqr(this.m_Center.m_X) + DoubleUtil.Sqr(this.m_Center.m_Y) + DoubleUtil.Sqr(l.StartPt.m_X) + DoubleUtil.Sqr(l.StartPt.m_Y) - (2.0) * (this.m_Center.m_X * l.StartPt.m_X + this.m_Center.m_Y * l.StartPt.m_Y) - DoubleUtil.Sqr(this.m_Radius) let det = b * b - (4.0) * a * c if (DoubleUtil.EqualX(det, 0.0, 0.1)) { let delta = -b / ((2.0) * a) let pt = new Point2d(l.StartPt.m_X + delta * (l.EndPt.m_X - l.StartPt.m_X), l.StartPt.m_Y + delta * (l.EndPt.m_Y - l.StartPt.m_Y)) retIns.push(pt) return } else if (det > (0.0)) { let sqrt_det = Math.sqrt(det) let delta = (-b + sqrt_det) / ((2.0) * a) let p2 = new Point2d(l.StartPt.m_X + delta * (l.EndPt.m_X - l.StartPt.m_X), l.StartPt.m_Y + delta * (l.EndPt.m_Y - l.StartPt.m_Y)) delta = (-b - sqrt_det) / ((2.0) * a) let p3 = new Point2d(l.StartPt.m_X + delta * (l.EndPt.m_X - l.StartPt.m_X), l.StartPt.m_Y + delta * (l.EndPt.m_Y - l.StartPt.m_Y)) retIns.push(p2) retIns.push(p3) return } } else if (cu instanceof Arc2d) { let arc = cu as (Arc2d) let dist = arc.m_Center.DistensTo(this.m_Center) if (dist < Math.abs(this.m_Radius - cu.m_Radius) - 1e-3 || dist > (this.m_Radius + cu.m_Radius + 1e-3)) return let dstsqr = dist * dist let r1sqr = this.m_Radius * this.m_Radius let r2sqr = arc.m_Radius * arc.m_Radius let a = (dstsqr - r2sqr + r1sqr) / (2 * dist) let h = Math.sqrt(Math.abs(r1sqr - (a * a))) let ratio_a = a / dist let ratio_h = h / dist let dx = arc.m_Center.m_X - this.m_Center.m_X let dy = arc.m_Center.m_Y - this.m_Center.m_Y let phix = this.m_Center.m_X + (ratio_a * dx) let phiy = this.m_Center.m_Y + (ratio_a * dy) dx = dx * ratio_h dy = dy * ratio_h let pt = new Point2d(phix + dy, phiy - dx) let p2 = new Point2d(phix - dy, phiy + dx) retIns.push(pt) retIns.push(p2) } } Offset(distens: number): Curve2d { let rn = new Arc2d(new Point2d(0, 0), new Point2d(0, 0), 0) rn.m_Center = this.m_Center rn.m_Radius = this.m_Radius rn.m_StartAngle = this.m_StartAngle rn.m_EndAngle = this.m_EndAngle if (this.m_Bul > 0) rn.m_Radius += distens else rn.m_Radius -= distens rn.Bul = this.m_Bul rn.ParsePointFormAngle() return rn } Parse() { // 解析角度 this.ParseAngleFormPoint() this.ParseAllAngle() this.ParseBul() } ParseAllAngle() { this.m_AllAngle = this.ComputeAnlge(this.m_EndAngle) } ComputeAnlge(endAngle: number) { // 顺时针 if (this.m_Bul < 0) { if (this.m_StartAngle > endAngle) return this.m_StartAngle - endAngle else // 越过0点绘制圆弧 return (Math.PI * 2) - (endAngle - this.m_StartAngle) } else { if (endAngle > this.m_StartAngle) return endAngle - this.m_StartAngle else return (Math.PI * 2) - (this.m_StartAngle - endAngle) } } ParseBul() { this.m_Bul = Math.tan(this.m_AllAngle * 0.25) * (this.m_Bul < 0 ? -1 : 1) } ParsePointFormAngle() { this.m_StartPoint = this.m_Center.Polar(this.m_StartAngle, this.m_Radius) this.m_EndPoint = this.m_Center.Polar(this.m_EndAngle, this.m_Radius) } ParseAngleFormPoint() { this.m_StartAngle = Point2d.OpSubtract(this.StartPoint, this.m_Center).Angle this.m_EndAngle = Point2d.OpSubtract(this.EndPoint, this.m_Center).Angle } PtInCurve(pt: Point2d): boolean { let param = this.GetParametersAt(pt) return param > -1e-3 && param < 1.0001 } ClosePointTo(pt: Point2d): Point2d { return null } GetParametersAt(pt: Point2d): number { let vec = Point2d.OpSubtract(pt, this.m_Center) let an = vec.Angle this.ParseAllAngle() // 如果以pt为终点,那么所有的角度为 let ptAllAn = this.ComputeAnlge(an) let allAn = this.m_AllAngle // 减去圆弧角度,剩余角度的一半 let surplusAngleHalf = Math.PI - allAn / 2 if (ptAllAn > allAn + surplusAngleHalf)// 返回负数 return ((ptAllAn - allAn) - (surplusAngleHalf * 2)) / allAn else// 返回正数 return ptAllAn / allAn } GetAngleAtParam(param: number) { return this.clampRad(this.m_StartAngle + param * this.m_AllAngle) } private clampRad(an: number) { an = an % (Math.PI * 2) if (an < 0) an += Math.PI * 2 return an } /** 凸度. */ m_Bul: number get Bul() { return this.m_Bul } set Bul(v: number) { this.m_Bul = v } static New(x0: number, y0: number, x1: number, y1: number, bul: number): Arc2d { return new Arc2d(new Point2d(x0, y0), new Point2d(x1, y1), bul) } } function EntityEncode(c: Curve2d) { if (c instanceof Line2d) return 1 else return 2 } function EntityEncode2(c1: Curve2d, c2: Curve2d) { return EntityEncode(c1) & EntityEncode(c2) } /** CAD 运算库 */ export class Utils { static Intersec(e1: Curve2d, e2: Curve2d, oldE1: Curve2d): Point2d { if (e1.EndPoint.DistensTo(e2.StartPoint) < 1e-2) { return e1.EndPoint } let ptsIns: Point2d[] = [] e1.IntersectWith(e2, ptsIns) if (ptsIns.length == 1) { return ptsIns[0] } else if (ptsIns.length == 2) { // 求一个最近的点. let pt = oldE1.EndPoint let d1 = pt.DistensTo(ptsIns[0]) let d2 = pt.DistensTo(ptsIns[1]) return d1 < d2 ? ptsIns[0] : ptsIns[1] } else // 0点 { return null } } /** 曲线偏移 */ static offsetPoints(points, offsetDistens: number): any[] { let ens: Curve2d[] = [] for (let i = 0; i < points.length; i++) { let j = i + 1 if (j == points.length) j = 0 let line = new Line2d(new Point2d(points[i].x, points[i].y), new Point2d(points[j].x, points[j].y)) if (line.m_Length == 0) continue ens.push(line) } let ens2 = Utils.OffsetCurveList(ens, offsetDistens) let newPs = [] for (let c of ens2) { newPs.push({ x: c.StartPoint.m_X, y: c.StartPoint.m_Y }) } return newPs } // 1.曲线外偏移. //* 2.曲线尝试首尾相连 //* ->如果两条不相连,导致曲线连接失败,将添加圆弧过度 //* //* 2017-11-17对曲线相连算法进行修改. //* 现在会先求出连接点,然后在重新进行曲线连接,避免连接点出错. 参见#118 static OffsetCurveList(ens: Curve2d[], offsetDistens: number): Curve2d[] { // 只处理偏移的曲线 let offEns: Curve2d[] = [] ens = ens.filter(e => e.StartPoint.DistensTo(e.EndPoint) > 0.05) for (const item of ens) { offEns.push(item.Offset(offsetDistens)) } // 处理偏移 并且对交点进行处理 let offEns2: Curve2d[] = [] let linkPts: Point2d[] = [] // 计算链接点,如果有必要会新增圆弧 for (let i = 0; i < offEns.length; i++) { let index2 = i + 1 == offEns.length ? 0 : i + 1 let e1 = offEns[i] let e2 = offEns[index2] offEns2.push(offEns[i]) let iPts: Point2d[] = [] e1.IntersectWith(e2, iPts) let tPts = iPts.filter(p => e1.PtOnCurve(p) && e2.PtOnCurve(p)) let code = EntityEncode2(e1, e2) if (code === 1) { if (tPts.length > 0) linkPts.push(tPts[0])// 直接连接 else { let refP = ens[i].EndPoint let distSq = iPts[0].DistensTo(refP) if (distSq > offsetDistens ** 2 * 2.1) // 补圆弧 { let arc = new Arc2d(e1.EndPoint, e2.StartPoint, Math.sign(offsetDistens)) arc.m_Center = ens[i].EndPoint arc.m_Radius = offsetDistens// 半径 linkPts.push(e1.EndPoint) linkPts.push(e2.StartPoint) offEns2.push(arc)// 添加圆弧. } else linkPts.push(iPts[0]) } } else if ( code === 2 && ( equalp((e1).m_Center, (e2).m_Center, 0.1) // 都是圆弧,且同心圆(由于精度丢失,这里我们给0.01的容差) || equalp(e1.EndPoint, e2.StartPoint, 0.1)) ) linkPts.push(e1.EndPoint) else { function SelectNearP(pts: Point2d[], refPt: Point2d): Point2d { if (pts.length > 1) { let dist1 = refPt.DistensTo(pts[0]) let dist2 = refPt.DistensTo(pts[1]) return dist1 <= dist2 ? pts[0] : pts[1] } return pts[0] } let refP = ens[i].EndPoint if (tPts.length > 0) linkPts.push(SelectNearP(iPts, refP))// 直接连接 else { let arc = new Arc2d(e1.EndPoint, e2.StartPoint, Math.sign(offsetDistens)) arc.m_Center = ens[i].EndPoint arc.m_Radius = offsetDistens// 半径 linkPts.push(e1.EndPoint) linkPts.push(e2.StartPoint) offEns2.push(arc)// 添加圆弧. } } } // 更新链接点位置 for (let i = 0; i < offEns2.length; i++) { offEns2[i].EndPoint = linkPts[i] let nextIndex = i == offEns2.length - 1 ? 0 : i + 1 offEns2[nextIndex].StartPoint = linkPts[i] } // 测试数据 // TestPolyline([ens, offEns2]) return offEns2 } /** //自交曲线优化 去除自交部分. */ static RemoveSelfIntersect(cus: Curve2d[]): Curve2d[] { let res = new Array() let cout = cus.length for (let i = 0; i < cout; i++) { let cu = cus[i] res.push(cu) for (let j = cout - 2; j > i + 1; j--) { let cu2 = cus[j] let pts = new Array() cu.IntersectWith(cu2, pts) // if (pts.length == 0) { continue } if (pts.length == 2) { if (cu instanceof Line2d) { if (cu.GetParametersAt(pts[0]) > cu.GetParametersAt(pts[1])) { pts[0] = pts[1] } } else if (cu instanceof Arc2d) { // 使用上一条线. 因为我们保证它和上一条线是有一个交点的 let arc = cu as Arc2d // last cu let lastIndex = i - 1 if (i == 0) lastIndex = cout - 1 let lastCu = cus[lastIndex] let insPts = new Array() lastCu.IntersectWith(cu, insPts) for (let insP of insPts) { let p1 = arc.GetParametersAt(insP) let p2 = arc.GetParametersAt(insP) // 从起点圆弧开始 if (insP.DistensTo(cu.StartPoint) < 1e-3) { if (p1 > p2) { pts[0] = pts[1] } break } // 从终点圆弧开始. else if (insP.DistensTo(cu.EndPoint) < 1e-3) { if (p1 < p2) { pts[0] = pts[1] } break } } } } if (cu.PtInCurve(pts[0]) && cu2.PtInCurve(pts[0])) { Utils.changeP(cus, cu, i, -1, pts[0]) Utils.changeP(cus, cu, j, 1, pts[0]) i = j - 1 break } } } Utils.RemoveZeroCurve(res) return res } static changeP(cus: Curve2d[], cu: Curve2d, index: number, next: number, _pt: Point2d) { let cout = cus.length let lastIndex = index + next let _cu = cus[index] if (lastIndex == -1) lastIndex = cout - 1 else if (lastIndex == cout) lastIndex = 0 let lastCu = cus[lastIndex] let insPts = new Array() lastCu.IntersectWith(_cu, insPts) for (const insP of insPts) { if (insP.DistensTo(_cu.StartPoint) < 1e-3) { _cu.EndPoint = _pt break } else if (insP.DistensTo(_cu.EndPoint) < 1e-3) { _cu.StartPoint = _pt break } } if (cu instanceof Arc2d) { cu.Parse() } } static RemoveZeroCurve(cus: Curve2d[]) { // remove zero length curve /* cus.RemoveAll((Curve2d o) => { if (o is Line2d) { return ((Line2d)o).m_Length < 1e-3; } else if (o is Arc2d) { return ((Arc2d)o).m_AllAngle < 1e-3; } return false; }); */ let length = cus.length let isZero = false for (let i = length - 1; i >= 0; i--) { let o = cus[i] isZero = false if (o instanceof Line2d) { let line = o as Line2d isZero = line.m_Length < 1e-3 } else if (o instanceof Arc2d) { let arc = o as Arc2d isZero = arc.m_AllAngle < 1e-3 } if (isZero) { cus.splice(i, 1) } } } /** 曲线倒角 */ static FilletCurveList(culist: Curve2d[], offsetDistens: number): Curve2d[] { /* let rn = new List(); let len = cuList.Count; for (int i = 0; i < len; i++) { let en = cuList[i]; let index =i==len-1 ? 0 : i + 1; rn.Add(en); if (en is Line2d && cuList[index] is Line2d) { let l1 = en as Line2d; let l2 = cuList[index] as Line2d; if (l1.m_Length < offsetDistens || l2.m_Length < offsetDistens || (l1.EndPoint-l1.StartPoint).CrossProduct(l2.EndPoint-l2.StartPoint) <1e-3 ) { continue; } let tempEn1 = en.Offset(-offsetDistens) as Line2d; let tempEn2 = cuList[index].Offset(-offsetDistens)as Line2d; let pts = new List(); tempEn1.IntersectWith(tempEn2, pts); if (pts.Count>0 && tempEn1.PtInCurve(pts.First())) { let pt = pts.First(); //求交点. en.EndPoint= en.ClosePointTo(pt); let en2 = cuList[index]; en2.StartPoint = en2.ClosePointTo(pt); let arc2d = new Arc2d(en.EndPoint, en2.StartPoint, 1);//凸圆弧 arc2d.m_Center = pt; arc2d.m_Radius = pt.DistensTo(en.EndPoint); arc2d.Parse();//解析凸度 角度 rn.Add(arc2d); } } } return rn; */ let rn = new Array() let len = culist.length for (let i = 0; i < len; i++) { let en = culist[i] let index = i == len - 1 ? 0 : i + 1 rn.push(en) if (en instanceof Line2d && culist[index] instanceof Line2d) { let l1 = en as Line2d let l2 = culist[index] as Line2d if (l1.m_Length < offsetDistens || l2.m_Length < offsetDistens || (Point2d.OpSubtract(l1.EndPoint, l1.StartPoint)).CrossProduct(Point2d.OpSubtract(l2.EndPoint, l2.StartPoint)) < 1e-3 ) { continue } let tempEn1 = en.Offset(-offsetDistens) as Line2d let tempEn2 = culist[index].Offset(-offsetDistens) as Line2d let pts = new Array() tempEn1.IntersectWith(tempEn2, pts) if (pts.length > 0 && tempEn1.PtInCurve(pts[0])) { let pt = pts[0] // 求交点. en.EndPoint = en.ClosePointTo(pt) let en2 = culist[index] en2.StartPoint = en2.ClosePointTo(pt) let arc2d = new Arc2d(en.EndPoint, en2.StartPoint, 1)// 凸圆弧 arc2d.m_Center = pt arc2d.m_Radius = pt.DistensTo(en.EndPoint) arc2d.Parse()// 解析凸度 角度 rn.push(arc2d) } } } return rn } /** 圆弧细分 如果大于90度则分割 */ static SplitCurveListArc(cuList: Curve2d[]): Curve2d[] { /* C# 代码 let rnList = new List();//返回的曲线表 foreach (let cu in cuList) { if (cu is Arc2d) { let arc = cu as Arc2d; arc.Parse();//解析圆弧的凸度 角度 if (arc.m_AllAngle > Math.PI * 0.5) { int cout =(int)( arc.m_AllAngle / (Math.PI * 0.5)) + 1; let an = arc.m_AllAngle / cout; let startAn = arc.m_StartAngle; for (int i = 0; i < cout; i++) { let arcNew = new Arc2d(arc.StartPoint, arc.EndPoint, 0); arcNew.m_Radius = arc.m_Radius; arcNew.m_Center = arc.m_Center; arcNew.m_StartAngle = startAn; arcNew.m_EndAngle = arcNew.m_StartAngle + an; startAn = arcNew.m_EndAngle; rnList.Add(arcNew); } } else { rnList.Add(cu); } } else { rnList.Add(cu); } } return rnList; */ let rnList = new Array()// 返回的曲线表 for (let cu of cuList) { if (cu instanceof Arc2d) { let arc = cu as Arc2d arc.Parse()// 解析圆弧的凸度 角度 if (arc.m_AllAngle > Math.PI * 0.5) { let cout = arc.m_AllAngle / (Math.PI * 0.5) + 1 let an = arc.m_AllAngle / cout let startAn = arc.m_StartAngle for (let i = 0; i < cout; i++) { let arcNew = new Arc2d(arc.StartPoint, arc.EndPoint, 0) arcNew.m_Radius = arc.m_Radius arcNew.m_Center = arc.m_Center arcNew.m_StartAngle = startAn arcNew.m_EndAngle = arcNew.m_StartAngle + an startAn = arcNew.m_EndAngle rnList.push(arcNew) } } else { rnList.push(cu) } } else { rnList.push(cu) } } return rnList } // 三点面积 static Det(p1: Point2d, p2: Point2d, p3: Point2d): number { return this.Det2(p1.AsVector(), p2.AsVector(), p3.AsVector()) } // 三点面积 static Det2(p1: Vector2d, p2: Vector2d, p3: Vector2d): number { return p1.CrossProduct(p2) + p2.CrossProduct(p3) + p3.CrossProduct(p1) } // static AngleForm3Pt(pSrc: Point2d, p1: Point2d, p2: Point2d): number { /* double angle = 0.0f; // 夹角 let vec1 = p1 - pSrc; let vec2 = p2 - pSrc; double productValue = vec1 * vec2; double cosValue = productValue / (vec1.Length * vec2.Length); // 余弦公式 // acos的输入参数范围必须在[-1, 1]之间,否则会"domain error" // 对输入参数作校验和处理 if (cosValue < -1 && cosValue > -2) cosValue = -1; else if (cosValue > 1 && cosValue < 2) cosValue = 1; // acos返回的是弧度值,转换为角度值 angle = Math.Acos(cosValue) * 180 / Math.PI; return angle; */ let angle = 0.0 // 夹角 let vec1 = Point2d.OpSubtract(p1, pSrc) let vec2 = Point2d.OpSubtract(p2, pSrc) let productValue = Vector2d.OpMultiplyValue(vec1, vec2) let cosValue = productValue / (vec1.Length * vec2.Length) // 余弦公式 // acos的输入参数范围必须在[-1, 1]之间,否则会"domain error" // 对输入参数作校验和处理 if (cosValue < -1 && cosValue > -2) cosValue = -1 else if (cosValue > 1 && cosValue < 2) cosValue = 1 // acos返回的是弧度值,转换为角度值 angle = Math.acos(cosValue) * 180 / Math.PI return angle } } export class DoubleUtil { static EqualX(v1: number, v2: number, fuzz: number): boolean { return (Math.abs(v1 - v2) < fuzz) } static Sqr(v: number): number { return v * v } } export class CADExt { static GetX(arc: Curve2d): number { return Math.min(arc.StartPoint.m_X, arc.EndPoint.m_X) } static GetY(arc: Curve2d): number { return Math.min(arc.StartPoint.m_Y, arc.EndPoint.m_Y) } static GetWidth(arc: Curve2d): number { return Math.abs(arc.StartPoint.m_X - arc.EndPoint.m_X) } static GetHeight(arc: Curve2d): number { return Math.abs(arc.StartPoint.m_Y - arc.EndPoint.m_Y) } static GetRadius(p1: Point2d, p2: Point2d, bul: number): number { // let arc = new Arc2d(p1, p2, bul); // return arc.m_Radius; // this.m_Radius = length / Math.sin(2 * Math.atan(bul)) / 2; return CADExt.getR(p1.m_X, p1.m_Y, p2.m_X, p2.m_Y, bul) } static getR(x1: number, y1: number, x2: number, y2: number, bul: number): number { let length = Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)) return length / Math.sin(2 * Math.atan(bul)) / 2 } /** 圆弧分割成count段 */ static SplitArc(arc: Arc2d, count: number): Arc2d[] { let list: Arc2d[] list = [] let an = arc.m_AllAngle / count let an_0 = arc.m_StartAngle for (let i = 0; i < count; i++) { let arcNew = new Arc2d(arc.StartPoint, arc.EndPoint, 0) arcNew.m_Radius = arc.m_Radius arcNew.m_Center = arc.m_Center arcNew.m_StartAngle = an_0 arcNew.m_EndAngle = arcNew.m_StartAngle + an an_0 = arcNew.m_EndAngle arcNew.ParsePointFormAngle() list.push(arcNew) } return list } static SplitArcByCell(ar: Arc2d, cellWidth: number): any[] { // 弧长 let fullLenth = Math.abs(ar.m_Radius * (ar.m_EndAngle - ar.m_StartAngle)) let count = fullLenth / cellWidth if (count < 2) count = 2 let childs = CADExt.SplitArc(ar, count) let pts = [] for (let c of childs) { pts.push({ x: c.StartPoint.m_X, y: c.StartPoint.m_Y }) } pts.push({ x: ar.EndPoint.m_X, y: ar.EndPoint.m_Y }) return pts } /** 根据两点,深的弧度 中间的n个点 */ static getCurvePoints(x1: number, y1: number, x2: number, y2: number, d: number, num: number): any { // atan(h/(L/2)) * 4 h: let dis = 0.5 * Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)) let mul = d / dis let arc = new Arc2d(new Point2d(x1, y1), new Point2d(x2, y2), mul) let arc_list = CADExt.SplitArc(arc, num - 1) let points = [] for (let li of arc_list) { points.push({ x: li.StartPoint.m_X, y: li.StartPoint.m_Y }) } points.push({ x: x2, y: y2 }) return points } /** 求弧长 */ static getLength(arc: Arc2d): number { return arc.m_Radius * Math.abs(arc.m_AllAngle) } /** 按固定线长分割圆弧 */ static getPointsFromCurve(x1: number, y1: number, x2: number, y2: number, bul: number, dis: number) { let arc = new Arc2d(new Point2d(x1, y1), new Point2d(x2, y2), bul) let length = this.getLength(arc) let count = Math.max(4, Math.ceil(length / dis)) let arc_list = CADExt.SplitArc(arc, count) let points = [] for (let li of arc_list) { points.push({ x: li.StartPoint.m_X, y: li.StartPoint.m_Y }) } points.push({ x: x2, y: y2 }) return points } /** 返回弧形中心点 (不是圆心点) */ static getCenterPoint(arc: Arc2d): Point2d { let arcNew = new Arc2d(arc.StartPoint, arc.EndPoint, 0) arcNew.m_Radius = arc.m_Radius arcNew.m_Center = arc.m_Center arcNew.m_StartAngle = arc.m_StartAngle arcNew.m_EndAngle = arc.m_StartAngle + arc.m_AllAngle / 2 arcNew.ParsePointFormAngle() return arcNew.EndPoint } /** 获得第3点到前2点的距离差 */ static getDisOff(x1: number, y1: number, x2: number, y2: number, x3: number, y3: number): number { let dis31 = Math.sqrt((x3 - x1) * (x3 - x1) + (y3 - y1) * (y3 - y1)) let dis32 = Math.sqrt((x3 - x2) * (x3 - x2) + (y3 - y2) * (y3 - y2)) let dis12 = Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)) return dis31 + dis32 - dis12 } } function TestPolyline(offEnsS: Curve2d[][]) { let str = `[${offEnsS.length}` for (let i = 0; i < offEnsS.length; i++) { let offEns = offEnsS[i] let linedString = `${offEns.length},` for (let cu of offEns) { let bul = (cu instanceof Arc2d) ? cu.m_Bul : 0 linedString += `[${cu.StartPoint.m_X},${cu.StartPoint.m_Y}],${bul},` } str += `,"Polyline",8,2,116,false,1,${7 + i % 2},0,[1,0,0,0,0,1,0,0,0,0,1,0,${Math.floor(i / 2) * 1000},0,0,1],0,0,true,[1,0,0,0,0,1,0,0,0,0,1,0,3000,0,0,1],0,2,${linedString}true` } str += ']' console.log(str) copyTextToClipboard(str) } let DebugCurves: (Curve2d[])[] = [] export function CADDebugInit() { DebugCurves = [] } export function CADCopyPolylines() { TestPolyline(DebugCurves) DebugCurves = [] } function fallbackCopyTextToClipboard(text: string) { let textArea = document.createElement('textarea') textArea.value = text document.body.appendChild(textArea) textArea.focus() textArea.select() try { let successful = document.execCommand('copy') } catch (err) { } document.body.removeChild(textArea) } export async function copyTextToClipboard(text: string) { if (!navigator.clipboard) { fallbackCopyTextToClipboard(text) return } return await navigator.clipboard.writeText(text) } // ref: https://stackoverflow.com/questions/400212/how-do-i-copy-to-the-clipboard-in-javascript /** * 读取剪切板的字符串 */ export async function readClipboardText() { if (navigator.clipboard) return await navigator.clipboard.readText() return '' }