Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into feature/batch-angle

This commit is contained in:
hyojun.choi 2025-09-03 09:48:17 +09:00
commit 61a44704fd
38 changed files with 2467 additions and 8 deletions

View File

@ -55,11 +55,14 @@
},
"devDependencies": {
"@turf/turf": "^7.0.0",
"@types/node": "^24.3.0",
"@types/react": "^19.1.11",
"convertapi": "^1.14.0",
"postcss": "^8",
"prettier": "^3.3.3",
"react-color-palette": "^7.2.2",
"sass": "^1.77.8",
"tailwindcss": "^3.4.1"
"tailwindcss": "^3.4.1",
"typescript": "^5.9.2"
}
}

View File

@ -2,6 +2,7 @@ import { fabric } from 'fabric'
import { v4 as uuidv4 } from 'uuid'
import { getDirectionByPoint } from '@/util/canvas-util'
import { calcLinePlaneSize } from '@/util/qpolygon-utils'
import { logger } from '@/util/logger'
export const QLine = fabric.util.createClass(fabric.Line, {
type: 'QLine',
@ -69,7 +70,14 @@ export const QLine = fabric.util.createClass(fabric.Line, {
},
setLength() {
this.length = calcLinePlaneSize(this) / 10
// Ensure all required properties are valid numbers
const { x1, y1, x2, y2 } = this;
if (isNaN(x1) || isNaN(y1) || isNaN(x2) || isNaN(y2)) {
logger.error('Invalid coordinates in QLine:', { x1, y1, x2, y2 });
this.length = 0;
return;
}
this.length = calcLinePlaneSize({ x1, y1, x2, y2 }) / 10;
},
addLengthText() {

View File

@ -169,6 +169,7 @@ const Placement = forwardRef((props, refs) => {
<div className="roof-module-table">
<table>
<thead>
<tr>
{moduleData.header.map((data) => (
<th key={data.prop} style={{ width: data.width ? data.width : '' }}>
{data.type === 'check' ? (
@ -181,6 +182,7 @@ const Placement = forwardRef((props, refs) => {
)}
</th>
))}
</tr>
</thead>
<tbody>
{selectedModules?.itemList &&
@ -216,7 +218,7 @@ const Placement = forwardRef((props, refs) => {
className="input-origin block"
name="row"
value={props.layoutSetup[index]?.row ?? 1}
defaultValue={0}
//defaultValue={0}
onChange={(e) => handleLayoutSetup(e, item.itemId, index)}
/>
</div>
@ -228,7 +230,7 @@ const Placement = forwardRef((props, refs) => {
className="input-origin block"
name="col"
value={props.layoutSetup[index]?.col ?? 1}
defaultValue={0}
//defaultValue={0}
onChange={(e) => handleLayoutSetup(e, item.itemId, index)}
/>
</div>

View File

@ -86,7 +86,7 @@ export default function RoofAllocationSetting(props) {
return (
<div className="grid-option-box" key={index}>
<div className="d-check-radio pop no-text">
<input type="radio" name="radio01" checked={roof.selected && 'checked'} readOnly={true} />
<input type="radio" name="radio01" checked={roof.selected} readOnly />
<label
htmlFor="ra01"
onClick={(e) => {
@ -213,7 +213,6 @@ export default function RoofAllocationSetting(props) {
handleChangePitch(e, index)
}}
value={currentAngleType === 'slope' ? roof.pitch : roof.angle}
defaultValue={currentAngleType === 'slope' ? roof.pitch : roof.angle}
/>
</div>
<span className="absol">{pitchText}</span>

View File

@ -10,7 +10,7 @@ import {
selectedModelsState,
seriesState,
} from '@/store/circuitTrestleAtom'
import { moduleSelectionDataState, selectedModuleState } from '@/store/selectedModuleOptions'
import { selectedModuleState } from '@/store/selectedModuleOptions'
import { useContext, useEffect } from 'react'
import { useRecoilState, useRecoilValue, useResetRecoilState, useSetRecoilState } from 'recoil'
import { useMessage } from './useMessage'
@ -101,7 +101,11 @@ export function useCircuitTrestle(executeEffect = false) {
// result 배열에서 roofSurface 값을 기준으로 순서대로 정렬한다.
return groupSort(result)
if (pcsCheck.division) {
return groupSort(result)
} else {
return result
}
}
const groupSort = (arr) => {

View File

@ -0,0 +1,113 @@
import CircularNode from "./CircularNode";
export interface ICircularList {
readonly Size: number;
AddNext(node: CircularNode, newNode: CircularNode): void;
AddPrevious(node: CircularNode, newNode: CircularNode): void;
AddLast(node: CircularNode): void;
Remove(node: CircularNode): void;
}
export default class CircularList<T extends CircularNode> implements ICircularList {
private _first: T = null;
private _size: number = 0;
public AddNext(node: CircularNode, newNode: CircularNode) {
if (newNode.List !== null)
throw new Error("Node is already assigned to different list!");
newNode.List = this;
newNode.Previous = node;
newNode.Next = node.Next;
node.Next.Previous = newNode;
node.Next = newNode;
this._size++;
}
AddPrevious(node: CircularNode, newNode: CircularNode) {
if (newNode.List !== null)
throw new Error("Node is already assigned to different list!");
newNode.List = this;
newNode.Previous = node.Previous;
newNode.Next = node;
node.Previous.Next = newNode;
node.Previous = newNode;
this._size++;
}
AddLast(node: CircularNode) {
if (node.List !== null)
throw new Error("Node is already assigned to different list!");
if (this._first === null) {
this._first = node as T;
node.List = this;
node.Next = node;
node.Previous = node;
this._size++;
} else
this.AddPrevious(this._first, node);
}
Remove(node: CircularNode) {
if (node.List !== this)
throw new Error("Node is not assigned to this list!");
if (this._size <= 0)
throw new Error("List is empty can't remove!");
node.List = null;
if (this._size === 1)
this._first = null;
else {
if (this._first === node)
this._first = <T>this._first.Next;
node.Previous.Next = node.Next;
node.Next.Previous = node.Previous;
}
node.Previous = null;
node.Next = null;
this._size--;
}
public get Size(): number {
return this._size;
}
public First(): T {
return this._first;
}
public* Iterate(): Generator<T> {
let current = this._first;
let i = 0;
while (current !== null) {
yield current;
if (++i === this.Size) {
return;
}
current = <T>current.Next;
}
}
}

View File

@ -0,0 +1,19 @@
import {ICircularList} from "./CircularList";
export default class CircularNode {
public List: ICircularList = null;
public Next: CircularNode = null;
public Previous: CircularNode = null;
public AddNext(node: CircularNode) {
this.List.AddNext(this, node);
}
public AddPrevious(node: CircularNode) {
this.List.AddPrevious(this, node);
}
public Remove() {
this.List.Remove(this);
}
}

View File

@ -0,0 +1,28 @@
import CircularNode from "./CircularNode";
import Vector2d from "../Primitives/Vector2d";
import LineLinear2d from "../Primitives/LineLinear2d";
import LineParametric2d from "../Primitives/LineParametric2d";
export default class Edge extends CircularNode {
public readonly Begin: Vector2d;
public readonly End: Vector2d;
public readonly Norm: Vector2d;
public readonly LineLinear2d: LineLinear2d;
public BisectorNext: LineParametric2d = null;
public BisectorPrevious: LineParametric2d = null;
constructor(begin: Vector2d, end: Vector2d) {
super();
this.Begin = begin;
this.End = end;
this.LineLinear2d = new LineLinear2d(begin, end);
this.Norm = end.Sub(begin).Normalized();
}
public ToString(): string {
return `Edge [p1=${this.Begin}, p2=${this.End}]`;
}
}

View File

@ -0,0 +1,39 @@
import CircularNode from "./CircularNode";
import Vector2d from "../Primitives/Vector2d";
import LineParametric2d from "../Primitives/LineParametric2d";
import Edge from "./Edge";
import {FaceNode} from "../Path/FaceNode";
export default class Vertex extends CircularNode {
readonly RoundDigitCount = 5;
public Point: Vector2d = null;
public readonly Distance: number;
public readonly Bisector: LineParametric2d = null;
public readonly NextEdge: Edge = null;
public readonly PreviousEdge: Edge = null;
public LeftFace: FaceNode = null;
public RightFace: FaceNode = null;
public IsProcessed: boolean;
constructor(point: Vector2d, distance: number, bisector: LineParametric2d, previousEdge: Edge, nextEdge: Edge) {
super();
this.Point = point;
this.Distance = +distance.toFixed(this.RoundDigitCount);
this.Bisector = bisector;
this.PreviousEdge = previousEdge;
this.NextEdge = nextEdge;
this.IsProcessed = false;
}
public ToString(): string {
return "Vertex [v=" + this.Point + ", IsProcessed=" + this.IsProcessed +
", Bisector=" + this.Bisector + ", PreviousEdge=" + this.PreviousEdge +
", NextEdge=" + this.NextEdge;
}
}

View File

@ -0,0 +1,13 @@
import Edge from "./Circular/Edge";
import Vector2d from "./Primitives/Vector2d";
import {List} from "./Utils";
export default class EdgeResult {
public readonly Edge: Edge;
public readonly Polygon: List<Vector2d>;
constructor(edge: Edge, polygon: List<Vector2d>) {
this.Edge = edge;
this.Polygon = polygon;
}
}

View File

@ -0,0 +1,7 @@
enum ChainType {
Edge,
ClosedEdge,
Split
}
export default ChainType;

View File

@ -0,0 +1,40 @@
import IChain from "./IChain";
import EdgeEvent from "../EdgeEvent";
import {List} from "../../Utils";
import Edge from "../../Circular/Edge";
import Vertex from "../../Circular/Vertex";
import ChainType from "./ChainType";
export default class EdgeChain implements IChain {
private readonly _closed: boolean;
public EdgeList: List<EdgeEvent>;
constructor(edgeList: List<EdgeEvent>) {
this.EdgeList = edgeList;
this._closed = this.PreviousVertex === this.NextVertex;
}
public get PreviousEdge(): Edge {
return this.EdgeList[0].PreviousVertex.PreviousEdge;
}
public get NextEdge(): Edge {
return this.EdgeList[this.EdgeList.Count - 1].NextVertex.NextEdge;
}
public get PreviousVertex(): Vertex {
return this.EdgeList[0].PreviousVertex;
}
public get NextVertex(): Vertex {
return this.EdgeList[this.EdgeList.Count - 1].NextVertex;
}
public get CurrentVertex(): Vertex {
return null;
}
public get ChainType(): ChainType {
return this._closed ? ChainType.ClosedEdge : ChainType.Edge;
}
}

View File

@ -0,0 +1,17 @@
import Edge from "../../Circular/Edge";
import Vertex from "../../Circular/Vertex";
import ChainType from "./ChainType";
export default interface IChain {
get PreviousEdge(): Edge;
get NextEdge(): Edge;
get PreviousVertex(): Vertex;
get NextVertex(): Vertex;
get CurrentVertex(): Vertex;
get ChainType(): ChainType;
}

View File

@ -0,0 +1,40 @@
import IChain from "./IChain";
import Edge from "../../Circular/Edge";
import Vertex from "../../Circular/Vertex";
import ChainType from "./ChainType";
export default class SingleEdgeChain implements IChain {
private readonly _nextVertex: Vertex;
private readonly _oppositeEdge: Edge;
private readonly _previousVertex: Vertex;
constructor(oppositeEdge: Edge, nextVertex: Vertex) {
this._oppositeEdge = oppositeEdge;
this._nextVertex = nextVertex;
this._previousVertex = nextVertex.Previous as Vertex;
}
public get PreviousEdge(): Edge {
return this._oppositeEdge;
}
public get NextEdge(): Edge {
return this._oppositeEdge;
}
public get PreviousVertex(): Vertex {
return this._previousVertex;
}
public get NextVertex(): Vertex {
return this._nextVertex;
}
public get CurrentVertex(): Vertex {
return null;
}
public get ChainType(): ChainType {
return ChainType.Split;
}
}

View File

@ -0,0 +1,45 @@
import IChain from "./IChain";
import Edge from "../../Circular/Edge";
import Vertex from "../../Circular/Vertex";
import ChainType from "./ChainType";
import VertexSplitEvent from "../VertexSplitEvent";
import SplitEvent from "../SplitEvent";
export default class SplitChain implements IChain {
private readonly _splitEvent: SplitEvent;
constructor(event: SplitEvent) {
this._splitEvent = event;
}
public get OppositeEdge(): Edge {
if (!(this._splitEvent instanceof VertexSplitEvent))
return this._splitEvent.OppositeEdge;
return null;
}
public get PreviousEdge(): Edge {
return this._splitEvent.Parent.PreviousEdge;
}
public get NextEdge(): Edge {
return this._splitEvent.Parent.NextEdge;
}
public get PreviousVertex(): Vertex {
return this._splitEvent.Parent.Previous as Vertex;
}
public get NextVertex(): Vertex {
return this._splitEvent.Parent.Next as Vertex;
}
public get CurrentVertex(): Vertex {
return this._splitEvent.Parent;
}
public get ChainType(): ChainType {
return ChainType.Split;
}
}

View File

@ -0,0 +1,27 @@
import SkeletonEvent from "./SkeletonEvent";
import Vertex from "../Circular/Vertex";
import Vector2d from "../Primitives/Vector2d";
export default class EdgeEvent extends SkeletonEvent {
public readonly NextVertex: Vertex;
public readonly PreviousVertex: Vertex;
public override get IsObsolete(): boolean {
return this.PreviousVertex.IsProcessed || this.NextVertex.IsProcessed;
}
constructor(point: Vector2d, distance: number, previousVertex: Vertex, nextVertex: Vertex) {
super(point, distance);
this.PreviousVertex = previousVertex;
this.NextVertex = nextVertex;
}
public override ToString(): string {
return "EdgeEvent [V=" + this.V + ", PreviousVertex="
+ (this.PreviousVertex !== null ? this.PreviousVertex.Point.ToString() : "null") +
", NextVertex="
+ (this.NextVertex !== null ? this.NextVertex.Point.ToString() : "null") + ", Distance=" +
this.Distance + "]";
}
}

View File

@ -0,0 +1,17 @@
import SkeletonEvent from "./SkeletonEvent";
import Vector2d from "../Primitives/Vector2d";
import EdgeChain from "./Chains/EdgeChain";
export default class MultiEdgeEvent extends SkeletonEvent {
public readonly Chain: EdgeChain;
public override get IsObsolete(): boolean {
return false;
}
constructor(point: Vector2d, distance: number, chain: EdgeChain) {
super(point, distance);
this.Chain = chain;
}
}

View File

@ -0,0 +1,18 @@
import SkeletonEvent from "./SkeletonEvent";
import {List} from "../Utils";
import IChain from "./Chains/IChain";
import Vector2d from "../Primitives/Vector2d";
export default class MultiSplitEvent extends SkeletonEvent {
public readonly Chains: List<IChain>;
public override get IsObsolete(): boolean {
return false;
}
constructor(point: Vector2d, distance: number, chains: List<IChain>) {
super(point, distance);
this.Chains = chains;
}
}

View File

@ -0,0 +1,17 @@
import SkeletonEvent from "./SkeletonEvent";
import Vector2d from "../Primitives/Vector2d";
import EdgeChain from "./Chains/EdgeChain";
export default class PickEvent extends SkeletonEvent {
public readonly Chain: EdgeChain;
public override get IsObsolete(): boolean {
return false;
}
constructor(point: Vector2d, distance: number, chain: EdgeChain) {
super(point, distance);
this.Chain = chain;
}
}

View File

@ -0,0 +1,22 @@
import Vector2d from "../Primitives/Vector2d";
export default abstract class SkeletonEvent {
public V: Vector2d = null;
public Distance: number;
public abstract get IsObsolete(): boolean;
protected constructor(point: Vector2d, distance: number) {
this.V = point;
this.Distance = distance;
}
public ToString(): string {
return "IntersectEntry [V=" + this.V + ", Distance=" + this.Distance + "]";
}
public GetType(): string {
return this.constructor.name;
}
}

View File

@ -0,0 +1,26 @@
import SkeletonEvent from "./SkeletonEvent";
import Edge from "../Circular/Edge";
import Vertex from "../Circular/Vertex";
import Vector2d from "../Primitives/Vector2d";
export default class SplitEvent extends SkeletonEvent {
public readonly OppositeEdge: Edge = null;
public readonly Parent: Vertex = null;
constructor(point: Vector2d, distance: number, parent: Vertex, oppositeEdge: Edge) {
super(point, distance);
this.Parent = parent;
this.OppositeEdge = oppositeEdge;
}
public override get IsObsolete(): boolean {
return this.Parent.IsProcessed;
}
public override ToString(): string {
return "SplitEvent [V=" + this.V + ", Parent=" + (this.Parent !== null ? this.Parent.Point.ToString() : "null") +
", Distance=" + this.Distance + "]";
}
}

View File

@ -0,0 +1,15 @@
import SplitEvent from "./SplitEvent";
import Vector2d from "../Primitives/Vector2d";
import Vertex from "../Circular/Vertex";
export default class VertexSplitEvent extends SplitEvent {
constructor(point: Vector2d, distance: number, parent: Vertex) {
super(point, distance, parent, null);
}
public override ToString(): string {
return "VertexSplitEvent [V=" + this.V + ", Parent=" +
(this.Parent !== null ? this.Parent.Point.ToString() : "null")
+ ", Distance=" + this.Distance + "]";
}
}

View File

@ -0,0 +1,56 @@
import Vertex from "./Circular/Vertex";
import {List} from "./Utils";
import CircularList from "./Circular/CircularList";
export default class LavUtil {
public static IsSameLav(v1: Vertex, v2: Vertex): boolean {
if (v1.List === null || v2.List === null)
return false;
return v1.List === v2.List;
}
public static RemoveFromLav(vertex: Vertex) {
if (vertex === null || vertex.List === null)
return;
vertex.Remove();
}
public static CutLavPart(startVertex: Vertex, endVertex: Vertex): List<Vertex> {
const ret = new List<Vertex>();
const size = startVertex.List.Size;
let next = startVertex;
for (let i = 0; i < size; i++) {
const current = next;
next = current.Next as Vertex;
current.Remove();
ret.Add(current);
if (current === endVertex)
return ret;
}
throw new Error("End vertex can't be found in start vertex lav");
}
public static MergeBeforeBaseVertex(base: Vertex, merged: Vertex) {
const size = merged.List.Size;
for (let i = 0; i < size; i++) {
const nextMerged = merged.Next as Vertex;
nextMerged.Remove();
base.AddPrevious(nextMerged);
}
}
public static MoveAllVertexToLavEnd(vertex: Vertex, newLaw: CircularList<Vertex>) {
const size = vertex.List.Size;
for (let i = 0; i < size; i++) {
const ver = vertex;
vertex = vertex.Next as Vertex;
ver.Remove();
newLaw.AddLast(ver);
}
}
}

View File

@ -0,0 +1,24 @@
import PathQueueNode from "./PathQueueNode";
import Vertex from "../Circular/Vertex";
import FaceQueue from "./FaceQueue";
export class FaceNode extends PathQueueNode<FaceNode> {
public readonly Vertex: Vertex = null;
constructor(vertex: Vertex) {
super();
this.Vertex = vertex;
}
public get FaceQueue(): FaceQueue {
return <FaceQueue>this.List;
}
public get IsQueueUnconnected(): boolean {
return this.FaceQueue.IsUnconnected;
}
public QueueClose() {
this.FaceQueue.Close();
}
}

View File

@ -0,0 +1,24 @@
import PathQueue from "./PathQueue";
import {FaceNode} from "./FaceNode";
import PathQueueNode from "./PathQueueNode";
import Edge from "../Circular/Edge";
export default class FaceQueue extends PathQueue<FaceNode> {
public Edge: Edge = null;
public Closed: boolean = false;
public get IsUnconnected(): boolean {
return this.Edge === null;
}
public override AddPush(node: PathQueueNode<FaceNode>, newNode: PathQueueNode<FaceNode>) {
if (this.Closed)
throw new Error("Can't add node to closed FaceQueue");
super.AddPush(node, newNode);
}
public Close() {
this.Closed = true;
}
}

View File

@ -0,0 +1,39 @@
import {FaceNode} from "./FaceNode";
export default class FaceQueueUtil {
public static ConnectQueues(firstFace: FaceNode, secondFace: FaceNode) {
if (firstFace.List === null)
throw new Error("firstFace.list cannot be null.");
if (secondFace.List === null)
throw new Error("secondFace.list cannot be null.");
if (firstFace.List === secondFace.List) {
if (!firstFace.IsEnd || !secondFace.IsEnd)
throw new Error("try to connect the same list not on end nodes");
if (firstFace.IsQueueUnconnected || secondFace.IsQueueUnconnected)
throw new Error("can't close node queue not conected with edges");
firstFace.QueueClose();
return;
}
if (!firstFace.IsQueueUnconnected && !secondFace.IsQueueUnconnected)
throw new Error(
"can't connect two diffrent queues if each of them is connected to edge");
if (!firstFace.IsQueueUnconnected) {
const qLeft = secondFace.FaceQueue;
this.MoveNodes(firstFace, secondFace);
qLeft.Close();
} else {
const qRight = firstFace.FaceQueue;
this.MoveNodes(secondFace, firstFace);
qRight.Close();
}
}
private static MoveNodes(firstFace: FaceNode, secondFace: FaceNode) {
firstFace.AddQueue(secondFace);
}
}

View File

@ -0,0 +1,103 @@
import PathQueueNode from "./PathQueueNode";
export default class PathQueue<T extends PathQueueNode<T>> {
public Size: number = 0;
public First: PathQueueNode<T> = null;
public AddPush(node: PathQueueNode<T>, newNode: PathQueueNode<T>) {
if (newNode.List !== null)
throw new Error("Node is already assigned to different list!");
if (node.Next !== null && node.Previous !== null)
throw new Error("Can't push new node. Node is inside a Quere. " +
"New node can by added only at the end of queue.");
newNode.List = this;
this.Size++;
if (node.Next === null) {
newNode.Previous = node;
newNode.Next = null;
node.Next = newNode;
} else {
newNode.Previous = null;
newNode.Next = node;
node.Previous = newNode;
}
}
public AddFirst(node: T) {
if (node.List !== null)
throw new Error("Node is already assigned to different list!");
if (this.First === null) {
this.First = node;
node.List = this;
node.Next = null;
node.Previous = null;
this.Size++;
} else
throw new Error("First element already exist!");
}
public Pop(node: PathQueueNode<T>): PathQueueNode<T> {
if (node.List !== this)
throw new Error("Node is not assigned to this list!");
if (this.Size <= 0)
throw new Error("List is empty can't remove!");
if (!node.IsEnd)
throw new Error("Can pop only from end of queue!");
node.List = null;
let previous: PathQueueNode<T> = null;
if (this.Size === 1)
this.First = null;
else {
if (this.First === node) {
if (node.Next !== null)
this.First = node.Next;
else if (node.Previous !== null)
this.First = node.Previous;
else
throw new Error("Ups ?");
}
if (node.Next !== null) {
node.Next.Previous = null;
previous = node.Next;
} else if (node.Previous !== null) {
node.Previous.Next = null;
previous = node.Previous;
}
}
node.Previous = null;
node.Next = null;
this.Size--;
return previous;
}
public* Iterate(): Generator<T> {
let current: T = <T>(this.First !== null ? this.First.FindEnd() : null);
let i = 0;
while (current !== null)
{
yield current;
if (++i === this.Size)
return;
current = <T>current.Next;
}
}
}

View File

@ -0,0 +1,51 @@
import PathQueue from "./PathQueue";
export default class PathQueueNode<T extends PathQueueNode<T>> {
public List: PathQueue<T> = null;
public Next: PathQueueNode<T> = null;
public Previous: PathQueueNode<T> = null;
public get IsEnd(): boolean {
return this.Next === null || this.Previous === null;
}
public AddPush(node: PathQueueNode<T>) {
this.List.AddPush(this, node);
}
public AddQueue(queue: PathQueueNode<T>): PathQueueNode<T> {
if (this.List === queue.List)
return null;
let currentQueue: PathQueueNode<T> = this;
let current = queue;
while (current !== null) {
const next = current.Pop();
currentQueue.AddPush(current);
currentQueue = current;
current = next;
}
return currentQueue;
}
public FindEnd(): PathQueueNode<T> {
if (this.IsEnd)
return this;
let current: PathQueueNode<T> = this;
while (current.Previous !== null)
current = current.Previous;
return current;
}
public Pop(): PathQueueNode<T> {
return this.List.Pop(this);
}
}

View File

@ -0,0 +1,41 @@
import Vector2d from "./Vector2d";
export default class LineLinear2d {
public A: number;
public B: number;
public C: number;
constructor(pP1: Vector2d = Vector2d.Empty, pP2: Vector2d = Vector2d.Empty) {
this.A = pP1.Y - pP2.Y;
this.B = pP2.X - pP1.X;
this.C = pP1.X * pP2.Y - pP2.X * pP1.Y;
}
public SetFromCoefficients(a: number, b: number, c: number): LineLinear2d {
this.A = a;
this.B = b;
this.C = c;
return this;
}
public Collide(pLine: LineLinear2d): Vector2d {
return LineLinear2d.Collide(this, pLine);
}
public static Collide(pLine1: LineLinear2d, pLine2: LineLinear2d): Vector2d {
return LineLinear2d.CollideCoeff(pLine1.A, pLine1.B, pLine1.C, pLine2.A, pLine2.B, pLine2.C);
}
public static CollideCoeff(A1: number, B1: number, C1: number, A2: number, B2: number, C2: number): Vector2d {
const WAB = A1 * B2 - A2 * B1;
const WBC = B1 * C2 - B2 * C1;
const WCA = C1 * A2 - C2 * A1;
return WAB === 0 ? Vector2d.Empty : new Vector2d(WBC / WAB, WCA / WAB);
}
public Contains(point: Vector2d): boolean {
return Math.abs((point.X * this.A + point.Y * this.B + this.C)) < Number.EPSILON;
}
}

View File

@ -0,0 +1,47 @@
import Vector2d from "./Vector2d";
import LineLinear2d from "./LineLinear2d";
import PrimitiveUtils from "./PrimitiveUtils";
export default class LineParametric2d {
public static readonly Empty: LineParametric2d = new LineParametric2d(Vector2d.Empty, Vector2d.Empty);
public A: Vector2d = null;
public U: Vector2d = null;
constructor(pA: Vector2d, pU: Vector2d) {
this.A = pA;
this.U = pU;
}
public CreateLinearForm(): LineLinear2d {
const x = this.A.X;
const y = this.A.Y;
const B = -this.U.X;
const A = this.U.Y;
const C = -(A * x + B * y);
return new LineLinear2d().SetFromCoefficients(A, B, C);
}
public static Collide(ray: LineParametric2d, line: LineLinear2d, epsilon: number): Vector2d {
const collide = LineLinear2d.Collide(ray.CreateLinearForm(), line);
if (collide.Equals(Vector2d.Empty)) {
return Vector2d.Empty;
}
const collideVector = collide.Sub(ray.A);
return ray.U.Dot(collideVector) < epsilon ? Vector2d.Empty : collide;
}
public IsOnLeftSite(point: Vector2d, epsilon: number): boolean {
const direction = point.Sub(this.A);
return PrimitiveUtils.OrthogonalRight(this.U).Dot(direction) < epsilon;
}
public IsOnRightSite(point: Vector2d, epsilon: number): boolean {
const direction = point.Sub(this.A);
return PrimitiveUtils.OrthogonalRight(this.U).Dot(direction) > -epsilon;
}
}

View File

@ -0,0 +1,241 @@
import Vector2d from "./Vector2d";
import LineParametric2d from "./LineParametric2d";
import {List} from "../Utils";
class IntersectPoints {
public readonly Intersect: Vector2d = null;
public readonly IntersectEnd: Vector2d = null;
constructor(intersect?: Vector2d, intersectEnd?: Vector2d) {
if (!intersect) {
intersect = Vector2d.Empty;
}
if (!intersectEnd) {
intersectEnd = Vector2d.Empty;
}
this.Intersect = intersect;
this.IntersectEnd = intersectEnd;
}
}
export default class PrimitiveUtils {
public static FromTo(begin: Vector2d, end: Vector2d): Vector2d {
return new Vector2d(end.X - begin.X, end.Y - begin.Y);
}
public static OrthogonalLeft(v: Vector2d): Vector2d {
return new Vector2d(-v.Y, v.X);
}
public static OrthogonalRight(v: Vector2d): Vector2d {
return new Vector2d(v.Y, -v.X);
}
public static OrthogonalProjection(unitVector: Vector2d, vectorToProject: Vector2d): Vector2d {
const n = new Vector2d(unitVector.X, unitVector.Y).Normalized();
const px = vectorToProject.X;
const py = vectorToProject.Y;
const ax = n.X;
const ay = n.Y;
return new Vector2d(px * ax * ax + py * ax * ay, px * ax * ay + py * ay * ay);
}
public static BisectorNormalized(norm1: Vector2d, norm2: Vector2d): Vector2d {
const e1v = PrimitiveUtils.OrthogonalLeft(norm1);
const e2v = PrimitiveUtils.OrthogonalLeft(norm2);
if (norm1.Dot(norm2) > 0)
return e1v.Add(e2v);
let ret = new Vector2d(norm1.X, norm1.Y);
ret.Negate();
ret = ret.Add(norm2);
if (e1v.Dot(norm2) < 0)
ret.Negate();
return ret;
}
private static readonly SmallNum = 0.00000001;
private static readonly Empty: IntersectPoints = new IntersectPoints();
public static IsPointOnRay(point: Vector2d, ray: LineParametric2d, epsilon: number): boolean {
const rayDirection = new Vector2d(ray.U.X, ray.U.Y).Normalized();
const pointVector = point.Sub(ray.A);
let dot = rayDirection.Dot(pointVector);
if (dot < epsilon)
return false;
const x = rayDirection.X;
rayDirection.X = rayDirection.Y;
rayDirection.Y = -x;
dot = rayDirection.Dot(pointVector);
return -epsilon < dot && dot < epsilon;
}
public static IntersectRays2D(r1: LineParametric2d, r2: LineParametric2d): IntersectPoints {
const s1p0 = r1.A;
const s1p1 = r1.A.Add(r1.U);
const s2p0 = r2.A;
const u = r1.U;
const v = r2.U;
const w = s1p0.Sub(s2p0);
const d = PrimitiveUtils.Perp(u, v);
if (Math.abs(d) < PrimitiveUtils.SmallNum) {
if (PrimitiveUtils.Perp(u, w) !== 0 || PrimitiveUtils.Perp(v, w) !== 0)
return PrimitiveUtils.Empty;
const du = PrimitiveUtils.Dot(u, u);
const dv = PrimitiveUtils.Dot(v, v);
if (du === 0 && dv === 0) {
if (s1p0.NotEquals(s2p0))
return PrimitiveUtils.Empty;
return new IntersectPoints(s1p0);
}
if (du === 0) {
if (!PrimitiveUtils.InCollinearRay(s1p0, s2p0, v))
return PrimitiveUtils.Empty;
return new IntersectPoints(s1p0);
}
if (dv === 0) {
if (!PrimitiveUtils.InCollinearRay(s2p0, s1p0, u))
return PrimitiveUtils.Empty;
return new IntersectPoints(s2p0);
}
let t0, t1;
var w2 = s1p1.Sub(s2p0);
if (v.X !== 0) {
t0 = w.X / v.X;
t1 = w2.X / v.X;
} else {
t0 = w.Y / v.Y;
t1 = w2.Y / v.Y;
}
if (t0 > t1) {
const t = t0;
t0 = t1;
t1 = t;
}
if (t1 < 0)
return PrimitiveUtils.Empty;
t0 = t0 < 0 ? 0 : t0;
if (t0 === t1) {
let I0 = new Vector2d(v.X, v.Y);
I0 = I0.MultiplyScalar(t0);
I0 = I0.Add(s2p0);
return new IntersectPoints(I0);
}
let I_0 = new Vector2d(v.X, v.Y);
I_0 = I_0.MultiplyScalar(t0);
I_0 = I_0.Add(s2p0);
let I1 = new Vector2d(v.X, v.Y);
I1 = I1.MultiplyScalar(t1);
I1 = I1.Add(s2p0);
return new IntersectPoints(I_0, I1);
}
const sI = PrimitiveUtils.Perp(v, w) / d;
if (sI < 0 /* || sI > 1 */)
return PrimitiveUtils.Empty;
const tI = PrimitiveUtils.Perp(u, w) / d;
if (tI < 0 /* || tI > 1 */)
return PrimitiveUtils.Empty;
let IO = new Vector2d(u.X, u.Y);
IO = IO.MultiplyScalar(sI);
IO = IO.Add(s1p0);
return new IntersectPoints(IO);
}
private static InCollinearRay(p: Vector2d, rayStart: Vector2d, rayDirection: Vector2d): boolean {
const collideVector = p.Sub(rayStart);
const dot = rayDirection.Dot(collideVector);
return !(dot < 0);
}
private static Dot(u: Vector2d, v: Vector2d): number {
return u.Dot(v);
}
private static Perp(u: Vector2d, v: Vector2d): number {
return u.X * v.Y - u.Y * v.X;
}
public static IsClockwisePolygon(polygon: List<Vector2d>): boolean {
return PrimitiveUtils.Area(polygon) < 0;
}
private static Area(polygon: List<Vector2d>): number {
const n = polygon.Count;
let A = 0;
for (let p = n - 1, q = 0; q < n; p = q++)
A += polygon[p].X * polygon[q].Y - polygon[q].X * polygon[p].Y;
return A * 0.5;
}
public static MakeCounterClockwise(polygon: List<Vector2d>): List<Vector2d> {
if (PrimitiveUtils.IsClockwisePolygon(polygon))
polygon.Reverse();
return polygon;
}
public static IsPointInsidePolygon(point: Vector2d, points: List<Vector2d>): boolean {
const numpoints = points.Count;
if (numpoints < 3)
return false;
let it = 0;
const first = points[it];
let oddNodes = false;
for (let i = 0; i < numpoints; i++) {
const node1 = points[it];
it++;
const node2 = i === numpoints - 1 ? first : points[it];
const x = point.X;
const y = point.Y;
if (node1.Y < y && node2.Y >= y || node2.Y < y && node1.Y >= y) {
if (node1.X + (y - node1.Y) / (node2.Y - node1.Y) * (node2.X - node1.X) < x)
oddNodes = !oddNodes;
}
}
return oddNodes;
}
}

View File

@ -0,0 +1,63 @@
import {IComparer, List} from "../Utils";
export default class PriorityQueue<T> {
private readonly _comparer: IComparer<T> = null;
private readonly _heap: List<T> = null;
constructor(capacity: number, comparer: IComparer<T>) {
this._heap = new List<T>(capacity);
this._comparer = comparer;
}
public Clear() {
this._heap.Clear();
}
public Add(item: T) {
let n = this._heap.Count;
this._heap.Add(item);
while (n !== 0) {
const p = Math.floor(n / 2);
if (this._comparer.Compare(this._heap[n], (this._heap[p])) >= 0) break;
const tmp: T = this._heap[n];
this._heap[n] = this._heap[p];
this._heap[p] = tmp;
n = p;
}
}
get Count(): number {
return this._heap.Count;
}
get Empty(): boolean {
return this._heap.Count === 0;
}
public Peek(): T {
return !this._heap.Any() ? null : this._heap[0];
}
public Next(): T {
const val: T = this._heap[0];
const nMax = this._heap.Count - 1;
this._heap[0] = this._heap[nMax];
this._heap.RemoveAt(nMax);
let p = 0;
while (true) {
let c = p * 2;
if (c >= nMax) break;
if (c + 1 < nMax && this._comparer.Compare(this._heap[c + 1], this._heap[c]) < 0) c++;
if (this._comparer.Compare(this._heap[p], (this._heap[c])) <= 0) break;
const tmp: T = this._heap[p];
this._heap[p] = this._heap[c];
this._heap[c] = tmp;
p = c;
}
return val;
}
}

View File

@ -0,0 +1,61 @@
export default class Vector2d {
public static Empty: Vector2d = new Vector2d(Number.MIN_VALUE, Number.MIN_VALUE);
public X: number = 0;
public Y: number = 0;
constructor(x: number, y: number) {
this.X = x;
this.Y = y;
}
public Negate() {
this.X = -this.X;
this.Y = -this.Y;
}
public DistanceTo(var1: Vector2d): number {
const var2 = this.X - var1.X;
const var4 = this.Y - var1.Y;
return Math.sqrt(var2 * var2 + var4 * var4);
}
public Normalized(): Vector2d {
const var1 = 1 / Math.sqrt(this.X * this.X + this.Y * this.Y);
return new Vector2d(this.X * var1, this.Y * var1);
}
public Dot(var1: Vector2d): number {
return this.X * var1.X + this.Y * var1.Y;
}
public DistanceSquared(var1: Vector2d): number {
const var2 = this.X - var1.X;
const var4 = this.Y - var1.Y;
return var2 * var2 + var4 * var4;
}
public Add(v: Vector2d): Vector2d {
return new Vector2d(this.X + v.X, this.Y + v.Y);
}
public Sub(v: Vector2d): Vector2d {
return new Vector2d(this.X - v.X, this.Y - v.Y);
}
public MultiplyScalar(scale: number): Vector2d {
return new Vector2d(this.X * scale, this.Y * scale);
}
public Equals(v: Vector2d): boolean {
return this.X === v.X && this.Y === v.Y;
}
public NotEquals(v: Vector2d): boolean {
return !this.Equals(v);
}
public ToString(): string {
return `${this.X}, ${this.Y}`;
}
}

View File

@ -0,0 +1,13 @@
import Vector2d from "./Primitives/Vector2d";
import EdgeResult from "./EdgeResult";
import {Dictionary, List} from "./Utils";
export class Skeleton {
public readonly Edges: List<EdgeResult> = null;
public readonly Distances: Dictionary<Vector2d, number> = null;
constructor(edges: List<EdgeResult>, distances: Dictionary<Vector2d, number>) {
this.Edges = edges;
this.Distances = distances;
}
}

View File

@ -0,0 +1,994 @@
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;
}
}

133
src/lib/skeletons/Utils.ts Normal file
View File

@ -0,0 +1,133 @@
function insertInArray<T>(array: Array<T>, index: number, item: T): Array<T> {
const items = Array.prototype.slice.call(arguments, 2);
return [].concat(array.slice(0, index), items, array.slice(index));
}
export interface IComparable<T> {
CompareTo(other: T): number;
}
export interface IComparer<T> {
Compare(a: T, b: T): number;
}
export type GeoJSONMultipolygon = [number, number][][][];
export class List<T> extends Array<T> {
constructor(capacity = 0) {
super();
}
public Add(item: T) {
this.push(item);
}
public Insert(index: number, item: T) {
const newArr = insertInArray(this, index, item);
this.length = newArr.length;
for(let i = 0; i < newArr.length; i++) {
this[i] = newArr[i];
}
}
public Reverse() {
this.reverse();
}
public Clear() {
this.length = 0;
}
get Count(): number {
return this.length;
}
public Any(filter?: (item: T) => boolean): boolean {
if (!filter) {
filter = T => true;
}
for (const item of this) {
if (filter(item)) {
return true;
}
}
return false;
}
public RemoveAt(index: number) {
this.splice(index, 1);
}
public Remove(itemToRemove: T) {
const newArr = this.filter(item => item !== itemToRemove);
this.length = newArr.length;
for(let i = 0; i < newArr.length; i++) {
this[i] = newArr[i];
}
}
public AddRange(list: List<T>) {
for (const item of list) {
this.Add(item);
}
}
public Sort(comparer: IComparer<T>) {
this.sort(comparer.Compare.bind(comparer));
}
}
export class HashSet<T> implements Iterable<T> {
private Set: Set<T>;
constructor() {
this.Set = new Set();
}
public Add(item: T) {
this.Set.add(item);
}
public Remove(item: T) {
this.Set.delete(item);
}
public RemoveWhere(filter: (item: T) => boolean) {
for (const item of this.Set.values()) {
if (filter(item)) {
this.Set.delete(item);
}
}
}
public Contains(item: T): boolean {
return this.Set.has(item);
}
public Clear() {
this.Set.clear();
}
public* [Symbol.iterator](): Generator<T> {
for (const item of this.Set.values()) {
yield item;
}
}
}
export class Dictionary<T1, T2> extends Map<T1, T2> {
public ContainsKey(key: T1): boolean {
return this.has(key);
}
public Add(key: T1, value: T2) {
return this.set(key, value);
}
}

View File

@ -0,0 +1,9 @@
import {GeoJSONMultipolygon, List} from "./Utils";
import Vector2d from "./Primitives/Vector2d";
import SkeletonBuilder from "./SkeletonBuilder";
import { Skeleton } from "./Skeleton";
import EdgeResult from "./EdgeResult";
import Edge from "./Circular/Edge";
import Vertex from "./Circular/Vertex";
export {SkeletonBuilder, List, Vector2d, GeoJSONMultipolygon, Skeleton, EdgeResult, Edge, Vertex};

41
tsconfig.json Normal file
View File

@ -0,0 +1,41 @@
{
"compilerOptions": {
"target": "es2015",
"downlevelIteration": true,
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"noEmit": true,
"incremental": true,
"module": "esnext",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true, //
"moduleResolution": "bundler", // : "node" "bundler"
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"baseUrl": ".", //
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./src/*"]
}
},
"include": [
"next-env.d.ts",
".next/types/**/*.ts",
"**/*.ts",
"**/*.tsx"
],
"exclude": [
"node_modules"
]
}