import { Vector3 } from 'three'; import { arrayRemoveDuplicateBySort, arrayRemoveIf } from '../Common/ArrayExt'; import { Arc } from '../DatabaseServices/Entity/Arc'; import { Curve } from '../DatabaseServices/Entity/Curve'; import { Polyline } from '../DatabaseServices/Entity/Polyline'; import { Count } from './Count'; import { CurveMap } from './CurveMap'; import { angle, equalv3 } from './GeUtils'; //路线 export interface Route { curve: Curve; //路线的曲线 to: Stand; //终点的点 } //站台 export interface Stand { //位置 position: Vector3; //路径 routes: Array; } //区域的路线表 表示了一个区域 type RegionRouteS = Array>; //区域搜索算法 export class RegionParse { //曲线使用计数器 private _CountCu = new Count(); //区域列表 通常是外轮廓 RegionsOutline: RegionRouteS = []; //区域列表 通常是内轮廓 RegionsInternal: RegionRouteS = []; //碎线 曲线进入到这里会被炸开. ExpLineMap: Map = new Map(); /** * Creates an instance of RegionParse. * @param {Curve[]} cuList 请不要传递圆和椭圆. * @memberof RegionParse */ constructor(cuList: Curve[], public fractionDigits = 3) { //需要搜索的站 let needFinds = this.GenerateNodeMap(cuList); //搜索大轮廓. while (needFinds.size > 0) { //找到最小的站. let minStand = this.FindMinStand(needFinds); needFinds.delete(minStand); //逆时针+逆时针 for (let i = minStand.routes.length; i--;) { let wayS = new Set(); let routeS = new Set(); let isFind = this.FindRegion(minStand, minStand, undefined, wayS, routeS, 1); if (isFind) { this.RegionsOutline.push(routeS); //计数增加 for (let route of routeS) this._CountCu.AddCount(route.curve, 1); this.FindMinRegion(wayS); } } } } /** * 曲线是否已经被算法使用 * @param cu * @returns true if cueve used */ GetCueveUsed(cu: Curve): boolean { if (this.ExpLineMap.has(cu)) { let use = this.ExpLineMap.get(cu).some(c => this._CountCu.GetCount(c) > 0); if (!use) this.ExpLineMap.delete(cu); return use; } else return this._CountCu.GetCount(cu) > 0; } /** * 使用类似涨潮的方式,从最低的地方,采用顺时针填充. * 算法允许搜索到的区域在外轮廓外. * * @param {Set} standS 外轮廓站点 * @memberof RegionAlg */ private FindMinRegion(standS: Set) { //需要搜索的. let needFinds = new Set(); standS.forEach(w => needFinds.add(w)); //已经走过的 let passingStands = new Set(); let regs: RegionRouteS = []; while (needFinds.size > 0) { //找到最小站 let minStand = this.FindMinStand(needFinds); //添加为已经计算 passingStands.add(minStand); needFinds.delete(minStand); //顺时针搜索 for (let j = minStand.routes.length; j--;) { let route = minStand.routes[j]; if (!needFinds.has(route.to)) continue; let wayS = new Set(); let routeS = new Set(); routeS.add(route); wayS.add(route.to); let isFindMin = this.FindRegion(minStand, route.to, route.curve, wayS, routeS, 2); if (isFindMin) { regs.push(routeS); wayS.forEach(w => { //站点拓展,如果该地点没有被走过,那么加入到需要搜寻的站点表 if (!passingStands.has(w)) needFinds.add(w); }); for (let route of routeS) this._CountCu.AddCount(route.curve, 1); } } } if (regs.length > 1) this.RegionsInternal.push(...regs); } /** * 找到左下角的站. * * @private * @param {Set} standS * @returns * @memberof RegionAlg */ private FindMinStand(standS: Set) { let minStand: Stand; for (let stand of standS) { if (!minStand) { minStand = stand; continue; } if (minStand.position.y > stand.position.y) { minStand = stand; } else if (minStand.position.y === stand.position.y && minStand.position.x > stand.position.x) { minStand = stand; } } return minStand; } /** * 构造路线图. 每个节点对应下一个路口的路线表. 路口表使用逆时针排序,起始角度使用正x轴. * * @private * @param {Curve[]} cuList * @returns {Set} 站点列表 * @memberof RegionParse */ private GenerateNodeMap(cuList: Curve[]): Set { let curveMap = new CurveMap(this.fractionDigits); //将多段线炸开 let plcus: Curve[] = []; arrayRemoveIf(cuList, c => { if (c instanceof Polyline) { let cus = c.Explode(); //如果为圆弧,提前打断 let arcs: Arc[] = []; arrayRemoveIf(cus, c => { if (c instanceof Arc) { let arcBrs = this.BreakArc(c); if (arcBrs.length > 1) { arcs.push(...arcBrs); return true; } } return false; }); //加入到计算 cus.push(...arcs); this.ExpLineMap.set(c, cus); plcus.push(...cus); return true; } return false; }); cuList.push(...plcus); for (let cu of cuList) { //由于圆弧可能导致最低点计算错误的问题. if (cu instanceof Arc) { let arcs = this.BreakArc(cu); if (arcs.length > 1) { arcs.forEach(a => curveMap.addCurveToMap(a)); this.ExpLineMap.set(cu, arcs); continue; } } curveMap.addCurveToMap(cu); } //排序,根据角度逆时针排序. curveMap.m_NodeMap.forEach(s => { s.routes.sort((r1, r2) => { let a1: number, a2: number; if (equalv3(r1.curve.StartPoint, s.position)) a1 = angle(r1.curve.GetFistDeriv(0)); else a1 = angle(r1.curve.GetFistDeriv(r1.curve.EndParam).negate()); if (equalv3(r2.curve.StartPoint, s.position)) a2 = angle(r2.curve.GetFistDeriv(0)); else a2 = angle(r2.curve.GetFistDeriv(r1.curve.EndParam).negate()); return a1 - a2; }); //移除重复的线 arrayRemoveDuplicateBySort(s.routes, (r1, r2) => { let isEqual = r1.to === r2.to && r1.curve.constructor.name === r2.curve.constructor.name; if (isEqual && r1.curve instanceof Arc) return equalv3(r1.curve.GetPointAtParam(0.5), r2.curve.GetPointAtParam(0.5)); return isEqual; }); }); return new Set(curveMap.Stands); } private BreakArc(arc: Arc) { let underPt = arc.Center.add(new Vector3(0, -arc.Radius)); let param = arc.GetParamAtPoint(underPt); if (param > 0.01 && param < 0.99) return arc.GetSplitCurves(param); else return [arc]; } //寻找闭合轮廓,下一站总是使用逆时针规划. private FindRegion(firstS: Stand, //起点 nowStand: Stand, //当前站 lastCurve: Curve, //上一条线索引 wayStands: Set, //走过的站 routeS: Set, //走过的路 cuMaximumCount: number, //允许最大的行走次数 ) { let routeCount = nowStand.routes.length; //查找上一条线的索引. let lastIndex = -1; if (lastCurve) { for (let i = routeCount; i--;)//顺时针搜索. { if (nowStand.routes[i].curve === lastCurve) { lastIndex = i; break; } } } for (let i = 0; i < routeCount - 1; i++) { let index = lastIndex + i + 1;//转弯,逆时针 if (index >= routeCount) index -= routeCount; //下一站 let route = nowStand.routes[index]; let usedCount = this._CountCu.GetCount(route.curve); if (usedCount >= cuMaximumCount) continue; //如果发现这条路已经走回去,中途回路 if (wayStands.has(route.to)) { return false; // for (let stand of wayStands) // { // if (stand === route.to) // break; // wayStands.delete(stand);//删除不是回路的部分 // } // return true; } //加入到已经走过的道路. wayStands.add(route.to); routeS.add(route); //已经回原地,此处不验证该轮廓的正确性. if (route.to === firstS) return true; //在下个路口试着往前走 let isFind = this.FindRegion(firstS, route.to, route.curve, wayStands, routeS, cuMaximumCount); if (isFind) return true; else { wayStands.delete(route.to);//不走这条路 routeS.delete(route); } } return false; } }