Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into feature/batch-angle
This commit is contained in:
commit
61a44704fd
@ -55,11 +55,14 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@turf/turf": "^7.0.0",
|
"@turf/turf": "^7.0.0",
|
||||||
|
"@types/node": "^24.3.0",
|
||||||
|
"@types/react": "^19.1.11",
|
||||||
"convertapi": "^1.14.0",
|
"convertapi": "^1.14.0",
|
||||||
"postcss": "^8",
|
"postcss": "^8",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"react-color-palette": "^7.2.2",
|
"react-color-palette": "^7.2.2",
|
||||||
"sass": "^1.77.8",
|
"sass": "^1.77.8",
|
||||||
"tailwindcss": "^3.4.1"
|
"tailwindcss": "^3.4.1",
|
||||||
|
"typescript": "^5.9.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { fabric } from 'fabric'
|
|||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import { getDirectionByPoint } from '@/util/canvas-util'
|
import { getDirectionByPoint } from '@/util/canvas-util'
|
||||||
import { calcLinePlaneSize } from '@/util/qpolygon-utils'
|
import { calcLinePlaneSize } from '@/util/qpolygon-utils'
|
||||||
|
import { logger } from '@/util/logger'
|
||||||
|
|
||||||
export const QLine = fabric.util.createClass(fabric.Line, {
|
export const QLine = fabric.util.createClass(fabric.Line, {
|
||||||
type: 'QLine',
|
type: 'QLine',
|
||||||
@ -69,7 +70,14 @@ export const QLine = fabric.util.createClass(fabric.Line, {
|
|||||||
},
|
},
|
||||||
|
|
||||||
setLength() {
|
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() {
|
addLengthText() {
|
||||||
|
|||||||
@ -169,6 +169,7 @@ const Placement = forwardRef((props, refs) => {
|
|||||||
<div className="roof-module-table">
|
<div className="roof-module-table">
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
|
<tr>
|
||||||
{moduleData.header.map((data) => (
|
{moduleData.header.map((data) => (
|
||||||
<th key={data.prop} style={{ width: data.width ? data.width : '' }}>
|
<th key={data.prop} style={{ width: data.width ? data.width : '' }}>
|
||||||
{data.type === 'check' ? (
|
{data.type === 'check' ? (
|
||||||
@ -181,6 +182,7 @@ const Placement = forwardRef((props, refs) => {
|
|||||||
)}
|
)}
|
||||||
</th>
|
</th>
|
||||||
))}
|
))}
|
||||||
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{selectedModules?.itemList &&
|
{selectedModules?.itemList &&
|
||||||
@ -216,7 +218,7 @@ const Placement = forwardRef((props, refs) => {
|
|||||||
className="input-origin block"
|
className="input-origin block"
|
||||||
name="row"
|
name="row"
|
||||||
value={props.layoutSetup[index]?.row ?? 1}
|
value={props.layoutSetup[index]?.row ?? 1}
|
||||||
defaultValue={0}
|
//defaultValue={0}
|
||||||
onChange={(e) => handleLayoutSetup(e, item.itemId, index)}
|
onChange={(e) => handleLayoutSetup(e, item.itemId, index)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -228,7 +230,7 @@ const Placement = forwardRef((props, refs) => {
|
|||||||
className="input-origin block"
|
className="input-origin block"
|
||||||
name="col"
|
name="col"
|
||||||
value={props.layoutSetup[index]?.col ?? 1}
|
value={props.layoutSetup[index]?.col ?? 1}
|
||||||
defaultValue={0}
|
//defaultValue={0}
|
||||||
onChange={(e) => handleLayoutSetup(e, item.itemId, index)}
|
onChange={(e) => handleLayoutSetup(e, item.itemId, index)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -86,7 +86,7 @@ export default function RoofAllocationSetting(props) {
|
|||||||
return (
|
return (
|
||||||
<div className="grid-option-box" key={index}>
|
<div className="grid-option-box" key={index}>
|
||||||
<div className="d-check-radio pop no-text">
|
<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
|
<label
|
||||||
htmlFor="ra01"
|
htmlFor="ra01"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
@ -213,7 +213,6 @@ export default function RoofAllocationSetting(props) {
|
|||||||
handleChangePitch(e, index)
|
handleChangePitch(e, index)
|
||||||
}}
|
}}
|
||||||
value={currentAngleType === 'slope' ? roof.pitch : roof.angle}
|
value={currentAngleType === 'slope' ? roof.pitch : roof.angle}
|
||||||
defaultValue={currentAngleType === 'slope' ? roof.pitch : roof.angle}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span className="absol">{pitchText}</span>
|
<span className="absol">{pitchText}</span>
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import {
|
|||||||
selectedModelsState,
|
selectedModelsState,
|
||||||
seriesState,
|
seriesState,
|
||||||
} from '@/store/circuitTrestleAtom'
|
} from '@/store/circuitTrestleAtom'
|
||||||
import { moduleSelectionDataState, selectedModuleState } from '@/store/selectedModuleOptions'
|
import { selectedModuleState } from '@/store/selectedModuleOptions'
|
||||||
import { useContext, useEffect } from 'react'
|
import { useContext, useEffect } from 'react'
|
||||||
import { useRecoilState, useRecoilValue, useResetRecoilState, useSetRecoilState } from 'recoil'
|
import { useRecoilState, useRecoilValue, useResetRecoilState, useSetRecoilState } from 'recoil'
|
||||||
import { useMessage } from './useMessage'
|
import { useMessage } from './useMessage'
|
||||||
@ -101,7 +101,11 @@ export function useCircuitTrestle(executeEffect = false) {
|
|||||||
|
|
||||||
// result 배열에서 roofSurface 값을 기준으로 순서대로 정렬한다.
|
// result 배열에서 roofSurface 값을 기준으로 순서대로 정렬한다.
|
||||||
|
|
||||||
return groupSort(result)
|
if (pcsCheck.division) {
|
||||||
|
return groupSort(result)
|
||||||
|
} else {
|
||||||
|
return result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const groupSort = (arr) => {
|
const groupSort = (arr) => {
|
||||||
|
|||||||
113
src/lib/skeletons/Circular/CircularList.ts
Normal file
113
src/lib/skeletons/Circular/CircularList.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
src/lib/skeletons/Circular/CircularNode.ts
Normal file
19
src/lib/skeletons/Circular/CircularNode.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/lib/skeletons/Circular/Edge.ts
Normal file
28
src/lib/skeletons/Circular/Edge.ts
Normal 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}]`;
|
||||||
|
}
|
||||||
|
}
|
||||||
39
src/lib/skeletons/Circular/Vertex.ts
Normal file
39
src/lib/skeletons/Circular/Vertex.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/lib/skeletons/EdgeResult.ts
Normal file
13
src/lib/skeletons/EdgeResult.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/lib/skeletons/Events/Chains/ChainType.ts
Normal file
7
src/lib/skeletons/Events/Chains/ChainType.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
enum ChainType {
|
||||||
|
Edge,
|
||||||
|
ClosedEdge,
|
||||||
|
Split
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ChainType;
|
||||||
40
src/lib/skeletons/Events/Chains/EdgeChain.ts
Normal file
40
src/lib/skeletons/Events/Chains/EdgeChain.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/lib/skeletons/Events/Chains/IChain.ts
Normal file
17
src/lib/skeletons/Events/Chains/IChain.ts
Normal 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;
|
||||||
|
}
|
||||||
40
src/lib/skeletons/Events/Chains/SingleEdgeChain.ts
Normal file
40
src/lib/skeletons/Events/Chains/SingleEdgeChain.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
45
src/lib/skeletons/Events/Chains/SplitChain.ts
Normal file
45
src/lib/skeletons/Events/Chains/SplitChain.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/lib/skeletons/Events/EdgeEvent.ts
Normal file
27
src/lib/skeletons/Events/EdgeEvent.ts
Normal 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 + "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/lib/skeletons/Events/MultiEdgeEvent.ts
Normal file
17
src/lib/skeletons/Events/MultiEdgeEvent.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/lib/skeletons/Events/MultiSplitEvent.ts
Normal file
18
src/lib/skeletons/Events/MultiSplitEvent.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/lib/skeletons/Events/PickEvent.ts
Normal file
17
src/lib/skeletons/Events/PickEvent.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
22
src/lib/skeletons/Events/SkeletonEvent.ts
Normal file
22
src/lib/skeletons/Events/SkeletonEvent.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
26
src/lib/skeletons/Events/SplitEvent.ts
Normal file
26
src/lib/skeletons/Events/SplitEvent.ts
Normal 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 + "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/lib/skeletons/Events/VertexSplitEvent.ts
Normal file
15
src/lib/skeletons/Events/VertexSplitEvent.ts
Normal 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 + "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
56
src/lib/skeletons/LavUtil.ts
Normal file
56
src/lib/skeletons/LavUtil.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
24
src/lib/skeletons/Path/FaceNode.ts
Normal file
24
src/lib/skeletons/Path/FaceNode.ts
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
24
src/lib/skeletons/Path/FaceQueue.ts
Normal file
24
src/lib/skeletons/Path/FaceQueue.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
39
src/lib/skeletons/Path/FaceQueueUtil.ts
Normal file
39
src/lib/skeletons/Path/FaceQueueUtil.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
103
src/lib/skeletons/Path/PathQueue.ts
Normal file
103
src/lib/skeletons/Path/PathQueue.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
51
src/lib/skeletons/Path/PathQueueNode.ts
Normal file
51
src/lib/skeletons/Path/PathQueueNode.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
41
src/lib/skeletons/Primitives/LineLinear2d.ts
Normal file
41
src/lib/skeletons/Primitives/LineLinear2d.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
47
src/lib/skeletons/Primitives/LineParametric2d.ts
Normal file
47
src/lib/skeletons/Primitives/LineParametric2d.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
241
src/lib/skeletons/Primitives/PrimitiveUtils.ts
Normal file
241
src/lib/skeletons/Primitives/PrimitiveUtils.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
63
src/lib/skeletons/Primitives/PriorityQueue.ts
Normal file
63
src/lib/skeletons/Primitives/PriorityQueue.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
61
src/lib/skeletons/Primitives/Vector2d.ts
Normal file
61
src/lib/skeletons/Primitives/Vector2d.ts
Normal 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}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/lib/skeletons/Skeleton.ts
Normal file
13
src/lib/skeletons/Skeleton.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
994
src/lib/skeletons/SkeletonBuilder.ts
Normal file
994
src/lib/skeletons/SkeletonBuilder.ts
Normal 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
133
src/lib/skeletons/Utils.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
9
src/lib/skeletons/index.ts
Normal file
9
src/lib/skeletons/index.ts
Normal 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
41
tsconfig.json
Normal 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"
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user