import FastPriorityQueue from 'fastpriorityqueue'; import { EdgeEvent } from './EdgeEvent'; import { assert } from './langUtil'; import MovingNode from './MovingNode'; import SkeletonEvent from './SkeletonEvent'; import SplitEvent from './SplitEvent'; export default class SkeletonContext { private nextMovingNodeId: number = 1; private movingNodes: Set = new Set(); private readonly eventQueue: FastPriorityQueue = new FastPriorityQueue((a, b) => { return a.compareTo(b) < 0; }); // Contains reflex nodes of aborted SplitEvents. Since we only enqueue the nearest SplitEvent to reduce strain on the queue, // when a SplitEvent is aborted we must recheck if a reflex node collides with another edge that was originally further away. private readonly abortedReflex: Set = new Set(); distance: number; distanceSign: number; time: number = 0; epsilon: number = 0.0001; epsilonMinusOne: number = this.epsilon - 1; // -0.9999 infiniteLoopGuard = 1e5; setEpsilon(epsilon: number) { this.epsilon = epsilon; this.epsilonMinusOne = epsilon - 1; } getNodes() { // TODO freeze it return this.movingNodes; } reset(distance: number, distanceSign: number) { this.distance = distance; this.distanceSign = distanceSign; this.time = 0; this.nextMovingNodeId = 1; this.movingNodes.clear(); this.eventQueue.removeMany(() => true); // Clear this.abortedReflex.clear(); } // // Moving Nodes // createMovingNode(id?: string) { id = id || this.nextMovingNodeId++ + ''; const node = new MovingNode(id); this.movingNodes.add(node); return node; } removeMovingNode(node: MovingNode) { node.next = null; node.prev = null; this.abortEvents(node); this.movingNodes.delete(node); } // // Event Queue // pollQueue() { return this.eventQueue.poll(); } peekQueue() { return this.eventQueue.peek(); } enqueue(event: SkeletonEvent) { const time = this.time; if (event.time < time) { throw 'time: ' + time + ', event.time: ' + event.time + ' // ' + event; } this.eventQueue.add(event); event.onEventQueued(); if (this.eventQueue.size > this.infiniteLoopGuard) { throw 'Event queue size overflow. Potential infinite loop.'; } } addAbortedReflex(reflexNode: MovingNode) { this.abortedReflex.add(reflexNode); } /** * Node invalidated. */ abortEvents(adjacentNode: MovingNode) { adjacentNode.events().forEach((ev) => { ev.onEventAborted(adjacentNode, this); this.eventQueue.remove(ev); }); adjacentNode.clearEvents(); } /** * Edge invalidated. */ abortEvents2(edgeNode0: MovingNode, edgeNode1: MovingNode) { // Abort all events that exist in both edgeNode0's and edgeNode1's list edgeNode0.filterEvents((event) => { if (edgeNode1.tryRemoveEvent(event)) { event.onEventAborted2(edgeNode0, edgeNode1, this); this.eventQueue.remove(event); return false; } return true; }); // for(Iterator it0=edgeNode0.events().iterator(); it0.hasNext(); ) { // SkeletonEvent event = it0.next(); // if(edgeNode1.tryRemoveEvent(event)) { // it0.remove(); // event.onEventAborted(edgeNode0, edgeNode1, this); // eventQueue.remove(event); // } // } } printEvents() { console.log('Events:'); this.eventQueue.forEach((ev) => { console.log(' - ' + ev); }); } printNodes() { console.log('Nodes:'); this.movingNodes.forEach((node) => { console.log(' - ' + node); }); } // // Event Factory // tryQueueEdgeEvent(n0: MovingNode, n1: MovingNode) { const eventTime = this.time + n0.edgeCollapseTime; // In case of an invalid time (=NaN), this condition will be false. if (eventTime <= this.distance) { this.enqueue(new EdgeEvent(n0, n1, eventTime)); } } tryQueueSplitEvent(reflexNode: MovingNode, op0: MovingNode, op1: MovingNode) { assert(reflexNode.isReflex()); const eventTime = this.time + SplitEvent.calcTime(reflexNode, op0, this.distanceSign); // In case of an invalid time (=NaN), this condition will be false. if (eventTime <= this.distance) { const splitEvent = new SplitEvent(reflexNode, op0, op1, eventTime); this.enqueue(splitEvent); } } tryReplaceNearestSplitEvent( reflexNode: MovingNode, op0: MovingNode, op1: MovingNode, nearest?: SplitEvent | null ) { assert(reflexNode.isReflex()); const eventTime = this.time + SplitEvent.calcTime(reflexNode, op0, this.distanceSign); if (nearest != null && nearest.time <= eventTime) { return nearest; } // In case of an invalid time (=NaN), this condition will be false. if (eventTime <= this.distance) { const splitEvent = new SplitEvent(reflexNode, op0, op1, eventTime); return splitEvent; } return nearest; } recheckAbortedReflexNodes() { this.abortedReflex.forEach((reflexNode) => { if (reflexNode.next != null && reflexNode.isReflex()) SkeletonEvent.createSplitEvents(reflexNode, this); }); this.abortedReflex.clear(); } }