qcast-front/src/lib/skeletons/SkeletonBuilder.ts

995 lines
31 KiB
TypeScript

import {Skeleton} from "./Skeleton";
import {HashSet, List, IComparer, Dictionary, GeoJSONMultipolygon} from "./Utils";
import Vector2d from "./Primitives/Vector2d";
import PriorityQueue from "./Primitives/PriorityQueue";
import Edge from "./Circular/Edge";
import Vertex from "./Circular/Vertex";
import CircularList from "./Circular/CircularList";
import FaceQueue from "./Path/FaceQueue";
import SkeletonEvent from "./Events/SkeletonEvent";
import FaceQueueUtil from "./Path/FaceQueueUtil";
import LavUtil from "./LavUtil";
import IChain from "./Events/Chains/IChain";
import PrimitiveUtils from "./Primitives/PrimitiveUtils";
import LineParametric2d from "./Primitives/LineParametric2d";
import {FaceNode} from "./Path/FaceNode";
import MultiEdgeEvent from "./Events/MultiEdgeEvent";
import EdgeEvent from "./Events/EdgeEvent";
import PickEvent from "./Events/PickEvent";
import MultiSplitEvent from "./Events/MultiSplitEvent";
import SingleEdgeChain from "./Events/Chains/SingleEdgeChain";
import SplitChain from "./Events/Chains/SplitChain";
import SplitEvent from "./Events/SplitEvent";
import VertexSplitEvent from "./Events/VertexSplitEvent";
import EdgeChain from "./Events/Chains/EdgeChain";
import LineLinear2d from "./Primitives/LineLinear2d";
import EdgeResult from "./EdgeResult";
import ChainType from "./Events/Chains/ChainType";
export default class SkeletonBuilder {
private static readonly SplitEpsilon = 1e-10;
public static BuildFromGeoJSON(multipolygon: GeoJSONMultipolygon): Skeleton {
const allEdges: List<EdgeResult> = new List();
const allDistances: Dictionary<Vector2d, number> = new Dictionary();
for (const polygon of multipolygon) {
if (polygon.length > 0) {
const outer = this.ListFromCoordinatesArray(polygon[0]);
const holes: List<List<Vector2d>> = new List();
for (let i = 1; i < polygon.length; i++) {
holes.Add(this.ListFromCoordinatesArray(polygon[i]));
}
const skeleton = this.Build(outer, holes);
for (const edge of skeleton.Edges) {
allEdges.Add(edge);
}
for (const [key, distance] of skeleton.Distances.entries()) {
allDistances.Add(key, distance);
}
}
}
return new Skeleton(allEdges, allDistances);
}
private static ListFromCoordinatesArray(arr: [number, number][]): List<Vector2d> {
const list: List<Vector2d> = new List();
for (const [x, y] of arr) {
list.Add(new Vector2d(x, y));
}
return list;
}
public static Build(polygon: List<Vector2d>, holes: List<List<Vector2d>> = null): Skeleton {
polygon = this.InitPolygon(polygon);
holes = this.MakeClockwise(holes);
const queue = new PriorityQueue<SkeletonEvent>(3, new SkeletonEventDistanseComparer());
const sLav = new HashSet<CircularList<Vertex>>();
const faces = new List<FaceQueue>();
const edges = new List<Edge>();
this.InitSlav(polygon, sLav, edges, faces);
if (holes !== null) {
for (const inner of holes) {
this.InitSlav(inner, sLav, edges, faces);
}
}
this.InitEvents(sLav, queue, edges);
let count = 0;
while (!queue.Empty) {
count = this.AssertMaxNumberOfInteraction(count);
const levelHeight = queue.Peek().Distance;
for (const event of this.LoadAndGroupLevelEvents(queue)) {
if (event.IsObsolete)
continue;
if (event instanceof EdgeEvent)
throw new Error("All edge@events should be converted to MultiEdgeEvents for given level");
if (event instanceof SplitEvent)
throw new Error("All split events should be converted to MultiSplitEvents for given level");
if (event instanceof MultiSplitEvent)
this.MultiSplitEvent(<MultiSplitEvent>event, sLav, queue, edges);
else if (event instanceof PickEvent)
this.PickEvent(<PickEvent>event);
else if (event instanceof MultiEdgeEvent)
this.MultiEdgeEvent(<MultiEdgeEvent>event, queue, edges);
else
throw new Error("Unknown event type: " + event.GetType());
}
this.ProcessTwoNodeLavs(sLav);
this.RemoveEventsUnderHeight(queue, levelHeight);
this.RemoveEmptyLav(sLav);
}
return this.AddFacesToOutput(faces);
}
private static InitPolygon(polygon: List<Vector2d>): List<Vector2d> {
if (polygon === null)
throw new Error("polygon can't be null");
if (polygon[0].Equals(polygon[polygon.Count - 1]))
throw new Error("polygon can't start and end with the same point");
return this.MakeCounterClockwise(polygon);
}
private static ProcessTwoNodeLavs(sLav: HashSet<CircularList<Vertex>>) {
for (const lav of sLav) {
if (lav.Size === 2) {
const first = lav.First();
const last = first.Next as Vertex;
FaceQueueUtil.ConnectQueues(first.LeftFace, last.RightFace);
FaceQueueUtil.ConnectQueues(first.RightFace, last.LeftFace);
first.IsProcessed = true;
last.IsProcessed = true;
LavUtil.RemoveFromLav(first);
LavUtil.RemoveFromLav(last);
}
}
}
private static RemoveEmptyLav(sLav: HashSet<CircularList<Vertex>>) {
sLav.RemoveWhere(circularList => circularList.Size === 0);
}
private static MultiEdgeEvent(event: MultiEdgeEvent, queue: PriorityQueue<SkeletonEvent>, edges: List<Edge>) {
const center = event.V;
const edgeList = event.Chain.EdgeList;
const previousVertex = event.Chain.PreviousVertex;
previousVertex.IsProcessed = true;
const nextVertex = event.Chain.NextVertex;
nextVertex.IsProcessed = true;
const bisector = this.CalcBisector(center, previousVertex.PreviousEdge, nextVertex.NextEdge);
const edgeVertex = new Vertex(center, event.Distance, bisector, previousVertex.PreviousEdge,
nextVertex.NextEdge);
this.AddFaceLeft(edgeVertex, previousVertex);
this.AddFaceRight(edgeVertex, nextVertex);
previousVertex.AddPrevious(edgeVertex);
this.AddMultiBackFaces(edgeList, edgeVertex);
this.ComputeEvents(edgeVertex, queue, edges);
}
private static AddMultiBackFaces(edgeList: List<EdgeEvent>, edgeVertex: Vertex) {
for (const edgeEvent of edgeList) {
const leftVertex = edgeEvent.PreviousVertex;
leftVertex.IsProcessed = true;
LavUtil.RemoveFromLav(leftVertex);
const rightVertex = edgeEvent.NextVertex;
rightVertex.IsProcessed = true;
LavUtil.RemoveFromLav(rightVertex);
this.AddFaceBack(edgeVertex, leftVertex, rightVertex);
}
}
private static PickEvent(event: PickEvent) {
const center = event.V;
const edgeList = event.Chain.EdgeList;
const vertex = new Vertex(center, event.Distance, LineParametric2d.Empty, null, null);
vertex.IsProcessed = true;
this.AddMultiBackFaces(edgeList, vertex);
}
private static MultiSplitEvent(event: MultiSplitEvent, sLav: HashSet<CircularList<Vertex>>, queue: PriorityQueue<SkeletonEvent>, edges: List<Edge>) {
const chains = event.Chains;
const center = event.V;
this.CreateOppositeEdgeChains(sLav, chains, center);
chains.Sort(new ChainComparer(center));
let lastFaceNode: FaceNode = null;
let edgeListSize = chains.Count;
for (let i = 0; i < edgeListSize; i++) {
const chainBegin = chains[i];
const chainEnd = chains[(i + 1) % edgeListSize];
const newVertex = this.CreateMultiSplitVertex(chainBegin.NextEdge, chainEnd.PreviousEdge, center, event.Distance);
const beginNextVertex = chainBegin.NextVertex;
const endPreviousVertex = chainEnd.PreviousVertex;
this.CorrectBisectorDirection(newVertex.Bisector, beginNextVertex, endPreviousVertex, chainBegin.NextEdge, chainEnd.PreviousEdge);
if (LavUtil.IsSameLav(beginNextVertex, endPreviousVertex)) {
const lavPart = LavUtil.CutLavPart(beginNextVertex, endPreviousVertex);
const lav = new CircularList<Vertex>();
sLav.Add(lav);
lav.AddLast(newVertex);
for (const vertex of lavPart)
lav.AddLast(vertex);
} else {
LavUtil.MergeBeforeBaseVertex(beginNextVertex, endPreviousVertex);
endPreviousVertex.AddNext(newVertex);
}
this.ComputeEvents(newVertex, queue, edges);
lastFaceNode = this.AddSplitFaces(lastFaceNode, chainBegin, chainEnd, newVertex);
}
edgeListSize = chains.Count;
for (let i = 0; i < edgeListSize; i++) {
const chainBegin = chains[i];
const chainEnd = chains[(i + 1) % edgeListSize];
LavUtil.RemoveFromLav(chainBegin.CurrentVertex);
LavUtil.RemoveFromLav(chainEnd.CurrentVertex);
if (chainBegin.CurrentVertex !== null)
chainBegin.CurrentVertex.IsProcessed = true;
if (chainEnd.CurrentVertex !== null)
chainEnd.CurrentVertex.IsProcessed = true;
}
}
private static CorrectBisectorDirection(bisector: LineParametric2d, beginNextVertex: Vertex, endPreviousVertex: Vertex, beginEdge: Edge, endEdge: Edge) {
const beginEdge2 = beginNextVertex.PreviousEdge;
const endEdge2 = endPreviousVertex.NextEdge;
if (beginEdge !== beginEdge2 || endEdge !== endEdge2)
throw new Error();
if (beginEdge.Norm.Dot(endEdge.Norm) < -0.97) {
const n1 = PrimitiveUtils.FromTo(endPreviousVertex.Point, bisector.A).Normalized();
const n2 = PrimitiveUtils.FromTo(bisector.A, beginNextVertex.Point).Normalized();
const bisectorPrediction = this.CalcVectorBisector(n1, n2);
if (bisector.U.Dot(bisectorPrediction) < 0)
bisector.U.Negate();
}
}
private static AddSplitFaces(lastFaceNode: FaceNode, chainBegin: IChain, chainEnd: IChain, newVertex: Vertex): FaceNode {
if (chainBegin instanceof SingleEdgeChain) {
if (lastFaceNode === null) {
const beginVertex = this.CreateOppositeEdgeVertex(newVertex);
newVertex.RightFace = beginVertex.RightFace;
lastFaceNode = beginVertex.LeftFace;
} else {
if (newVertex.RightFace !== null)
throw new Error("newVertex.RightFace should be null");
newVertex.RightFace = lastFaceNode;
lastFaceNode = null;
}
} else {
const beginVertex = chainBegin.CurrentVertex;
this.AddFaceRight(newVertex, beginVertex);
}
if (chainEnd instanceof SingleEdgeChain) {
if (lastFaceNode === null) {
const endVertex = this.CreateOppositeEdgeVertex(newVertex);
newVertex.LeftFace = endVertex.LeftFace;
lastFaceNode = endVertex.LeftFace;
} else {
if (newVertex.LeftFace !== null)
throw new Error("newVertex.LeftFace should be null.");
newVertex.LeftFace = lastFaceNode;
lastFaceNode = null;
}
} else {
const endVertex = chainEnd.CurrentVertex;
this.AddFaceLeft(newVertex, endVertex);
}
return lastFaceNode;
}
private static CreateOppositeEdgeVertex(newVertex: Vertex): Vertex {
const vertex = new Vertex(newVertex.Point, newVertex.Distance, newVertex.Bisector, newVertex.PreviousEdge, newVertex.NextEdge);
const fn = new FaceNode(vertex);
vertex.LeftFace = fn;
vertex.RightFace = fn;
const rightFace = new FaceQueue();
rightFace.AddFirst(fn);
return vertex;
}
private static CreateOppositeEdgeChains(sLav: HashSet<CircularList<Vertex>>, chains: List<IChain>, center: Vector2d) {
const oppositeEdges = new HashSet<Edge>();
const oppositeEdgeChains = new List<IChain>();
const chainsForRemoval = new List<IChain>();
for (const chain of chains) {
if (chain instanceof SplitChain) {
const splitChain = <SplitChain>chain;
const oppositeEdge = splitChain.OppositeEdge;
if (oppositeEdge !== null && !oppositeEdges.Contains(oppositeEdge)) {
const nextVertex = this.FindOppositeEdgeLav(sLav, oppositeEdge, center);
if (nextVertex !== null)
oppositeEdgeChains.Add(new SingleEdgeChain(oppositeEdge, nextVertex));
else {
this.FindOppositeEdgeLav(sLav, oppositeEdge, center);
chainsForRemoval.Add(chain);
}
oppositeEdges.Add(oppositeEdge);
}
}
}
for (let chain of chainsForRemoval)
chains.Remove(chain);
chains.AddRange(oppositeEdgeChains);
}
private static CreateMultiSplitVertex(nextEdge: Edge, previousEdge: Edge, center: Vector2d, distance: number): Vertex {
const bisector = this.CalcBisector(center, previousEdge, nextEdge);
return new Vertex(center, distance, bisector, previousEdge, nextEdge);
}
private static CreateChains(cluster: List<SkeletonEvent>): List<IChain> {
const edgeCluster = new List<EdgeEvent>();
const splitCluster = new List<SplitEvent>();
const vertexEventsParents = new HashSet<Vertex>();
for (const skeletonEvent of cluster) {
if (skeletonEvent instanceof EdgeEvent)
edgeCluster.Add(<EdgeEvent>skeletonEvent);
else {
if (skeletonEvent instanceof VertexSplitEvent) {
} else if (skeletonEvent instanceof SplitEvent) {
const splitEvent = <SplitEvent>skeletonEvent;
vertexEventsParents.Add(splitEvent.Parent);
splitCluster.Add(splitEvent);
}
}
}
for (let skeletonEvent of cluster) {
if (skeletonEvent instanceof VertexSplitEvent) {
const vertexEvent = <VertexSplitEvent>skeletonEvent;
if (!vertexEventsParents.Contains(vertexEvent.Parent)) {
vertexEventsParents.Add(vertexEvent.Parent);
splitCluster.Add(vertexEvent);
}
}
}
const edgeChains = new List<EdgeChain>();
while (edgeCluster.Count > 0)
edgeChains.Add(new EdgeChain(this.CreateEdgeChain(edgeCluster)));
const chains = new List<IChain>(edgeChains.Count);
for (const edgeChain of edgeChains)
chains.Add(edgeChain);
splitEventLoop:
while (splitCluster.Any()) {
const split = splitCluster[0];
splitCluster.RemoveAt(0);
for (const chain of edgeChains) {
if (this.IsInEdgeChain(split, chain))
continue splitEventLoop; //goto splitEventLoop;
}
chains.Add(new SplitChain(split));
}
return chains;
}
private static IsInEdgeChain(split: SplitEvent, chain: EdgeChain): boolean {
const splitParent = split.Parent;
const edgeList = chain.EdgeList;
return edgeList.Any(edgeEvent => edgeEvent.PreviousVertex === splitParent || edgeEvent.NextVertex === splitParent);
}
private static CreateEdgeChain(edgeCluster: List<EdgeEvent>): List<EdgeEvent> {
const edgeList = new List<EdgeEvent>();
edgeList.Add(edgeCluster[0]);
edgeCluster.RemoveAt(0);
loop:
for (; ;) {
const beginVertex = edgeList[0].PreviousVertex;
const endVertex = edgeList[edgeList.Count - 1].NextVertex;
for (let i = 0; i < edgeCluster.Count; i++) {
const edge = edgeCluster[i];
if (edge.PreviousVertex === endVertex) {
edgeCluster.RemoveAt(i);
edgeList.Add(edge);
//goto loop;
continue loop;
}
if (edge.NextVertex === beginVertex) {
edgeCluster.RemoveAt(i);
edgeList.Insert(0, edge);
//goto loop;
continue loop;
}
}
break;
}
return edgeList;
}
private static RemoveEventsUnderHeight(queue: PriorityQueue<SkeletonEvent>, levelHeight: number) {
while (!queue.Empty) {
if (queue.Peek().Distance > levelHeight + this.SplitEpsilon)
break;
queue.Next();
}
}
private static LoadAndGroupLevelEvents(queue: PriorityQueue<SkeletonEvent>): List<SkeletonEvent> {
const levelEvents = this.LoadLevelEvents(queue);
return this.GroupLevelEvents(levelEvents);
}
private static GroupLevelEvents(levelEvents: List<SkeletonEvent>): List<SkeletonEvent> {
const ret = new List<SkeletonEvent>();
const parentGroup = new HashSet<Vertex>();
while (levelEvents.Count > 0) {
parentGroup.Clear();
const event = levelEvents[0];
levelEvents.RemoveAt(0);
const eventCenter = event.V;
const distance = event.Distance;
this.AddEventToGroup(parentGroup, event);
const cluster = new List<SkeletonEvent>();
cluster.Add(event);
for (let j = 0; j < levelEvents.Count; j++) {
const test = levelEvents[j];
if (this.IsEventInGroup(parentGroup, test)) {
const item = levelEvents[j];
levelEvents.RemoveAt(j);
cluster.Add(item);
this.AddEventToGroup(parentGroup, test);
j--;
} else if (eventCenter.DistanceTo(test.V) < this.SplitEpsilon) {
const item = levelEvents[j];
levelEvents.RemoveAt(j);
cluster.Add(item);
this.AddEventToGroup(parentGroup, test);
j--;
}
}
ret.Add(this.CreateLevelEvent(eventCenter, distance, cluster));
}
return ret;
}
private static IsEventInGroup(parentGroup: HashSet<Vertex>, event: SkeletonEvent): boolean {
if (event instanceof SplitEvent)
return parentGroup.Contains((<SplitEvent>event).Parent);
if (event instanceof EdgeEvent)
return parentGroup.Contains((<EdgeEvent>event).PreviousVertex)
|| parentGroup.Contains((<EdgeEvent>event).NextVertex);
return false;
}
private static AddEventToGroup(parentGroup: HashSet<Vertex>, event: SkeletonEvent) {
if (event instanceof SplitEvent)
parentGroup.Add((<SplitEvent>event).Parent);
else if (event instanceof EdgeEvent) {
parentGroup.Add((<EdgeEvent>event).PreviousVertex);
parentGroup.Add((<EdgeEvent>event).NextVertex);
}
}
private static CreateLevelEvent(eventCenter: Vector2d, distance: number, eventCluster: List<SkeletonEvent>): SkeletonEvent {
const chains = this.CreateChains(eventCluster);
if (chains.Count === 1) {
const chain = chains[0];
if (chain.ChainType === ChainType.ClosedEdge)
return new PickEvent(eventCenter, distance, <EdgeChain>chain);
if (chain.ChainType === ChainType.Edge)
return new MultiEdgeEvent(eventCenter, distance, <EdgeChain>chain);
if (chain.ChainType === ChainType.Split)
return new MultiSplitEvent(eventCenter, distance, chains);
}
if (chains.Any(chain => chain.ChainType === ChainType.ClosedEdge))
throw new Error("Found closed chain of events for single point, but found more then one chain");
return new MultiSplitEvent(eventCenter, distance, chains);
}
private static LoadLevelEvents(queue: PriorityQueue<SkeletonEvent>): List<SkeletonEvent> {
const level = new List<SkeletonEvent>();
let levelStart: SkeletonEvent;
do {
levelStart = queue.Empty ? null : queue.Next();
}
while (levelStart !== null && levelStart.IsObsolete);
if (levelStart === null || levelStart.IsObsolete)
return level;
const levelStartHeight = levelStart.Distance;
level.Add(levelStart);
let event: SkeletonEvent;
while ((event = queue.Peek()) !== null &&
Math.abs(event.Distance - levelStartHeight) < this.SplitEpsilon) {
const nextLevelEvent = queue.Next();
if (!nextLevelEvent.IsObsolete)
level.Add(nextLevelEvent);
}
return level;
}
private static AssertMaxNumberOfInteraction(count: number): number {
count++;
if (count > 10000)
throw new Error("Too many interaction: bug?");
return count;
}
private static MakeClockwise(holes: List<List<Vector2d>>): List<List<Vector2d>> {
if (holes === null)
return null;
const ret = new List<List<Vector2d>>(holes.Count);
for (const hole of holes) {
if (PrimitiveUtils.IsClockwisePolygon(hole))
ret.Add(hole);
else {
hole.Reverse();
ret.Add(hole);
}
}
return ret;
}
private static MakeCounterClockwise(polygon: List<Vector2d>): List<Vector2d> {
return PrimitiveUtils.MakeCounterClockwise(polygon);
}
private static InitSlav(polygon: List<Vector2d>, sLav: HashSet<CircularList<Vertex>>, edges: List<Edge>, faces: List<FaceQueue>) {
const edgesList = new CircularList<Edge>();
const size = polygon.Count;
for (let i = 0; i < size; i++) {
const j = (i + 1) % size;
edgesList.AddLast(new Edge(polygon[i], polygon[j]));
}
for (const edge of edgesList.Iterate()) {
const nextEdge = edge.Next as Edge;
const bisector = this.CalcBisector(edge.End, edge, nextEdge);
edge.BisectorNext = bisector;
nextEdge.BisectorPrevious = bisector;
edges.Add(edge);
}
const lav = new CircularList<Vertex>();
sLav.Add(lav);
for (const edge of edgesList.Iterate()) {
const nextEdge = edge.Next as Edge;
const vertex = new Vertex(edge.End, 0, edge.BisectorNext, edge, nextEdge);
lav.AddLast(vertex);
}
for (const vertex of lav.Iterate()) {
const next = vertex.Next as Vertex;
const rightFace = new FaceNode(vertex);
const faceQueue = new FaceQueue();
faceQueue.Edge = (vertex.NextEdge);
faceQueue.AddFirst(rightFace);
faces.Add(faceQueue);
vertex.RightFace = rightFace;
const leftFace = new FaceNode(next);
rightFace.AddPush(leftFace);
next.LeftFace = leftFace;
}
}
private static AddFacesToOutput(faces: List<FaceQueue>): Skeleton {
const edgeOutputs = new List<EdgeResult>();
const distances = new Dictionary<Vector2d, number>();
for (const face of faces) {
if (face.Size > 0) {
const faceList = new List<Vector2d>();
for (const fn of face.Iterate()) {
const point = fn.Vertex.Point;
faceList.Add(point);
if (!distances.ContainsKey(point))
distances.Add(point, fn.Vertex.Distance);
}
edgeOutputs.Add(new EdgeResult(face.Edge, faceList));
}
}
return new Skeleton(edgeOutputs, distances);
}
private static InitEvents(sLav: HashSet<CircularList<Vertex>>, queue: PriorityQueue<SkeletonEvent>, edges: List<Edge>) {
for (const lav of sLav) {
for (const vertex of lav.Iterate())
this.ComputeSplitEvents(vertex, edges, queue, -1);
}
for (const lav of sLav) {
for (const vertex of lav.Iterate()) {
const nextVertex = vertex.Next as Vertex;
this.ComputeEdgeEvents(vertex, nextVertex, queue);
}
}
}
private static ComputeSplitEvents(vertex: Vertex, edges: List<Edge>, queue: PriorityQueue<SkeletonEvent>, distanceSquared: number) {
const source = vertex.Point;
const oppositeEdges = this.CalcOppositeEdges(vertex, edges);
for (const oppositeEdge of oppositeEdges) {
const point = oppositeEdge.Point;
if (Math.abs(distanceSquared - (-1)) > this.SplitEpsilon) {
if (source.DistanceSquared(point) > distanceSquared + this.SplitEpsilon) {
continue;
}
}
if (oppositeEdge.OppositePoint.NotEquals(Vector2d.Empty)) {
queue.Add(new VertexSplitEvent(point, oppositeEdge.Distance, vertex));
continue;
}
queue.Add(new SplitEvent(point, oppositeEdge.Distance, vertex, oppositeEdge.OppositeEdge));
}
}
private static ComputeEvents(vertex: Vertex, queue: PriorityQueue<SkeletonEvent>, edges: List<Edge>) {
const distanceSquared = this.ComputeCloserEdgeEvent(vertex, queue);
this.ComputeSplitEvents(vertex, edges, queue, distanceSquared);
}
private static ComputeCloserEdgeEvent(vertex: Vertex, queue: PriorityQueue<SkeletonEvent>): number {
const nextVertex = vertex.Next as Vertex;
const previousVertex = vertex.Previous as Vertex;
const point = vertex.Point;
const point1 = this.ComputeIntersectionBisectors(vertex, nextVertex);
const point2 = this.ComputeIntersectionBisectors(previousVertex, vertex);
if (point1.Equals(Vector2d.Empty) && point2.Equals(Vector2d.Empty))
return -1;
let distance1 = Number.MAX_VALUE;
let distance2 = Number.MAX_VALUE;
if (point1.NotEquals(Vector2d.Empty))
distance1 = point.DistanceSquared(point1);
if (point2.NotEquals(Vector2d.Empty))
distance2 = point.DistanceSquared(point2);
if (Math.abs(distance1 - this.SplitEpsilon) < distance2)
queue.Add(this.CreateEdgeEvent(point1, vertex, nextVertex));
if (Math.abs(distance2 - this.SplitEpsilon) < distance1)
queue.Add(this.CreateEdgeEvent(point2, previousVertex, vertex));
return distance1 < distance2 ? distance1 : distance2;
}
private static CreateEdgeEvent(point: Vector2d, previousVertex: Vertex, nextVertex: Vertex): SkeletonEvent {
return new EdgeEvent(point, this.CalcDistance(point, previousVertex.NextEdge), previousVertex, nextVertex);
}
private static ComputeEdgeEvents(previousVertex: Vertex, nextVertex: Vertex, queue: PriorityQueue<SkeletonEvent>) {
const point = this.ComputeIntersectionBisectors(previousVertex, nextVertex);
if (point.NotEquals(Vector2d.Empty))
queue.Add(this.CreateEdgeEvent(point, previousVertex, nextVertex));
}
private static CalcOppositeEdges(vertex: Vertex, edges: List<Edge>): List<SplitCandidate> {
const ret = new List<SplitCandidate>();
for (const edgeEntry of edges) {
const edge = edgeEntry.LineLinear2d;
if (this.EdgeBehindBisector(vertex.Bisector, edge))
continue;
const candidatePoint = this.CalcCandidatePointForSplit(vertex, edgeEntry);
if (candidatePoint !== null)
ret.Add(candidatePoint);
}
ret.Sort(new SplitCandidateComparer());
return ret;
}
private static EdgeBehindBisector(bisector: LineParametric2d, edge: LineLinear2d): boolean {
return LineParametric2d.Collide(bisector, edge, this.SplitEpsilon).Equals(Vector2d.Empty);
}
private static CalcCandidatePointForSplit(vertex: Vertex, edge: Edge): SplitCandidate {
const vertexEdge = this.ChoseLessParallelVertexEdge(vertex, edge);
if (vertexEdge === null)
return null;
const vertexEdteNormNegate = vertexEdge.Norm;
const edgesBisector = this.CalcVectorBisector(vertexEdteNormNegate, edge.Norm);
const edgesCollide = vertexEdge.LineLinear2d.Collide(edge.LineLinear2d);
if (edgesCollide.Equals(Vector2d.Empty))
throw new Error("Ups this should not happen");
const edgesBisectorLine = new LineParametric2d(edgesCollide, edgesBisector).CreateLinearForm();
const candidatePoint = LineParametric2d.Collide(vertex.Bisector, edgesBisectorLine, this.SplitEpsilon);
if (candidatePoint.Equals(Vector2d.Empty))
return null;
if (edge.BisectorPrevious.IsOnRightSite(candidatePoint, this.SplitEpsilon)
&& edge.BisectorNext.IsOnLeftSite(candidatePoint, this.SplitEpsilon)) {
const distance = this.CalcDistance(candidatePoint, edge);
if (edge.BisectorPrevious.IsOnLeftSite(candidatePoint, this.SplitEpsilon))
return new SplitCandidate(candidatePoint, distance, null, edge.Begin);
if (edge.BisectorNext.IsOnRightSite(candidatePoint, this.SplitEpsilon))
return new SplitCandidate(candidatePoint, distance, null, edge.Begin);
return new SplitCandidate(candidatePoint, distance, edge, Vector2d.Empty);
}
return null;
}
private static ChoseLessParallelVertexEdge(vertex: Vertex, edge: Edge): Edge {
const edgeA = vertex.PreviousEdge;
const edgeB = vertex.NextEdge;
let vertexEdge = edgeA;
const edgeADot = Math.abs(edge.Norm.Dot(edgeA.Norm));
const edgeBDot = Math.abs(edge.Norm.Dot(edgeB.Norm));
if (edgeADot + edgeBDot >= 2 - this.SplitEpsilon)
return null;
if (edgeADot > edgeBDot)
vertexEdge = edgeB;
return vertexEdge;
}
private static ComputeIntersectionBisectors(vertexPrevious: Vertex, vertexNext: Vertex): Vector2d {
const bisectorPrevious = vertexPrevious.Bisector;
const bisectorNext = vertexNext.Bisector;
const intersectRays2d = PrimitiveUtils.IntersectRays2D(bisectorPrevious, bisectorNext);
const intersect = intersectRays2d.Intersect;
if (vertexPrevious.Point.Equals(intersect) || vertexNext.Point.Equals(intersect))
return Vector2d.Empty;
return intersect;
}
private static FindOppositeEdgeLav(sLav: HashSet<CircularList<Vertex>>, oppositeEdge: Edge, center: Vector2d): Vertex {
const edgeLavs = this.FindEdgeLavs(sLav, oppositeEdge, null);
return this.ChooseOppositeEdgeLav(edgeLavs, oppositeEdge, center);
}
private static ChooseOppositeEdgeLav(edgeLavs: List<Vertex>, oppositeEdge: Edge, center: Vector2d): Vertex {
if (!edgeLavs.Any())
return null;
if (edgeLavs.Count === 1)
return edgeLavs[0];
const edgeStart = oppositeEdge.Begin;
const edgeNorm = oppositeEdge.Norm;
const centerVector = center.Sub(edgeStart);
const centerDot = edgeNorm.Dot(centerVector);
for (const end of edgeLavs) {
const begin = end.Previous as Vertex;
const beginVector = begin.Point.Sub(edgeStart);
const endVector = end.Point.Sub(edgeStart);
const beginDot = edgeNorm.Dot(beginVector);
const endDot = edgeNorm.Dot(endVector);
if (beginDot < centerDot && centerDot < endDot ||
beginDot > centerDot && centerDot > endDot)
return end;
}
for (const end of edgeLavs) {
const size = end.List.Size;
const points = new List<Vector2d>(size);
let next = end;
for (let i = 0; i < size; i++) {
points.Add(next.Point);
next = next.Next as Vertex;
}
if (PrimitiveUtils.IsPointInsidePolygon(center, points))
return end;
}
throw new Error("Could not find lav for opposite edge, it could be correct but need some test data to check.");
}
private static FindEdgeLavs(sLav: HashSet<CircularList<Vertex>>, oppositeEdge: Edge, skippedLav: CircularList<Vertex>): List<Vertex> {
const edgeLavs = new List<Vertex>();
for (const lav of sLav) {
if (lav === skippedLav)
continue;
const vertexInLav = this.GetEdgeInLav(lav, oppositeEdge);
if (vertexInLav !== null)
edgeLavs.Add(vertexInLav);
}
return edgeLavs;
}
private static GetEdgeInLav(lav: CircularList<Vertex>, oppositeEdge: Edge): Vertex {
for (const node of lav.Iterate())
if (oppositeEdge === node.PreviousEdge ||
oppositeEdge === node.Previous.Next)
return node;
return null;
}
private static AddFaceBack(newVertex: Vertex, va: Vertex, vb: Vertex) {
const fn = new FaceNode(newVertex);
va.RightFace.AddPush(fn);
FaceQueueUtil.ConnectQueues(fn, vb.LeftFace);
}
private static AddFaceRight(newVertex: Vertex, vb: Vertex) {
const fn = new FaceNode(newVertex);
vb.RightFace.AddPush(fn);
newVertex.RightFace = fn;
}
private static AddFaceLeft(newVertex: Vertex, va: Vertex) {
const fn = new FaceNode(newVertex);
va.LeftFace.AddPush(fn);
newVertex.LeftFace = fn;
}
private static CalcDistance(intersect: Vector2d, currentEdge: Edge): number {
const edge = currentEdge.End.Sub(currentEdge.Begin);
const vector = intersect.Sub(currentEdge.Begin);
const pointOnVector = PrimitiveUtils.OrthogonalProjection(edge, vector);
return vector.DistanceTo(pointOnVector);
}
private static CalcBisector(p: Vector2d, e1: Edge, e2: Edge): LineParametric2d {
const norm1 = e1.Norm;
const norm2 = e2.Norm;
const bisector = this.CalcVectorBisector(norm1, norm2);
return new LineParametric2d(p, bisector);
}
private static CalcVectorBisector(norm1: Vector2d, norm2: Vector2d): Vector2d {
return PrimitiveUtils.BisectorNormalized(norm1, norm2);
}
}
class SkeletonEventDistanseComparer implements IComparer<SkeletonEvent> {
public Compare(left: SkeletonEvent, right: SkeletonEvent): number {
if (left.Distance > right.Distance)
return 1;
if (left.Distance < right.Distance)
return -1;
return 0;
}
}
class ChainComparer implements IComparer<IChain> {
private readonly _center: Vector2d;
constructor(center: Vector2d) {
this._center = center;
}
public Compare(x: IChain, y: IChain): number {
if (x === y)
return 0;
const angle1 = ChainComparer.Angle(this._center, x.PreviousEdge.Begin);
const angle2 = ChainComparer.Angle(this._center, y.PreviousEdge.Begin);
return angle1 > angle2 ? 1 : -1;
}
private static Angle(p0: Vector2d, p1: Vector2d): number {
const dx = p1.X - p0.X;
const dy = p1.Y - p0.Y;
return Math.atan2(dy, dx);
}
}
class SplitCandidateComparer implements IComparer<SplitCandidate> {
public Compare(left: SplitCandidate, right: SplitCandidate): number {
if (left.Distance > right.Distance)
return 1;
if (left.Distance < right.Distance)
return -1;
return 0;
}
}
class SplitCandidate {
public readonly Distance: number;
public readonly OppositeEdge: Edge = null;
public readonly OppositePoint: Vector2d = null;
public readonly Point: Vector2d = null;
constructor(point: Vector2d, distance: number, oppositeEdge: Edge, oppositePoint: Vector2d) {
this.Point = point;
this.Distance = distance;
this.OppositeEdge = oppositeEdge;
this.OppositePoint = oppositePoint;
}
}