mirror of https://gitee.com/cf-fz/WebCAD.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
361 lines
11 KiB
361 lines
11 KiB
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<Route>;
|
|
}
|
|
|
|
//区域的路线表 表示了一个区域
|
|
type RegionRouteS = Array<Set<Route>>;
|
|
|
|
//区域搜索算法
|
|
export class RegionParse
|
|
{
|
|
//曲线使用计数器
|
|
private _CountCu = new Count();
|
|
|
|
//区域列表 通常是外轮廓
|
|
RegionsOutline: RegionRouteS = [];
|
|
//区域列表 通常是内轮廓
|
|
RegionsInternal: RegionRouteS = [];
|
|
|
|
//碎线 曲线进入到这里会被炸开.
|
|
ExpLineMap: Map<Curve, Curve[]> = 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<Stand>();
|
|
let routeS = new Set<Route>();
|
|
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<Stand>} standS 外轮廓站点
|
|
* @memberof RegionAlg
|
|
*/
|
|
private FindMinRegion(standS: Set<Stand>)
|
|
{
|
|
//需要搜索的.
|
|
let needFinds = new Set<Stand>();
|
|
standS.forEach(w => needFinds.add(w));
|
|
//已经走过的
|
|
let passingStands = new Set<Stand>();
|
|
|
|
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<Stand>();
|
|
let routeS = new Set<Route>();
|
|
|
|
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<Stand>} standS
|
|
* @returns
|
|
* @memberof RegionAlg
|
|
*/
|
|
private FindMinStand(standS: Set<Stand>)
|
|
{
|
|
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<Stand>} 站点列表
|
|
* @memberof RegionParse
|
|
*/
|
|
private GenerateNodeMap(cuList: Curve[]): Set<Stand>
|
|
{
|
|
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<Stand>, //走过的站
|
|
routeS: Set<Route>, //走过的路
|
|
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;
|
|
}
|
|
}
|