From 01a956eef6f5f02d54736e17195c6c4f6ceb7a67 Mon Sep 17 00:00:00 2001 From: hyperion51 Date: Tue, 14 Apr 2020 00:42:04 +0100 Subject: [PATCH] MSAGL Support Added new tech tree layout implementation using Microsoft Automatic Graph Layouts Added some functionality to the database editor tools --- .../.idea/contentModel.xml | 616 ++- Assembly-CSharp-Editor.csproj | 8 +- Assembly-CSharp-firstpass.csproj | 8 +- Assembly-CSharp.csproj | 519 +- Assets/Resources/Elysium.asset | 4 +- Assets/Scenes/Main.unity | 131 +- Assets/Scripts/Editor/DatabaseInspector.cs | 21 +- Assets/Scripts/Editor/DatabaseView.cs | 3 +- Assets/Scripts/NIH/MSAGL.meta | 8 + .../NIH/MSAGL/AutomaticGraphLayout.csproj | 17 + .../MSAGL/AutomaticGraphLayout.csproj.meta | 7 + .../NIH/MSAGL/AutomaticGraphLayout.nuspec | 24 + .../MSAGL/AutomaticGraphLayout.nuspec.meta | 7 + Assets/Scripts/NIH/MSAGL/Core.meta | 8 + .../Scripts/NIH/MSAGL/Core/AlgorithmBase.cs | 305 ++ .../NIH/MSAGL/Core/AlgorithmBase.cs.meta | 11 + Assets/Scripts/NIH/MSAGL/Core/CancelToken.cs | 57 + .../NIH/MSAGL/Core/CancelToken.cs.meta | 11 + .../NIH/MSAGL/Core/DataStructures.meta | 8 + .../DataStructures/BinaryHeapPriorityQueue.cs | 130 + .../BinaryHeapPriorityQueue.cs.meta | 11 + .../DataStructures/BinaryHeapWithComparer.cs | 141 + .../BinaryHeapWithComparer.cs.meta | 11 + .../DataStructures/CollectionUtilities.cs | 37 + .../CollectionUtilities.cs.meta | 11 + .../GenericBinaryHeapPriorityQueue.cs | 281 ++ .../GenericBinaryHeapPriorityQueue.cs.meta | 11 + ...ricBinaryHeapPriorityQueueWithTimestamp.cs | 270 + ...naryHeapPriorityQueueWithTimestamp.cs.meta | 11 + .../Core/DataStructures/GenericHeapElement.cs | 12 + .../DataStructures/GenericHeapElement.cs.meta | 11 + .../GenericHeapElementWithTimestamp.cs | 26 + .../GenericHeapElementWithTimestamp.cs.meta | 11 + .../NIH/MSAGL/Core/DataStructures/RBTree.meta | 8 + .../RBTree/ComparerOnDelegate.cs | 16 + .../RBTree/ComparerOnDelegate.cs.meta | 11 + .../DataStructures/RBTree/DefaultComperer.cs | 11 + .../RBTree/DefaultComperer.cs.meta | 11 + .../Core/DataStructures/RBTree/RBColor.cs | 8 + .../DataStructures/RBTree/RBColor.cs.meta | 11 + .../Core/DataStructures/RBTree/RBNode.cs | 29 + .../Core/DataStructures/RBTree/RBNode.cs.meta | 11 + .../Core/DataStructures/RBTree/RBTree.cs | 483 ++ .../Core/DataStructures/RBTree/RBTree.cs.meta | 11 + .../DataStructures/RBTree/RBTreeEnumerator.cs | 53 + .../RBTree/RBTreeEnumerator.cs.meta | 11 + .../Core/DataStructures/RealNumberSpan.cs | 43 + .../DataStructures/RealNumberSpan.cs.meta | 11 + .../NIH/MSAGL/Core/DataStructures/Set.cs | 300 ++ .../NIH/MSAGL/Core/DataStructures/Set.cs.meta | 11 + .../NIH/MSAGL/Core/DataStructures/Size.cs | 72 + .../MSAGL/Core/DataStructures/Size.cs.meta | 11 + .../Core/DataStructures/SymmetricTuple.cs | 63 + .../DataStructures/SymmetricTuple.cs.meta | 11 + Assets/Scripts/NIH/MSAGL/Core/Geometry.meta | 8 + .../Core/Geometry/ApproximateComparer.cs | 198 + .../Core/Geometry/ApproximateComparer.cs.meta | 11 + .../NIH/MSAGL/Core/Geometry/CompassVector.cs | 254 + .../MSAGL/Core/Geometry/CompassVector.cs.meta | 11 + .../NIH/MSAGL/Core/Geometry/ConvexHull.meta | 8 + .../Core/Geometry/ConvexHull/ConvexHull.cs | 196 + .../Geometry/ConvexHull/ConvexHull.cs.meta | 11 + .../Core/Geometry/ConvexHull/HullPoint.cs | 27 + .../Geometry/ConvexHull/HullPoint.cs.meta | 11 + .../Geometry/ConvexHull/HullPointComparer.cs | 76 + .../ConvexHull/HullPointComparer.cs.meta | 11 + .../Core/Geometry/ConvexHull/HullStack.cs | 25 + .../Geometry/ConvexHull/HullStack.cs.meta | 11 + .../NIH/MSAGL/Core/Geometry/Curves.meta | 8 + .../Geometry/Curves/ClosestPointOnCurve.cs | 63 + .../Curves/ClosestPointOnCurve.cs.meta | 11 + .../Geometry/Curves/CubicBezierSegment.cs | 630 +++ .../Curves/CubicBezierSegment.cs.meta | 11 + .../Geometry/Curves/CurvatureDerivative.cs | 69 + .../Curves/CurvatureDerivative.cs.meta | 11 + .../NIH/MSAGL/Core/Geometry/Curves/Curve.cs | 2109 ++++++++ .../MSAGL/Core/Geometry/Curves/Curve.cs.meta | 11 + .../Core/Geometry/Curves/CurveFactory.cs | 397 ++ .../Core/Geometry/Curves/CurveFactory.cs.meta | 11 + .../Geometry/Curves/CurveLengthAndParams.cs | 128 + .../Curves/CurveLengthAndParams.cs.meta | 11 + .../Core/Geometry/Curves/CurveTangent.cs | 21 + .../Core/Geometry/Curves/CurveTangent.cs.meta | 11 + .../NIH/MSAGL/Core/Geometry/Curves/Ellipse.cs | 621 +++ .../Core/Geometry/Curves/Ellipse.cs.meta | 11 + .../NIH/MSAGL/Core/Geometry/Curves/ICurve.cs | 217 + .../MSAGL/Core/Geometry/Curves/ICurve.cs.meta | 11 + .../MSAGL/Core/Geometry/Curves/IFunction.cs | 46 + .../Core/Geometry/Curves/IFunction.cs.meta | 11 + .../Core/Geometry/Curves/IntersectionInfo.cs | 89 + .../Geometry/Curves/IntersectionInfo.cs.meta | 11 + .../MSAGL/Core/Geometry/Curves/LineSegment.cs | 518 ++ .../Core/Geometry/Curves/LineSegment.cs.meta | 11 + .../Core/Geometry/Curves/LinearSystem2.cs | 31 + .../Geometry/Curves/LinearSystem2.cs.meta | 11 + .../Core/Geometry/Curves/MinDistCurveCurve.cs | 357 ++ .../Geometry/Curves/MinDistCurveCurve.cs.meta | 11 + .../Core/Geometry/Curves/Parallelogram.cs | 461 ++ .../Geometry/Curves/Parallelogram.cs.meta | 11 + .../Curves/ParallelogramBinaryTreeNode.cs | 27 + .../ParallelogramBinaryTreeNode.cs.meta | 11 + .../Curves/ParallelogramInternalTreeNode.cs | 41 + .../ParallelogramInternalTreeNode.cs.meta | 11 + .../Core/Geometry/Curves/ParallelogramLeaf.cs | 54 + .../Geometry/Curves/ParallelogramLeaf.cs.meta | 11 + .../Core/Geometry/Curves/ParallelogramNode.cs | 31 + .../Geometry/Curves/ParallelogramNode.cs.meta | 11 + .../Curves/ParallelogramNodeOverICurve.cs | 218 + .../ParallelogramNodeOverICurve.cs.meta | 11 + .../Core/Geometry/Curves/PenetrationDepth.cs | 202 + .../Geometry/Curves/PenetrationDepth.cs.meta | 11 + .../Geometry/Curves/PlaneTransformation.cs | 277 ++ .../Curves/PlaneTransformation.cs.meta | 11 + .../Core/Geometry/Curves/PointLocation.cs | 20 + .../Geometry/Curves/PointLocation.cs.meta | 11 + .../MSAGL/Core/Geometry/Curves/Polyline.cs | 853 ++++ .../Core/Geometry/Curves/Polyline.cs.meta | 11 + .../Core/Geometry/Curves/PolylineIterator.cs | 74 + .../Geometry/Curves/PolylineIterator.cs.meta | 11 + .../Core/Geometry/Curves/PolylinePoint.cs | 109 + .../Geometry/Curves/PolylinePoint.cs.meta | 11 + .../MSAGL/Core/Geometry/Curves/RootFinder.cs | 74 + .../Core/Geometry/Curves/RootFinder.cs.meta | 11 + .../MSAGL/Core/Geometry/Curves/RoundedRect.cs | 331 ++ .../Core/Geometry/Curves/RoundedRect.cs.meta | 11 + .../NIH/MSAGL/Core/Geometry/Direction.cs | 30 + .../NIH/MSAGL/Core/Geometry/Direction.cs.meta | 11 + .../MSAGL/Core/Geometry/MinEnclosingDisc.meta | 8 + .../Core/Geometry/MinEnclosingDisc/Disc.cs | 202 + .../Geometry/MinEnclosingDisc/Disc.cs.meta | 11 + .../MinEnclosingDisc/MinimumEnclosingDisc.cs | 178 + .../MinimumEnclosingDisc.cs.meta | 11 + .../Core/Geometry/MultidimensionalScaling.cs | 468 ++ .../Geometry/MultidimensionalScaling.cs.meta | 11 + .../MSAGL/Core/Geometry/OverlapRemoval.meta | 8 + .../Geometry/OverlapRemoval/BorderInfo.cs | 252 + .../OverlapRemoval/BorderInfo.cs.meta | 11 + .../OverlapRemoval/ConstraintGenerator.cs | 326 ++ .../ConstraintGenerator.cs.meta | 11 + .../OverlapRemoval/OverlapRemoval Design.docx | Bin 0 -> 76325 bytes .../OverlapRemoval Design.docx.meta | 7 + .../OverlapRemoval/OverlapRemovalCluster.cs | 1100 +++++ .../OverlapRemovalCluster.cs.meta | 11 + .../OverlapRemovalClusterEvent.cs | 83 + .../OverlapRemovalClusterEvent.cs.meta | 11 + .../OverlapRemovalClusterScanLine.cs | 61 + .../OverlapRemovalClusterScanLine.cs.meta | 11 + .../OverlapRemovalGlobalConfiguration.cs | 45 + .../OverlapRemovalGlobalConfiguration.cs.meta | 11 + .../OverlapRemoval/OverlapRemovalNode.cs | 340 ++ .../OverlapRemoval/OverlapRemovalNode.cs.meta | 11 + .../OverlapRemovalParameters.cs | 86 + .../OverlapRemovalParameters.cs.meta | 11 + .../RectangularClusterBoundary.cs | 293 ++ .../RectangularClusterBoundary.cs.meta | 11 + .../Scripts/NIH/MSAGL/Core/Geometry/Point.cs | 831 ++++ .../NIH/MSAGL/Core/Geometry/Point.cs.meta | 11 + .../NIH/MSAGL/Core/Geometry/PointNodesList.cs | 73 + .../Core/Geometry/PointNodesList.cs.meta | 11 + .../NIH/MSAGL/Core/Geometry/PointPair.cs | 86 + .../NIH/MSAGL/Core/Geometry/PointPair.cs.meta | 11 + .../NIH/MSAGL/Core/Geometry/RTree.meta | 8 + .../Core/Geometry/RTree/HitTestBehavior.cs | 15 + .../Geometry/RTree/HitTestBehavior.cs.meta | 11 + .../MSAGL/Core/Geometry/RTree/IntervalNode.cs | 478 ++ .../Core/Geometry/RTree/IntervalNode.cs.meta | 11 + .../Core/Geometry/RTree/IntervalNodeUtils.cs | 115 + .../Geometry/RTree/IntervalNodeUtils.cs.meta | 11 + .../Core/Geometry/RTree/IntervalRTree.cs | 270 + .../Core/Geometry/RTree/IntervalRTree.cs.meta | 11 + .../NIH/MSAGL/Core/Geometry/RTree/RTree.cs | 310 ++ .../MSAGL/Core/Geometry/RTree/RTree.cs.meta | 11 + .../Core/Geometry/RTree/RectangleNode.cs | 488 ++ .../Core/Geometry/RTree/RectangleNode.cs.meta | 11 + .../Core/Geometry/RTree/RectangleNodeUtils.cs | 115 + .../Geometry/RTree/RectangleNodeUtils.cs.meta | 11 + .../NIH/MSAGL/Core/Geometry/Rectangle.cs | 596 +++ .../NIH/MSAGL/Core/Geometry/Rectangle.cs.meta | 11 + .../MSAGL/Core/Geometry/RectanglePacking.meta | 8 + .../RectanglePacking/ColumnPacking.cs | 57 + .../RectanglePacking/ColumnPacking.cs.meta | 11 + .../RectanglePacking/OptimalColumnPacking.cs | 56 + .../OptimalColumnPacking.cs.meta | 11 + .../RectanglePacking/OptimalPacking.cs | 177 + .../RectanglePacking/OptimalPacking.cs.meta | 11 + .../OptimalRectanglePacking.cs | 58 + .../OptimalRectanglePacking.cs.meta | 11 + .../Core/Geometry/RectanglePacking/Packing.cs | 35 + .../Geometry/RectanglePacking/Packing.cs.meta | 11 + .../RectanglePacking/PackingConstants.cs | 25 + .../RectanglePacking/PackingConstants.cs.meta | 11 + .../RectanglePacking/RectanglePacking.cs | 98 + .../RectanglePacking/RectanglePacking.cs.meta | 11 + .../RectanglePacking/RectangleToPack.cs | 35 + .../RectanglePacking/RectangleToPack.cs.meta | 11 + .../Scripts/NIH/MSAGL/Core/Geometry/Site.cs | 147 + .../NIH/MSAGL/Core/Geometry/Site.cs.meta | 11 + .../MSAGL/Core/Geometry/SmoothedPolyline.cs | 173 + .../Core/Geometry/SmoothedPolyline.cs.meta | 11 + .../Core/Geometry/TriangleOrientation.cs | 19 + .../Core/Geometry/TriangleOrientation.cs.meta | 11 + .../NIH/MSAGL/Core/GraphAlgorithms.meta | 8 + .../MSAGL/Core/GraphAlgorithms/BasicGraph.cs | 275 ++ .../Core/GraphAlgorithms/BasicGraph.cs.meta | 11 + .../Core/GraphAlgorithms/CycleRemoval.meta | 8 + .../ConnectedComponentCalculator.cs | 47 + .../ConnectedComponentCalculator.cs.meta | 11 + .../CycleRemoval/CycleRemoval.cs | 110 + .../CycleRemoval/CycleRemoval.cs.meta | 11 + .../CycleRemovalWithConstraints.cs | 82 + .../CycleRemovalWithConstraints.cs.meta | 11 + .../CycleRemoval/GraphForCycleRemoval.cs | 154 + .../CycleRemoval/GraphForCycleRemoval.cs.meta | 11 + .../GraphAlgorithms/CycleRemoval/NodeInfo.cs | 91 + .../CycleRemoval/NodeInfo.cs.meta | 11 + .../NIH/MSAGL/Core/GraphAlgorithms/IEdge.cs | 15 + .../MSAGL/Core/GraphAlgorithms/IEdge.cs.meta | 11 + .../NIH/MSAGL/Core/GraphAlgorithms/IntPair.cs | 167 + .../Core/GraphAlgorithms/IntPair.cs.meta | 11 + .../MinimumSpanningTreeByPrim.cs | 128 + .../MinimumSpanningTreeByPrim.cs.meta | 11 + .../Core/GraphAlgorithms/TopologicalSort.cs | 140 + .../GraphAlgorithms/TopologicalSort.cs.meta | 11 + Assets/Scripts/NIH/MSAGL/Core/Layout.meta | 8 + .../NIH/MSAGL/Core/Layout/Arrowhead.cs | 62 + .../NIH/MSAGL/Core/Layout/Arrowhead.cs.meta | 11 + .../NIH/MSAGL/Core/Layout/Arrowheads.cs | 292 ++ .../NIH/MSAGL/Core/Layout/Arrowheads.cs.meta | 11 + .../Scripts/NIH/MSAGL/Core/Layout/Cluster.cs | 497 ++ .../NIH/MSAGL/Core/Layout/Cluster.cs.meta | 11 + .../NIH/MSAGL/Core/Layout/CurvePort.cs | 52 + .../NIH/MSAGL/Core/Layout/CurvePort.cs.meta | 11 + Assets/Scripts/NIH/MSAGL/Core/Layout/Edge.cs | 387 ++ .../NIH/MSAGL/Core/Layout/Edge.cs.meta | 11 + .../NIH/MSAGL/Core/Layout/EdgeCollection.cs | 135 + .../MSAGL/Core/Layout/EdgeCollection.cs.meta | 11 + .../NIH/MSAGL/Core/Layout/EdgeGeometry.cs | 164 + .../MSAGL/Core/Layout/EdgeGeometry.cs.meta | 11 + .../MSAGL/Core/Layout/EdgeLabelPlacement.cs | 719 +++ .../Core/Layout/EdgeLabelPlacement.cs.meta | 11 + .../NIH/MSAGL/Core/Layout/FloatingPort.cs | 58 + .../MSAGL/Core/Layout/FloatingPort.cs.meta | 11 + .../NIH/MSAGL/Core/Layout/GeometryGraph.cs | 346 ++ .../MSAGL/Core/Layout/GeometryGraph.cs.meta | 11 + .../NIH/MSAGL/Core/Layout/GeometryObject.cs | 65 + .../MSAGL/Core/Layout/GeometryObject.cs.meta | 11 + .../Core/Layout/GraphConnectedComponents.cs | 339 ++ .../Layout/GraphConnectedComponents.cs.meta | 11 + .../Layout/HookUpAnywhereFromInsidePort.cs | 72 + .../HookUpAnywhereFromInsidePort.cs.meta | 11 + .../NIH/MSAGL/Core/Layout/ILabeledObject.cs | 14 + .../MSAGL/Core/Layout/ILabeledObject.cs.meta | 11 + .../NIH/MSAGL/Core/Layout/IObstacle.cs | 7 + .../NIH/MSAGL/Core/Layout/IObstacle.cs.meta | 11 + .../Core/Layout/IdealEdgeLengthSettings.cs | 91 + .../Layout/IdealEdgeLengthSettings.cs.meta | 11 + .../Core/Layout/IsOpenChangeEventArgs.cs | 21 + .../Core/Layout/IsOpenChangeEventArgs.cs.meta | 11 + Assets/Scripts/NIH/MSAGL/Core/Layout/Label.cs | 249 + .../NIH/MSAGL/Core/Layout/Label.cs.meta | 11 + .../MSAGL/Core/Layout/LabelPlacementResult.cs | 25 + .../Core/Layout/LabelPlacementResult.cs.meta | 11 + .../Core/Layout/LayoutAlgorithmHelpers.cs | 119 + .../Layout/LayoutAlgorithmHelpers.cs.meta | 11 + .../Core/Layout/LayoutAlgorithmSettings.cs | 145 + .../Layout/LayoutAlgorithmSettings.cs.meta | 11 + .../Core/Layout/LayoutChangeEventArgs.cs | 17 + .../Core/Layout/LayoutChangeEventArgs.cs.meta | 11 + .../NIH/MSAGL/Core/Layout/LgInfoBase.cs | 41 + .../NIH/MSAGL/Core/Layout/LgInfoBase.cs.meta | 11 + .../NIH/MSAGL/Core/Layout/LgNodeInfo.cs | 129 + .../NIH/MSAGL/Core/Layout/LgNodeInfo.cs.meta | 11 + .../NIH/MSAGL/Core/Layout/LgNodeInfoKind.cs | 22 + .../MSAGL/Core/Layout/LgNodeInfoKind.cs.meta | 11 + .../Core/Layout/MultiLocationFloatingPort.cs | 93 + .../Layout/MultiLocationFloatingPort.cs.meta | 11 + Assets/Scripts/NIH/MSAGL/Core/Layout/Node.cs | 365 ++ .../NIH/MSAGL/Core/Layout/Node.cs.meta | 11 + .../NIH/MSAGL/Core/Layout/NodeCollection.cs | 170 + .../MSAGL/Core/Layout/NodeCollection.cs.meta | 11 + .../NIH/MSAGL/Core/Layout/PackingMethod.cs | 23 + .../MSAGL/Core/Layout/PackingMethod.cs.meta | 11 + Assets/Scripts/NIH/MSAGL/Core/Layout/Port.cs | 35 + .../NIH/MSAGL/Core/Layout/Port.cs.meta | 11 + .../NIH/MSAGL/Core/Layout/PortEntry.cs | 16 + .../NIH/MSAGL/Core/Layout/PortEntry.cs.meta | 11 + .../NIH/MSAGL/Core/Layout/PortEntryOnCurve.cs | 149 + .../Core/Layout/PortEntryOnCurve.cs.meta | 11 + .../NIH/MSAGL/Core/Layout/PortObstacle.cs | 16 + .../MSAGL/Core/Layout/PortObstacle.cs.meta | 11 + .../Core/Layout/Ports and Port Accesses.doc | Bin 0 -> 30208 bytes .../Layout/Ports and Port Accesses.doc.meta | 7 + .../Core/Layout/ProximityOverlapRemoval.meta | 8 + .../ConjugateGradient.meta | 8 + .../ConjugateGradient/LinearSystemSolver.cs | 181 + .../LinearSystemSolver.cs.meta | 11 + .../ConjugateGradient/SparseMatrix.cs | 174 + .../ConjugateGradient/SparseMatrix.cs.meta | 11 + .../ConjugateGradient/Vector.cs | 124 + .../ConjugateGradient/Vector.cs.meta | 11 + .../IOverlapRemoval.cs | 28 + .../IOverlapRemoval.cs.meta | 11 + .../Layout/ProximityOverlapRemoval/MST.meta | 8 + .../MST/GTreeOverlapRemoval.cs | 517 ++ .../MST/GTreeOverlapRemoval.cs.meta | 11 + .../MST/MstLineSweeper.cs | 90 + .../MST/MstLineSweeper.cs.meta | 11 + .../MST/MstOnDelaunayTriangulation.cs | 105 + .../MST/MstOnDelaunayTriangulation.cs.meta | 11 + .../OverlapRemovalMethod.cs | 15 + .../OverlapRemovalMethod.cs.meta | 11 + .../OverlapRemovalSettings.cs | 133 + .../OverlapRemovalSettings.cs.meta | 11 + .../ProximityOverlapRemoval.cs | 759 +++ .../ProximityOverlapRemoval.cs.meta | 11 + .../ProximityOverlapRemoval/StressEnergy.meta | 8 + .../StressEnergy/ExampleStressMajorization.cs | 101 + .../ExampleStressMajorization.cs.meta | 11 + .../StressEnergy/MaxIterationMethod.cs | 27 + .../StressEnergy/MaxIterationMethod.cs.meta | 11 + .../StressEnergy/NodeVoting.cs | 42 + .../StressEnergy/NodeVoting.cs.meta | 11 + .../StressEnergy/SolvingMethod.cs | 27 + .../StressEnergy/SolvingMethod.cs.meta | 11 + .../StressEnergy/StressMajorization.cs | 428 ++ .../StressEnergy/StressMajorization.cs.meta | 11 + .../StressMajorizationSettings.cs | 127 + .../StressMajorizationSettings.cs.meta | 11 + .../StressEnergy/UpdateMethod.cs | 21 + .../StressEnergy/UpdateMethod.cs.meta | 11 + .../StressEnergy/Vote.cs | 45 + .../StressEnergy/Vote.cs.meta | 11 + .../StressEnergy/VoteBlock.cs | 31 + .../StressEnergy/VoteBlock.cs.meta | 11 + .../MSAGL/Core/Layout/RectangleObstacle.cs | 30 + .../Core/Layout/RectangleObstacle.cs.meta | 11 + .../MSAGL/Core/Layout/RelativeFloatingPort.cs | 79 + .../Core/Layout/RelativeFloatingPort.cs.meta | 11 + .../NIH/MSAGL/Core/Layout/SimpleIntEdge.cs | 9 + .../MSAGL/Core/Layout/SimpleIntEdge.cs.meta | 11 + .../NIH/MSAGL/Core/ParallelUtilities.cs | 127 + .../NIH/MSAGL/Core/ParallelUtilities.cs.meta | 11 + .../MSAGL/Core/ProgressChangedEventArgs.cs | 49 + .../Core/ProgressChangedEventArgs.cs.meta | 11 + .../NIH/MSAGL/Core/ProjectionSolver.meta | 8 + .../NIH/MSAGL/Core/ProjectionSolver/Block.cs | 1127 +++++ .../MSAGL/Core/ProjectionSolver/Block.cs.meta | 11 + .../Core/ProjectionSolver/BlockVector.cs | 56 + .../Core/ProjectionSolver/BlockVector.cs.meta | 11 + .../MSAGL/Core/ProjectionSolver/Constraint.cs | 225 + .../Core/ProjectionSolver/Constraint.cs.meta | 11 + .../Core/ProjectionSolver/ConstraintVector.cs | 167 + .../ProjectionSolver/ConstraintVector.cs.meta | 11 + .../MSAGL/Core/ProjectionSolver/DfDvNode.cs | 71 + .../Core/ProjectionSolver/DfDvNode.cs.meta | 11 + .../ProjectionSolver/GlobalConfiguration.cs | 29 + .../GlobalConfiguration.cs.meta | 11 + .../Core/ProjectionSolver/ISolverShell.cs | 122 + .../ProjectionSolver/ISolverShell.cs.meta | 11 + .../MSAGL/Core/ProjectionSolver/Parameters.cs | 173 + .../Core/ProjectionSolver/Parameters.cs.meta | 11 + .../ProjectionSolver Design.docx | Bin 0 -> 60441 bytes .../ProjectionSolver Design.docx.meta | 7 + .../NIH/MSAGL/Core/ProjectionSolver/QPSC.cs | 791 +++ .../MSAGL/Core/ProjectionSolver/QPSC.cs.meta | 11 + .../MSAGL/Core/ProjectionSolver/Solution.cs | 101 + .../Core/ProjectionSolver/Solution.cs.meta | 11 + .../NIH/MSAGL/Core/ProjectionSolver/Solver.cs | 1363 ++++++ .../Core/ProjectionSolver/Solver.cs.meta | 11 + .../Core/ProjectionSolver/SolverAlgorithm.cs | 32 + .../ProjectionSolver/SolverAlgorithm.cs.meta | 11 + .../Core/ProjectionSolver/SolverShell.cs | 487 ++ .../Core/ProjectionSolver/SolverShell.cs.meta | 11 + .../UniformOneDimensionalSolver.cs | 150 + .../UniformOneDimensionalSolver.cs.meta | 11 + .../Core/ProjectionSolver/UniformSolverVar.cs | 48 + .../ProjectionSolver/UniformSolverVar.cs.meta | 11 + .../MSAGL/Core/ProjectionSolver/Variable.cs | 270 + .../Core/ProjectionSolver/Variable.cs.meta | 11 + .../Core/ProjectionSolver/ViolationCache.cs | 270 + .../ProjectionSolver/ViolationCache.cs.meta | 11 + Assets/Scripts/NIH/MSAGL/Core/Routing.meta | 8 + .../MSAGL/Core/Routing/BundlingSettings.cs | 172 + .../Core/Routing/BundlingSettings.cs.meta | 11 + .../NIH/MSAGL/Core/Routing/EdgeRoutingMode.cs | 37 + .../Core/Routing/EdgeRoutingMode.cs.meta | 11 + .../MSAGL/Core/Routing/EdgeRoutingSettings.cs | 122 + .../Core/Routing/EdgeRoutingSettings.cs.meta | 11 + Assets/Scripts/NIH/MSAGL/DebugHelpers.meta | 8 + .../NIH/MSAGL/DebugHelpers/CurveSerializer.cs | 52 + .../DebugHelpers/CurveSerializer.cs.meta | 11 + .../NIH/MSAGL/DebugHelpers/DebugCurve.cs | 188 + .../NIH/MSAGL/DebugHelpers/DebugCurve.cs.meta | 11 + .../DebugHelpers/DebugCurveCollection.cs | 64 + .../DebugHelpers/DebugCurveCollection.cs.meta | 11 + .../NIH/MSAGL/DebugHelpers/DebugEllipse.cs | 14 + .../MSAGL/DebugHelpers/DebugEllipse.cs.meta | 11 + .../NIH/MSAGL/DebugHelpers/DebugGraphics.cs | 16 + .../MSAGL/DebugHelpers/DebugGraphics.cs.meta | 11 + .../NIH/MSAGL/DebugHelpers/DebugRectangle.cs | 14 + .../MSAGL/DebugHelpers/DebugRectangle.cs.meta | 11 + .../NIH/MSAGL/DebugHelpers/DebugShape.cs | 23 + .../NIH/MSAGL/DebugHelpers/DebugShape.cs.meta | 11 + .../NIH/MSAGL/DebugHelpers/DevTrace.cs | 148 + .../NIH/MSAGL/DebugHelpers/DevTrace.cs.meta | 11 + .../NIH/MSAGL/DebugHelpers/IDebugGraphics.cs | 18 + .../MSAGL/DebugHelpers/IDebugGraphics.cs.meta | 11 + .../NIH/MSAGL/DebugHelpers/Persistence.meta | 8 + .../Persistence/CharStreamElement.cs | 9 + .../Persistence/CharStreamElement.cs.meta | 11 + .../Persistence/ClusterWithChildLists.cs | 18 + .../Persistence/ClusterWithChildLists.cs.meta | 11 + .../DebugHelpers/Persistence/CurveStream.cs | 56 + .../Persistence/CurveStream.cs.meta | 11 + .../Persistence/CurveStreamElement.cs | 5 + .../Persistence/CurveStreamElement.cs.meta | 11 + .../Persistence/DoubleStreamElement.cs | 9 + .../Persistence/DoubleStreamElement.cs.meta | 11 + .../Persistence/GeometryGraphReader.cs | 1778 +++++++ .../Persistence/GeometryGraphReader.cs.meta | 11 + .../Persistence/GeometryGraphWriter.cs | 1033 ++++ .../Persistence/GeometryGraphWriter.cs.meta | 11 + .../DebugHelpers/Persistence/GeometryToken.cs | 515 ++ .../Persistence/GeometryToken.cs.meta | 11 + Assets/Scripts/NIH/MSAGL/DebugHelpers/Show.cs | 17 + .../NIH/MSAGL/DebugHelpers/Show.cs.meta | 11 + .../NIH/MSAGL/DebugHelpers/ShowDatabase.cs | 14 + .../MSAGL/DebugHelpers/ShowDatabase.cs.meta | 11 + .../NIH/MSAGL/DebugHelpers/ShowDebugCurves.cs | 9 + .../DebugHelpers/ShowDebugCurves.cs.meta | 11 + .../ShowDebugCurvesEnumeration.cs | 11 + .../ShowDebugCurvesEnumeration.cs.meta | 11 + .../Scripts/NIH/MSAGL/DebugHelpers/Timer.cs | 68 + .../NIH/MSAGL/DebugHelpers/Timer.cs.meta | 11 + .../Scripts/NIH/MSAGL/GlobalSuppressions.cs | 50 + .../NIH/MSAGL/GlobalSuppressions.cs.meta | 11 + .../Scripts/NIH/MSAGL/GraphmapsWithMesh.meta | 8 + .../NIH/MSAGL/GraphmapsWithMesh/Angle.cs | 57 + .../NIH/MSAGL/GraphmapsWithMesh/Angle.cs.meta | 11 + .../GraphmapsWithMesh/ComponentCollection.cs | 58 + .../ComponentCollection.cs.meta | 11 + .../MSAGL/GraphmapsWithMesh/DijkstraAlgo.cs | 564 +++ .../GraphmapsWithMesh/DijkstraAlgo.cs.meta | 11 + .../GraphmapsWithMesh/LocalModifications.cs | 356 ++ .../LocalModifications.cs.meta | 11 + .../MSAGL/GraphmapsWithMesh/MeshCreator.cs | 1730 +++++++ .../GraphmapsWithMesh/MeshCreator.cs.meta | 11 + .../NIH/MSAGL/GraphmapsWithMesh/MeshGraph.cs | 19 + .../MSAGL/GraphmapsWithMesh/MeshGraph.cs.meta | 11 + .../MSAGL/GraphmapsWithMesh/MsaglUtilities.cs | 69 + .../GraphmapsWithMesh/MsaglUtilities.cs.meta | 11 + .../NIH/MSAGL/GraphmapsWithMesh/Network.cs | 53 + .../MSAGL/GraphmapsWithMesh/Network.cs.meta | 11 + .../GraphmapsWithMesh/PlanarGraphUtilities.cs | 272 ++ .../PlanarGraphUtilities.cs.meta | 11 + .../NIH/MSAGL/GraphmapsWithMesh/PointSet.cs | 166 + .../MSAGL/GraphmapsWithMesh/PointSet.cs.meta | 11 + .../PointToSegmentDistance.cs | 91 + .../PointToSegmentDistance.cs.meta | 11 + .../MSAGL/GraphmapsWithMesh/SteinerTree.cs | 50 + .../GraphmapsWithMesh/SteinerTree.cs.meta | 11 + .../NIH/MSAGL/GraphmapsWithMesh/Tiling.cs | 1864 +++++++ .../MSAGL/GraphmapsWithMesh/Tiling.cs.meta | 11 + Assets/Scripts/NIH/MSAGL/Layout.meta | 8 + .../Scripts/NIH/MSAGL/Layout/Incremental.meta | 8 + .../MSAGL/Layout/Incremental/AxisSolver.cs | 401 ++ .../Layout/Incremental/AxisSolver.cs.meta | 11 + .../Incremental/EdgeConstraintGenerator.cs | 209 + .../EdgeConstraintGenerator.cs.meta | 11 + .../Incremental/FastIncrementalLayout.cs | 631 +++ .../Incremental/FastIncrementalLayout.cs.meta | 11 + .../FastIncrementalLayoutSettings.cs | 680 +++ .../FastIncrementalLayoutSettings.cs.meta | 11 + .../MSAGL/Layout/Incremental/Feasibility.cs | 125 + .../Layout/Incremental/Feasibility.cs.meta | 11 + .../NIH/MSAGL/Layout/Incremental/FiEdge.cs | 37 + .../MSAGL/Layout/Incremental/FiEdge.cs.meta | 11 + .../NIH/MSAGL/Layout/Incremental/FiNode.cs | 87 + .../MSAGL/Layout/Incremental/FiNode.cs.meta | 11 + .../HorizontalSeparationConstraint.cs | 96 + .../HorizontalSeparationConstraint.cs.meta | 11 + .../MSAGL/Layout/Incremental/IConstraint.cs | 28 + .../Layout/Incremental/IConstraint.cs.meta | 11 + .../MSAGL/Layout/Incremental/LockPosition.cs | 213 + .../Layout/Incremental/LockPosition.cs.meta | 11 + .../Incremental/MaxSeparationConstraint.cs | 29 + .../MaxSeparationConstraint.cs.meta | 11 + .../Incremental/MinSeparationConstraint.cs | 31 + .../MinSeparationConstraint.cs.meta | 11 + .../MSAGL/Layout/Incremental/Multipole.meta | 8 + .../Layout/Incremental/Multipole/KDTree.cs | 287 ++ .../Incremental/Multipole/KDTree.cs.meta | 11 + .../Multipole/MultipoleCoefficients.cs | 231 + .../Multipole/MultipoleCoefficients.cs.meta | 11 + .../Incremental/ProcrustesCircleConstraint.cs | 349 ++ .../ProcrustesCircleConstraint.cs.meta | 11 + .../NIH/MSAGL/Layout/Incremental/Relayout.cs | 294 ++ .../MSAGL/Layout/Incremental/Relayout.cs.meta | 11 + .../Layout/Incremental/StickConstraint.cs | 62 + .../Incremental/StickConstraint.cs.meta | 11 + .../VerticalSeparationConstraint.cs | 97 + .../VerticalSeparationConstraint.cs.meta | 11 + Assets/Scripts/NIH/MSAGL/Layout/Initial.meta | 8 + .../NIH/MSAGL/Layout/Initial/InitialLayout.cs | 181 + .../Layout/Initial/InitialLayout.cs.meta | 11 + .../Layout/Initial/InitialLayoutByCluster.cs | 609 +++ .../Initial/InitialLayoutByCluster.cs.meta | 11 + .../Layout/Initial/InitialLayoutHelpers.cs | 78 + .../Initial/InitialLayoutHelpers.cs.meta | 11 + .../NIH/MSAGL/Layout/LargeGraphLayout.meta | 8 + .../DeviceIndependedZoomCalculatorForNodes.cs | 256 + ...ceIndependedZoomCalculatorForNodes.cs.meta | 11 + .../Layout/LargeGraphLayout/EdgePicker.cs | 98 + .../LargeGraphLayout/EdgePicker.cs.meta | 11 + .../LargeGraphLayout/IZoomLevelCalculator.cs | 27 + .../IZoomLevelCalculator.cs.meta | 11 + .../MSAGL/Layout/LargeGraphLayout/Interval.cs | 88 + .../Layout/LargeGraphLayout/Interval.cs.meta | 11 + .../LargeGraphLayout/LevelCalculator.cs | 114 + .../LargeGraphLayout/LevelCalculator.cs.meta | 11 + .../Layout/LargeGraphLayout/LgCellInfo.cs | 21 + .../LargeGraphLayout/LgCellInfo.cs.meta | 11 + .../MSAGL/Layout/LargeGraphLayout/LgData.cs | 572 +++ .../Layout/LargeGraphLayout/LgData.cs.meta | 11 + .../Layout/LargeGraphLayout/LgEdgeInfo.cs | 44 + .../LargeGraphLayout/LgEdgeInfo.cs.meta | 11 + .../Layout/LargeGraphLayout/LgInteractor.cs | 4344 +++++++++++++++++ .../LargeGraphLayout/LgInteractor.cs.meta | 11 + .../LargeGraphLayout/LgLayoutSettings.cs | 300 ++ .../LargeGraphLayout/LgLayoutSettings.cs.meta | 11 + .../MSAGL/Layout/LargeGraphLayout/LgLevel.cs | 402 ++ .../Layout/LargeGraphLayout/LgLevel.cs.meta | 11 + .../LargeGraphLayout/LgNodeCollection.cs | 67 + .../LargeGraphLayout/LgNodeCollection.cs.meta | 11 + .../Layout/LargeGraphLayout/LgPathRouter.cs | 567 +++ .../LargeGraphLayout/LgPathRouter.cs.meta | 11 + .../LargeGraphLayout/LgSkeletonLevel.cs | 153 + .../LargeGraphLayout/LgSkeletonLevel.cs.meta | 11 + .../NodeRailLevelCalculator.meta | 8 + .../GreedyNodeRailLevelCalculator.cs | 491 ++ .../GreedyNodeRailLevelCalculator.cs.meta | 11 + .../NIH/MSAGL/Layout/LargeGraphLayout/Rail.cs | 188 + .../Layout/LargeGraphLayout/Rail.cs.meta | 11 + .../Layout/LargeGraphLayout/RailGraph.cs | 26 + .../Layout/LargeGraphLayout/RailGraph.cs.meta | 11 + .../LargeGraphLayout/RouteSimplifier.cs | 162 + .../LargeGraphLayout/RouteSimplifier.cs.meta | 11 + .../ShortestPartRouterForLg.cs | 172 + .../ShortestPartRouterForLg.cs.meta | 11 + .../LargeGraphLayout/SimpleEdgeCollection.cs | 35 + .../SimpleEdgeCollection.cs.meta | 11 + .../LargeGraphLayout/SimpleNodeCollection.cs | 61 + .../SimpleNodeCollection.cs.meta | 11 + Assets/Scripts/NIH/MSAGL/Layout/Layered.meta | 8 + .../NIH/MSAGL/Layout/Layered/Anchor.cs | 482 ++ .../NIH/MSAGL/Layout/Layered/Anchor.cs.meta | 11 + .../NIH/MSAGL/Layout/Layered/Balancing.cs | 222 + .../MSAGL/Layout/Layered/Balancing.cs.meta | 11 + .../NIH/MSAGL/Layout/Layered/DataBase.cs | 143 + .../NIH/MSAGL/Layout/Layered/DataBase.cs.meta | 11 + .../MSAGL/Layout/Layered/EdgePathsInserter.cs | 196 + .../Layout/Layered/EdgePathsInserter.cs.meta | 11 + .../MSAGL/Layout/Layered/FlatEdgeRouter.cs | 29 + .../Layout/Layered/FlatEdgeRouter.cs.meta | 11 + .../Layout/Layered/HierarchyCalculator.cs | 122 + .../Layered/HierarchyCalculator.cs.meta | 11 + .../HorizontalConstraintsForSugiyama.cs | 185 + .../HorizontalConstraintsForSugiyama.cs.meta | 11 + .../NIH/MSAGL/Layout/Layered/IntEdge.cs | 353 ++ .../NIH/MSAGL/Layout/Layered/IntEdge.cs.meta | 11 + .../NIH/MSAGL/Layout/Layered/LayerArrays.cs | 174 + .../MSAGL/Layout/Layered/LayerArrays.cs.meta | 11 + .../MSAGL/Layout/Layered/LayerCalculator.cs | 14 + .../Layout/Layered/LayerCalculator.cs.meta | 11 + .../NIH/MSAGL/Layout/Layered/LayerEdge.cs | 84 + .../MSAGL/Layout/Layered/LayerEdge.cs.meta | 11 + .../NIH/MSAGL/Layout/Layered/LayerInserter.cs | 330 ++ .../Layout/Layered/LayerInserter.cs.meta | 11 + .../NIH/MSAGL/Layout/Layered/LayeredLayout.cs | 165 + .../Layout/Layered/LayeredLayout.cs.meta | 11 + .../Layout/Layered/LayeredLayoutEngine.cs | 1653 +++++++ .../Layered/LayeredLayoutEngine.cs.meta | 11 + .../NIH/MSAGL/Layout/Layered/Layering.meta | 8 + .../Layered/Layering/LongestPathLayering.cs | 43 + .../Layering/LongestPathLayering.cs.meta | 11 + .../Layout/Layered/Layering/NetworkSimplex.cs | 841 ++++ .../Layered/Layering/NetworkSimplex.cs.meta | 11 + .../NIH/MSAGL/Layout/Layered/NetworkEdge.cs | 26 + .../MSAGL/Layout/Layered/NetworkEdge.cs.meta | 11 + .../Layered/NetworkSimplexForGeneralGraph.cs | 81 + .../NetworkSimplexForGeneralGraph.cs.meta | 11 + .../NIH/MSAGL/Layout/Layered/Ordering.meta | 8 + .../Layout/Layered/Ordering/AdjacentSwaps.cs | 568 +++ .../Layered/Ordering/AdjacentSwaps.cs.meta | 11 + .../Ordering/AdjacentSwapsWithConstraints.cs | 488 ++ .../AdjacentSwapsWithConstraints.cs.meta | 11 + .../Ordering/ConstrainedOrderMeasure.cs | 20 + .../Ordering/ConstrainedOrderMeasure.cs.meta | 11 + .../Layered/Ordering/ConstrainedOrdering.cs | 614 +++ .../Ordering/ConstrainedOrdering.cs.meta | 11 + .../Layered/Ordering/EdgeComparerBySource.cs | 27 + .../Ordering/EdgeComparerBySource.cs.meta | 11 + .../Layered/Ordering/EdgeComparerByTarget.cs | 27 + .../Ordering/EdgeComparerByTarget.cs.meta | 11 + .../Layout/Layered/Ordering/LayerInfo.cs | 23 + .../Layout/Layered/Ordering/LayerInfo.cs.meta | 11 + .../Layered/Ordering/MetroMapOrdering.cs | 177 + .../Layered/Ordering/MetroMapOrdering.cs.meta | 11 + .../MSAGL/Layout/Layered/Ordering/Ordering.cs | 582 +++ .../Layout/Layered/Ordering/Ordering.cs.meta | 11 + .../Layered/Ordering/OrderingMeasure.cs | 104 + .../Layered/Ordering/OrderingMeasure.cs.meta | 11 + .../Layout/Layered/Ordering/SweepMode.cs | 7 + .../Layout/Layered/Ordering/SweepMode.cs.meta | 11 + .../Layout/Layered/ProperLayeredGraph.cs | 239 + .../Layout/Layered/ProperLayeredGraph.cs.meta | 11 + .../Layout/Layered/RecoveryLayerCalculator.cs | 14 + .../Layered/RecoveryLayerCalculator.cs.meta | 11 + .../Layered/RecoveryLayeredLayoutEngine.cs | 1281 +++++ .../RecoveryLayeredLayoutEngine.cs.meta | 11 + .../Layout/Layered/RefinerBetweenTwoLayers.cs | 372 ++ .../Layered/RefinerBetweenTwoLayers.cs.meta | 11 + .../NIH/MSAGL/Layout/Layered/Routing.cs | 262 + .../NIH/MSAGL/Layout/Layered/Routing.cs.meta | 11 + .../Layered/SmoothedPolylineCalculator.cs | 808 +++ .../SmoothedPolylineCalculator.cs.meta | 11 + .../Layout/Layered/SugiyamaLayoutLogger.cs | 43 + .../Layered/SugiyamaLayoutLogger.cs.meta | 11 + .../Layout/Layered/SugiyamaLayoutSettings.cs | 441 ++ .../Layered/SugiyamaLayoutSettings.cs.meta | 11 + .../Layout/Layered/TwoLayerFlatEdgeRouter.cs | 225 + .../Layered/TwoLayerFlatEdgeRouter.cs.meta | 11 + .../MSAGL/Layout/Layered/VertexEnumerators.cs | 169 + .../Layout/Layered/VertexEnumerators.cs.meta | 11 + .../Layered/VerticalConstraintsForSugiyama.cs | 324 ++ .../VerticalConstraintsForSugiyama.cs.meta | 11 + .../Layout/Layered/XCoordsWithAlignment.cs | 860 ++++ .../Layered/XCoordsWithAlignment.cs.meta | 11 + .../NIH/MSAGL/Layout/Layered/XLayoutGraph.cs | 97 + .../MSAGL/Layout/Layered/XLayoutGraph.cs.meta | 11 + Assets/Scripts/NIH/MSAGL/Layout/MDS.meta | 8 + .../NIH/MSAGL/Layout/MDS/AllPairsDistances.cs | 109 + .../Layout/MDS/AllPairsDistances.cs.meta | 11 + .../NIH/MSAGL/Layout/MDS/MDSGraphLayout.cs | 350 ++ .../MSAGL/Layout/MDS/MDSGraphLayout.cs.meta | 11 + .../NIH/MSAGL/Layout/MDS/MDSLayoutSettings.cs | 180 + .../Layout/MDS/MDSLayoutSettings.cs.meta | 11 + .../NIH/MSAGL/Layout/MDS/PivotDistances.cs | 81 + .../MSAGL/Layout/MDS/PivotDistances.cs.meta | 11 + .../Scripts/NIH/MSAGL/Layout/MDS/PivotMDS.cs | 141 + .../NIH/MSAGL/Layout/MDS/PivotMDS.cs.meta | 11 + .../MSAGL/Layout/MDS/SingleSourceDistances.cs | 154 + .../Layout/MDS/SingleSourceDistances.cs.meta | 11 + .../Scripts/NIH/MSAGL/Layout/MDS/Transform.cs | 26 + .../NIH/MSAGL/Layout/MDS/Transform.cs.meta | 11 + .../Layout/OverlapRemovalFixedSegments.meta | 8 + .../OverlapRemovalFixedSegments/FakeBitmap.cs | 215 + .../FakeBitmap.cs.meta | 11 + .../OverlapRemovalFixedSegmentsBitmap.cs | 252 + .../OverlapRemovalFixedSegmentsBitmap.cs.meta | 11 + .../OverlapRemovalFixedSegmentsMst.cs | 967 ++++ .../OverlapRemovalFixedSegmentsMst.cs.meta | 11 + .../OverlapRemovalFixedSegments/PixelPoint.cs | 33 + .../PixelPoint.cs.meta | 11 + .../RectSegIntersection.cs | 409 ++ .../RectSegIntersection.cs.meta | 11 + Assets/Scripts/NIH/MSAGL/Miscellaneous.meta | 8 + .../Miscellaneous/ConstrainedSkeleton.meta | 8 + .../ConstrainedSkeleton/SteinerCdt.cs | 448 ++ .../ConstrainedSkeleton/SteinerCdt.cs.meta | 11 + .../NIH/MSAGL/Miscellaneous/Constraints.meta | 8 + .../Constraints/AlignmentConstraint.cs | 72 + .../Constraints/AlignmentConstraint.cs.meta | 11 + .../MSAGL/Miscellaneous/LayoutEditing.meta | 8 + .../LayoutEditing/BumperPusher.cs | 167 + .../LayoutEditing/BumperPusher.cs.meta | 11 + .../LayoutEditing/EdgeRestoreData.cs | 79 + .../LayoutEditing/EdgeRestoreData.cs.meta | 11 + .../LayoutEditing/GraphRestoreData.cs | 8 + .../LayoutEditing/GraphRestoreData.cs.meta | 11 + .../LayoutEditing/IncrementalDragger.cs | 218 + .../LayoutEditing/IncrementalDragger.cs.meta | 11 + .../LayoutEditing/LabelFixture.cs | 12 + .../LayoutEditing/LabelFixture.cs.meta | 11 + .../LayoutEditing/LabelRestoreData.cs | 27 + .../LayoutEditing/LabelRestoreData.cs.meta | 11 + .../LayoutEditing/NodeRestoreData.cs | 25 + .../LayoutEditing/NodeRestoreData.cs.meta | 11 + .../LayoutEditing/ObstacleCalculator.cs | 626 +++ .../LayoutEditing/ObstacleCalculator.cs.meta | 11 + .../LayoutEditing/RestoreData.cs | 14 + .../LayoutEditing/RestoreData.cs.meta | 11 + .../LayoutEditing/RestoreHelper.cs | 79 + .../LayoutEditing/RestoreHelper.cs.meta | 11 + .../LayoutEditing/RouterBetweenTwoNodes.cs | 469 ++ .../RouterBetweenTwoNodes.cs.meta | 11 + .../LayoutEditing/WeightedEdge.cs | 40 + .../LayoutEditing/WeightedEdge.cs.meta | 11 + .../NIH/MSAGL/Miscellaneous/LayoutHelpers.cs | 297 ++ .../MSAGL/Miscellaneous/LayoutHelpers.cs.meta | 11 + .../NIH/MSAGL/Miscellaneous/MultiScale.meta | 8 + .../MultiScale/MultiScaleLayout.cs | 196 + .../MultiScale/MultiScaleLayout.cs.meta | 11 + .../NonOverlappingBoundaries.meta | 8 + .../AllPairsNonOverlappingBoundaries.cs | 316 ++ .../AllPairsNonOverlappingBoundaries.cs.meta | 11 + .../ProximityQuery.cs | 102 + .../ProximityQuery.cs.meta | 11 + .../NIH/MSAGL/Miscellaneous/Phylo.meta | 8 + .../MSAGL/Miscellaneous/Phylo/PhyloEdge.cs | 15 + .../Miscellaneous/Phylo/PhyloEdge.cs.meta | 11 + .../MSAGL/Miscellaneous/Phylo/PhyloTree.cs | 46 + .../Miscellaneous/Phylo/PhyloTree.cs.meta | 11 + .../Phylo/PhyloTreeLayoutCalclulation.cs | 525 ++ .../Phylo/PhyloTreeLayoutCalclulation.cs.meta | 11 + .../NIH/MSAGL/Miscellaneous/Ranking.meta | 8 + .../MSAGL/Miscellaneous/Ranking/Centrality.cs | 56 + .../Miscellaneous/Ranking/Centrality.cs.meta | 11 + .../Miscellaneous/Ranking/RankingLayout.cs | 117 + .../Ranking/RankingLayout.cs.meta | 11 + .../Ranking/RankingLayoutSettings.cs | 101 + .../Ranking/RankingLayoutSettings.cs.meta | 11 + .../NIH/MSAGL/Miscellaneous/RegularGrid.meta | 8 + .../RegularGrid/GridTraversal.cs | 119 + .../RegularGrid/GridTraversal.cs.meta | 11 + .../NIH/MSAGL/Miscellaneous/Routing.meta | 8 + .../Miscellaneous/Routing/ShortestPathTree.cs | 61 + .../Routing/ShortestPathTree.cs.meta | 11 + Assets/Scripts/NIH/MSAGL/Routing.meta | 8 + .../NIH/MSAGL/Routing/ClusterBoundaryPort.cs | 34 + .../MSAGL/Routing/ClusterBoundaryPort.cs.meta | 11 + .../ConstrainedDelaunayTriangulation.meta | 8 + .../ConstrainedDelaunayTriangulation/Cdt.cs | 331 ++ .../Cdt.cs.meta | 11 + .../CdtEdge.cs | 130 + .../CdtEdge.cs.meta | 11 + .../CdtFront.cs | 11 + .../CdtFront.cs.meta | 11 + .../CdtFrontElement.cs | 24 + .../CdtFrontElement.cs.meta | 11 + .../CdtSite.cs | 126 + .../CdtSite.cs.meta | 11 + .../CdtSweeper.cs | 810 +++ .../CdtSweeper.cs.meta | 11 + .../CdtTriangle.cs | 136 + .../CdtTriangle.cs.meta | 11 + .../EdgeInserter.cs | 84 + .../EdgeInserter.cs.meta | 11 + .../EdgeTracer.cs | 241 + .../EdgeTracer.cs.meta | 11 + .../PerimeterEdge.cs | 18 + .../PerimeterEdge.cs.meta | 11 + .../ThreeArray.cs | 104 + .../ThreeArray.cs.meta | 11 + Assets/Scripts/NIH/MSAGL/Routing/Corner.cs | 49 + .../Scripts/NIH/MSAGL/Routing/Corner.cs.meta | 11 + .../MSAGL/Routing/InteractiveEdgeRouter.cs | 1787 +++++++ .../Routing/InteractiveEdgeRouter.cs.meta | 11 + .../Routing/InteractiveObstacleCalculator.cs | 531 ++ .../InteractiveObstacleCalculator.cs.meta | 11 + .../NIH/MSAGL/Routing/MultiEdgeRouter.cs | 159 + .../NIH/MSAGL/Routing/MultiEdgeRouter.cs.meta | 11 + ...pleTargetsShortestPathOnVisibilityGraph.cs | 122 + ...rgetsShortestPathOnVisibilityGraph.cs.meta | 11 + Assets/Scripts/NIH/MSAGL/Routing/NodeKind.cs | 9 + .../NIH/MSAGL/Routing/NodeKind.cs.meta | 11 + Assets/Scripts/NIH/MSAGL/Routing/PreGraph.cs | 38 + .../NIH/MSAGL/Routing/PreGraph.cs.meta | 11 + .../NIH/MSAGL/Routing/Rectilinear.meta | 8 + .../Rectilinear/AxisCoordinateEvent.cs | 22 + .../Rectilinear/AxisCoordinateEvent.cs.meta | 11 + .../Routing/Rectilinear/BasicObstacleSide.cs | 39 + .../Rectilinear/BasicObstacleSide.cs.meta | 11 + .../Rectilinear/BasicReflectionEvent.cs | 44 + .../Rectilinear/BasicReflectionEvent.cs.meta | 11 + .../Routing/Rectilinear/BasicVertexEvent.cs | 19 + .../Rectilinear/BasicVertexEvent.cs.meta | 11 + .../Routing/Rectilinear/CloseVertexEvent.cs | 13 + .../Rectilinear/CloseVertexEvent.cs.meta | 11 + .../NIH/MSAGL/Routing/Rectilinear/Clump.cs | 27 + .../MSAGL/Routing/Rectilinear/Clump.cs.meta | 11 + .../Design for Rectilinear Edge Routing.docx | Bin 0 -> 524313 bytes ...ign for Rectilinear Edge Routing.docx.meta | 7 + .../Routing/Rectilinear/EnumeratorWrapper.cs | 50 + .../Rectilinear/EnumeratorWrapper.cs.meta | 11 + .../MSAGL/Routing/Rectilinear/EventQueue.cs | 102 + .../Routing/Rectilinear/EventQueue.cs.meta | 11 + .../MSAGL/Routing/Rectilinear/FreePoint.cs | 133 + .../Routing/Rectilinear/FreePoint.cs.meta | 11 + .../FullVisibilityGraphGenerator.cs | 319 ++ .../FullVisibilityGraphGenerator.cs.meta | 11 + .../Rectilinear/GroupBoundaryCrossing.cs | 41 + .../Rectilinear/GroupBoundaryCrossing.cs.meta | 11 + .../Rectilinear/GroupBoundaryCrossingMap.cs | 84 + .../GroupBoundaryCrossingMap.cs.meta | 11 + .../Rectilinear/HighBendVertexEvent.cs | 13 + .../Rectilinear/HighBendVertexEvent.cs.meta | 11 + .../Routing/Rectilinear/HighObstacleSide.cs | 23 + .../Rectilinear/HighObstacleSide.cs.meta | 11 + .../Rectilinear/HighReflectionEvent.cs | 21 + .../Rectilinear/HighReflectionEvent.cs.meta | 11 + .../Routing/Rectilinear/LookaheadScan.cs | 130 + .../Routing/Rectilinear/LookaheadScan.cs.meta | 11 + .../Routing/Rectilinear/LowBendVertexEvent.cs | 13 + .../Rectilinear/LowBendVertexEvent.cs.meta | 11 + .../Routing/Rectilinear/LowObstacleSide.cs | 22 + .../Rectilinear/LowObstacleSide.cs.meta | 11 + .../Routing/Rectilinear/LowReflectionEvent.cs | 21 + .../Rectilinear/LowReflectionEvent.cs.meta | 11 + .../Rectilinear/MsmtRectilinearPath.cs | 221 + .../Rectilinear/MsmtRectilinearPath.cs.meta | 11 + .../Routing/Rectilinear/NeighborSides.cs | 81 + .../Routing/Rectilinear/NeighborSides.cs.meta | 11 + .../MSAGL/Routing/Rectilinear/Nudging.meta | 8 + .../Routing/Rectilinear/Nudging/AxisEdge.cs | 62 + .../Rectilinear/Nudging/AxisEdge.cs.meta | 11 + .../Nudging/AxisEdgeHighPointEvent.cs | 20 + .../Nudging/AxisEdgeHighPointEvent.cs.meta | 11 + .../Nudging/AxisEdgeLowPointEvent.cs | 21 + .../Nudging/AxisEdgeLowPointEvent.cs.meta | 11 + .../Rectilinear/Nudging/AxisEdgesContainer.cs | 62 + .../Nudging/AxisEdgesContainer.cs.meta | 11 + .../Nudging/CombinatorialNudger.cs | 221 + .../Nudging/CombinatorialNudger.cs.meta | 11 + .../Rectilinear/Nudging/FreeSpaceFinder.cs | 534 ++ .../Nudging/FreeSpaceFinder.cs.meta | 11 + .../Rectilinear/Nudging/LinkedPoint.cs | 62 + .../Rectilinear/Nudging/LinkedPoint.cs.meta | 11 + .../Nudging/LinkedPointSplitter.cs | 132 + .../Nudging/LinkedPointSplitter.cs.meta | 11 + .../Nudging/LongestNudgedSegment.cs | 122 + .../Nudging/LongestNudgedSegment.cs.meta | 11 + .../Routing/Rectilinear/Nudging/Nudger.cs | 994 ++++ .../Rectilinear/Nudging/Nudger.cs.meta | 11 + .../MSAGL/Routing/Rectilinear/Nudging/Path.cs | 81 + .../Routing/Rectilinear/Nudging/Path.cs.meta | 11 + .../Routing/Rectilinear/Nudging/PathEdge.cs | 92 + .../Rectilinear/Nudging/PathEdge.cs.meta | 11 + .../Routing/Rectilinear/Nudging/PathMerger.cs | 150 + .../Rectilinear/Nudging/PathMerger.cs.meta | 11 + .../Rectilinear/Nudging/PathRefiner.cs | 183 + .../Rectilinear/Nudging/PathRefiner.cs.meta | 11 + .../Nudging/PointByDelegateComparer.cs | 35 + .../Nudging/PointByDelegateComparer.cs.meta | 11 + .../Rectilinear/Nudging/PointProjection.cs | 5 + .../Nudging/PointProjection.cs.meta | 11 + .../Rectilinear/Nudging/SegWithIndex.cs | 46 + .../Rectilinear/Nudging/SegWithIndex.cs.meta | 11 + .../Nudging/SpaceCalculationEvent.cs | 4 + .../Nudging/SpaceCalculationEvent.cs.meta | 11 + .../Rectilinear/Nudging/StaircaseRemover.cs | 200 + .../Nudging/StaircaseRemover.cs.meta | 11 + .../NIH/MSAGL/Routing/Rectilinear/Obstacle.cs | 325 ++ .../Routing/Rectilinear/Obstacle.cs.meta | 11 + .../MSAGL/Routing/Rectilinear/ObstaclePort.cs | 88 + .../Routing/Rectilinear/ObstaclePort.cs.meta | 11 + .../Rectilinear/ObstaclePortEntrance.cs | 200 + .../Rectilinear/ObstaclePortEntrance.cs.meta | 11 + .../MSAGL/Routing/Rectilinear/ObstacleTree.cs | 767 +++ .../Routing/Rectilinear/ObstacleTree.cs.meta | 11 + .../Routing/Rectilinear/OpenVertexEvent.cs | 13 + .../Rectilinear/OpenVertexEvent.cs.meta | 11 + .../Routing/Rectilinear/OverlapConvexHull.cs | 42 + .../Rectilinear/OverlapConvexHull.cs.meta | 11 + .../Routing/Rectilinear/PointAndCrossings.cs | 19 + .../Rectilinear/PointAndCrossings.cs.meta | 11 + .../Rectilinear/PointAndCrossingsList.cs | 144 + .../Rectilinear/PointAndCrossingsList.cs.meta | 11 + .../Routing/Rectilinear/PointComparer.cs | 165 + .../Routing/Rectilinear/PointComparer.cs.meta | 11 + .../MSAGL/Routing/Rectilinear/PortManager.cs | 1036 ++++ .../Routing/Rectilinear/PortManager.cs.meta | 11 + .../Rectilinear/RectilinearEdgeRouter.cs | 922 ++++ .../Rectilinear/RectilinearEdgeRouter.cs.meta | 11 + .../RectilinearInteractiveEditor.cs | 209 + .../RectilinearInteractiveEditor.cs.meta | 11 + .../Rectilinear/RectilinearScanLine.cs | 271 + .../Rectilinear/RectilinearScanLine.cs.meta | 11 + .../Routing/Rectilinear/ScanDirection.cs | 92 + .../Routing/Rectilinear/ScanDirection.cs.meta | 11 + .../MSAGL/Routing/Rectilinear/ScanSegment.cs | 438 ++ .../Routing/Rectilinear/ScanSegment.cs.meta | 11 + .../Routing/Rectilinear/ScanSegmentTree.cs | 372 ++ .../Rectilinear/ScanSegmentTree.cs.meta | 11 + .../Routing/Rectilinear/ScanSegmentVector.cs | 186 + .../Rectilinear/ScanSegmentVector.cs.meta | 11 + .../Rectilinear/ScanSegmentVectorItem.cs | 180 + .../Rectilinear/ScanSegmentVectorItem.cs.meta | 11 + .../Routing/Rectilinear/SegmentIntersector.cs | 308 ++ .../Rectilinear/SegmentIntersector.cs.meta | 11 + .../SparseVisibilityGraphGenerator.cs | 605 +++ .../SparseVisibilityGraphGenerator.cs.meta | 11 + .../Routing/Rectilinear/SpliceUtility.cs | 70 + .../Routing/Rectilinear/SpliceUtility.cs.meta | 11 + .../Rectilinear/SsstRectilinearPath.cs | 743 +++ .../Rectilinear/SsstRectilinearPath.cs.meta | 11 + .../Routing/Rectilinear/StaticGraphUtility.cs | 332 ++ .../Rectilinear/StaticGraphUtility.cs.meta | 11 + .../Rectilinear/TransientGraphUtility.cs | 808 +++ .../Rectilinear/TransientGraphUtility.cs.meta | 11 + .../MSAGL/Routing/Rectilinear/VertexEntry.cs | 85 + .../Routing/Rectilinear/VertexEntry.cs.meta | 11 + .../Rectilinear/VisibilityGraphGenerator.cs | 1010 ++++ .../VisibilityGraphGenerator.cs.meta | 11 + .../VisibilityVertexRectilinear.cs | 25 + .../VisibilityVertexRectilinear.cs.meta | 11 + .../MSAGL/Routing/Rectilinear/WaypointPort.cs | 30 + .../Routing/Rectilinear/WaypointPort.cs.meta | 11 + .../NIH/MSAGL/Routing/RelativeShape.cs | 41 + .../NIH/MSAGL/Routing/RelativeShape.cs.meta | 11 + .../NIH/MSAGL/Routing/RelaxedPolylinePoint.cs | 37 + .../Routing/RelaxedPolylinePoint.cs.meta | 11 + Assets/Scripts/NIH/MSAGL/Routing/Shape.cs | 164 + .../Scripts/NIH/MSAGL/Routing/Shape.cs.meta | 11 + .../Scripts/NIH/MSAGL/Routing/ShapeCreator.cs | 123 + .../NIH/MSAGL/Routing/ShapeCreator.cs.meta | 11 + .../ShapeCreatorForRoutingToParents.cs | 107 + .../ShapeCreatorForRoutingToParents.cs.meta | 11 + .../MSAGL/Routing/ShapeObstacleCalculator.cs | 108 + .../Routing/ShapeObstacleCalculator.cs.meta | 11 + ...pleTargetsShortestPathOnVisibilityGraph.cs | 115 + ...rgetsShortestPathOnVisibilityGraph.cs.meta | 11 + ...ngleTargetShortestPathOnVisibilityGraph.cs | 221 + ...argetShortestPathOnVisibilityGraph.cs.meta | 11 + Assets/Scripts/NIH/MSAGL/Routing/Spline.meta | 8 + .../NIH/MSAGL/Routing/Spline/Bundling.meta | 8 + .../Routing/Spline/Bundling/BundleBase.cs | 221 + .../Spline/Bundling/BundleBase.cs.meta | 11 + .../Spline/Bundling/BundleBasesCalculator.cs | 643 +++ .../Bundling/BundleBasesCalculator.cs.meta | 11 + .../Routing/Spline/Bundling/BundleInfo.cs | 307 ++ .../Spline/Bundling/BundleInfo.cs.meta | 11 + .../Routing/Spline/Bundling/BundleRouter.cs | 341 ++ .../Spline/Bundling/BundleRouter.cs.meta | 11 + .../Routing/Spline/Bundling/BundlingStatus.cs | 15 + .../Spline/Bundling/BundlingStatus.cs.meta | 11 + .../Spline/Bundling/CdtIntersections.cs | 219 + .../Spline/Bundling/CdtIntersections.cs.meta | 11 + .../Routing/Spline/Bundling/CdtThreader.cs | 201 + .../Spline/Bundling/CdtThreader.cs.meta | 11 + .../Routing/Spline/Bundling/ChannelFlag.cs | 7 + .../Spline/Bundling/ChannelFlag.cs.meta | 11 + .../Routing/Spline/Bundling/CostCalculator.cs | 209 + .../Spline/Bundling/CostCalculator.cs.meta | 11 + .../Routing/Spline/Bundling/EdgeNudger.cs | 573 +++ .../Spline/Bundling/EdgeNudger.cs.meta | 11 + .../Routing/Spline/Bundling/FlipCollapser.cs | 108 + .../Spline/Bundling/FlipCollapser.cs.meta | 11 + .../Routing/Spline/Bundling/FlipSwitcher.cs | 371 ++ .../Spline/Bundling/FlipSwitcher.cs.meta | 11 + .../Bundling/GeneralMetroMapOrdering.cs | 217 + .../Bundling/GeneralMetroMapOrdering.cs.meta | 11 + .../Routing/Spline/Bundling/HubDebugger.cs | 163 + .../Spline/Bundling/HubDebugger.cs.meta | 11 + .../Spline/Bundling/HubRadiiCalculator.cs | 234 + .../Bundling/HubRadiiCalculator.cs.meta | 11 + .../Bundling/IMetroMapOrderingAlgorithm.cs | 25 + .../IMetroMapOrderingAlgorithm.cs.meta | 11 + .../Spline/Bundling/IntersectionCache.cs | 77 + .../Spline/Bundling/IntersectionCache.cs.meta | 11 + .../Routing/Spline/Bundling/Intersections.cs | 261 + .../Spline/Bundling/Intersections.cs.meta | 11 + .../Spline/Bundling/LinearMetroMapOrdering.cs | 558 +++ .../Bundling/LinearMetroMapOrdering.cs.meta | 11 + .../Routing/Spline/Bundling/MetroGraphData.cs | 482 ++ .../Spline/Bundling/MetroGraphData.cs.meta | 11 + .../Routing/Spline/Bundling/MetroLine.cs | 44 + .../Routing/Spline/Bundling/MetroLine.cs.meta | 11 + .../Routing/Spline/Bundling/MetroNodeInfo.cs | 23 + .../Spline/Bundling/MetroNodeInfo.cs.meta | 11 + .../Spline/Bundling/NodePositionsAdjuster.cs | 753 +++ .../Bundling/NodePositionsAdjuster.cs.meta | 11 + .../Spline/Bundling/OrientedHubSegment.cs | 22 + .../Bundling/OrientedHubSegment.cs.meta | 11 + .../Routing/Spline/Bundling/PathFixer.cs | 228 + .../Routing/Spline/Bundling/PathFixer.cs.meta | 11 + .../Routing/Spline/Bundling/PointPairOrder.cs | 14 + .../Spline/Bundling/PointPairOrder.cs.meta | 11 + .../Routing/Spline/Bundling/SdBoneEdge.cs | 52 + .../Spline/Bundling/SdBoneEdge.cs.meta | 11 + .../Routing/Spline/Bundling/SdShortestPath.cs | 543 +++ .../Spline/Bundling/SdShortestPath.cs.meta | 11 + .../MSAGL/Routing/Spline/Bundling/SdVertex.cs | 48 + .../Routing/Spline/Bundling/SdVertex.cs.meta | 11 + .../Spline/Bundling/SimulatedAnnealing.cs | 345 ++ .../Bundling/SimulatedAnnealing.cs.meta | 11 + .../MSAGL/Routing/Spline/Bundling/Station.cs | 113 + .../Routing/Spline/Bundling/Station.cs.meta | 11 + .../Spline/Bundling/StationEdgeInfo.cs | 27 + .../Spline/Bundling/StationEdgeInfo.cs.meta | 11 + .../Routing/Spline/Bundling/TimeMeasurer.cs | 27 + .../Spline/Bundling/TimeMeasurer.cs.meta | 11 + .../NIH/MSAGL/Routing/Spline/ConeSpanner.meta | 8 + .../Spline/ConeSpanner/BrokenConeSide.cs | 45 + .../Spline/ConeSpanner/BrokenConeSide.cs.meta | 11 + .../MSAGL/Routing/Spline/ConeSpanner/Cone.cs | 53 + .../Routing/Spline/ConeSpanner/Cone.cs.meta | 11 + .../Spline/ConeSpanner/ConeClosureEvent.cs | 29 + .../ConeSpanner/ConeClosureEvent.cs.meta | 11 + .../Spline/ConeSpanner/ConeLeftSide.cs | 20 + .../Spline/ConeSpanner/ConeLeftSide.cs.meta | 11 + .../Spline/ConeSpanner/ConeRightSide.cs | 21 + .../Spline/ConeSpanner/ConeRightSide.cs.meta | 11 + .../Routing/Spline/ConeSpanner/ConeSide.cs | 13 + .../Spline/ConeSpanner/ConeSide.cs.meta | 11 + .../Spline/ConeSpanner/ConeSideComparer.cs | 88 + .../ConeSpanner/ConeSideComparer.cs.meta | 11 + .../Routing/Spline/ConeSpanner/ConeSpanner.cs | 110 + .../Spline/ConeSpanner/ConeSpanner.cs.meta | 11 + .../ConeSpannerForPortLocations.cs | 60 + .../ConeSpannerForPortLocations.cs.meta | 11 + .../Spline/ConeSpanner/IConeSweeper.cs | 10 + .../Spline/ConeSpanner/IConeSweeper.cs.meta | 11 + .../ConeSpanner/LeftIntersectionEvent.cs | 33 + .../ConeSpanner/LeftIntersectionEvent.cs.meta | 11 + .../Spline/ConeSpanner/LeftObstacleSide.cs | 20 + .../ConeSpanner/LeftObstacleSide.cs.meta | 11 + .../Spline/ConeSpanner/LeftVertexEvent.cs | 7 + .../ConeSpanner/LeftVertexEvent.cs.meta | 11 + .../Routing/Spline/ConeSpanner/LineSweeper.cs | 1150 +++++ .../Spline/ConeSpanner/LineSweeper.cs.meta | 11 + .../LineSweeperForPortLocations.cs | 865 ++++ .../LineSweeperForPortLocations.cs.meta | 11 + .../Spline/ConeSpanner/LowestVertexEvent.cs | 8 + .../ConeSpanner/LowestVertexEvent.cs.meta | 11 + .../Spline/ConeSpanner/ObstacleSide.cs | 41 + .../Spline/ConeSpanner/ObstacleSide.cs.meta | 11 + .../Spline/ConeSpanner/PortLocationEvent.cs | 15 + .../ConeSpanner/PortLocationEvent.cs.meta | 11 + .../ConeSpanner/RightIntersectionEvent.cs | 33 + .../RightIntersectionEvent.cs.meta | 11 + .../Spline/ConeSpanner/RightObstacleSide.cs | 25 + .../ConeSpanner/RightObstacleSide.cs.meta | 11 + .../Spline/ConeSpanner/RightVertexEvent.cs | 7 + .../ConeSpanner/RightVertexEvent.cs.meta | 11 + .../Routing/Spline/ConeSpanner/SweepEvent.cs | 18 + .../Spline/ConeSpanner/SweepEvent.cs.meta | 11 + .../Routing/Spline/ConeSpanner/VertexEvent.cs | 20 + .../Spline/ConeSpanner/VertexEvent.cs.meta | 11 + .../Scripts/NIH/MSAGL/Routing/SplineRouter.cs | 1124 +++++ .../NIH/MSAGL/Routing/SplineRouter.cs.meta | 11 + .../NIH/MSAGL/Routing/StraightLineEdges.cs | 251 + .../MSAGL/Routing/StraightLineEdges.cs.meta | 11 + .../NIH/MSAGL/Routing/TightLooseCouple.cs | 54 + .../MSAGL/Routing/TightLooseCouple.cs.meta | 11 + .../Scripts/NIH/MSAGL/Routing/Visibility.meta | 8 + .../ActiveDiagonalComparerWithRay.cs | 52 + .../ActiveDiagonalComparerWithRay.cs.meta | 11 + .../Visibility/ActiveEdgeComparerWithRay.cs | 71 + .../ActiveEdgeComparerWithRay.cs.meta | 11 + .../Routing/Visibility/BimodalSequence.cs | 64 + .../Visibility/BimodalSequence.cs.meta | 11 + .../NIH/MSAGL/Routing/Visibility/Diagonal.cs | 48 + .../MSAGL/Routing/Visibility/Diagonal.cs.meta | 11 + .../NIH/MSAGL/Routing/Visibility/Drawings.png | 3 + .../Routing/Visibility/Drawings.png.meta | 88 + ...ractiveTangentVisibilityGraphCalculator.cs | 231 + ...veTangentVisibilityGraphCalculator.cs.meta | 11 + .../Routing/Visibility/LineSweeperBase.cs | 174 + .../Visibility/LineSweeperBase.cs.meta | 11 + .../Visibility/ObstacleSideComparer.cs | 49 + .../Visibility/ObstacleSideComparer.cs.meta | 11 + .../Visibility/PointVisibilityCalculator.cs | 349 ++ .../PointVisibilityCalculator.cs.meta | 11 + .../NIH/MSAGL/Routing/Visibility/Polygon.cs | 216 + .../MSAGL/Routing/Visibility/Polygon.cs.meta | 11 + .../Routing/Visibility/PortObstacleEvent.cs | 16 + .../Visibility/PortObstacleEvent.cs.meta | 11 + .../MSAGL/Routing/Visibility/SegmentBase.cs | 14 + .../Routing/Visibility/SegmentBase.cs.meta | 11 + .../Visibility/Spanner visibility graph.docx | Bin 0 -> 90039 bytes .../Spanner visibility graph.docx.meta | 7 + .../NIH/MSAGL/Routing/Visibility/Stem.cs | 58 + .../NIH/MSAGL/Routing/Visibility/Stem.cs.meta | 11 + .../Visibility/StemStartPointComparer.cs | 83 + .../Visibility/StemStartPointComparer.cs.meta | 11 + .../NIH/MSAGL/Routing/Visibility/Tangent.cs | 72 + .../MSAGL/Routing/Visibility/Tangent.cs.meta | 11 + .../MSAGL/Routing/Visibility/TangentPair.cs | 642 +++ .../Routing/Visibility/TangentPair.cs.meta | 11 + .../TangentVisibilityGraphCalculator.cs | 218 + .../TangentVisibilityGraphCalculator.cs.meta | 11 + .../Visibility/TollFreeVisibilityEdge.cs | 18 + .../Visibility/TollFreeVisibilityEdge.cs.meta | 11 + .../Routing/Visibility/UnimodalSequence.cs | 120 + .../Visibility/UnimodalSequence.cs.meta | 11 + .../Routing/Visibility/VisibilityEdge.cs | 76 + .../Routing/Visibility/VisibilityEdge.cs.meta | 11 + .../Routing/Visibility/VisibilityGraph.cs | 420 ++ .../Visibility/VisibilityGraph.cs.meta | 11 + .../Routing/Visibility/VisibilityKind.cs | 15 + .../Routing/Visibility/VisibilityKind.cs.meta | 11 + .../Routing/Visibility/VisibilityVertex.cs | 172 + .../Visibility/VisibilityVertex.cs.meta | 11 + .../NIH/MSAGL/Routing/Visibility/makefile | 3 + .../MSAGL/Routing/Visibility/makefile.meta | 7 + Assets/Scripts/NIH/MSAGL/ValidateArg.cs | 80 + Assets/Scripts/NIH/MSAGL/ValidateArg.cs.meta | 11 + Assets/Scripts/ServerShared/BlueprintData.cs | 15 +- Assets/Scripts/ServerShared/Extensions.cs | 2 +- Assets/Scripts/ServerShared/Technology.cs | 16 - .../Scripts/ServerShared/Technology.cs.meta | 3 - Assets/Scripts/TechTreeTest.cs | 178 +- Assets/Scripts/TechTreeTestMsagl.cs | 350 ++ Assets/Scripts/TechTreeTestMsagl.cs.meta | 11 + Assets/csc.rsp | 1 + Assets/csc.rsp.meta | 7 + Economy.Shared.Unity.csproj | 9 +- MessagePack.csproj | 8 +- ProjectSettings/ProjectSettings.asset | 4 +- 1110 files changed, 119054 insertions(+), 151 deletions(-) create mode 100644 Assets/Scripts/NIH/MSAGL.meta create mode 100644 Assets/Scripts/NIH/MSAGL/AutomaticGraphLayout.csproj create mode 100644 Assets/Scripts/NIH/MSAGL/AutomaticGraphLayout.csproj.meta create mode 100644 Assets/Scripts/NIH/MSAGL/AutomaticGraphLayout.nuspec create mode 100644 Assets/Scripts/NIH/MSAGL/AutomaticGraphLayout.nuspec.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/AlgorithmBase.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/AlgorithmBase.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/CancelToken.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/CancelToken.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/DataStructures.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/DataStructures/BinaryHeapPriorityQueue.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/DataStructures/BinaryHeapPriorityQueue.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/DataStructures/BinaryHeapWithComparer.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/DataStructures/BinaryHeapWithComparer.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/DataStructures/CollectionUtilities.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/DataStructures/CollectionUtilities.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/DataStructures/GenericBinaryHeapPriorityQueue.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/DataStructures/GenericBinaryHeapPriorityQueue.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/DataStructures/GenericBinaryHeapPriorityQueueWithTimestamp.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/DataStructures/GenericBinaryHeapPriorityQueueWithTimestamp.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/DataStructures/GenericHeapElement.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/DataStructures/GenericHeapElement.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/DataStructures/GenericHeapElementWithTimestamp.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/DataStructures/GenericHeapElementWithTimestamp.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/ComparerOnDelegate.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/ComparerOnDelegate.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/DefaultComperer.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/DefaultComperer.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/RBColor.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/RBColor.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/RBNode.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/RBNode.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/RBTree.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/RBTree.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/RBTreeEnumerator.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/RBTreeEnumerator.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/DataStructures/RealNumberSpan.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/DataStructures/RealNumberSpan.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/DataStructures/Set.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/DataStructures/Set.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/DataStructures/Size.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/DataStructures/Size.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/DataStructures/SymmetricTuple.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/DataStructures/SymmetricTuple.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/ApproximateComparer.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/ApproximateComparer.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/CompassVector.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/CompassVector.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/ConvexHull.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/ConvexHull/ConvexHull.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/ConvexHull/ConvexHull.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/ConvexHull/HullPoint.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/ConvexHull/HullPoint.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/ConvexHull/HullPointComparer.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/ConvexHull/HullPointComparer.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/ConvexHull/HullStack.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/ConvexHull/HullStack.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ClosestPointOnCurve.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ClosestPointOnCurve.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/CubicBezierSegment.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/CubicBezierSegment.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/CurvatureDerivative.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/CurvatureDerivative.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/Curve.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/Curve.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/CurveFactory.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/CurveFactory.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/CurveLengthAndParams.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/CurveLengthAndParams.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/CurveTangent.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/CurveTangent.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/Ellipse.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/Ellipse.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ICurve.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ICurve.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/IFunction.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/IFunction.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/IntersectionInfo.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/IntersectionInfo.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/LineSegment.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/LineSegment.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/LinearSystem2.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/LinearSystem2.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/MinDistCurveCurve.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/MinDistCurveCurve.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/Parallelogram.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/Parallelogram.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ParallelogramBinaryTreeNode.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ParallelogramBinaryTreeNode.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ParallelogramInternalTreeNode.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ParallelogramInternalTreeNode.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ParallelogramLeaf.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ParallelogramLeaf.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ParallelogramNode.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ParallelogramNode.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ParallelogramNodeOverICurve.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ParallelogramNodeOverICurve.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/PenetrationDepth.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/PenetrationDepth.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/PlaneTransformation.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/PlaneTransformation.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/PointLocation.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/PointLocation.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/Polyline.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/Polyline.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/PolylineIterator.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/PolylineIterator.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/PolylinePoint.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/PolylinePoint.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/RootFinder.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/RootFinder.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/RoundedRect.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/RoundedRect.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Direction.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Direction.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/MinEnclosingDisc.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/MinEnclosingDisc/Disc.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/MinEnclosingDisc/Disc.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/MinEnclosingDisc/MinimumEnclosingDisc.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/MinEnclosingDisc/MinimumEnclosingDisc.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/MultidimensionalScaling.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/MultidimensionalScaling.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/BorderInfo.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/BorderInfo.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/ConstraintGenerator.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/ConstraintGenerator.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemoval Design.docx create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemoval Design.docx.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalCluster.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalCluster.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalClusterEvent.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalClusterEvent.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalClusterScanLine.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalClusterScanLine.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalGlobalConfiguration.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalGlobalConfiguration.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalNode.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalNode.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalParameters.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalParameters.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/RectangularClusterBoundary.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/RectangularClusterBoundary.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Point.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Point.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/PointNodesList.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/PointNodesList.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/PointPair.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/PointPair.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/HitTestBehavior.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/HitTestBehavior.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/IntervalNode.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/IntervalNode.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/IntervalNodeUtils.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/IntervalNodeUtils.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/IntervalRTree.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/IntervalRTree.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/RTree.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/RTree.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/RectangleNode.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/RectangleNode.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/RectangleNodeUtils.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/RectangleNodeUtils.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Rectangle.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Rectangle.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/ColumnPacking.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/ColumnPacking.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/OptimalColumnPacking.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/OptimalColumnPacking.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/OptimalPacking.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/OptimalPacking.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/OptimalRectanglePacking.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/OptimalRectanglePacking.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/Packing.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/Packing.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/PackingConstants.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/PackingConstants.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/RectanglePacking.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/RectanglePacking.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/RectangleToPack.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/RectangleToPack.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Site.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/Site.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/SmoothedPolyline.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/SmoothedPolyline.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/TriangleOrientation.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Geometry/TriangleOrientation.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/BasicGraph.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/BasicGraph.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/CycleRemoval.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/CycleRemoval/ConnectedComponentCalculator.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/CycleRemoval/ConnectedComponentCalculator.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/CycleRemoval/CycleRemoval.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/CycleRemoval/CycleRemoval.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/CycleRemoval/CycleRemovalWithConstraints.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/CycleRemoval/CycleRemovalWithConstraints.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/CycleRemoval/GraphForCycleRemoval.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/CycleRemoval/GraphForCycleRemoval.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/CycleRemoval/NodeInfo.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/CycleRemoval/NodeInfo.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/IEdge.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/IEdge.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/IntPair.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/IntPair.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/MinimumSpanningTreeByPrim.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/MinimumSpanningTreeByPrim.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/TopologicalSort.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/TopologicalSort.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/Arrowhead.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/Arrowhead.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/Arrowheads.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/Arrowheads.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/Cluster.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/Cluster.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/CurvePort.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/CurvePort.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/Edge.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/Edge.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/EdgeCollection.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/EdgeCollection.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/EdgeGeometry.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/EdgeGeometry.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/EdgeLabelPlacement.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/EdgeLabelPlacement.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/FloatingPort.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/FloatingPort.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/GeometryGraph.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/GeometryGraph.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/GeometryObject.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/GeometryObject.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/GraphConnectedComponents.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/GraphConnectedComponents.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/HookUpAnywhereFromInsidePort.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/HookUpAnywhereFromInsidePort.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/ILabeledObject.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/ILabeledObject.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/IObstacle.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/IObstacle.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/IdealEdgeLengthSettings.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/IdealEdgeLengthSettings.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/IsOpenChangeEventArgs.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/IsOpenChangeEventArgs.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/Label.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/Label.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/LabelPlacementResult.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/LabelPlacementResult.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/LayoutAlgorithmHelpers.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/LayoutAlgorithmHelpers.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/LayoutAlgorithmSettings.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/LayoutAlgorithmSettings.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/LayoutChangeEventArgs.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/LayoutChangeEventArgs.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/LgInfoBase.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/LgInfoBase.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/LgNodeInfo.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/LgNodeInfo.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/LgNodeInfoKind.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/LgNodeInfoKind.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/MultiLocationFloatingPort.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/MultiLocationFloatingPort.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/Node.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/Node.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/NodeCollection.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/NodeCollection.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/PackingMethod.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/PackingMethod.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/Port.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/Port.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/PortEntry.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/PortEntry.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/PortEntryOnCurve.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/PortEntryOnCurve.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/PortObstacle.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/PortObstacle.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/Ports and Port Accesses.doc create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/Ports and Port Accesses.doc.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/ProximityOverlapRemoval.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/ProximityOverlapRemoval/ConjugateGradient.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/ProximityOverlapRemoval/ConjugateGradient/LinearSystemSolver.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/ProximityOverlapRemoval/ConjugateGradient/LinearSystemSolver.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/ProximityOverlapRemoval/ConjugateGradient/SparseMatrix.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/ProximityOverlapRemoval/ConjugateGradient/SparseMatrix.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/ProximityOverlapRemoval/ConjugateGradient/Vector.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/ProximityOverlapRemoval/ConjugateGradient/Vector.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/ProximityOverlapRemoval/IOverlapRemoval.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/ProximityOverlapRemoval/IOverlapRemoval.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/ProximityOverlapRemoval/MST.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/ProximityOverlapRemoval/MST/GTreeOverlapRemoval.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/ProximityOverlapRemoval/MST/GTreeOverlapRemoval.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/ProximityOverlapRemoval/MST/MstLineSweeper.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/ProximityOverlapRemoval/MST/MstLineSweeper.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/ProximityOverlapRemoval/MST/MstOnDelaunayTriangulation.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/ProximityOverlapRemoval/MST/MstOnDelaunayTriangulation.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/ProximityOverlapRemoval/OverlapRemovalMethod.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/ProximityOverlapRemoval/OverlapRemovalMethod.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/ProximityOverlapRemoval/OverlapRemovalSettings.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/ProximityOverlapRemoval/OverlapRemovalSettings.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/ProximityOverlapRemoval/ProximityOverlapRemoval.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/ProximityOverlapRemoval/ProximityOverlapRemoval.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/ProximityOverlapRemoval/StressEnergy.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/ProximityOverlapRemoval/StressEnergy/ExampleStressMajorization.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/ProximityOverlapRemoval/StressEnergy/ExampleStressMajorization.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/ProximityOverlapRemoval/StressEnergy/MaxIterationMethod.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/ProximityOverlapRemoval/StressEnergy/MaxIterationMethod.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/ProximityOverlapRemoval/StressEnergy/NodeVoting.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/ProximityOverlapRemoval/StressEnergy/NodeVoting.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/ProximityOverlapRemoval/StressEnergy/SolvingMethod.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/ProximityOverlapRemoval/StressEnergy/SolvingMethod.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/ProximityOverlapRemoval/StressEnergy/StressMajorization.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/ProximityOverlapRemoval/StressEnergy/StressMajorization.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/ProximityOverlapRemoval/StressEnergy/StressMajorizationSettings.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/ProximityOverlapRemoval/StressEnergy/StressMajorizationSettings.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/ProximityOverlapRemoval/StressEnergy/UpdateMethod.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/ProximityOverlapRemoval/StressEnergy/UpdateMethod.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/ProximityOverlapRemoval/StressEnergy/Vote.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/ProximityOverlapRemoval/StressEnergy/Vote.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/ProximityOverlapRemoval/StressEnergy/VoteBlock.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/ProximityOverlapRemoval/StressEnergy/VoteBlock.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/RectangleObstacle.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/RectangleObstacle.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/RelativeFloatingPort.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/RelativeFloatingPort.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/SimpleIntEdge.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Layout/SimpleIntEdge.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/ParallelUtilities.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/ParallelUtilities.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/ProgressChangedEventArgs.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/ProgressChangedEventArgs.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/ProjectionSolver.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/ProjectionSolver/Block.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/ProjectionSolver/Block.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/ProjectionSolver/BlockVector.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/ProjectionSolver/BlockVector.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/ProjectionSolver/Constraint.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/ProjectionSolver/Constraint.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/ProjectionSolver/ConstraintVector.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/ProjectionSolver/ConstraintVector.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/ProjectionSolver/DfDvNode.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/ProjectionSolver/DfDvNode.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/ProjectionSolver/GlobalConfiguration.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/ProjectionSolver/GlobalConfiguration.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/ProjectionSolver/ISolverShell.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/ProjectionSolver/ISolverShell.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/ProjectionSolver/Parameters.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/ProjectionSolver/Parameters.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/ProjectionSolver/ProjectionSolver Design.docx create mode 100644 Assets/Scripts/NIH/MSAGL/Core/ProjectionSolver/ProjectionSolver Design.docx.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/ProjectionSolver/QPSC.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/ProjectionSolver/QPSC.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/ProjectionSolver/Solution.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/ProjectionSolver/Solution.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/ProjectionSolver/Solver.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/ProjectionSolver/Solver.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/ProjectionSolver/SolverAlgorithm.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/ProjectionSolver/SolverAlgorithm.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/ProjectionSolver/SolverShell.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/ProjectionSolver/SolverShell.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/ProjectionSolver/UniformOneDimensionalSolver.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/ProjectionSolver/UniformOneDimensionalSolver.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/ProjectionSolver/UniformSolverVar.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/ProjectionSolver/UniformSolverVar.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/ProjectionSolver/Variable.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/ProjectionSolver/Variable.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/ProjectionSolver/ViolationCache.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/ProjectionSolver/ViolationCache.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Routing.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Routing/BundlingSettings.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Routing/BundlingSettings.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Routing/EdgeRoutingMode.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Routing/EdgeRoutingMode.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Routing/EdgeRoutingSettings.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Core/Routing/EdgeRoutingSettings.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/DebugHelpers.meta create mode 100644 Assets/Scripts/NIH/MSAGL/DebugHelpers/CurveSerializer.cs create mode 100644 Assets/Scripts/NIH/MSAGL/DebugHelpers/CurveSerializer.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/DebugHelpers/DebugCurve.cs create mode 100644 Assets/Scripts/NIH/MSAGL/DebugHelpers/DebugCurve.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/DebugHelpers/DebugCurveCollection.cs create mode 100644 Assets/Scripts/NIH/MSAGL/DebugHelpers/DebugCurveCollection.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/DebugHelpers/DebugEllipse.cs create mode 100644 Assets/Scripts/NIH/MSAGL/DebugHelpers/DebugEllipse.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/DebugHelpers/DebugGraphics.cs create mode 100644 Assets/Scripts/NIH/MSAGL/DebugHelpers/DebugGraphics.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/DebugHelpers/DebugRectangle.cs create mode 100644 Assets/Scripts/NIH/MSAGL/DebugHelpers/DebugRectangle.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/DebugHelpers/DebugShape.cs create mode 100644 Assets/Scripts/NIH/MSAGL/DebugHelpers/DebugShape.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/DebugHelpers/DevTrace.cs create mode 100644 Assets/Scripts/NIH/MSAGL/DebugHelpers/DevTrace.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/DebugHelpers/IDebugGraphics.cs create mode 100644 Assets/Scripts/NIH/MSAGL/DebugHelpers/IDebugGraphics.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/DebugHelpers/Persistence.meta create mode 100644 Assets/Scripts/NIH/MSAGL/DebugHelpers/Persistence/CharStreamElement.cs create mode 100644 Assets/Scripts/NIH/MSAGL/DebugHelpers/Persistence/CharStreamElement.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/DebugHelpers/Persistence/ClusterWithChildLists.cs create mode 100644 Assets/Scripts/NIH/MSAGL/DebugHelpers/Persistence/ClusterWithChildLists.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/DebugHelpers/Persistence/CurveStream.cs create mode 100644 Assets/Scripts/NIH/MSAGL/DebugHelpers/Persistence/CurveStream.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/DebugHelpers/Persistence/CurveStreamElement.cs create mode 100644 Assets/Scripts/NIH/MSAGL/DebugHelpers/Persistence/CurveStreamElement.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/DebugHelpers/Persistence/DoubleStreamElement.cs create mode 100644 Assets/Scripts/NIH/MSAGL/DebugHelpers/Persistence/DoubleStreamElement.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/DebugHelpers/Persistence/GeometryGraphReader.cs create mode 100644 Assets/Scripts/NIH/MSAGL/DebugHelpers/Persistence/GeometryGraphReader.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/DebugHelpers/Persistence/GeometryGraphWriter.cs create mode 100644 Assets/Scripts/NIH/MSAGL/DebugHelpers/Persistence/GeometryGraphWriter.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/DebugHelpers/Persistence/GeometryToken.cs create mode 100644 Assets/Scripts/NIH/MSAGL/DebugHelpers/Persistence/GeometryToken.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/DebugHelpers/Show.cs create mode 100644 Assets/Scripts/NIH/MSAGL/DebugHelpers/Show.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/DebugHelpers/ShowDatabase.cs create mode 100644 Assets/Scripts/NIH/MSAGL/DebugHelpers/ShowDatabase.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/DebugHelpers/ShowDebugCurves.cs create mode 100644 Assets/Scripts/NIH/MSAGL/DebugHelpers/ShowDebugCurves.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/DebugHelpers/ShowDebugCurvesEnumeration.cs create mode 100644 Assets/Scripts/NIH/MSAGL/DebugHelpers/ShowDebugCurvesEnumeration.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/DebugHelpers/Timer.cs create mode 100644 Assets/Scripts/NIH/MSAGL/DebugHelpers/Timer.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/GlobalSuppressions.cs create mode 100644 Assets/Scripts/NIH/MSAGL/GlobalSuppressions.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/GraphmapsWithMesh.meta create mode 100644 Assets/Scripts/NIH/MSAGL/GraphmapsWithMesh/Angle.cs create mode 100644 Assets/Scripts/NIH/MSAGL/GraphmapsWithMesh/Angle.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/GraphmapsWithMesh/ComponentCollection.cs create mode 100644 Assets/Scripts/NIH/MSAGL/GraphmapsWithMesh/ComponentCollection.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/GraphmapsWithMesh/DijkstraAlgo.cs create mode 100644 Assets/Scripts/NIH/MSAGL/GraphmapsWithMesh/DijkstraAlgo.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/GraphmapsWithMesh/LocalModifications.cs create mode 100644 Assets/Scripts/NIH/MSAGL/GraphmapsWithMesh/LocalModifications.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/GraphmapsWithMesh/MeshCreator.cs create mode 100644 Assets/Scripts/NIH/MSAGL/GraphmapsWithMesh/MeshCreator.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/GraphmapsWithMesh/MeshGraph.cs create mode 100644 Assets/Scripts/NIH/MSAGL/GraphmapsWithMesh/MeshGraph.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/GraphmapsWithMesh/MsaglUtilities.cs create mode 100644 Assets/Scripts/NIH/MSAGL/GraphmapsWithMesh/MsaglUtilities.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/GraphmapsWithMesh/Network.cs create mode 100644 Assets/Scripts/NIH/MSAGL/GraphmapsWithMesh/Network.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/GraphmapsWithMesh/PlanarGraphUtilities.cs create mode 100644 Assets/Scripts/NIH/MSAGL/GraphmapsWithMesh/PlanarGraphUtilities.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/GraphmapsWithMesh/PointSet.cs create mode 100644 Assets/Scripts/NIH/MSAGL/GraphmapsWithMesh/PointSet.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/GraphmapsWithMesh/PointToSegmentDistance.cs create mode 100644 Assets/Scripts/NIH/MSAGL/GraphmapsWithMesh/PointToSegmentDistance.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/GraphmapsWithMesh/SteinerTree.cs create mode 100644 Assets/Scripts/NIH/MSAGL/GraphmapsWithMesh/SteinerTree.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/GraphmapsWithMesh/Tiling.cs create mode 100644 Assets/Scripts/NIH/MSAGL/GraphmapsWithMesh/Tiling.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Incremental.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Incremental/AxisSolver.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Incremental/AxisSolver.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Incremental/EdgeConstraintGenerator.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Incremental/EdgeConstraintGenerator.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Incremental/FastIncrementalLayout.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Incremental/FastIncrementalLayout.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Incremental/FastIncrementalLayoutSettings.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Incremental/FastIncrementalLayoutSettings.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Incremental/Feasibility.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Incremental/Feasibility.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Incremental/FiEdge.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Incremental/FiEdge.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Incremental/FiNode.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Incremental/FiNode.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Incremental/HorizontalSeparationConstraint.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Incremental/HorizontalSeparationConstraint.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Incremental/IConstraint.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Incremental/IConstraint.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Incremental/LockPosition.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Incremental/LockPosition.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Incremental/MaxSeparationConstraint.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Incremental/MaxSeparationConstraint.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Incremental/MinSeparationConstraint.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Incremental/MinSeparationConstraint.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Incremental/Multipole.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Incremental/Multipole/KDTree.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Incremental/Multipole/KDTree.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Incremental/Multipole/MultipoleCoefficients.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Incremental/Multipole/MultipoleCoefficients.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Incremental/ProcrustesCircleConstraint.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Incremental/ProcrustesCircleConstraint.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Incremental/Relayout.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Incremental/Relayout.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Incremental/StickConstraint.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Incremental/StickConstraint.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Incremental/VerticalSeparationConstraint.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Incremental/VerticalSeparationConstraint.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Initial.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Initial/InitialLayout.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Initial/InitialLayout.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Initial/InitialLayoutByCluster.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Initial/InitialLayoutByCluster.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Initial/InitialLayoutHelpers.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Initial/InitialLayoutHelpers.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/LargeGraphLayout.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/LargeGraphLayout/DeviceIndependedZoomCalculatorForNodes.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/LargeGraphLayout/DeviceIndependedZoomCalculatorForNodes.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/LargeGraphLayout/EdgePicker.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/LargeGraphLayout/EdgePicker.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/LargeGraphLayout/IZoomLevelCalculator.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/LargeGraphLayout/IZoomLevelCalculator.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/LargeGraphLayout/Interval.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/LargeGraphLayout/Interval.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/LargeGraphLayout/LevelCalculator.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/LargeGraphLayout/LevelCalculator.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/LargeGraphLayout/LgCellInfo.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/LargeGraphLayout/LgCellInfo.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/LargeGraphLayout/LgData.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/LargeGraphLayout/LgData.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/LargeGraphLayout/LgEdgeInfo.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/LargeGraphLayout/LgEdgeInfo.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/LargeGraphLayout/LgInteractor.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/LargeGraphLayout/LgInteractor.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/LargeGraphLayout/LgLayoutSettings.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/LargeGraphLayout/LgLayoutSettings.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/LargeGraphLayout/LgLevel.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/LargeGraphLayout/LgLevel.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/LargeGraphLayout/LgNodeCollection.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/LargeGraphLayout/LgNodeCollection.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/LargeGraphLayout/LgPathRouter.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/LargeGraphLayout/LgPathRouter.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/LargeGraphLayout/LgSkeletonLevel.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/LargeGraphLayout/LgSkeletonLevel.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/LargeGraphLayout/NodeRailLevelCalculator.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/LargeGraphLayout/NodeRailLevelCalculator/GreedyNodeRailLevelCalculator.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/LargeGraphLayout/NodeRailLevelCalculator/GreedyNodeRailLevelCalculator.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/LargeGraphLayout/Rail.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/LargeGraphLayout/Rail.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/LargeGraphLayout/RailGraph.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/LargeGraphLayout/RailGraph.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/LargeGraphLayout/RouteSimplifier.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/LargeGraphLayout/RouteSimplifier.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/LargeGraphLayout/ShortestPartRouterForLg.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/LargeGraphLayout/ShortestPartRouterForLg.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/LargeGraphLayout/SimpleEdgeCollection.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/LargeGraphLayout/SimpleEdgeCollection.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/LargeGraphLayout/SimpleNodeCollection.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/LargeGraphLayout/SimpleNodeCollection.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/Anchor.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/Anchor.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/Balancing.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/Balancing.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/DataBase.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/DataBase.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/EdgePathsInserter.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/EdgePathsInserter.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/FlatEdgeRouter.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/FlatEdgeRouter.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/HierarchyCalculator.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/HierarchyCalculator.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/HorizontalConstraintsForSugiyama.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/HorizontalConstraintsForSugiyama.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/IntEdge.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/IntEdge.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/LayerArrays.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/LayerArrays.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/LayerCalculator.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/LayerCalculator.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/LayerEdge.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/LayerEdge.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/LayerInserter.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/LayerInserter.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/LayeredLayout.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/LayeredLayout.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/LayeredLayoutEngine.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/LayeredLayoutEngine.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/Layering.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/Layering/LongestPathLayering.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/Layering/LongestPathLayering.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/Layering/NetworkSimplex.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/Layering/NetworkSimplex.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/NetworkEdge.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/NetworkEdge.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/NetworkSimplexForGeneralGraph.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/NetworkSimplexForGeneralGraph.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/Ordering.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/Ordering/AdjacentSwaps.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/Ordering/AdjacentSwaps.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/Ordering/AdjacentSwapsWithConstraints.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/Ordering/AdjacentSwapsWithConstraints.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/Ordering/ConstrainedOrderMeasure.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/Ordering/ConstrainedOrderMeasure.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/Ordering/ConstrainedOrdering.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/Ordering/ConstrainedOrdering.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/Ordering/EdgeComparerBySource.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/Ordering/EdgeComparerBySource.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/Ordering/EdgeComparerByTarget.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/Ordering/EdgeComparerByTarget.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/Ordering/LayerInfo.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/Ordering/LayerInfo.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/Ordering/MetroMapOrdering.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/Ordering/MetroMapOrdering.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/Ordering/Ordering.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/Ordering/Ordering.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/Ordering/OrderingMeasure.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/Ordering/OrderingMeasure.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/Ordering/SweepMode.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/Ordering/SweepMode.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/ProperLayeredGraph.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/ProperLayeredGraph.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/RecoveryLayerCalculator.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/RecoveryLayerCalculator.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/RecoveryLayeredLayoutEngine.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/RecoveryLayeredLayoutEngine.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/RefinerBetweenTwoLayers.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/RefinerBetweenTwoLayers.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/Routing.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/Routing.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/SmoothedPolylineCalculator.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/SmoothedPolylineCalculator.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/SugiyamaLayoutLogger.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/SugiyamaLayoutLogger.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/SugiyamaLayoutSettings.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/SugiyamaLayoutSettings.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/TwoLayerFlatEdgeRouter.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/TwoLayerFlatEdgeRouter.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/VertexEnumerators.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/VertexEnumerators.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/VerticalConstraintsForSugiyama.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/VerticalConstraintsForSugiyama.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/XCoordsWithAlignment.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/XCoordsWithAlignment.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/XLayoutGraph.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/Layered/XLayoutGraph.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/MDS.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/MDS/AllPairsDistances.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/MDS/AllPairsDistances.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/MDS/MDSGraphLayout.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/MDS/MDSGraphLayout.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/MDS/MDSLayoutSettings.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/MDS/MDSLayoutSettings.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/MDS/PivotDistances.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/MDS/PivotDistances.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/MDS/PivotMDS.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/MDS/PivotMDS.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/MDS/SingleSourceDistances.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/MDS/SingleSourceDistances.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/MDS/Transform.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/MDS/Transform.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/OverlapRemovalFixedSegments.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/OverlapRemovalFixedSegments/FakeBitmap.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/OverlapRemovalFixedSegments/FakeBitmap.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/OverlapRemovalFixedSegments/OverlapRemovalFixedSegmentsBitmap.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/OverlapRemovalFixedSegments/OverlapRemovalFixedSegmentsBitmap.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/OverlapRemovalFixedSegments/OverlapRemovalFixedSegmentsMst.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/OverlapRemovalFixedSegments/OverlapRemovalFixedSegmentsMst.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/OverlapRemovalFixedSegments/PixelPoint.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/OverlapRemovalFixedSegments/PixelPoint.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/OverlapRemovalFixedSegments/RectSegIntersection.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Layout/OverlapRemovalFixedSegments/RectSegIntersection.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/ConstrainedSkeleton.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/ConstrainedSkeleton/SteinerCdt.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/ConstrainedSkeleton/SteinerCdt.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/Constraints.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/Constraints/AlignmentConstraint.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/Constraints/AlignmentConstraint.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/LayoutEditing.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/LayoutEditing/BumperPusher.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/LayoutEditing/BumperPusher.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/LayoutEditing/EdgeRestoreData.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/LayoutEditing/EdgeRestoreData.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/LayoutEditing/GraphRestoreData.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/LayoutEditing/GraphRestoreData.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/LayoutEditing/IncrementalDragger.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/LayoutEditing/IncrementalDragger.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/LayoutEditing/LabelFixture.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/LayoutEditing/LabelFixture.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/LayoutEditing/LabelRestoreData.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/LayoutEditing/LabelRestoreData.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/LayoutEditing/NodeRestoreData.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/LayoutEditing/NodeRestoreData.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/LayoutEditing/ObstacleCalculator.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/LayoutEditing/ObstacleCalculator.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/LayoutEditing/RestoreData.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/LayoutEditing/RestoreData.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/LayoutEditing/RestoreHelper.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/LayoutEditing/RestoreHelper.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/LayoutEditing/RouterBetweenTwoNodes.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/LayoutEditing/RouterBetweenTwoNodes.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/LayoutEditing/WeightedEdge.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/LayoutEditing/WeightedEdge.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/LayoutHelpers.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/LayoutHelpers.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/MultiScale.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/MultiScale/MultiScaleLayout.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/MultiScale/MultiScaleLayout.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/NonOverlappingBoundaries.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/NonOverlappingBoundaries/AllPairsNonOverlappingBoundaries.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/NonOverlappingBoundaries/AllPairsNonOverlappingBoundaries.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/NonOverlappingBoundaries/ProximityQuery.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/NonOverlappingBoundaries/ProximityQuery.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/Phylo.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/Phylo/PhyloEdge.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/Phylo/PhyloEdge.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/Phylo/PhyloTree.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/Phylo/PhyloTree.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/Phylo/PhyloTreeLayoutCalclulation.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/Phylo/PhyloTreeLayoutCalclulation.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/Ranking.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/Ranking/Centrality.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/Ranking/Centrality.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/Ranking/RankingLayout.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/Ranking/RankingLayout.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/Ranking/RankingLayoutSettings.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/Ranking/RankingLayoutSettings.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/RegularGrid.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/RegularGrid/GridTraversal.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/RegularGrid/GridTraversal.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/Routing.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/Routing/ShortestPathTree.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Miscellaneous/Routing/ShortestPathTree.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/ClusterBoundaryPort.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/ClusterBoundaryPort.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/ConstrainedDelaunayTriangulation.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/ConstrainedDelaunayTriangulation/Cdt.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/ConstrainedDelaunayTriangulation/Cdt.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/ConstrainedDelaunayTriangulation/CdtEdge.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/ConstrainedDelaunayTriangulation/CdtEdge.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/ConstrainedDelaunayTriangulation/CdtFront.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/ConstrainedDelaunayTriangulation/CdtFront.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/ConstrainedDelaunayTriangulation/CdtFrontElement.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/ConstrainedDelaunayTriangulation/CdtFrontElement.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/ConstrainedDelaunayTriangulation/CdtSite.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/ConstrainedDelaunayTriangulation/CdtSite.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/ConstrainedDelaunayTriangulation/CdtSweeper.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/ConstrainedDelaunayTriangulation/CdtSweeper.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/ConstrainedDelaunayTriangulation/CdtTriangle.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/ConstrainedDelaunayTriangulation/CdtTriangle.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/ConstrainedDelaunayTriangulation/EdgeInserter.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/ConstrainedDelaunayTriangulation/EdgeInserter.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/ConstrainedDelaunayTriangulation/EdgeTracer.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/ConstrainedDelaunayTriangulation/EdgeTracer.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/ConstrainedDelaunayTriangulation/PerimeterEdge.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/ConstrainedDelaunayTriangulation/PerimeterEdge.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/ConstrainedDelaunayTriangulation/ThreeArray.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/ConstrainedDelaunayTriangulation/ThreeArray.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Corner.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Corner.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/InteractiveEdgeRouter.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/InteractiveEdgeRouter.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/InteractiveObstacleCalculator.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/InteractiveObstacleCalculator.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/MultiEdgeRouter.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/MultiEdgeRouter.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/MultipleSourceMultipleTargetsShortestPathOnVisibilityGraph.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/MultipleSourceMultipleTargetsShortestPathOnVisibilityGraph.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/NodeKind.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/NodeKind.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/PreGraph.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/PreGraph.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/AxisCoordinateEvent.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/AxisCoordinateEvent.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/BasicObstacleSide.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/BasicObstacleSide.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/BasicReflectionEvent.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/BasicReflectionEvent.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/BasicVertexEvent.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/BasicVertexEvent.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/CloseVertexEvent.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/CloseVertexEvent.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/Clump.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/Clump.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/Design for Rectilinear Edge Routing.docx create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/Design for Rectilinear Edge Routing.docx.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/EnumeratorWrapper.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/EnumeratorWrapper.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/EventQueue.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/EventQueue.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/FreePoint.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/FreePoint.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/FullVisibilityGraphGenerator.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/FullVisibilityGraphGenerator.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/GroupBoundaryCrossing.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/GroupBoundaryCrossing.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/GroupBoundaryCrossingMap.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/GroupBoundaryCrossingMap.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/HighBendVertexEvent.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/HighBendVertexEvent.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/HighObstacleSide.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/HighObstacleSide.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/HighReflectionEvent.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/HighReflectionEvent.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/LookaheadScan.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/LookaheadScan.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/LowBendVertexEvent.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/LowBendVertexEvent.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/LowObstacleSide.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/LowObstacleSide.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/LowReflectionEvent.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/LowReflectionEvent.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/MsmtRectilinearPath.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/MsmtRectilinearPath.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/NeighborSides.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/NeighborSides.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/Nudging.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/Nudging/AxisEdge.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/Nudging/AxisEdge.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/Nudging/AxisEdgeHighPointEvent.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/Nudging/AxisEdgeHighPointEvent.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/Nudging/AxisEdgeLowPointEvent.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/Nudging/AxisEdgeLowPointEvent.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/Nudging/AxisEdgesContainer.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/Nudging/AxisEdgesContainer.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/Nudging/CombinatorialNudger.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/Nudging/CombinatorialNudger.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/Nudging/FreeSpaceFinder.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/Nudging/FreeSpaceFinder.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/Nudging/LinkedPoint.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/Nudging/LinkedPoint.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/Nudging/LinkedPointSplitter.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/Nudging/LinkedPointSplitter.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/Nudging/LongestNudgedSegment.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/Nudging/LongestNudgedSegment.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/Nudging/Nudger.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/Nudging/Nudger.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/Nudging/Path.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/Nudging/Path.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/Nudging/PathEdge.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/Nudging/PathEdge.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/Nudging/PathMerger.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/Nudging/PathMerger.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/Nudging/PathRefiner.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/Nudging/PathRefiner.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/Nudging/PointByDelegateComparer.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/Nudging/PointByDelegateComparer.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/Nudging/PointProjection.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/Nudging/PointProjection.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/Nudging/SegWithIndex.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/Nudging/SegWithIndex.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/Nudging/SpaceCalculationEvent.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/Nudging/SpaceCalculationEvent.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/Nudging/StaircaseRemover.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/Nudging/StaircaseRemover.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/Obstacle.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/Obstacle.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/ObstaclePort.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/ObstaclePort.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/ObstaclePortEntrance.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/ObstaclePortEntrance.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/ObstacleTree.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/ObstacleTree.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/OpenVertexEvent.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/OpenVertexEvent.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/OverlapConvexHull.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/OverlapConvexHull.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/PointAndCrossings.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/PointAndCrossings.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/PointAndCrossingsList.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/PointAndCrossingsList.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/PointComparer.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/PointComparer.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/PortManager.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/PortManager.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/RectilinearEdgeRouter.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/RectilinearEdgeRouter.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/RectilinearInteractiveEditor.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/RectilinearInteractiveEditor.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/RectilinearScanLine.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/RectilinearScanLine.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/ScanDirection.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/ScanDirection.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/ScanSegment.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/ScanSegment.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/ScanSegmentTree.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/ScanSegmentTree.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/ScanSegmentVector.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/ScanSegmentVector.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/ScanSegmentVectorItem.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/ScanSegmentVectorItem.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/SegmentIntersector.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/SegmentIntersector.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/SparseVisibilityGraphGenerator.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/SparseVisibilityGraphGenerator.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/SpliceUtility.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/SpliceUtility.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/SsstRectilinearPath.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/SsstRectilinearPath.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/StaticGraphUtility.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/StaticGraphUtility.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/TransientGraphUtility.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/TransientGraphUtility.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/VertexEntry.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/VertexEntry.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/VisibilityGraphGenerator.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/VisibilityGraphGenerator.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/VisibilityVertexRectilinear.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/VisibilityVertexRectilinear.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/WaypointPort.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/WaypointPort.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/RelativeShape.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/RelativeShape.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/RelaxedPolylinePoint.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/RelaxedPolylinePoint.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Shape.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Shape.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/ShapeCreator.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/ShapeCreator.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/ShapeCreatorForRoutingToParents.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/ShapeCreatorForRoutingToParents.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/ShapeObstacleCalculator.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/ShapeObstacleCalculator.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/SingleSourceMultipleTargetsShortestPathOnVisibilityGraph.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/SingleSourceMultipleTargetsShortestPathOnVisibilityGraph.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/SingleSourceSingleTargetShortestPathOnVisibilityGraph.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/SingleSourceSingleTargetShortestPathOnVisibilityGraph.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/BundleBase.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/BundleBase.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/BundleBasesCalculator.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/BundleBasesCalculator.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/BundleInfo.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/BundleInfo.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/BundleRouter.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/BundleRouter.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/BundlingStatus.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/BundlingStatus.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/CdtIntersections.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/CdtIntersections.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/CdtThreader.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/CdtThreader.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/ChannelFlag.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/ChannelFlag.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/CostCalculator.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/CostCalculator.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/EdgeNudger.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/EdgeNudger.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/FlipCollapser.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/FlipCollapser.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/FlipSwitcher.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/FlipSwitcher.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/GeneralMetroMapOrdering.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/GeneralMetroMapOrdering.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/HubDebugger.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/HubDebugger.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/HubRadiiCalculator.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/HubRadiiCalculator.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/IMetroMapOrderingAlgorithm.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/IMetroMapOrderingAlgorithm.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/IntersectionCache.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/IntersectionCache.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/Intersections.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/Intersections.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/LinearMetroMapOrdering.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/LinearMetroMapOrdering.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/MetroGraphData.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/MetroGraphData.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/MetroLine.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/MetroLine.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/MetroNodeInfo.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/MetroNodeInfo.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/NodePositionsAdjuster.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/NodePositionsAdjuster.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/OrientedHubSegment.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/OrientedHubSegment.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/PathFixer.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/PathFixer.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/PointPairOrder.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/PointPairOrder.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/SdBoneEdge.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/SdBoneEdge.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/SdShortestPath.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/SdShortestPath.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/SdVertex.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/SdVertex.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/SimulatedAnnealing.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/SimulatedAnnealing.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/Station.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/Station.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/StationEdgeInfo.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/StationEdgeInfo.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/TimeMeasurer.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/Bundling/TimeMeasurer.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner/BrokenConeSide.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner/BrokenConeSide.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner/Cone.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner/Cone.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner/ConeClosureEvent.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner/ConeClosureEvent.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner/ConeLeftSide.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner/ConeLeftSide.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner/ConeRightSide.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner/ConeRightSide.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner/ConeSide.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner/ConeSide.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner/ConeSideComparer.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner/ConeSideComparer.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner/ConeSpanner.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner/ConeSpanner.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner/ConeSpannerForPortLocations.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner/ConeSpannerForPortLocations.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner/IConeSweeper.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner/IConeSweeper.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner/LeftIntersectionEvent.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner/LeftIntersectionEvent.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner/LeftObstacleSide.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner/LeftObstacleSide.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner/LeftVertexEvent.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner/LeftVertexEvent.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner/LineSweeper.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner/LineSweeper.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner/LineSweeperForPortLocations.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner/LineSweeperForPortLocations.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner/LowestVertexEvent.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner/LowestVertexEvent.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner/ObstacleSide.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner/ObstacleSide.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner/PortLocationEvent.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner/PortLocationEvent.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner/RightIntersectionEvent.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner/RightIntersectionEvent.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner/RightObstacleSide.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner/RightObstacleSide.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner/RightVertexEvent.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner/RightVertexEvent.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner/SweepEvent.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner/SweepEvent.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner/VertexEvent.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Spline/ConeSpanner/VertexEvent.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/SplineRouter.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/SplineRouter.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/StraightLineEdges.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/StraightLineEdges.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/TightLooseCouple.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/TightLooseCouple.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/ActiveDiagonalComparerWithRay.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/ActiveDiagonalComparerWithRay.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/ActiveEdgeComparerWithRay.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/ActiveEdgeComparerWithRay.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/BimodalSequence.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/BimodalSequence.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/Diagonal.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/Diagonal.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/Drawings.png create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/Drawings.png.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/InteractiveTangentVisibilityGraphCalculator.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/InteractiveTangentVisibilityGraphCalculator.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/LineSweeperBase.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/LineSweeperBase.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/ObstacleSideComparer.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/ObstacleSideComparer.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/PointVisibilityCalculator.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/PointVisibilityCalculator.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/Polygon.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/Polygon.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/PortObstacleEvent.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/PortObstacleEvent.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/SegmentBase.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/SegmentBase.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/Spanner visibility graph.docx create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/Spanner visibility graph.docx.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/Stem.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/Stem.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/StemStartPointComparer.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/StemStartPointComparer.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/Tangent.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/Tangent.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/TangentPair.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/TangentPair.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/TangentVisibilityGraphCalculator.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/TangentVisibilityGraphCalculator.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/TollFreeVisibilityEdge.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/TollFreeVisibilityEdge.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/UnimodalSequence.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/UnimodalSequence.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/VisibilityEdge.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/VisibilityEdge.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/VisibilityGraph.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/VisibilityGraph.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/VisibilityKind.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/VisibilityKind.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/VisibilityVertex.cs create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/VisibilityVertex.cs.meta create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/makefile create mode 100644 Assets/Scripts/NIH/MSAGL/Routing/Visibility/makefile.meta create mode 100644 Assets/Scripts/NIH/MSAGL/ValidateArg.cs create mode 100644 Assets/Scripts/NIH/MSAGL/ValidateArg.cs.meta delete mode 100644 Assets/Scripts/ServerShared/Technology.cs delete mode 100644 Assets/Scripts/ServerShared/Technology.cs.meta create mode 100644 Assets/Scripts/TechTreeTestMsagl.cs create mode 100644 Assets/Scripts/TechTreeTestMsagl.cs.meta create mode 100644 Assets/csc.rsp create mode 100644 Assets/csc.rsp.meta diff --git a/.idea/.idea.Aetheria-Economy/.idea/contentModel.xml b/.idea/.idea.Aetheria-Economy/.idea/contentModel.xml index 3f7010721..904cce64b 100644 --- a/.idea/.idea.Aetheria-Economy/.idea/contentModel.xml +++ b/.idea/.idea.Aetheria-Economy/.idea/contentModel.xml @@ -409,6 +409,620 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1258,12 +1872,12 @@ - + diff --git a/Assembly-CSharp-Editor.csproj b/Assembly-CSharp-Editor.csproj index c4086ad73..06c1863c1 100644 --- a/Assembly-CSharp-Editor.csproj +++ b/Assembly-CSharp-Editor.csproj @@ -26,7 +26,7 @@ full false Temp\bin\Debug\ - DEBUG;TRACE;UNITY_5_3_OR_NEWER;UNITY_5_4_OR_NEWER;UNITY_5_5_OR_NEWER;UNITY_5_6_OR_NEWER;UNITY_2017_1_OR_NEWER;UNITY_2017_2_OR_NEWER;UNITY_2017_3_OR_NEWER;UNITY_2017_4_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2018_2_OR_NEWER;UNITY_2018_3_OR_NEWER;UNITY_2018_4_OR_NEWER;UNITY_2018_4_17;UNITY_2018_4;UNITY_2018;PLATFORM_ARCH_64;UNITY_64;UNITY_INCLUDE_TESTS;ENABLE_AUDIO;ENABLE_CACHING;ENABLE_CLOTH;ENABLE_DUCK_TYPING;ENABLE_MICROPHONE;ENABLE_MULTIPLE_DISPLAYS;ENABLE_PHYSICS;ENABLE_SPRITES;ENABLE_GRID;ENABLE_TILEMAP;ENABLE_TERRAIN;ENABLE_TEXTURE_STREAMING;ENABLE_DIRECTOR;ENABLE_UNET;ENABLE_LZMA;ENABLE_UNITYEVENTS;ENABLE_WEBCAM;ENABLE_WWW;ENABLE_CLOUD_SERVICES_COLLAB;ENABLE_CLOUD_SERVICES_COLLAB_SOFTLOCKS;ENABLE_CLOUD_SERVICES_ADS;ENABLE_CLOUD_HUB;ENABLE_CLOUD_PROJECT_ID;ENABLE_CLOUD_SERVICES_USE_WEBREQUEST;ENABLE_CLOUD_SERVICES_UNET;ENABLE_CLOUD_SERVICES_BUILD;ENABLE_CLOUD_LICENSE;ENABLE_EDITOR_HUB;ENABLE_EDITOR_HUB_LICENSE;ENABLE_WEBSOCKET_CLIENT;ENABLE_DIRECTOR_AUDIO;ENABLE_DIRECTOR_TEXTURE;ENABLE_TIMELINE;ENABLE_EDITOR_METRICS;ENABLE_EDITOR_METRICS_CACHING;ENABLE_MANAGED_JOBS;ENABLE_MANAGED_TRANSFORM_JOBS;ENABLE_MANAGED_ANIMATION_JOBS;INCLUDE_DYNAMIC_GI;INCLUDE_GI;ENABLE_MONO_BDWGC;PLATFORM_SUPPORTS_MONO;RENDER_SOFTWARE_CURSOR;INCLUDE_PUBNUB;ENABLE_VIDEO;ENABLE_CUSTOM_RENDER_TEXTURE;ENABLE_LOCALIZATION;PLATFORM_STANDALONE_WIN;PLATFORM_STANDALONE;UNITY_STANDALONE_WIN;UNITY_STANDALONE;ENABLE_SUBSTANCE;ENABLE_RUNTIME_GI;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_CRUNCH_TEXTURE_COMPRESSION;ENABLE_UNITYWEBREQUEST;ENABLE_CLOUD_SERVICES;ENABLE_CLOUD_SERVICES_ANALYTICS;ENABLE_CLOUD_SERVICES_PURCHASING;ENABLE_CLOUD_SERVICES_CRASH_REPORTING;ENABLE_OUT_OF_PROCESS_CRASH_HANDLER;ENABLE_EVENT_QUEUE;ENABLE_CLUSTER_SYNC;ENABLE_CLUSTERINPUT;ENABLE_VR;ENABLE_AR;ENABLE_WEBSOCKET_HOST;ENABLE_MONO;NET_4_6;ENABLE_PROFILER;UNITY_ASSERTIONS;UNITY_EDITOR;UNITY_EDITOR_64;UNITY_EDITOR_WIN;ENABLE_UNITY_COLLECTIONS_CHECKS;ENABLE_BURST_AOT;UNITY_TEAM_LICENSE;UNITY_PRO_LICENSE;UNITY_POST_PROCESSING_STACK_V2;CSHARP_7_OR_LATER;CSHARP_7_3_OR_NEWER + DEBUG;TRACE;UNITY_5_3_OR_NEWER;UNITY_5_4_OR_NEWER;UNITY_5_5_OR_NEWER;UNITY_5_6_OR_NEWER;UNITY_2017_1_OR_NEWER;UNITY_2017_2_OR_NEWER;UNITY_2017_3_OR_NEWER;UNITY_2017_4_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2018_2_OR_NEWER;UNITY_2018_3_OR_NEWER;UNITY_2018_4_OR_NEWER;UNITY_2018_4_17;UNITY_2018_4;UNITY_2018;PLATFORM_ARCH_64;UNITY_64;UNITY_INCLUDE_TESTS;ENABLE_AUDIO;ENABLE_CACHING;ENABLE_CLOTH;ENABLE_DUCK_TYPING;ENABLE_MICROPHONE;ENABLE_MULTIPLE_DISPLAYS;ENABLE_PHYSICS;ENABLE_SPRITES;ENABLE_GRID;ENABLE_TILEMAP;ENABLE_TERRAIN;ENABLE_TEXTURE_STREAMING;ENABLE_DIRECTOR;ENABLE_UNET;ENABLE_LZMA;ENABLE_UNITYEVENTS;ENABLE_WEBCAM;ENABLE_WWW;ENABLE_CLOUD_SERVICES_COLLAB;ENABLE_CLOUD_SERVICES_COLLAB_SOFTLOCKS;ENABLE_CLOUD_SERVICES_ADS;ENABLE_CLOUD_HUB;ENABLE_CLOUD_PROJECT_ID;ENABLE_CLOUD_SERVICES_USE_WEBREQUEST;ENABLE_CLOUD_SERVICES_UNET;ENABLE_CLOUD_SERVICES_BUILD;ENABLE_CLOUD_LICENSE;ENABLE_EDITOR_HUB;ENABLE_EDITOR_HUB_LICENSE;ENABLE_WEBSOCKET_CLIENT;ENABLE_DIRECTOR_AUDIO;ENABLE_DIRECTOR_TEXTURE;ENABLE_TIMELINE;ENABLE_EDITOR_METRICS;ENABLE_EDITOR_METRICS_CACHING;ENABLE_MANAGED_JOBS;ENABLE_MANAGED_TRANSFORM_JOBS;ENABLE_MANAGED_ANIMATION_JOBS;INCLUDE_DYNAMIC_GI;INCLUDE_GI;ENABLE_MONO_BDWGC;PLATFORM_SUPPORTS_MONO;RENDER_SOFTWARE_CURSOR;INCLUDE_PUBNUB;ENABLE_VIDEO;ENABLE_CUSTOM_RENDER_TEXTURE;ENABLE_LOCALIZATION;PLATFORM_STANDALONE_WIN;PLATFORM_STANDALONE;UNITY_STANDALONE_WIN;UNITY_STANDALONE;ENABLE_SUBSTANCE;ENABLE_RUNTIME_GI;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_CRUNCH_TEXTURE_COMPRESSION;ENABLE_UNITYWEBREQUEST;ENABLE_CLOUD_SERVICES;ENABLE_CLOUD_SERVICES_ANALYTICS;ENABLE_CLOUD_SERVICES_PURCHASING;ENABLE_CLOUD_SERVICES_CRASH_REPORTING;ENABLE_OUT_OF_PROCESS_CRASH_HANDLER;ENABLE_EVENT_QUEUE;ENABLE_CLUSTER_SYNC;ENABLE_CLUSTERINPUT;ENABLE_VR;ENABLE_AR;ENABLE_WEBSOCKET_HOST;ENABLE_MONO;NET_4_6;ENABLE_PROFILER;UNITY_ASSERTIONS;UNITY_EDITOR;UNITY_EDITOR_64;UNITY_EDITOR_WIN;ENABLE_UNITY_COLLECTIONS_CHECKS;ENABLE_BURST_AOT;UNITY_TEAM_LICENSE;UNITY_PRO_LICENSE;UNITY_POST_PROCESSING_STACK_V2;TEST_MSAGL;REPORTING;PERSISTENCE;CSHARP_7_OR_LATER;CSHARP_7_3_OR_NEWER prompt 4 0169 @@ -383,6 +383,9 @@ C:/Users/rwvens/Documents/GitHub/Aetheria-Economy/Assets/Plugins/Newtonsoft.Json.dll + + C:/Users/rwvens/Documents/GitHub/Aetheria-Economy/Assets/Plugins/QuickGraph.dll + C:/Users/rwvens/Documents/GitHub/Aetheria-Economy/Assets/Plugins/Remotion.Linq.dll @@ -410,9 +413,6 @@ C:/Users/rwvens/Documents/GitHub/Aetheria-Economy/Assets/Plugins/Grpc.Core.Api/lib/net45/Grpc.Core.Api.dll - - C:/Users/rwvens/Documents/GitHub/Aetheria-Economy/Assets/Plugins/QuickGraph.dll - C:/Program Files/Unity/Hub/Editor/2018.4.17f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/mscorlib.dll diff --git a/Assembly-CSharp-firstpass.csproj b/Assembly-CSharp-firstpass.csproj index c213bfb9b..b5d64bebf 100644 --- a/Assembly-CSharp-firstpass.csproj +++ b/Assembly-CSharp-firstpass.csproj @@ -26,7 +26,7 @@ full false Temp\bin\Debug\ - DEBUG;TRACE;UNITY_5_3_OR_NEWER;UNITY_5_4_OR_NEWER;UNITY_5_5_OR_NEWER;UNITY_5_6_OR_NEWER;UNITY_2017_1_OR_NEWER;UNITY_2017_2_OR_NEWER;UNITY_2017_3_OR_NEWER;UNITY_2017_4_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2018_2_OR_NEWER;UNITY_2018_3_OR_NEWER;UNITY_2018_4_OR_NEWER;UNITY_2018_4_17;UNITY_2018_4;UNITY_2018;PLATFORM_ARCH_64;UNITY_64;UNITY_INCLUDE_TESTS;ENABLE_AUDIO;ENABLE_CACHING;ENABLE_CLOTH;ENABLE_DUCK_TYPING;ENABLE_MICROPHONE;ENABLE_MULTIPLE_DISPLAYS;ENABLE_PHYSICS;ENABLE_SPRITES;ENABLE_GRID;ENABLE_TILEMAP;ENABLE_TERRAIN;ENABLE_TEXTURE_STREAMING;ENABLE_DIRECTOR;ENABLE_UNET;ENABLE_LZMA;ENABLE_UNITYEVENTS;ENABLE_WEBCAM;ENABLE_WWW;ENABLE_CLOUD_SERVICES_COLLAB;ENABLE_CLOUD_SERVICES_COLLAB_SOFTLOCKS;ENABLE_CLOUD_SERVICES_ADS;ENABLE_CLOUD_HUB;ENABLE_CLOUD_PROJECT_ID;ENABLE_CLOUD_SERVICES_USE_WEBREQUEST;ENABLE_CLOUD_SERVICES_UNET;ENABLE_CLOUD_SERVICES_BUILD;ENABLE_CLOUD_LICENSE;ENABLE_EDITOR_HUB;ENABLE_EDITOR_HUB_LICENSE;ENABLE_WEBSOCKET_CLIENT;ENABLE_DIRECTOR_AUDIO;ENABLE_DIRECTOR_TEXTURE;ENABLE_TIMELINE;ENABLE_EDITOR_METRICS;ENABLE_EDITOR_METRICS_CACHING;ENABLE_MANAGED_JOBS;ENABLE_MANAGED_TRANSFORM_JOBS;ENABLE_MANAGED_ANIMATION_JOBS;INCLUDE_DYNAMIC_GI;INCLUDE_GI;ENABLE_MONO_BDWGC;PLATFORM_SUPPORTS_MONO;RENDER_SOFTWARE_CURSOR;INCLUDE_PUBNUB;ENABLE_VIDEO;ENABLE_CUSTOM_RENDER_TEXTURE;ENABLE_LOCALIZATION;PLATFORM_STANDALONE_WIN;PLATFORM_STANDALONE;UNITY_STANDALONE_WIN;UNITY_STANDALONE;ENABLE_SUBSTANCE;ENABLE_RUNTIME_GI;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_CRUNCH_TEXTURE_COMPRESSION;ENABLE_UNITYWEBREQUEST;ENABLE_CLOUD_SERVICES;ENABLE_CLOUD_SERVICES_ANALYTICS;ENABLE_CLOUD_SERVICES_PURCHASING;ENABLE_CLOUD_SERVICES_CRASH_REPORTING;ENABLE_OUT_OF_PROCESS_CRASH_HANDLER;ENABLE_EVENT_QUEUE;ENABLE_CLUSTER_SYNC;ENABLE_CLUSTERINPUT;ENABLE_VR;ENABLE_AR;ENABLE_WEBSOCKET_HOST;ENABLE_MONO;NET_4_6;ENABLE_PROFILER;UNITY_ASSERTIONS;UNITY_EDITOR;UNITY_EDITOR_64;UNITY_EDITOR_WIN;ENABLE_UNITY_COLLECTIONS_CHECKS;ENABLE_BURST_AOT;UNITY_TEAM_LICENSE;UNITY_PRO_LICENSE;UNITY_POST_PROCESSING_STACK_V2;CSHARP_7_OR_LATER;CSHARP_7_3_OR_NEWER + DEBUG;TRACE;UNITY_5_3_OR_NEWER;UNITY_5_4_OR_NEWER;UNITY_5_5_OR_NEWER;UNITY_5_6_OR_NEWER;UNITY_2017_1_OR_NEWER;UNITY_2017_2_OR_NEWER;UNITY_2017_3_OR_NEWER;UNITY_2017_4_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2018_2_OR_NEWER;UNITY_2018_3_OR_NEWER;UNITY_2018_4_OR_NEWER;UNITY_2018_4_17;UNITY_2018_4;UNITY_2018;PLATFORM_ARCH_64;UNITY_64;UNITY_INCLUDE_TESTS;ENABLE_AUDIO;ENABLE_CACHING;ENABLE_CLOTH;ENABLE_DUCK_TYPING;ENABLE_MICROPHONE;ENABLE_MULTIPLE_DISPLAYS;ENABLE_PHYSICS;ENABLE_SPRITES;ENABLE_GRID;ENABLE_TILEMAP;ENABLE_TERRAIN;ENABLE_TEXTURE_STREAMING;ENABLE_DIRECTOR;ENABLE_UNET;ENABLE_LZMA;ENABLE_UNITYEVENTS;ENABLE_WEBCAM;ENABLE_WWW;ENABLE_CLOUD_SERVICES_COLLAB;ENABLE_CLOUD_SERVICES_COLLAB_SOFTLOCKS;ENABLE_CLOUD_SERVICES_ADS;ENABLE_CLOUD_HUB;ENABLE_CLOUD_PROJECT_ID;ENABLE_CLOUD_SERVICES_USE_WEBREQUEST;ENABLE_CLOUD_SERVICES_UNET;ENABLE_CLOUD_SERVICES_BUILD;ENABLE_CLOUD_LICENSE;ENABLE_EDITOR_HUB;ENABLE_EDITOR_HUB_LICENSE;ENABLE_WEBSOCKET_CLIENT;ENABLE_DIRECTOR_AUDIO;ENABLE_DIRECTOR_TEXTURE;ENABLE_TIMELINE;ENABLE_EDITOR_METRICS;ENABLE_EDITOR_METRICS_CACHING;ENABLE_MANAGED_JOBS;ENABLE_MANAGED_TRANSFORM_JOBS;ENABLE_MANAGED_ANIMATION_JOBS;INCLUDE_DYNAMIC_GI;INCLUDE_GI;ENABLE_MONO_BDWGC;PLATFORM_SUPPORTS_MONO;RENDER_SOFTWARE_CURSOR;INCLUDE_PUBNUB;ENABLE_VIDEO;ENABLE_CUSTOM_RENDER_TEXTURE;ENABLE_LOCALIZATION;PLATFORM_STANDALONE_WIN;PLATFORM_STANDALONE;UNITY_STANDALONE_WIN;UNITY_STANDALONE;ENABLE_SUBSTANCE;ENABLE_RUNTIME_GI;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_CRUNCH_TEXTURE_COMPRESSION;ENABLE_UNITYWEBREQUEST;ENABLE_CLOUD_SERVICES;ENABLE_CLOUD_SERVICES_ANALYTICS;ENABLE_CLOUD_SERVICES_PURCHASING;ENABLE_CLOUD_SERVICES_CRASH_REPORTING;ENABLE_OUT_OF_PROCESS_CRASH_HANDLER;ENABLE_EVENT_QUEUE;ENABLE_CLUSTER_SYNC;ENABLE_CLUSTERINPUT;ENABLE_VR;ENABLE_AR;ENABLE_WEBSOCKET_HOST;ENABLE_MONO;NET_4_6;ENABLE_PROFILER;UNITY_ASSERTIONS;UNITY_EDITOR;UNITY_EDITOR_64;UNITY_EDITOR_WIN;ENABLE_UNITY_COLLECTIONS_CHECKS;ENABLE_BURST_AOT;UNITY_TEAM_LICENSE;UNITY_PRO_LICENSE;UNITY_POST_PROCESSING_STACK_V2;TEST_MSAGL;REPORTING;PERSISTENCE;CSHARP_7_OR_LATER;CSHARP_7_3_OR_NEWER prompt 4 0169 @@ -297,6 +297,9 @@ C:/Users/rwvens/Documents/GitHub/Aetheria-Economy/Assets/Plugins/Newtonsoft.Json.dll + + C:/Users/rwvens/Documents/GitHub/Aetheria-Economy/Assets/Plugins/QuickGraph.dll + C:/Users/rwvens/Documents/GitHub/Aetheria-Economy/Assets/Plugins/Remotion.Linq.dll @@ -321,9 +324,6 @@ C:/Users/rwvens/Documents/GitHub/Aetheria-Economy/Assets/Plugins/Grpc.Core.Api/lib/net45/Grpc.Core.Api.dll - - C:/Users/rwvens/Documents/GitHub/Aetheria-Economy/Assets/Plugins/QuickGraph.dll - C:/Program Files/Unity/Hub/Editor/2018.4.17f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/mscorlib.dll diff --git a/Assembly-CSharp.csproj b/Assembly-CSharp.csproj index 690530951..f74e7030c 100644 --- a/Assembly-CSharp.csproj +++ b/Assembly-CSharp.csproj @@ -26,7 +26,7 @@ full false Temp\bin\Debug\ - DEBUG;TRACE;UNITY_5_3_OR_NEWER;UNITY_5_4_OR_NEWER;UNITY_5_5_OR_NEWER;UNITY_5_6_OR_NEWER;UNITY_2017_1_OR_NEWER;UNITY_2017_2_OR_NEWER;UNITY_2017_3_OR_NEWER;UNITY_2017_4_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2018_2_OR_NEWER;UNITY_2018_3_OR_NEWER;UNITY_2018_4_OR_NEWER;UNITY_2018_4_17;UNITY_2018_4;UNITY_2018;PLATFORM_ARCH_64;UNITY_64;UNITY_INCLUDE_TESTS;ENABLE_AUDIO;ENABLE_CACHING;ENABLE_CLOTH;ENABLE_DUCK_TYPING;ENABLE_MICROPHONE;ENABLE_MULTIPLE_DISPLAYS;ENABLE_PHYSICS;ENABLE_SPRITES;ENABLE_GRID;ENABLE_TILEMAP;ENABLE_TERRAIN;ENABLE_TEXTURE_STREAMING;ENABLE_DIRECTOR;ENABLE_UNET;ENABLE_LZMA;ENABLE_UNITYEVENTS;ENABLE_WEBCAM;ENABLE_WWW;ENABLE_CLOUD_SERVICES_COLLAB;ENABLE_CLOUD_SERVICES_COLLAB_SOFTLOCKS;ENABLE_CLOUD_SERVICES_ADS;ENABLE_CLOUD_HUB;ENABLE_CLOUD_PROJECT_ID;ENABLE_CLOUD_SERVICES_USE_WEBREQUEST;ENABLE_CLOUD_SERVICES_UNET;ENABLE_CLOUD_SERVICES_BUILD;ENABLE_CLOUD_LICENSE;ENABLE_EDITOR_HUB;ENABLE_EDITOR_HUB_LICENSE;ENABLE_WEBSOCKET_CLIENT;ENABLE_DIRECTOR_AUDIO;ENABLE_DIRECTOR_TEXTURE;ENABLE_TIMELINE;ENABLE_EDITOR_METRICS;ENABLE_EDITOR_METRICS_CACHING;ENABLE_MANAGED_JOBS;ENABLE_MANAGED_TRANSFORM_JOBS;ENABLE_MANAGED_ANIMATION_JOBS;INCLUDE_DYNAMIC_GI;INCLUDE_GI;ENABLE_MONO_BDWGC;PLATFORM_SUPPORTS_MONO;RENDER_SOFTWARE_CURSOR;INCLUDE_PUBNUB;ENABLE_VIDEO;ENABLE_CUSTOM_RENDER_TEXTURE;ENABLE_LOCALIZATION;PLATFORM_STANDALONE_WIN;PLATFORM_STANDALONE;UNITY_STANDALONE_WIN;UNITY_STANDALONE;ENABLE_SUBSTANCE;ENABLE_RUNTIME_GI;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_CRUNCH_TEXTURE_COMPRESSION;ENABLE_UNITYWEBREQUEST;ENABLE_CLOUD_SERVICES;ENABLE_CLOUD_SERVICES_ANALYTICS;ENABLE_CLOUD_SERVICES_PURCHASING;ENABLE_CLOUD_SERVICES_CRASH_REPORTING;ENABLE_OUT_OF_PROCESS_CRASH_HANDLER;ENABLE_EVENT_QUEUE;ENABLE_CLUSTER_SYNC;ENABLE_CLUSTERINPUT;ENABLE_VR;ENABLE_AR;ENABLE_WEBSOCKET_HOST;ENABLE_MONO;NET_4_6;ENABLE_PROFILER;UNITY_ASSERTIONS;UNITY_EDITOR;UNITY_EDITOR_64;UNITY_EDITOR_WIN;ENABLE_UNITY_COLLECTIONS_CHECKS;ENABLE_BURST_AOT;UNITY_TEAM_LICENSE;UNITY_PRO_LICENSE;UNITY_POST_PROCESSING_STACK_V2;CSHARP_7_OR_LATER;CSHARP_7_3_OR_NEWER + DEBUG;TRACE;UNITY_5_3_OR_NEWER;UNITY_5_4_OR_NEWER;UNITY_5_5_OR_NEWER;UNITY_5_6_OR_NEWER;UNITY_2017_1_OR_NEWER;UNITY_2017_2_OR_NEWER;UNITY_2017_3_OR_NEWER;UNITY_2017_4_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2018_2_OR_NEWER;UNITY_2018_3_OR_NEWER;UNITY_2018_4_OR_NEWER;UNITY_2018_4_17;UNITY_2018_4;UNITY_2018;PLATFORM_ARCH_64;UNITY_64;UNITY_INCLUDE_TESTS;ENABLE_AUDIO;ENABLE_CACHING;ENABLE_CLOTH;ENABLE_DUCK_TYPING;ENABLE_MICROPHONE;ENABLE_MULTIPLE_DISPLAYS;ENABLE_PHYSICS;ENABLE_SPRITES;ENABLE_GRID;ENABLE_TILEMAP;ENABLE_TERRAIN;ENABLE_TEXTURE_STREAMING;ENABLE_DIRECTOR;ENABLE_UNET;ENABLE_LZMA;ENABLE_UNITYEVENTS;ENABLE_WEBCAM;ENABLE_WWW;ENABLE_CLOUD_SERVICES_COLLAB;ENABLE_CLOUD_SERVICES_COLLAB_SOFTLOCKS;ENABLE_CLOUD_SERVICES_ADS;ENABLE_CLOUD_HUB;ENABLE_CLOUD_PROJECT_ID;ENABLE_CLOUD_SERVICES_USE_WEBREQUEST;ENABLE_CLOUD_SERVICES_UNET;ENABLE_CLOUD_SERVICES_BUILD;ENABLE_CLOUD_LICENSE;ENABLE_EDITOR_HUB;ENABLE_EDITOR_HUB_LICENSE;ENABLE_WEBSOCKET_CLIENT;ENABLE_DIRECTOR_AUDIO;ENABLE_DIRECTOR_TEXTURE;ENABLE_TIMELINE;ENABLE_EDITOR_METRICS;ENABLE_EDITOR_METRICS_CACHING;ENABLE_MANAGED_JOBS;ENABLE_MANAGED_TRANSFORM_JOBS;ENABLE_MANAGED_ANIMATION_JOBS;INCLUDE_DYNAMIC_GI;INCLUDE_GI;ENABLE_MONO_BDWGC;PLATFORM_SUPPORTS_MONO;RENDER_SOFTWARE_CURSOR;INCLUDE_PUBNUB;ENABLE_VIDEO;ENABLE_CUSTOM_RENDER_TEXTURE;ENABLE_LOCALIZATION;PLATFORM_STANDALONE_WIN;PLATFORM_STANDALONE;UNITY_STANDALONE_WIN;UNITY_STANDALONE;ENABLE_SUBSTANCE;ENABLE_RUNTIME_GI;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_CRUNCH_TEXTURE_COMPRESSION;ENABLE_UNITYWEBREQUEST;ENABLE_CLOUD_SERVICES;ENABLE_CLOUD_SERVICES_ANALYTICS;ENABLE_CLOUD_SERVICES_PURCHASING;ENABLE_CLOUD_SERVICES_CRASH_REPORTING;ENABLE_OUT_OF_PROCESS_CRASH_HANDLER;ENABLE_EVENT_QUEUE;ENABLE_CLUSTER_SYNC;ENABLE_CLUSTERINPUT;ENABLE_VR;ENABLE_AR;ENABLE_WEBSOCKET_HOST;ENABLE_MONO;NET_4_6;ENABLE_PROFILER;UNITY_ASSERTIONS;UNITY_EDITOR;UNITY_EDITOR_64;UNITY_EDITOR_WIN;ENABLE_UNITY_COLLECTIONS_CHECKS;ENABLE_BURST_AOT;UNITY_TEAM_LICENSE;UNITY_PRO_LICENSE;UNITY_POST_PROCESSING_STACK_V2;TEST_MSAGL;REPORTING;PERSISTENCE;CSHARP_7_OR_LATER;CSHARP_7_3_OR_NEWER prompt 4 0169 @@ -241,6 +241,516 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -511,6 +1021,7 @@ + @@ -793,6 +1304,9 @@ C:/Users/rwvens/Documents/GitHub/Aetheria-Economy/Assets/Plugins/Newtonsoft.Json.dll + + C:/Users/rwvens/Documents/GitHub/Aetheria-Economy/Assets/Plugins/QuickGraph.dll + C:/Users/rwvens/Documents/GitHub/Aetheria-Economy/Assets/Plugins/Remotion.Linq.dll @@ -817,9 +1331,6 @@ C:/Users/rwvens/Documents/GitHub/Aetheria-Economy/Assets/Plugins/Grpc.Core.Api/lib/net45/Grpc.Core.Api.dll - - C:/Users/rwvens/Documents/GitHub/Aetheria-Economy/Assets/Plugins/QuickGraph.dll - C:/Program Files/Unity/Hub/Editor/2018.4.17f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/mscorlib.dll diff --git a/Assets/Resources/Elysium.asset b/Assets/Resources/Elysium.asset index f53b5b69b..270b14d10 100644 --- a/Assets/Resources/Elysium.asset +++ b/Assets/Resources/Elysium.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:30c451432161fff5c202ce8217149ad255f4f3a66cd71aec2ce8722dd3f6d310 -size 7770 +oid sha256:b50f0d89a6d4b0efcc7303858ec2ec24e72ca68c67b45596f58387cfd601f08e +size 7792 diff --git a/Assets/Scenes/Main.unity b/Assets/Scenes/Main.unity index a38f4124c..26fb0aba5 100644 --- a/Assets/Scenes/Main.unity +++ b/Assets/Scenes/Main.unity @@ -7880,7 +7880,7 @@ MonoBehaviour: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 435654366} - m_Enabled: 0 + m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 0ae6f9307731b8f469a018c69d95d2ba, type: 3} m_Name: @@ -11412,12 +11412,12 @@ PrefabInstance: - target: {fileID: 3856099931455214244, guid: b93e36d73a071274d8e9225e077ce92a, type: 3} propertyPath: m_Name - value: Zone + value: Sector objectReference: {fileID: 0} - target: {fileID: 4750078953547169287, guid: b93e36d73a071274d8e9225e077ce92a, type: 3} propertyPath: m_text - value: Zone + value: Sector objectReference: {fileID: 0} - target: {fileID: 4750078953547169287, guid: b93e36d73a071274d8e9225e077ce92a, type: 3} @@ -11427,7 +11427,7 @@ PrefabInstance: - target: {fileID: 4750078953547169287, guid: b93e36d73a071274d8e9225e077ce92a, type: 3} propertyPath: m_textInfo.characterCount - value: 4 + value: 6 objectReference: {fileID: 0} - target: {fileID: 4750078953547169287, guid: b93e36d73a071274d8e9225e077ce92a, type: 3} @@ -11449,6 +11449,11 @@ PrefabInstance: propertyPath: m_textInfo.pageCount value: 1 objectReference: {fileID: 0} + - target: {fileID: 4750078953547169287, guid: b93e36d73a071274d8e9225e077ce92a, + type: 3} + propertyPath: m_subTextObjects.Array.data[1] + value: + objectReference: {fileID: 105783976} - target: {fileID: 5318229203699776728, guid: b93e36d73a071274d8e9225e077ce92a, type: 3} propertyPath: m_LocalPosition.x @@ -11562,7 +11567,7 @@ PrefabInstance: - target: {fileID: 6493174789768221420, guid: b93e36d73a071274d8e9225e077ce92a, type: 3} propertyPath: m_AnchoredPosition.x - value: 68.3 + value: 78.765 objectReference: {fileID: 0} - target: {fileID: 6493174789768221420, guid: b93e36d73a071274d8e9225e077ce92a, type: 3} @@ -14296,6 +14301,7 @@ GameObject: - component: {fileID: 712278634} - component: {fileID: 712278633} - component: {fileID: 712278632} + - component: {fileID: 712278635} m_Layer: 0 m_Name: Tech View m_TagString: Untagged @@ -14310,7 +14316,7 @@ MonoBehaviour: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 712278631} - m_Enabled: 1 + m_Enabled: 0 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 6270ad993ac7b944786c936b7b058263, type: 3} m_Name: @@ -14322,24 +14328,28 @@ MonoBehaviour: RadialLink: {fileID: 1417816149} Arrow: {fileID: 1083812776} Algorithm: 1 - TechCount: 1000 - SeedTechs: 15 - RollingWindowSize: 50 + TechCount: 200 + SeedTechs: 10 + RollingWindowSize: 20 EliminateIslands: 0 - DisconnectedMultiplier: 2 + MultipleDependencies: 0 + MultipleDependencyProbability: 0.5 + MaxDependencyDistance: 4 + MaxDependencyTierDifference: 2 + DisconnectedMultiplier: 4 K: 0.25 - LengthFactor: 1 - Iterations: 100 + LengthFactor: 2 + Iterations: 1000 LayerDistance: 1 - VertexDistance: 0.1 + VertexDistance: 1 WidthPerHeight: 1 PositionMode: -1 Radial: 1 - MinimizeEdgeLength: 0 - OptimizeWidth: 0 + MinimizeEdgeLength: 1 + OptimizeWidth: 1 Sections: 360 PaddingRadians: 3.14 - StartTier: 10 + StartTier: 4 Scale: 2 LinkPoints: 32 LinkDepth: 5 @@ -14347,7 +14357,7 @@ MonoBehaviour: RingDepth: 6 ArrowDepth: 3 Saturation: 0.95 - HueMin: 0.5 + HueMin: 0 HueMax: 1 Brightness: 1 RingInnerSaturation: 1 @@ -14394,6 +14404,56 @@ Transform: m_Father: {fileID: 0} m_RootOrder: 6 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &712278635 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 712278631} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d47bb1c8e9ce08e41877db523d91a2f6, type: 3} + m_Name: + m_EditorClassIdentifier: + IconsPath: Sprites/Icons/Tech + Ring: {fileID: 680383218} + Tech: {fileID: 586612708} + RadialLink: {fileID: 1417816149} + Arrow: {fileID: 1083812776} + TechCount: 500 + SeedTechs: 15 + RollingWindowSize: 100 + Radial: 1 + MultipleDependencyProbability: 0.15 + DependencyDepth: 2 + DependencyAncestorDepth: 4 + PackingMethod: 0 + Sections: 360 + PaddingRadians: 3.14 + StartTier: 12 + Scale: 2 + LinkPoints: 16 + LinkDepth: 5 + LinkTargetDistance: 0.75 + RingDepth: 6 + ArrowDepth: 3 + LayerDistance: 1.25 + Saturation: 0.95 + HueMin: 0 + HueMax: 1 + Brightness: 1 + RingInnerSaturation: 1 + RingOuterSaturation: 0.25 + RingInnerBrightness: 1 + RingOuterBrightness: 0.25 + RingColorKeys: 8 + FillOpacity: 0.3 + GlowOpacity: 0.6 + TechFillMaterial: {fileID: 2100000, guid: c3c13ba00125fdf4c92bd96210dc7230, type: 2} + TechGlowMaterial: {fileID: 2100000, guid: 1426b25e088759647b77658321197be7, type: 2} + TechArrowMaterial: {fileID: 2100000, guid: 2a2ec810cb24ddb4d869e73fe8a1510a, type: 2} + TechLinkMaterial: {fileID: 2100000, guid: 67e23cda35beab546a57003dd2d33de1, type: 2} --- !u!1 &716116810 GameObject: m_ObjectHideFlags: 0 @@ -22522,7 +22582,7 @@ MonoBehaviour: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1126386499} - m_Enabled: 1 + m_Enabled: 0 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 1c1faf0a46b9cf24498cd5ec93c34c71, type: 3} m_Name: @@ -38510,6 +38570,11 @@ PrefabInstance: propertyPath: m_textInfo.pageCount value: 1 objectReference: {fileID: 0} + - target: {fileID: 4750078953547169287, guid: b93e36d73a071274d8e9225e077ce92a, + type: 3} + propertyPath: m_subTextObjects.Array.data[1] + value: + objectReference: {fileID: 426675036} - target: {fileID: 5318229203699776728, guid: b93e36d73a071274d8e9225e077ce92a, type: 3} propertyPath: m_LocalPosition.x @@ -39972,6 +40037,7 @@ GameObject: m_Component: - component: {fileID: 1941419274} - component: {fileID: 1941419275} + - component: {fileID: 1941419276} m_Layer: 0 m_Name: Link m_TagString: Untagged @@ -40006,6 +40072,19 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 1281fe44325f9b548aa42dc331b17266, type: 3} m_Name: m_EditorClassIdentifier: +--- !u!114 &1941419276 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1941419273} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d54298fa75eb6304489e19a84af3413f, type: 3} + m_Name: + m_EditorClassIdentifier: + Icon: {fileID: 1813193646} --- !u!1 &1944537660 GameObject: m_ObjectHideFlags: 0 @@ -43547,9 +43626,7 @@ PrefabInstance: - target: {fileID: 4750078953547169287, guid: b93e36d73a071274d8e9225e077ce92a, type: 3} propertyPath: m_text - value: 'Corporation - -' + value: Corporation objectReference: {fileID: 0} - target: {fileID: 4750078953547169287, guid: b93e36d73a071274d8e9225e077ce92a, type: 3} @@ -43559,7 +43636,7 @@ PrefabInstance: - target: {fileID: 4750078953547169287, guid: b93e36d73a071274d8e9225e077ce92a, type: 3} propertyPath: m_textInfo.characterCount - value: 12 + value: 11 objectReference: {fileID: 0} - target: {fileID: 4750078953547169287, guid: b93e36d73a071274d8e9225e077ce92a, type: 3} @@ -43581,6 +43658,16 @@ PrefabInstance: propertyPath: m_subTextObjects.Array.data[1] value: objectReference: {fileID: 650897775} + - target: {fileID: 4750078953547169287, guid: b93e36d73a071274d8e9225e077ce92a, + type: 3} + propertyPath: m_textInfo.lineCount + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 4750078953547169287, guid: b93e36d73a071274d8e9225e077ce92a, + type: 3} + propertyPath: m_textInfo.pageCount + value: 1 + objectReference: {fileID: 0} - target: {fileID: 5318229203699776728, guid: b93e36d73a071274d8e9225e077ce92a, type: 3} propertyPath: m_LocalPosition.x diff --git a/Assets/Scripts/Editor/DatabaseInspector.cs b/Assets/Scripts/Editor/DatabaseInspector.cs index 2f2606924..583c173cd 100644 --- a/Assets/Scripts/Editor/DatabaseInspector.cs +++ b/Assets/Scripts/Editor/DatabaseInspector.cs @@ -197,7 +197,7 @@ public class DatabaseInspector : EditorWindow else if (type == typeof(Dictionary)) { var dict = (Dictionary) field.GetValue(obj); - Inspect(field.Name.SplitCamelCase(), ref dict); + Inspect(field.Name.SplitCamelCase(), ref dict, link.EntryType); } else if (type == typeof(Dictionary)) { @@ -611,7 +611,7 @@ public class DatabaseInspector : EditorWindow return value; } - public void Inspect(string label, ref Dictionary value) + public void Inspect(string label, ref Dictionary value, Type referenceType) { Space(); LabelField(label, EditorStyles.boldLabel); @@ -629,7 +629,11 @@ public class DatabaseInspector : EditorWindow { using (var h = new HorizontalScope(_list.ListItemStyle)) { - GUILayout.Label(DatabaseCache.Get(ingredient.Key).Name); + var entry = DatabaseCache.Get(ingredient.Key); + if(entry is INamedEntry named) + GUILayout.Label(named.EntryName); + else GUILayout.Label(entry.ID.ToString()); + value[ingredient.Key] = DelayedIntField(value[ingredient.Key], GUILayout.Width(50)); var rect = GetControlRect(false, GUILayout.Width(EditorGUIUtility.singleLineHeight)); @@ -641,15 +645,18 @@ public class DatabaseInspector : EditorWindow if (v.rect.Contains(Event.current.mousePosition)) { + var dragData = DragAndDrop.GetGenericData("Item"); + var isId = dragData is Guid guid; + var dragEntry = isId ? DatabaseCache.Get(guid) : null; + var correctType = referenceType.IsInstanceOfType(dragEntry); if(Event.current.type == EventType.DragUpdated) - DragAndDrop.visualMode = DragAndDropVisualMode.Copy; + DragAndDrop.visualMode = correctType ? DragAndDropVisualMode.Copy : DragAndDropVisualMode.Rejected; else if(Event.current.type == EventType.DragPerform) { - var guid = DragAndDrop.GetGenericData("Item"); - if (guid is Guid) + if (isId && correctType) { DragAndDrop.AcceptDrag(); - value[(Guid) guid] = 1; + value[guid] = 1; GUI.changed = true; } } diff --git a/Assets/Scripts/Editor/DatabaseView.cs b/Assets/Scripts/Editor/DatabaseView.cs index 04f60971a..e21246d96 100644 --- a/Assets/Scripts/Editor/DatabaseView.cs +++ b/Assets/Scripts/Editor/DatabaseView.cs @@ -164,7 +164,8 @@ public class DatabaseListView : EditorWindow var result = await R .Db("Aetheria") .Table(table) - .Update(entry) + .Get(entry.ID) + .Replace(entry) .RunAsync(_connection); Debug.Log($"Uploaded entry to RethinkDB: {entry.ID} result: {result}"); }; diff --git a/Assets/Scripts/NIH/MSAGL.meta b/Assets/Scripts/NIH/MSAGL.meta new file mode 100644 index 000000000..9f850e3b7 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a80bb5b7ce4059d40a0eddd80b4b4ae5 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/AutomaticGraphLayout.csproj b/Assets/Scripts/NIH/MSAGL/AutomaticGraphLayout.csproj new file mode 100644 index 000000000..7884fbb28 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/AutomaticGraphLayout.csproj @@ -0,0 +1,17 @@ + + + false + + + netstandard2.0 + + + PERSISTENCE;REPORTING + + + PERSISTENCE;REPORTING;TEST_MSAGL + + + + + \ No newline at end of file diff --git a/Assets/Scripts/NIH/MSAGL/AutomaticGraphLayout.csproj.meta b/Assets/Scripts/NIH/MSAGL/AutomaticGraphLayout.csproj.meta new file mode 100644 index 000000000..272a93c90 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/AutomaticGraphLayout.csproj.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 40dcc7efbe87de246a2a5146c5128222 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/AutomaticGraphLayout.nuspec b/Assets/Scripts/NIH/MSAGL/AutomaticGraphLayout.nuspec new file mode 100644 index 000000000..e39e7a323 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/AutomaticGraphLayout.nuspec @@ -0,0 +1,24 @@ + + + AutomaticGraphLayout + 1.1.4 + paulovila + Microsoft + https://github.com/Microsoft/automatic-graph-layout/blob/master/LICENSE + https://github.com/Microsoft/automatic-graph-layout + true + This .NET asssembly contains the core MSAGL layout functionality. Use this library if you just want MSAGL to perform the layout only and afterwards you will use a separate tool to perform the rendering and visalization. + + + Copyright © Microsoft Corporation + en-US + MSAGL Graph Layout + + + + + + + + + \ No newline at end of file diff --git a/Assets/Scripts/NIH/MSAGL/AutomaticGraphLayout.nuspec.meta b/Assets/Scripts/NIH/MSAGL/AutomaticGraphLayout.nuspec.meta new file mode 100644 index 000000000..958717901 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/AutomaticGraphLayout.nuspec.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 64a9bda5ccebb9e45b4c45a2f2d7d9da +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core.meta b/Assets/Scripts/NIH/MSAGL/Core.meta new file mode 100644 index 000000000..b3411b557 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f3f9e18906f0e744eb7e5b695467d1cc +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/AlgorithmBase.cs b/Assets/Scripts/NIH/MSAGL/Core/AlgorithmBase.cs new file mode 100644 index 000000000..dac2cb835 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/AlgorithmBase.cs @@ -0,0 +1,305 @@ +using System; +namespace Microsoft.Msagl.Core +{ + /// + /// Base class for algorithms that are cancelable and report progress + /// + public abstract class AlgorithmBase + { + #region Running + /// + /// Runs the algorithm. + /// Inherits any preexisting cancel tokens. + /// + public void Run() + { + this.progressRatio = 0; + this.localStepCount = 0; + + this.cancelToken = threadStaticCancelToken; + this.ThrowIfCanceled(); + + this.ProgressSteps(0); // Initial progress + + this.RunInternal(); + + if (!this.IsCanceled) + { + this.ProgressComplete(); + } + } + + /// + /// Runs the algorithm, setting up the cancel token and reverting to the old cancel token before finishing + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "cancelToken")] + public void Run(CancelToken cancelToken) + { + this.progressRatio = 0; + this.localStepCount = 0; + + CancelToken oldCancelToken = this.SetCancelToken(cancelToken); + + try + { + this.ProgressSteps(0); // Initial progress + + this.RunInternal(); + + if (!this.IsCanceled) + { + this.ProgressComplete(); + } + } + finally + { + threadStaticCancelToken = oldCancelToken; + } + } + + /// + /// Executes the actual algorithm. + /// + protected abstract void RunInternal(); + + #endregion Running + + #region Cancel + + /// + /// Set the cancel token. Returns the old cancel token, in case you need to revert at some stage. + /// + /// new cancel token + /// old cancel token + public CancelToken SetCancelToken(CancelToken cancelToken) + { + this.cancelToken = cancelToken; + this.ThrowIfCanceled(); + CancelToken oldCancelToken = threadStaticCancelToken; + threadStaticCancelToken = cancelToken; + return oldCancelToken; + } + + /// + /// Cancels the algorithm that is currently in progress. + /// + public void Cancel() + { + if (this.cancelToken != null) + { + this.cancelToken.Canceled = true; + } + } + + /// + /// True if the algorithm has been canceled. + /// + public bool IsCanceled + { + get { return this.cancelToken != null && this.cancelToken.Canceled; } + } + + /// + /// Throws an OperationCanceledException if the current algorithm has been canceled. + /// + protected void ThrowIfCanceled() + { + if (this.cancelToken != null) + { + this.cancelToken.ThrowIfCanceled(); + } + } + + /// + /// The current cancel token - one per thread, cached by cancelToken, below. + /// + [ThreadStatic] + private static CancelToken threadStaticCancelToken; + + /// + /// ThreadStatic field above has about 10X the lookup cost of a local field, so we + /// cache its value in cancelToken + /// + private volatile CancelToken cancelToken; + + /// + /// the current cancel token + /// + protected CancelToken CancelToken { get { return cancelToken; } } + + #endregion Cancel + + #region Progress + + /// + /// notifies whenever progress is made by the algorithm. ProgressChangedEventArgs report + /// 0 < progressRatio <= 1 + /// + public event EventHandler ProgressChanged; + + /// + /// Total progress to date + /// + private double progressRatio = 0; + + /// + /// Step size to increment progressRatio when ProgressStep is called + /// + private double localProgressStepSize = LocalProgressStepSizeDefault; + + /// + /// True if the progress step count has been specified using StartListenToLocalProgress. + /// + private bool localProgressSpecified = false; + + /// + /// The number of times ProgressStep has been called. + /// + private int localStepCount = 0; + + /// + /// When running sub-algorithms this gives the progressRatio start point + /// + private double stageStartRatio = 0; + + /// + /// When running sub-algorithms this gives the progressRatio end point + /// + private double stageEndRatio = 1; + + /// + /// if StartListenToLocalProgress is not used to setup step size the following will be the + /// (rather arbitrary) default. + /// + private const double LocalProgressStepSizeDefault = 0.05; + + /// + /// setup to start listening to ProgressStep() invocations from this algorithm. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] + protected void StartListenToLocalProgress(int expectedSteps, double stageRatio = 1) + { + localProgressStepSize = stageRatio / Math.Max(1, expectedSteps); // at least 1, to prevent divide by 0 + localProgressSpecified = true; + localStepCount = 0; + stageStartRatio = 0; + stageEndRatio = 1; + } + + /// + /// When starting a sub-algorithm start listening to its progress, transforming + /// the progressRatio it reports (between 0 and 1) to between progressRatio and progressRatio+stageRatio + /// + /// The child algorithm. + /// The fraction of time the child algorithm will take of the parent algorithm. + protected void StartListenToProgress(AlgorithmBase childAlgorithm, double stageRatio) + { + ValidateArg.IsNotNull(childAlgorithm, "childAlgorithm"); + childAlgorithm.ProgressChanged += NotifyProgressChanged; + stageStartRatio = progressRatio; + stageEndRatio = progressRatio + stageRatio; + } + + /// + /// When the sub-algorithm is completed this reverts us to listening to local progress + /// + /// + protected void StopListenToProgress(AlgorithmBase childAlgorithm) + { + ValidateArg.IsNotNull(childAlgorithm, "childAlgorithm"); + childAlgorithm.ProgressChanged -= NotifyProgressChanged; + progressRatio = stageEndRatio; + stageStartRatio = 0; + stageEndRatio = 1; + } + + /// + /// Runs the child algorithm and listens for progress changes. + /// + protected void RunChildAlgorithm(AlgorithmBase childAlgorithm, double stageRatio) + { + ValidateArg.IsNotNull(childAlgorithm, "childAlgorithm"); + try + { + StartListenToProgress(childAlgorithm, stageRatio); + childAlgorithm.Run(); + } + finally + { + StopListenToProgress(childAlgorithm); + } + } + + /// + /// Call this to report 100% progress if your algorithm finishes early + /// + protected void ProgressComplete() + { + ThrowIfCanceled(); + if (progressRatio != 1.0) + { + this.progressRatio = 1.0; + this.NotifyProgressChanged(this, new ProgressChangedEventArgs(progressRatio)); + } + } + + /// + /// Checks for cancel, increases progress, and notifies any listeners of progress. + /// Call whenever the algorithm makes progress. + /// + protected void ProgressStep() + { + this.ProgressSteps(1); + } + + /// + /// Checks for cancel, increases progress, and notifies any listeners of progress. + /// Call whenever the algorithm makes progress. + /// + protected void ProgressSteps(int stepsTaken) + { + ThrowIfCanceled(); + localStepCount += stepsTaken; + + if (localProgressSpecified) + { + progressRatio = progressRatio + (localProgressStepSize * stepsTaken); + } + else + { + // If no local step count was specified, use a progress bar + // which never reaches 100% and gets slower as time goes on. + // It approaches 85% (instead of 100%) so it never appears to be completed. + const double Limit = 0.85; + const double HalfLife = 50.0; // 50 was chosen as the default since it represents one ProgressStep per percentage at the start (i.e. 50 steps reach 50%) + const double Numerator = Limit * HalfLife; + progressRatio = Limit - (Numerator / (HalfLife + localStepCount)); + } + + // Round up to 100% if the sum of steps doesn't quite reach it. + if (Math.Round(progressRatio, 6) == 1.0) + { + progressRatio = 1.0; + } + + this.NotifyProgressChanged(this, new ProgressChangedEventArgs(progressRatio)); + } + + /// + /// notifies whenever progress is made by the algorithm. ProgressChangedEventArgs report + /// 0 < progressRatio <= 1. Progress from sub-algorithms are converted to the correct ratio + /// of the local progress + /// + private void NotifyProgressChanged(object sender, ProgressChangedEventArgs args) + { + if (ProgressChanged != null) + { + double stageRatio = stageEndRatio - stageStartRatio; + double stageProgress = stageRatio * args.RatioComplete; + ProgressChanged(this, new ProgressChangedEventArgs(stageStartRatio + stageProgress)); + } + } + + #endregion Progress Logic + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/AlgorithmBase.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/AlgorithmBase.cs.meta new file mode 100644 index 000000000..7c6da9b74 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/AlgorithmBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8f0e154226de7c44d93b02694d82946a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/CancelToken.cs b/Assets/Scripts/NIH/MSAGL/Core/CancelToken.cs new file mode 100644 index 000000000..2404cea13 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/CancelToken.cs @@ -0,0 +1,57 @@ +using System; + +#if PPC +using System.Threading; +using System.Threading.Tasks; +#endif + +namespace Microsoft.Msagl.Core { + + /// + /// a place holder for the cancelled flag + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")] + public class CancelToken { + + volatile bool canceled; + + /// + /// Set this flag to true when you want to cancel the layout. + /// + public bool Canceled { + get { return canceled; } + set { + canceled = value; +#if PPC + if (canceled) + { + cancellationTokenSource.Cancel(); + } +#endif + } + } + /// + /// throws is the layout has been cancelled + /// + public void ThrowIfCanceled() + { +#if PPC + cancellationTokenSource.Token.ThrowIfCancellationRequested(); +#endif + if (this.Canceled) + throw new OperationCanceledException(); + } + +#if PPC + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + + /// + /// Pass this cancelation token for use in cancellable parallel for-each operations + /// + public CancellationToken CancellationToken + { + get { return cancellationTokenSource.Token; } + } +#endif + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/CancelToken.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/CancelToken.cs.meta new file mode 100644 index 000000000..7ef1d5001 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/CancelToken.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 838ad0009928e604e94e5580a15ada26 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/DataStructures.meta b/Assets/Scripts/NIH/MSAGL/Core/DataStructures.meta new file mode 100644 index 000000000..fd0a920ff --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/DataStructures.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 500f7d7386669de4abbd734ecd9ae3c3 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/DataStructures/BinaryHeapPriorityQueue.cs b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/BinaryHeapPriorityQueue.cs new file mode 100644 index 000000000..71dd3deeb --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/BinaryHeapPriorityQueue.cs @@ -0,0 +1,130 @@ +using System; + +namespace Microsoft.Msagl.Core.DataStructures { + + + /// + /// A priority queue based on the binary heap algorithm + /// + internal class BinaryHeapPriorityQueue { + //indexing for A starts from 1 + + readonly int[] _heap;//array of heap elements + readonly int[] _reverse_heap; // the map from [0,..., n-1] to their places in heap + /// + /// the array of priorities + /// + readonly double[] _priors; + internal int Count { get { return heapSize; } } + int heapSize; + /// + /// the constructor + /// we suppose that all integers inserted into the queue will be less then n + /// + /// it is the number of different integers that will be inserted into the queue + internal BinaryHeapPriorityQueue(int n) { + _priors = new double[n]; + _heap = new int[n + 1];//because indexing for A starts from 1 + _reverse_heap = new int[n]; + } + + + void SwapWithParent(int i) { + int parent = _heap[i >> 1]; + PutAtI(i >> 1, _heap[i]); + PutAtI(i, parent); + } + + internal void Enqueue(int o, double priority) { + heapSize++; + int i = heapSize; + _priors[o] = priority; + PutAtI(i, o); + while (i > 1 && _priors[_heap[i >> 1]] > priority) { + SwapWithParent(i); + i >>= 1; + } + } + + void PutAtI(int i, int h) { + _heap[i] = h; + _reverse_heap[h] = i; + } + + /// + /// return the first element of the queue and removes it from the queue + /// + /// + internal int Dequeue() { + if (heapSize == 0) + throw new InvalidOperationException(); + int ret = _heap[1]; + if (heapSize > 1) + { + PutAtI(1, _heap[heapSize]); + int i = 1; + while (true) + { + int smallest = i; + int l = i << 1; + + if (l <= heapSize && _priors[_heap[l]] <_priors[ _heap[i]]) + smallest = l; + + int r = l + 1; + + if (r <= heapSize && _priors[_heap[r]] < _priors[_heap[smallest]]) + smallest = r; + + if (smallest != i) + SwapWithParent(smallest); + else + break; + + i = smallest; + + } + } + heapSize--; + return ret; + } + + /// + /// sets the object priority to c + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal void DecreasePriority(int o, double newPriority) { + + //System.Diagnostics.Debug.WriteLine("delcrease "+ o.ToString()+" to "+ newPriority.ToString()); + + _priors[o] = newPriority; + int i = _reverse_heap[o]; + while (i > 1) { + if (_priors[_heap[i]] < _priors[_heap[i >> 1]]) + SwapWithParent(i); + else + break; + i >>= 1; + } + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + public static void Test() { + var q = new BinaryHeapPriorityQueue(10); + q.Enqueue(2, 2); + q.Enqueue(1, 1); + q.Enqueue(9, 9); + q.Enqueue(8, 8); + q.Enqueue(5, 5); + q.Enqueue(3, 3); + q.Enqueue(4, 4); + q.Enqueue(7, 7); + q.Enqueue(6, 6); + q.Enqueue(0, 0); + for (int i = 0; i < 10; i++) + Console.WriteLine(q.Dequeue()); + } + } +} + + diff --git a/Assets/Scripts/NIH/MSAGL/Core/DataStructures/BinaryHeapPriorityQueue.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/BinaryHeapPriorityQueue.cs.meta new file mode 100644 index 000000000..910ef65a8 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/BinaryHeapPriorityQueue.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a62428fef9112304eb0439efc749dc37 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/DataStructures/BinaryHeapWithComparer.cs b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/BinaryHeapWithComparer.cs new file mode 100644 index 000000000..7a5bcf6e7 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/BinaryHeapWithComparer.cs @@ -0,0 +1,141 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; + +namespace Microsoft.Msagl.Core.DataStructures { + /// + /// A priority queue based on the binary heap algorithm. + /// This class needs a comparer object to compare elements of the queue. + /// + /// + internal class BinaryHeapWithComparer { + const int initialHeapCapacity = 16; + T[] A; //array of the heap elems starting at A[1] + + int heapSize = 0; + + internal void Enqueue(T element) { + if (heapSize == A.Length - 1) { + var newA = new T[A.Length*2]; + Array.Copy(A, 1, newA, 1, heapSize); + A = newA; + } + + int i = heapSize + 1; + A[i] = element; + heapSize++; + int j = i >> 1; + T parent, son; + while (i > 1 && Less(son = A[i], parent = A[j])) { + A[j] = son; + A[i] = parent; + i = j; + j = i >> 1; + } + } + + + internal T Dequeue() { + if (heapSize < 1) + throw new InvalidOperationException(); + T ret = A[1]; + + T candidate = A[heapSize]; + + heapSize--; + + ChangeMinimum(candidate); + + return ret; + } + + internal void ChangeMinimum(T candidate) { + A[1] = candidate; + + int j = 1; + int i = 2; + bool done = false; + while (i < heapSize && !done) { + done = true; + //both sons exist + T leftSon = A[i]; + T rigthSon = A[i + 1]; + int compareResult = comparer.Compare(leftSon, rigthSon); + if (compareResult < 0) { + //left son is the smallest + if (comparer.Compare(leftSon, candidate) < 0) { + A[j] = leftSon; + A[i] = candidate; + done = false; + j = i; + i = j << 1; + } + } else { + //right son in not the greatest + if (comparer.Compare(rigthSon, candidate) < 0) { + A[j] = rigthSon; + A[i + 1] = candidate; + done = false; + j = i + 1; + i = j << 1; + } + } + } + + if (i == heapSize) { + //can we do one more step: + T leftSon = A[i]; + if (comparer.Compare(leftSon, candidate) < 0) { + A[j] = leftSon; + A[i] = candidate; + } + } + } + + internal int Count { + get { return heapSize; } + } + + internal bool Less(T a, T b) { + return comparer.Compare(a, b) < 0; + } + + IComparer comparer; + + internal BinaryHeapWithComparer(IComparer comparer) { + A = new T[initialHeapCapacity + 1]; + this.comparer = comparer; + } + +#if TEST_MSAGL + + public override string ToString() { + int i = 1; + return "{" + Print(i) + "}"; + } + + string Print(int i) { + if (2*i + 1 <= heapSize) + return String.Format(CultureInfo.InvariantCulture, "({0}->{1},{2})", A[i], A[i*2], A[i*2 + 1]) + + Print(i*2) + Print(i*2 + 1); + if (2*i == heapSize) + return String.Format(CultureInfo.InvariantCulture, "({0}->{1}", A[i], A[i*2]); + if (i == heapSize && i == 1) + return " " + A[i].ToString() + " "; + return ""; + } +#endif + + + public T GetMinimum() { + return A[1]; + } + +#if TEST_MSAGL + //internal void UpdateMinimum() { + // throw new NotImplementedException(); + //} +#endif + } +} \ No newline at end of file diff --git a/Assets/Scripts/NIH/MSAGL/Core/DataStructures/BinaryHeapWithComparer.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/BinaryHeapWithComparer.cs.meta new file mode 100644 index 000000000..83d424bb8 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/BinaryHeapWithComparer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 78dc14a0e35a20145a57e0314da9ab9b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/DataStructures/CollectionUtilities.cs b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/CollectionUtilities.cs new file mode 100644 index 000000000..b4942beb0 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/CollectionUtilities.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +namespace Microsoft.Msagl.Core.DataStructures { + /// + /// various utilities for collections + /// + public class CollectionUtilities { + /// + /// Add value to dictionary + /// + internal static void AddToMap(Dictionary dictionary, T key, TS value) + where TC : ICollection, new() { + TC tc; + if (!dictionary.TryGetValue(key, out tc)) + dictionary[key] = tc = new TC(); + + tc.Add(value); + } + + /// + /// Remove value from dictionary + /// + [SuppressMessage("Microsoft.Naming", "CA1715:IdentifiersShouldHaveCorrectPrefix", MessageId = "T"), + SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "S"), + SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + internal static void RemoveFromMap(Dictionary dictionary, T key, TS value) + where TC : ICollection { + var tc = dictionary[key]; + tc.Remove(value); + if (tc.Count == 0) + dictionary.Remove(key); + } + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/DataStructures/CollectionUtilities.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/CollectionUtilities.cs.meta new file mode 100644 index 000000000..3643eba77 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/CollectionUtilities.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6e8b582a3d0c32a41bc4eb7f0db98f96 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/DataStructures/GenericBinaryHeapPriorityQueue.cs b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/GenericBinaryHeapPriorityQueue.cs new file mode 100644 index 000000000..8160fcb3a --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/GenericBinaryHeapPriorityQueue.cs @@ -0,0 +1,281 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.Msagl.Core.DataStructures { + + + /// + /// A generic version priority queue based on the binary heap algorithm where + /// the priority of each element is passed as a parameter. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")] + public class GenericBinaryHeapPriorityQueue : IEnumerable { + + const int InitialHeapCapacity = 16; + + + //indexing for A starts from 1 + + //[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + //internal void Clear() + //{ + // if(heapSize>0) + // { + // for(int i=0;i[] A;//array of heap elements + // ReSharper restore InconsistentNaming + + /// + /// it is a mapping from queue elements and their correspondent HeapElements + /// + readonly Dictionary> cache; + internal int Count { get { return heapSize; } } + int heapSize; + + internal bool ContainsElement(T key) { + return cache.ContainsKey(key); + } + + + internal GenericBinaryHeapPriorityQueue() { + cache = new Dictionary>(); + A = new GenericHeapElement[InitialHeapCapacity + 1]; + } + + + void SwapWithParent(int i) { + var parent = A[i >> 1]; + + PutAtI(i >> 1, A[i]); + PutAtI(i, parent); + } + + + + internal void Enqueue(T element, double priority) { + if (heapSize == A.Length - 1) { + var newA = new GenericHeapElement[A.Length * 2]; + Array.Copy(A, 1, newA, 1, heapSize); + A = newA; + } + + heapSize++; + int i = heapSize; + GenericHeapElement h; + + A[i] = cache[element] = h = new GenericHeapElement(i, priority, element); + while (i > 1 && A[i >> 1].priority.CompareTo(priority) > 0) { + SwapWithParent(i); + i >>= 1; + } + } + + internal bool IsEmpty() { + return heapSize == 0; + } + + void PutAtI(int i, GenericHeapElement h) { + A[i] = h; + h.indexToA = i; + } + + ///// + ///// return the first element of the queue without removing it + ///// + ///// + //[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + //internal int Peek() { + // return A[1].v; + //} + + internal T Dequeue() { + if (heapSize == 0) + throw new InvalidOperationException(); + + var ret = A[1].v; + + MoveQueueOneStepForward(ret); + + return ret; + + } + + internal T Dequeue(out double priority) { + if (heapSize == 0) throw new InvalidOperationException(); + + var ret = A[1].v; + priority = A[1].priority; + MoveQueueOneStepForward(ret); + return ret; + } + + void MoveQueueOneStepForward(T ret) { + cache.Remove(ret); + PutAtI(1, A[heapSize]); + int i = 1; + while (true) { + int smallest = i; + int l = i << 1; + + if (l <= heapSize && A[l].priority.CompareTo(A[i].priority) < 0) + smallest = l; + + int r = l + 1; + + if (r <= heapSize && A[r].priority.CompareTo(A[smallest].priority) < 0) + smallest = r; + + if (smallest != i) + SwapWithParent(smallest); + else + break; + + i = smallest; + + } + + heapSize--; + } + + /// + /// sets the object priority to c + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal void DecreasePriority(T element, double newPriority) + { + GenericHeapElement h; + //ignore the element if it is not in the queue + if (!cache.TryGetValue(element, out h)) return; + + //var h = cache[element]; + h.priority = newPriority; + int i = h.indexToA; + while (i > 1) { + if (A[i].priority.CompareTo(A[i >> 1].priority) < 0) + SwapWithParent(i); + else + break; + i >>= 1; + } + } + + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + // public static void Test() { + // Timer nm = new Timer(); + // var bound = 1000000; + // nm.Start(); + // { + // var q = new BinaryHeapPriorityQueue(bound + 1); + // Random random = new Random(); + // for (int i = 0; i < bound; i++) { + // q.Enqueue(i, random.NextDouble()); + // } + // for (int i = 0; i < bound; i++) { + // q.DecreasePriority(i, -random.NextDouble()); + // } + // + // while (q.IsEmpty() == false) { + // q.Dequeue(); + // } + // + // } + // nm.Stop(); + // Console.WriteLine(nm.Duration); + // nm.Start(); + // { + // var q = new GenericBinaryHeapPriorityQueue(); + // Random random = new Random(); + // for (int i = 0; i < bound; i++) { + // q.Enqueue(i, random.NextDouble()); + // } + // for (int i = 0; i < bound; i++) { + // q.DecreasePriority(i, -random.NextDouble()); + // } + // + // while (q.IsEmpty() == false) { + // q.Dequeue(); + // } + // + // } + // nm.Stop(); + // Console.WriteLine(nm.Duration); + //} + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + public static void Test() { + var q = new GenericBinaryHeapPriorityQueue(); + q.Enqueue(2, 2); + q.Enqueue(1, 1); + q.Enqueue(9, 9); + q.Enqueue(8, 8); + q.Enqueue(5, 5); + q.Enqueue(3, 3); + q.Enqueue(4, 4); + q.Enqueue(7, 7); + q.Enqueue(6, 6); + q.Enqueue(0, 0); + + q.DecreasePriority(4, 2.5); + + while (q.IsEmpty() == false) + Console.WriteLine(q.Dequeue()); + + + } + /// + /// enumerator + /// + /// + public IEnumerator GetEnumerator() { + for (int i = 1; i <= heapSize; i++) + yield return A[i].v; + } + + /// + /// + /// + /// + /// + public T Peek(out double priority) { + if (Count == 0) { + priority = 0.0; + return default(T); + } + priority = A[1].priority; + return A[1].v; + } + + IEnumerator IEnumerable.GetEnumerator() { + for (int i = 1; i <= heapSize; i++) + yield return A[i].v; + } +#if TEST_MSAGL + /// + /// + /// + /// + public override string ToString() { + StringBuilder sb=new StringBuilder(); + foreach (var i in this) + sb.Append(i + ","); + return sb.ToString(); + } + +#endif + } +} + + + diff --git a/Assets/Scripts/NIH/MSAGL/Core/DataStructures/GenericBinaryHeapPriorityQueue.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/GenericBinaryHeapPriorityQueue.cs.meta new file mode 100644 index 000000000..ab47c1f2f --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/GenericBinaryHeapPriorityQueue.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7e122f1c5e2b7ce4e91aa5792a19a862 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/DataStructures/GenericBinaryHeapPriorityQueueWithTimestamp.cs b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/GenericBinaryHeapPriorityQueueWithTimestamp.cs new file mode 100644 index 000000000..b454b293f --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/GenericBinaryHeapPriorityQueueWithTimestamp.cs @@ -0,0 +1,270 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.Msagl.Core.DataStructures { + + // TODO: Move and expand the commented tests below into PriorityQueueTests unit tests for all 3 PQ classes. + // TODO: Reduce duplication with GenericBinaryHeapPriorityQueue by breaking most functionality out into a + // GenericBinaryHeapPriorityQueue class and using THeapElement.ComparePriority, keeping only Enqueue + // and DecreasePriority in the derived classes as these must know about timestamp. + + /// + /// A generic version priority queue based on the binary heap algorithm where + /// the priority of each element is passed as a parameter and priority ties are broken by timestamp. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")] + public class GenericBinaryHeapPriorityQueueWithTimestamp : IEnumerable { + + const int InitialHeapCapacity = 16; + + + //indexing for A starts from 1 + + //[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + //internal void Clear() + //{ + // if(heapSize>0) + // { + // for(int i=0;i[] A;//array of heap elements + // ReSharper restore InconsistentNaming + + UInt64 timestamp; + UInt64 NextTimestamp { get { return ++this.timestamp; } } + + /// + /// it is a mapping from queue elements and their correspondent HeapElements + /// + readonly Dictionary> cache; + internal int Count { get { return heapSize; } } + int heapSize; + + internal GenericBinaryHeapPriorityQueueWithTimestamp() { + cache = new Dictionary>(); + A = new GenericHeapElementWithTimestamp[InitialHeapCapacity + 1]; + } + + + void SwapWithParent(int i) { + var parent = A[i >> 1]; + + PutAtI(i >> 1, A[i]); + PutAtI(i, parent); + } + + internal void Enqueue(T element, double priority) { + if (heapSize == A.Length - 1) { + var newA = new GenericHeapElementWithTimestamp[A.Length * 2]; + Array.Copy(A, 1, newA, 1, heapSize); + A = newA; + } + + heapSize++; + int i = heapSize; + GenericHeapElementWithTimestamp h; + + A[i] = cache[element] = h = new GenericHeapElementWithTimestamp(i, priority, element, this.NextTimestamp); + while (i > 1 && A[i >> 1].ComparePriority(A[i]) > 0) { + SwapWithParent(i); + i >>= 1; + } + System.Diagnostics.Debug.Assert(A[i] == h); + A[i] = h; + + } + + void PutAtI(int i, GenericHeapElementWithTimestamp h) { + A[i] = h; + h.indexToA = i; + } + +#if TEST_MSAGL + /// + /// Gets the next element to be dequeued, without dequeueing it. + /// + internal T Peek() { + if (heapSize == 0) { + throw new InvalidOperationException(); + } + return A[1].v; + } + + /// + /// Gets the timestamp of the next element to be dequeued, without dequeueing it. + /// + internal UInt64 PeekTimestamp() { + if (heapSize == 0) { + throw new InvalidOperationException(); + } + return A[1].Timestamp; + } +#endif // TEST_MSAGL + + internal T Dequeue() { + if (heapSize == 0) + throw new InvalidOperationException(); + + var ret = A[1].v; + + MoveQueueOneStepForward(ret); + + return ret; + + } + + void MoveQueueOneStepForward(T ret) { + cache.Remove(ret); + PutAtI(1, A[heapSize]); + int i = 1; + while (true) { + int smallest = i; + int l = i << 1; + + if (l <= heapSize && A[l].ComparePriority(A[i]) < 0) + smallest = l; + + int r = l + 1; + + if (r <= heapSize && A[r].ComparePriority(A[smallest]) < 0) + smallest = r; + + if (smallest != i) + SwapWithParent(smallest); + else + break; + + i = smallest; + + } + + heapSize--; + } + + /// + /// sets the object priority to c + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal void DecreasePriority(T element, double newPriority) + { + GenericHeapElementWithTimestamp h; + //ignore the element if it is not in the queue + if (!cache.TryGetValue(element, out h)) return; + + //var h = cache[element]; + h.priority = newPriority; + int i = h.indexToA; + while (i > 1) { + if (A[i].ComparePriority(A[i >> 1]) < 0) + SwapWithParent(i); + else + break; + i >>= 1; + } + } + + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + // public static void Test() { + // Timer nm = new Timer(); + // var bound = 1000000; + // nm.Start(); + // { + // var q = new BinaryHeapPriorityQueue(bound + 1); + // Random random = new Random(); + // for (int i = 0; i < bound; i++) { + // q.Enqueue(i, random.NextDouble()); + // } + // for (int i = 0; i < bound; i++) { + // q.DecreasePriority(i, -random.NextDouble()); + // } + // + // while (q.IsEmpty() == false) { + // q.Dequeue(); + // } + // + // } + // nm.Stop(); + // Console.WriteLine(nm.Duration); + // nm.Start(); + // { + // var q = new GenericBinaryHeapPriorityQueue(); + // Random random = new Random(); + // for (int i = 0; i < bound; i++) { + // q.Enqueue(i, random.NextDouble()); + // } + // for (int i = 0; i < bound; i++) { + // q.DecreasePriority(i, -random.NextDouble()); + // } + // + // while (q.IsEmpty() == false) { + // q.Dequeue(); + // } + // + // } + // nm.Stop(); + // Console.WriteLine(nm.Duration); + //} + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + public static void Test() { + var q = new GenericBinaryHeapPriorityQueue(); + q.Enqueue(2, 2); + q.Enqueue(1, 1); + q.Enqueue(9, 9); + q.Enqueue(8, 8); + q.Enqueue(5, 5); + q.Enqueue(3, 3); + q.Enqueue(4, 4); + q.Enqueue(7, 7); + q.Enqueue(6, 6); + q.Enqueue(0, 0); + + q.DecreasePriority(4, 2.5); + + while (q.IsEmpty() == false) + Console.WriteLine(q.Dequeue()); + + + } + /// + /// enumerator + /// + /// + public IEnumerator GetEnumerator() { + for (int i = 1; i <= heapSize; i++) + yield return A[i].v; + } + + IEnumerator IEnumerable.GetEnumerator() { + for (int i = 1; i <= heapSize; i++) + yield return A[i].v; + } +#if TEST_MSAGL + /// + /// + /// + /// + public override string ToString() { + var sb=new StringBuilder(); + foreach (var i in this) + sb.Append(i + ","); + return sb.ToString(); + } + +#endif + } +} + + + diff --git a/Assets/Scripts/NIH/MSAGL/Core/DataStructures/GenericBinaryHeapPriorityQueueWithTimestamp.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/GenericBinaryHeapPriorityQueueWithTimestamp.cs.meta new file mode 100644 index 000000000..b25b29163 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/GenericBinaryHeapPriorityQueueWithTimestamp.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a84290ac5fe1bdb4f9bd404eb9dfc396 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/DataStructures/GenericHeapElement.cs b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/GenericHeapElement.cs new file mode 100644 index 000000000..914a55aaa --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/GenericHeapElement.cs @@ -0,0 +1,12 @@ +namespace Microsoft.Msagl.Core.DataStructures { + class GenericHeapElement { + internal int indexToA; + internal double priority; + internal T v;//value + internal GenericHeapElement(int index, double priority, T v) { + indexToA = index; + this.priority = priority; + this.v = v; + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/NIH/MSAGL/Core/DataStructures/GenericHeapElement.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/GenericHeapElement.cs.meta new file mode 100644 index 000000000..64e340d06 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/GenericHeapElement.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bc99aab509d5d5846b25d73984a920c1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/DataStructures/GenericHeapElementWithTimestamp.cs b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/GenericHeapElementWithTimestamp.cs new file mode 100644 index 000000000..59d4bf378 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/GenericHeapElementWithTimestamp.cs @@ -0,0 +1,26 @@ +using System; + +namespace Microsoft.Msagl.Core.DataStructures { + /// + /// A priority queue element that is compared by priority and timestamp + /// + internal class GenericHeapElementWithTimestamp : GenericHeapElement { + // Timestamp operates as a tiebreaker when priorities are equal. + internal UInt64 Timestamp { get; set; } + + internal GenericHeapElementWithTimestamp(int index, double priority, T v, UInt64 timestamp) + : base(index, priority, v) { + this.Timestamp = timestamp; + } + + internal int ComparePriority(GenericHeapElementWithTimestamp other) { + var cmp = base.priority.CompareTo(other.priority); + + // Consider a more recent sequence to have a lower priority value (== higher priority) + // so reverse the direction of comparison. Note: Timestamp only applies in the case + // of tied priorities, so if it wraps, we'll have suboptimal processing and likely a different + // path in the event of ties, but will not fail. + return (cmp != 0) ? cmp : other.Timestamp.CompareTo(this.Timestamp); + } + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/DataStructures/GenericHeapElementWithTimestamp.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/GenericHeapElementWithTimestamp.cs.meta new file mode 100644 index 000000000..3955945e1 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/GenericHeapElementWithTimestamp.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 91406a9d3ae7e79408f53eaac222784e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree.meta b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree.meta new file mode 100644 index 000000000..75248d3cb --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2db8c0017da4d1e42a8df84b52235c5b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/ComparerOnDelegate.cs b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/ComparerOnDelegate.cs new file mode 100644 index 000000000..3ef163d2d --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/ComparerOnDelegate.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; + +namespace Microsoft.Msagl.Core.DataStructures { + internal class ComparerOnDelegate : IComparer { + readonly Func comparer; + + public ComparerOnDelegate(Func compare) { + comparer= compare; + } + + public int Compare(T x, T y) { + return comparer(x, y); + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/ComparerOnDelegate.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/ComparerOnDelegate.cs.meta new file mode 100644 index 000000000..ae0f54347 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/ComparerOnDelegate.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 808dfa443622ca94d9f1a9e8cd9c13be +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/DefaultComperer.cs b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/DefaultComperer.cs new file mode 100644 index 000000000..52ad0d112 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/DefaultComperer.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; + +namespace Microsoft.Msagl.Core.DataStructures { + internal class DefaultComperer: IComparer { + public int Compare(T x, T y) { + return ((IComparable) x).CompareTo(y); + } + + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/DefaultComperer.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/DefaultComperer.cs.meta new file mode 100644 index 000000000..384cdaaac --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/DefaultComperer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9bcd9db072a6903479507b3a95b5d657 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/RBColor.cs b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/RBColor.cs new file mode 100644 index 000000000..06e0ed33b --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/RBColor.cs @@ -0,0 +1,8 @@ +namespace Microsoft.Msagl.Core.DataStructures { + + internal enum RBColor { + Red, + Black + } + +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/RBColor.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/RBColor.cs.meta new file mode 100644 index 000000000..c8b2d27c6 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/RBColor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ca5c296797071684880042932e6df5ee +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/RBNode.cs b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/RBNode.cs new file mode 100644 index 000000000..d2e441059 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/RBNode.cs @@ -0,0 +1,29 @@ +using System; +namespace Microsoft.Msagl.Core.DataStructures { + + +#if TEST_MSAGL + [Serializable] +#endif + internal class RBNode { + + internal RBColor color; + internal T Item; + internal RBNode parent, left, right; + internal RBNode(RBColor color) { this.color = color; } + internal RBNode(RBColor color, T item, RBNode p, RBNode left, RBNode right) { + this.color = color; + this.parent = p; + this.left = left; + this.right = right; + this.Item = item; + } + + /// + /// + /// + public override string ToString() { + return Item.ToString(); + } + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/RBNode.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/RBNode.cs.meta new file mode 100644 index 000000000..9c56b9b25 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/RBNode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: eb455e0993b23bc45814a25cd54d4a50 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/RBTree.cs b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/RBTree.cs new file mode 100644 index 000000000..c8d7430a6 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/RBTree.cs @@ -0,0 +1,483 @@ +using System; +using System.Collections.Generic; + +namespace Microsoft.Msagl.Core.DataStructures { + + //public class TestRB :IComparer{ + // public static void Test() { + // var tree = new RBTree(new TestRB()); + // tree.insert(1); + // tree.insert(6); + // tree.insert(8); + // tree.insert(11); + // tree.insert(13); + // tree.insert(17); + // tree.insert(15); + // tree.insert(25); + // tree.insert(22); + // tree.insert(27); + // tree.insert(16); + + // var t = tree.findLast(delegate(int i) { return i <= 15.5; }); + + // } + + + // #region IComparer Members + + // public int Compare(int x, int y) { + // return x - y; + // } + + // #endregion + //} + +#if TEST_MSAGL + [Serializable] +#endif + internal class RbTree : IEnumerable { + + /// + /// find the first, minimal, node in the tree such that predicate holds + /// + /// Has to be monotone in the sense that if it holds for t then it holds for any t' greater or equal than t + /// so the predicate values have a form (false, false, ..., false, true, true, ..., true) + /// + /// the first node where predicate holds or null + internal RBNode FindFirst(Func predicate) { + return FindFirst(root, predicate); + } + + internal RbTree() { + comparer = new DefaultComperer(); + root = nil = new RBNode(RBColor.Black); + } + + RBNode FindFirst(RBNode n, Func p) { + if (n == nil) + return null; + RBNode good = null; + while (n != nil) + n = p(n.Item) ? (good = n).left : n.right; + + return good; + } + + /// + /// find the last, maximal, node in the tree such that predicate holds + /// + /// Has to be monotone in the sense that if it holds for t then it holds for any t' less or equal than t + /// so the predicate values on the tree have a form (true, true, ..., true, false, false, ..., false) + /// + /// the last node where predicate holds or null + internal RBNode FindLast(Func predicate) { + return FindLast(root, predicate); + } + + RBNode FindLast(RBNode n, Func p) { + if (n == nil) + return null; + RBNode good = null; + while (n != nil) + n = p(n.Item) ? (good = n).right : n.left; + + return good; + } + + + readonly IComparer comparer; + + IComparer Comparer { + get { return comparer; } + } + + public IEnumerator GetEnumerator() { return new RBTreeEnumerator(this); } + + RBNode nil; + + internal RBNode Nil { get { return nil; } } + + RBNode root; + internal RBNode Root { get { return root; } } + + internal RBNode Next(RBNode x) { + if (x.right != nil) + return TreeMinimum(x.right); + RBNode y = x.parent; + while (y != nil && x == y.right) { + x = y; + y = y.parent; + } + return ToNull(y); + } + + RBNode ToNull(RBNode y) { + return y != nil ? y : null; + } + + internal RBNode Previous(RBNode x) { + if (x.left != nil) + return TreeMaximum(x.left); + RBNode y = x.parent; + while (y != nil && x == y.left) { + x = y; + y = y.parent; + } + return ToNull(y); + } + + RBNode TreeMinimum(RBNode x) { + while (x.left != nil) + x = x.left; + return ToNull(x); + } + + internal RBNode TreeMinimum() { + return TreeMinimum(root); + } + + + RBNode TreeMaximum(RBNode x) { + while (x.right != nil) + x = x.right; + return ToNull(x); + } + + internal RBNode TreeMaximum() { + return TreeMaximum(root); + } + + + //RBTree Clone() { + // RBTree clone = new RBTree(this.Comparer); + // foreach (T n in this) { + // clone.insert(n); + // } + // return clone; + //} + + + public override string ToString() { + string ret = "{"; + int i = 0; + foreach (T p in this) { + ret += p.ToString(); + if (i != count - 1) { + ret += ","; + } + + i++; + } + + return ret + "}"; + } + + + internal RBNode DeleteSubtree(RBNode z) { + System.Diagnostics.Debug.Assert(z != nil); + + RBNode y; + if (z.left == nil || z.right == nil) { + /* y has a nil node as a child */ + y = z; + } else { + /* find tree successor with a nil node as a child */ + y = z.right; + while (y.left != nil) y = y.left; + } + + /* x is y's only child */ + RBNode x = y.left != nil ? y.left : y.right; + + x.parent = y.parent; + if (y.parent == nil) + root = x; + else { + if (y == y.parent.left) + y.parent.left = x; + else + y.parent.right = x; + } + if (y != z) + z.Item = y.Item; + if (y.color == RBColor.Black) + DeleteFixup(x); + + // checkTheTree(); + + return ToNull(z); + + } + + int count; + public int Count { get { return count; } } + + internal RBNode Remove(T i) { + RBNode n = Find(i); + if (n != null) { + count--; + return DeleteSubtree(n); + } + return null; + } + + internal void DeleteNodeInternal(RBNode x) { + count--; + DeleteSubtree(x); + } + + RBNode Find(RBNode x, T i) { + int compareResult; + while (x != nil && (compareResult = Comparer.Compare(i, x.Item)) != 0) + x = compareResult < 0 ? x.left : x.right; + + return ToNull(x); + } + + internal RBNode Find(T i) { + return Find(root, i); + } + + void DeleteFixup(RBNode x) { + while (x != root && x.color == RBColor.Black) { + if (x == x.parent.left) { + RBNode w = x.parent.right; + if (w.color == RBColor.Red) { + w.color = RBColor.Black; + x.parent.color = RBColor.Red; + LeftRotate(x.parent); + w = x.parent.right; + } + if (w.left.color == RBColor.Black && w.right.color == RBColor.Black) { + w.color = RBColor.Red; + x = x.parent; + } else { + if (w.right.color == RBColor.Black) { + w.left.color = RBColor.Black; + w.color = RBColor.Red; + RightRotate(w); + w = x.parent.right; + } + w.color = x.parent.color; + x.parent.color = RBColor.Black; + w.right.color = RBColor.Black; + LeftRotate(x.parent); + x = root; + } + } else { + RBNode w = x.parent.left; + if (w.color == RBColor.Red) { + w.color = RBColor.Black; + x.parent.color = RBColor.Red; + RightRotate(x.parent); + w = x.parent.left; + } + if (w.right.color == RBColor.Black && w.left.color == RBColor.Black) { + w.color = RBColor.Red; + x = x.parent; + } else { + if (w.left.color == RBColor.Black) { + w.right.color = RBColor.Black; + w.color = RBColor.Red; + LeftRotate(w); + w = x.parent.left; + } + w.color = x.parent.color; + x.parent.color = RBColor.Black; + w.left.color = RBColor.Black; + RightRotate(x.parent); + x = root; + } + } + } + x.color = RBColor.Black; + } + + internal bool IsEmpty() { return root == nil; } + + RBNode TreeInsert(T z) { + var y = nil; + var x = root; + var compareRes = 0; + while (x != nil) { + y = x; +#if SHARPKIT //https://code.google.com/p/sharpkit/issues/detail?id=368 + compareRes = Comparer.Compare(z, x.Item); + x = compareRes < 0 ? x.left : x.right; +#else + x = (compareRes = Comparer.Compare(z, x.Item)) < 0 ? x.left : x.right; +#endif + } + + var nz = new RBNode(RBColor.Black, z, y, nil, nil); + + if (y == nil) + root = nz; + else if (compareRes < 0) + y.left = nz; + else + y.right = nz; + + return ToNull(nz); + } + + void InsertPrivate(RBNode x) { + count++; + x.color = RBColor.Red; + while (x != root && x.parent.color == RBColor.Red) { + if (x.parent == x.parent.parent.left) { + RBNode y = x.parent.parent.right; + if (y.color == RBColor.Red) { + x.parent.color = RBColor.Black; + y.color = RBColor.Black; + x.parent.parent.color = RBColor.Red; + x = x.parent.parent; + } else { + if (x == x.parent.right) { + x = x.parent; + LeftRotate(x); + } + x.parent.color = RBColor.Black; + x.parent.parent.color = RBColor.Red; + RightRotate(x.parent.parent); + } + } else { + RBNode y = x.parent.parent.left; + if (y.color == RBColor.Red) { + x.parent.color = RBColor.Black; + y.color = RBColor.Black; + x.parent.parent.color = RBColor.Red; + x = x.parent.parent; + } else { + if (x == x.parent.left) { + x = x.parent; + RightRotate(x); + } + x.parent.color = RBColor.Black; + x.parent.parent.color = RBColor.Red; + LeftRotate(x.parent.parent); + } + } + + } + + root.color = RBColor.Black; + + //checkTheTree(); + } + + internal RBNode Insert(T v) { + RBNode x = TreeInsert(v); + InsertPrivate(x); + return ToNull(x); + } + + void LeftRotate(RBNode x) { + RBNode y = x.right; + x.right = y.left; + if (y.left != nil) + y.left.parent = x; + y.parent = x.parent; + if (x.parent == nil) + root = y; + else if (x == x.parent.left) + x.parent.left = y; + else + x.parent.right = y; + + y.left = x; + x.parent = y; + } + + void RightRotate(RBNode x) { + RBNode y = x.left; + x.left = y.right; + if (y.right != nil) + y.right.parent = x; + y.parent = x.parent; + if (x.parent == nil) + root = y; + else if (x == x.parent.right) + x.parent.right = y; + else + x.parent.left = y; + + y.right = x; + x.parent = y; + + } + + internal RbTree(Func func) : this(new ComparerOnDelegate(func)) {} + + + internal RbTree(IComparer comparer) { + Clear(); + this.comparer = comparer; + } + + internal void Clear() { + root = nil = new RBNode(RBColor.Black); + } + + //void checkTheTree() { + // int blacks = -1; + // checkBlacks(root, 0, ref blacks); + // checkColors(root); + + //} + + + + //void checkBlacks(RBNode node, int nnow, ref int blacks) { + // if (node != nil) { + // RBNode l = node.left, r = node.right; + + // if (l != nil) + // if (Comparer.Compare(l.item,node.item) >= 0) + // throw new Exception("order"); + + // if (r != nil) + // if (Comparer.Compare(r.item,node.item) <= 0) + // throw new Exception("order"); + + + // } + + // if (node == nil) { + // if (blacks != nnow + 1 && blacks != -1) + // throw new Exception("blacks"); + + // blacks = nnow + 1; + + // } else if (node.color == RBColor.Black) { + // checkBlacks(node.left, nnow + 1, ref blacks); + // checkBlacks(node.right, nnow + 1, ref blacks); + // } else { + // checkBlacks(node.left, nnow, ref blacks); + // checkBlacks(node.right, nnow, ref blacks); + // } + + + //} + + //void checkColors(RBNode x) { + // if (x == nil) + // return; + // //property 3 + // if (x.color == RBColor.Red) + // if (x.left.color == RBColor.Red || x.right.color == RBColor.Red) + // throw new Exception("colors"); + + // checkColors(x.left); + // checkColors(x.right); + //} + + #region IEnumerable Members + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { + return new RBTreeEnumerator(this); + } + + #endregion + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/RBTree.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/RBTree.cs.meta new file mode 100644 index 000000000..f99d755e5 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/RBTree.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a151171447e096b488f0330e292d99bb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/RBTreeEnumerator.cs b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/RBTreeEnumerator.cs new file mode 100644 index 000000000..6f7f51544 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/RBTreeEnumerator.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; + +namespace Microsoft.Msagl.Core.DataStructures { + internal class RBTreeEnumerator : IEnumerator { + bool initialState; + RbTree tree; + RBNode c; + public T Current { + get { return c.Item; } + } + public void Reset() { + initialState = true; + } + + public bool MoveNext() { + if (tree.IsEmpty()) + return false; + + if (initialState == true) { + initialState = false; + c = tree.TreeMinimum(); + } else { + c = tree.Next(c); + } + return c != null; + } + + internal RBTreeEnumerator(RbTree tree) { + this.tree = tree; + Reset(); + + } + + #region IDisposable Members + + public void Dispose() { + GC.SuppressFinalize(this); + } + + #endregion + + #region IEnumerator Members + + object System.Collections.IEnumerator.Current { + get { return c.Item; } + } + + #endregion + } + + +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/RBTreeEnumerator.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/RBTreeEnumerator.cs.meta new file mode 100644 index 000000000..e4c607353 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/RBTree/RBTreeEnumerator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 395191aa1bdd41c49a6404fa3ab9b293 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/DataStructures/RealNumberSpan.cs b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/RealNumberSpan.cs new file mode 100644 index 000000000..5a4614bd2 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/RealNumberSpan.cs @@ -0,0 +1,43 @@ +using System; + +namespace Microsoft.Msagl.Core.DataStructures { + /// + /// this class behaves like one dimensional bounding box + /// + public class RealNumberSpan{ + internal RealNumberSpan(){ + IsEmpty = true; + } + + internal bool IsEmpty { get; set; } + + internal void AddValue(double x){ + if(IsEmpty){ + Min = Max = x; + IsEmpty = false; + } else if(x < Min) + Min = x; + else if(x > Max) + Max = x; + } + + internal double Min { get; set; } + internal double Max { get; set; } + /// + /// + /// + public double Length{ + get { return Max-Min; } + } +#if TEST_MSAGL + /// + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.String.Format(System.String,System.Object,System.Object)")] + public override string ToString() { + return IsEmpty ? "empty" : String.Format("{0},{1}", Min, Max); + } +#endif + } +} \ No newline at end of file diff --git a/Assets/Scripts/NIH/MSAGL/Core/DataStructures/RealNumberSpan.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/RealNumberSpan.cs.meta new file mode 100644 index 000000000..5471d3267 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/RealNumberSpan.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 02eb261364f08ad44855f1343b1c9b8b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/DataStructures/Set.cs b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/Set.cs new file mode 100644 index 000000000..96d4f6ede --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/Set.cs @@ -0,0 +1,300 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.Msagl.Core.DataStructures { + /// + /// Implementation of Set. + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Set")] + public class Set : MarshalByRefObject, ICollection { + HashSet hashSet = new HashSet(); + /// + /// inserts an element into the set + /// + /// + public void Insert(T element) { + hashSet.Add(element); +#if SHARPKIT + UpdateHashKey(); +#endif + } + void System.Collections.Generic.ICollection.Add(T t) { Insert(t); } + /// + /// returns true when the set contains the element + /// + /// + /// + public bool Contains(T item) { return hashSet.Contains(item); } + /// + /// deletes the element from the set + /// + /// + public void Delete(T item) { + hashSet.Remove(item); +#if SHARPKIT + UpdateHashKey(); +#endif + } + /// + /// deletes the element from the set + /// + /// + /// + public bool Remove(T item) { + var ret = hashSet.Remove(item); +#if SHARPKIT + UpdateHashKey(); +#endif + return ret; + } + /// + /// returns the number elements in the set + /// + public int Count { get { return hashSet.Count; } } + /// + /// returns the set entities enumerator + /// + /// + public IEnumerator GetEnumerator() { return hashSet.GetEnumerator(); } + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { + return hashSet.GetEnumerator(); + } + + + + /// + /// the read only flag + /// + public bool IsReadOnly { get { return false; } } + + // public bool IsSynchronized {get {return table.IsSynchronized; }} + + //public T SyncRoot {get {return table.SyncRoot;}} + + /// + /// copies to an array + /// + /// + /// + public void CopyTo(T[] array, int arrayIndex) { + hashSet.CopyTo(array, arrayIndex); + } + + /// + /// ToString + /// + /// + public override string ToString() { + string r = "{"; + int i = 0; + foreach (T o in this) { + r += o.ToString(); + i++; + if (i < Count) + r += ","; + } + r += "}"; + return r; + } + + /// + /// cleans the set + /// + public void Clear() { + this.hashSet.Clear(); +#if SHARPKIT + UpdateHashKey(); +#endif + } + /// + /// clones the set + /// + /// + public Set Clone() { + Set ret = new Set(); + foreach (T i in this) { + ret.Insert(i); + } + return ret; + } + + /// + /// constructor + /// + /// + public Set(IEnumerable enumerableCollection) { + ValidateArg.IsNotNull(enumerableCollection, "enumerableCollection"); + foreach (T j in enumerableCollection) + this.Insert(j); +#if SHARPKIT + UpdateHashKey(); +#endif + } + + /// + /// an empty constructor + /// + public Set() { + } + + /// + /// creates an array from the set elements + /// + /// + /// + public System.Array ToArray(System.Type type) { + System.Array ret = System.Array.CreateInstance(type, this.Count); + int i = 0; + foreach (T o in this) { + ret.SetValue(o, i++); + } + return ret; + } + /// + /// overloading plus operator + /// + /// + /// + /// + static public Set operator +(Set set0, Set set1) { + ValidateArg.IsNotNull(set1, "set1"); + Set ret = new Set(set0); + foreach (T t in set1) + ret.Insert(t); + return ret; + } + + /// + /// overloading plus operator + /// + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates")] + static public Set operator -(Set set0, Set set1) { + ValidateArg.IsNotNull(set1, "set1"); + Set ret = new Set(set0); + foreach (T t in set1) + ret.Remove(t); + return ret; + } + /// + /// overloading the == operator + /// + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "1"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0")] + static public bool operator ==(Set set0, Set set1) { + if ((object)set0 == null && (object)set1 == null) + return true; + if ((object)set0 == null && (object)set1 != null) + return false; + + if ((object)set1 == null && (object)set0 != null) + return false; + + foreach (T t in set0) + if (!set1.Contains(t)) + return false; + + foreach (T t in set1) + if (!set0.Contains(t)) + return false; + + return true; + } + + /// + /// overloading the == operator + /// + /// + /// + /// + static public bool operator !=(Set set0, Set set1) { + return !(set0 == set1); + } + /// + /// intersection + /// + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates")] + static public Set operator *(Set set0, Set set1) { + ValidateArg.IsNotNull(set0, "set0"); + ValidateArg.IsNotNull(set1, "set1"); + return + new Set(set0.Count < set1.Count + ? set0.Where(a => set1.Contains(a)) + : set1.Where(a => set0.Contains(a))); + } + /// + /// the equality + /// + /// the object to compare to + /// + public override bool Equals(object obj) { + Set set = obj as Set; + if (set == null) + return false; + return set == this; + } + + /// + /// hash code + /// + /// + public override int GetHashCode() { + int ret = 0; + foreach (T t in this) + ret |= t.GetHashCode(); + return ret; + } + + /// + /// a friendly add, union, operator + /// + /// + /// + /// + public Set Add(Set set0, Set set1) { + return set0 + set1; + } + + /// + /// inserts a range of elements into the set + /// + /// elements to insert + public void InsertRange(IEnumerable elements ) + { + ValidateArg.IsNotNull(elements,"elements"); + foreach (var element in elements) + Insert(element); + } + + /// + /// checks the set is contained in the "otherSet" + /// + /// + /// + public bool IsContained(Set otherSet) { return this.All(p => otherSet.Contains(p)); } + + /// + /// checks that this set conains the "otherSet" + /// + /// + /// + public bool Contains(Set otherSet) { return otherSet.All(p => Contains(p)); } + +#if SHARPKIT //https://code.google.com/p/sharpkit/issues/detail?id=289 + private SharpKit.JavaScript.JsString _hashKey; + private void UpdateHashKey() + { + _hashKey = GetHashCode().ToString(); + } +#endif + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/DataStructures/Set.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/Set.cs.meta new file mode 100644 index 000000000..9e3b76d16 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/Set.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 23411223e702a51449131ae3cb7deeb5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/DataStructures/Size.cs b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/Size.cs new file mode 100644 index 000000000..3fd718d2e --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/Size.cs @@ -0,0 +1,72 @@ +namespace Microsoft.Msagl.Core.DataStructures +{ + + + /// + /// Size structure + /// + public struct Size + { + double width; + /// + /// width + /// + public double Width + { + get { return width; } + set { width = value; } + } + double height; + /// + /// Height + /// + public double Height + { + get { return height; } + set { height = value; } + } + + /// + /// constructor + /// + /// + /// + public Size(double width, double height) + { + this.width = width; + + + this.height = height; + + } + + /// + /// + /// + /// + /// + /// + public static Size operator /(Size s, double d) { return new Size(s.Width / d, s.Height / d); } + + + /// + /// + /// + /// + /// + /// + public static Size operator *(Size s, double d) { return new Size(s.Width * d, s.Height * d); } + + + /// + /// padding the size ( from both sides!) + /// + /// + public void Pad(double padding) { + width += 2*padding; + height += 2*padding; + } + } + + +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/DataStructures/Size.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/Size.cs.meta new file mode 100644 index 000000000..4a7175771 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/Size.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 10ae915a97e51564585fa058c476f9cf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/DataStructures/SymmetricTuple.cs b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/SymmetricTuple.cs new file mode 100644 index 000000000..86c03a705 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/SymmetricTuple.cs @@ -0,0 +1,63 @@ +using System; + +namespace Microsoft.Msagl.Core.DataStructures { + /// + /// a tuple such that (a,b)==(b,a) for any a and b + /// + /// + public class SymmetricTuple { + bool Equals(SymmetricTuple other) { + return (A.Equals(other.A) && B.Equals(other.B)) || + (A.Equals(other.B) && B.Equals(other.A)); + } + + /// + /// Serves as a hash function for a particular type. + /// + /// + /// A hash code for the current . + /// + /// 2 + public override int GetHashCode() { + +#if !SHARPKIT + unchecked { +#endif + return A.GetHashCode() ^ B.GetHashCode(); // we need a symmetric hash code +#if !SHARPKIT + } +#endif + } + + public T A { get; private set; } + public T B { get; private set; } + /// + /// constructor + /// + /// + /// + public SymmetricTuple(T a, T b) { + A = a; + B = b; + } + + /// + /// Determines whether the specified is equal to the current . + /// + /// + /// true if the specified is equal to the current ; otherwise, false. + /// + /// The object to compare with the current object. 2 + public override bool Equals(object obj) { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != GetType()) return false; + return Equals((SymmetricTuple)obj); + } + + public override string ToString() { + return String.Format("({0},{1})", A, B); + } + } + +} \ No newline at end of file diff --git a/Assets/Scripts/NIH/MSAGL/Core/DataStructures/SymmetricTuple.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/SymmetricTuple.cs.meta new file mode 100644 index 000000000..e41968d53 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/DataStructures/SymmetricTuple.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4a850709a43d60748b3dd2674e1d902a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry.meta new file mode 100644 index 000000000..7c646e35e --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 90b057800aaf56a4c9d635fac7c02c89 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/ApproximateComparer.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/ApproximateComparer.cs new file mode 100644 index 000000000..0471b5ed2 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/ApproximateComparer.cs @@ -0,0 +1,198 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.Msagl.Core.Geometry { + /// + /// Class that provides methods for doing approximate comparisons. + /// + public static class ApproximateComparer { + /// + /// return true if the points are close enough + /// + public static bool Close(Point pointA, Point pointB, double tolerance) { + return (pointA - pointB).Length <= tolerance; + } + + /// + /// return true if the points are close enough + /// + public static bool Close(Point pointA, Point pointB) { + return Close(pointA, pointB, DistanceEpsilon); + } + + /// + /// return true if the numbers are close enough + /// + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "a"), SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "b")] + public static bool Close(double a, double b) { + return Math.Abs(a - b) <= DistanceEpsilon; + } + + /// + /// return true if the two rects are nearly identical (ignoring precision errors smaller than DistanceEpsilon) + /// + public static bool Close(Rectangle rect1, Rectangle rect2, double tolerance) { + return Close(rect1.LeftBottom, rect2.LeftBottom, tolerance) && Close(rect1.RightTop, rect2.RightTop, tolerance); + } + + /// + /// return true if the two rects are nearly identical (ignoring precision errors smaller than DistanceEpsilon) + /// + /// first Rectangle + /// second Rectangle + /// + public static bool Close(Rectangle rect1, Rectangle rect2) { + return Close(rect1, rect2, DistanceEpsilon); + } + + /// + /// The usual Compare operation, but within Curve.DistanceEpsilon. + /// + /// + /// + /// + public static int Compare(double numberA, double numberB) { + double c = numberA - numberB; + // The <= and >= here complement the < and > in Close(double, double). + if (c <= -DistanceEpsilon) + return -1; + if (c >= DistanceEpsilon) + return 1; + return 0; + } + + + /// + /// The usual > operation, but within Curve.DistanceEpsilon. + /// + /// + /// + /// + public static bool Greater(double numberA, double numberB) { + return Compare(numberA, numberB) > 0; + } + + /// + /// The usual >= operation, but within Curve.DistanceEpsilon. + /// + /// + /// + /// + public static bool GreaterOrEqual(double numberA, double numberB) { + return Compare(numberA, numberB) >= 0; + } + + /// + /// The usual less operation, but within Curve.DistanceEpsilon. + /// + /// + /// + /// + public static bool Less(double numberA, double numberB) { + return Compare(numberA, numberB) < 0; + } + + /// + /// The usual less operation, but within Curve.DistanceEpsilon. + /// + /// + /// + /// + public static bool LessOrEqual(double numberA, double numberB) { + return Compare(numberA, numberB) <= 0; + } + + /// + /// returns true if two intersections points are close enough + /// + /// + /// + /// + public static bool CloseIntersections(Point intersectionPoint0, Point intersectionPoint1) { + Point c = intersectionPoint0 - intersectionPoint1; + return c * c < IntersectionEpsilon * IntersectionEpsilon; + } + + /// + /// 0 iff value is close to zero; + /// 1 iff value is strictly greater than zero; + /// -1 iff value is strictly lower than zero; + /// + public static int Sign(double value) { + if (value > DistanceEpsilon) return 1; + if (value < -DistanceEpsilon) return -1; + return 0; + } + + static readonly double squareOfDistanceEpsilon = Math.Pow(10.0, -DistanceEpsilonPrecision * 2); + + /// + /// The distance for two points considered to be the same + /// + internal static double DistanceEpsilon = Math.Pow(10.0, -DistanceEpsilonPrecision); + + /// + /// The square of the distance epsilon for two points considered to be the same + /// + internal static double SquareOfDistanceEpsilon { + get { return squareOfDistanceEpsilon; } + } + + /// + /// The digits of precision for the distance for two points considered to be the same + /// + internal static int DistanceEpsilonPrecision { + get { return 6; } + } + + static double distXEps = 0.0001; + + /// + /// The distance for two intersection points considered to be the same + /// + public static double IntersectionEpsilon { + get { return distXEps; } + set { distXEps = value; } + } + + static double tolerance = 1.0E-8; + + /// + /// The distance where to real numbers are considered the same + /// + public static double Tolerance { + get { return tolerance; } + set { tolerance = value; } + } + + static double userDefinedTolerance = tolerance; + + /// + /// A tolerance that is settable by users of MSAGL to adjust performance. + /// + public static double UserDefinedTolerance { + get { return userDefinedTolerance; } + set { userDefinedTolerance = value; } + } + + /// + /// point coordinates will be rounded to NumberOfDigitsToRound for comparison + /// + static internal int NumberOfDigitsToRound { + get { return DistanceEpsilonPrecision; } + } + + static internal Point Round(Point point) { + return new Point(Round(point.X), Round(point.Y)); + } + + static internal Point Round(Point point, int numberDigitsToRound) + { + return new Point(Math.Round(point.X, numberDigitsToRound), Math.Round(point.Y, numberDigitsToRound)); + } + + static internal double Round(double value) { + return Math.Round(value, NumberOfDigitsToRound); + } + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/ApproximateComparer.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/ApproximateComparer.cs.meta new file mode 100644 index 000000000..2f1c8b1eb --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/ApproximateComparer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 380260d63cdb0a0418bb345f925c6291 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/CompassVector.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/CompassVector.cs new file mode 100644 index 000000000..0206ee2b2 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/CompassVector.cs @@ -0,0 +1,254 @@ +using System; +using System.Diagnostics; + +namespace Microsoft.Msagl.Core.Geometry { + internal struct CompassVector { + + /// + /// Override op== + /// + /// + /// + /// + public static bool operator ==(CompassVector a, CompassVector b) { + return a.Dir == b.Dir; + } + + /// + /// Return the hash code. + /// + /// + public override int GetHashCode() { + return (int)Dir; + } + + /// + /// Override op!= + /// + /// + /// + /// + public static bool operator !=(CompassVector a, CompassVector b) { + return a.Dir != b.Dir; + } + + internal CompassVector(Directions direction) + : this() { + Dir = direction; + } + + // Warning: do not use for VisibilityGraph generation. See VectorDirection(a) comments. + internal CompassVector(Point a) + : this() { + Dir = VectorDirection(a); + } + + internal CompassVector(Point a, Point b) + : this() { + Dir = VectorDirection(a, b); + } + + + internal Directions Dir { get; set; } + internal CompassVector Right { + get { return new CompassVector(RotateRight(Dir)); } + } + + internal static Directions RotateRight(Directions direction) { + switch (direction) { + case Directions.North: + return Directions.East; + case Directions.East: + return Directions.South; + case Directions.South: + return Directions.West; + case Directions.West: + return Directions.North; + default: + throw new InvalidOperationException(); + } + } + + internal static Directions RotateLeft(Directions direction) { + switch (direction) { + case Directions.North: + return Directions.West; + case Directions.West: + return Directions.South; + case Directions.South: + return Directions.East; + case Directions.East: + return Directions.North; + default: + throw new InvalidOperationException(); + } + } + + internal static int ToIndex(Directions direction) { + switch (direction) { + case Directions.North: + return 0; + case Directions.East: + return 1; + case Directions.South: + return 2; + case Directions.West: + return 3; + default: + throw new InvalidOperationException(); + } + } + + internal static Directions VectorDirection(Point d){ + Directions r = Directions.None; + if(d.X > ApproximateComparer.DistanceEpsilon) + r = Directions.East; + else if(d.X < -ApproximateComparer.DistanceEpsilon) + r = Directions.West; + if(d.Y > ApproximateComparer.DistanceEpsilon) + r |= Directions.North; + else if(d.Y < -ApproximateComparer.DistanceEpsilon) + r |= Directions.South; + return r; + } + + internal static Directions VectorDirection(Point a, Point b) { + Directions r = Directions.None; + + // This method is called a lot as part of rectilinear layout. + // Try to keep it quick. + double horizontalDiff = b.X - a.X; + double verticalDiff = b.Y - a.Y; + double halfEpsilon = ApproximateComparer.DistanceEpsilon / 2; + + if (horizontalDiff > halfEpsilon) + r = Directions.East; + else if (-horizontalDiff > halfEpsilon) + r = Directions.West; + if (verticalDiff > halfEpsilon) + r |= Directions.North; + else if (-verticalDiff > halfEpsilon) + r |= Directions.South; + return r; + } + + internal static Directions DirectionsFromPointToPoint(Point a, Point b) { + return VectorDirection(a, b); + } + + internal static Directions PureDirectionFromPointToPoint(Point a, Point b) { + Directions dir = VectorDirection(a, b); + Debug.Assert(IsPureDirection(dir), "Impure direction found"); + return dir; + } + + internal static Directions OppositeDir(Directions direction) { + switch (direction) { + case Directions.North: + return Directions.South; + case Directions.West: + return Directions.East; + case Directions.South: + return Directions.North; + case Directions.East: + return Directions.West; + default: + return Directions.None; + } + } + + internal static bool IsPureDirection(Directions direction) { + switch (direction) { + case Directions.North: + return true; + case Directions.East: + return true; + case Directions.South: + return true; + case Directions.West: + return true; + default: + return false; + } + } + + internal static bool IsPureDirection(Point a, Point b) { + return IsPureDirection(DirectionsFromPointToPoint(a, b)); + } + + /// + /// + /// + /// + /// + public bool Equals(CompassVector other) { + return Equals(other.Dir, Dir); + } + + + internal static bool DirectionsAreParallel(Directions a, Directions b){ + return a == b || a == OppositeDir(b); + } + /// + /// + /// + /// + /// + public override bool Equals(object obj) { + if (ReferenceEquals(null, obj)) return false; + if (obj.GetType() != typeof(CompassVector)) return false; + return Equals((CompassVector)obj); + } + + /// + /// Returns a string representing the direction. + /// + /// + public override string ToString() { + return Dir.ToString(); + } + + /// + /// Translates the CompassVector's direction into a new Point. + /// + /// + public Point ToPoint() { + var p = new Point(); + if ((Dir & Directions.East) == Directions.East) + p.X += 1; + if ((Dir & Directions.North) == Directions.North) + p.Y += 1; + if ((Dir & Directions.West) == Directions.West) + p.X -= 1; + if ((Dir & Directions.South) == Directions.South) + p.Y -= 1; + return p; + } + + /// + /// Translates a direction into a Point. + /// + /// + public static Point ToPoint(Directions dir) { + return (new CompassVector(dir)).ToPoint(); + } + + /// + /// the negation operator + /// + /// + /// + public static CompassVector operator -(CompassVector directionVector) { + return new CompassVector(OppositeDir(directionVector.Dir)); + } + /// + /// the negation operator + /// + /// + public CompassVector Negate() { + return -this; + } + + + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/CompassVector.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/CompassVector.cs.meta new file mode 100644 index 000000000..e94ce31fe --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/CompassVector.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5f1d55eb0ba264f45943033b4518baf5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/ConvexHull.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/ConvexHull.meta new file mode 100644 index 000000000..d5cbeec3d --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/ConvexHull.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6c814fa42e9363c49ac9b234daeea29b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/ConvexHull/ConvexHull.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/ConvexHull/ConvexHull.cs new file mode 100644 index 000000000..d89202871 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/ConvexHull/ConvexHull.cs @@ -0,0 +1,196 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using Microsoft.Msagl.Core.Geometry.Curves; +using Microsoft.Msagl.Core.Layout; +using Microsoft.Msagl.DebugHelpers; +using Microsoft.Msagl.Routing; + +namespace Microsoft.Msagl.Core.Geometry { + /// + /// Creates the convex hull of a set of points following "Computational Geometry, second edition" of O'Rourke + /// + public sealed class ConvexHull { + HullPoint[] hullPoints; + Point pivot; + HullStack stack; + HullPointComparer comparer; + + ConvexHull(IEnumerable bodyPoints) { + SetPivotAndAllocateHullPointsArray(bodyPoints); + } + + void SetPivotAndAllocateHullPointsArray(IEnumerable bodyPoints) { + pivot = new Point(0, Double.MaxValue); //set Y to a very big value + int pivotIndex = -1; + int n = 0; + foreach (Point point in bodyPoints) { + if (point.Y < pivot.Y) { + pivot = point; + pivotIndex = n; + } + else if (point.Y == pivot.Y) + if (point.X > pivot.X) { + pivot = point; + pivotIndex = n; + } + n++; + } + if (n >= 1) { + hullPoints = new HullPoint[n - 1]; //we will not copy the pivot into the hull points + n = 0; + foreach (Point point in bodyPoints) + if (n != pivotIndex) + hullPoints[n++] = new HullPoint(point); + else + pivotIndex = -1; //forget where the pivot was + } + } + + Point StackTopPoint { + get { return stack.Point; } + } + + Point StackSecondPoint { + get { return stack.Next.Point; } + } + + /// + /// calculates the convex hull of the given set of points + /// + /// Point of the convex hull. + /// The list of extreme points of the hull boundaries in the clockwise order + public static IEnumerable CalculateConvexHull(IEnumerable pointsOfTheBody) { + var convexHull = new ConvexHull(pointsOfTheBody); + return convexHull.Calculate(); + } + + IEnumerable Calculate() { + if (pivot.Y == Double.MaxValue) + return new Point[0]; + if (hullPoints.Length == 0) + return new[] {pivot}; + SortAllPointsWithoutPivot(); + Scan(); + return EnumerateStack(); + } + + IEnumerable EnumerateStack() { + HullStack stackCell = stack; + while (stackCell != null) { + yield return stackCell.Point; + stackCell = stackCell.Next; + } + } + + void Scan() { + int i = 0; + while (hullPoints[i].Deleted) + i++; + + stack = new HullStack(pivot); + Push(i++); + if (i < hullPoints.Length) { + if (!hullPoints[i].Deleted) + Push(i++); + else + i++; + } + + while (i < hullPoints.Length) { + if (!hullPoints[i].Deleted) { + if (LeftTurn(i)) + Push(i++); + else + Pop(); + } + else + i++; + } + + //cleanup the end + while (StackHasMoreThanTwoPoints() && !LeftTurnToPivot()) + Pop(); + } + + bool LeftTurnToPivot() { + return Point.GetTriangleOrientation(StackSecondPoint, StackTopPoint, pivot) == + TriangleOrientation.Counterclockwise; + } + + bool StackHasMoreThanTwoPoints() { + return stack.Next != null && stack.Next.Next != null; + } + + void Pop() { + stack = stack.Next; + } + + + bool LeftTurn(int i) { + if (stack.Next == null) + return true; //there is only one point in the stack + var orientation = Point.GetTriangleOrientationWithIntersectionEpsilon(StackSecondPoint, StackTopPoint, hullPoints[i].Point); + if (orientation == TriangleOrientation.Counterclockwise) + return true; + if (orientation == TriangleOrientation.Clockwise) + return false; + return BackSwitchOverPivot(hullPoints[i].Point); + } + bool BackSwitchOverPivot(Point point) { + //we know here that there at least two points in the stack but it has to be exaclty two + if (stack.Next.Next != null) + return false; + Debug.Assert(StackSecondPoint == pivot); + return StackTopPoint.X > pivot.X + ApproximateComparer.DistanceEpsilon && + point.X < pivot.X - ApproximateComparer.DistanceEpsilon; + } + + void Push(int p) { + var t = new HullStack(hullPoints[p].Point) {Next = stack}; + stack = t; + } + + void SortAllPointsWithoutPivot() { + comparer = new HullPointComparer(pivot); + Array.Sort(hullPoints, comparer); +// if (true) { +// var list = new List(); +// var d = 3.0; +// list.Add(new DebugCurve(100, 0.001, "magenta", El(ref d, pivot))); +// foreach (var hullPoint in hullPoints) { +// list.Add(new DebugCurve(100,0.001, hullPoint.Deleted ? "blue" : "green", El(ref d, hullPoint.Point))); +// } +// LayoutAlgorithmSettings.ShowDebugCurvesEnumeration(list); +// } + } + + +/* + Ellipse El(ref double d, Point p) { + var ret= new Ellipse(d, d, p); + d -= 0.01; + return ret; + } +*/ + + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.String.Format(System.String,System.Object,System.Object)")] + internal static Polyline CreateConvexHullAsClosedPolyline(IEnumerable points) { + var convexHull = new Polyline(CalculateConvexHull(points)) { Closed = true }; +#if TEST_MSAGL + foreach (var point in points) { + if (Curve.PointRelativeToCurveLocation(point, convexHull) == PointLocation.Outside) { + var hullPoint = convexHull[convexHull.ClosestParameter(point)]; + + // This can be too restrictive if very close points are put into the hull. It is probably + // better to clean up in the caller before doing this, but this assert can also be relaxed. + Debug.Assert(ApproximateComparer.Close(point, hullPoint, ApproximateComparer.IntersectionEpsilon * 20), String.Format("not CloseIntersections: initial point {0}, hull point {1}", point, hullPoint)); + + } + } +#endif // TEST_MSAGL + return convexHull; + } + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/ConvexHull/ConvexHull.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/ConvexHull/ConvexHull.cs.meta new file mode 100644 index 000000000..c34d4e380 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/ConvexHull/ConvexHull.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 848192ed3ea85614cb33c20793d74157 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/ConvexHull/HullPoint.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/ConvexHull/HullPoint.cs new file mode 100644 index 000000000..b95ec6449 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/ConvexHull/HullPoint.cs @@ -0,0 +1,27 @@ + +namespace Microsoft.Msagl.Core.Geometry { + internal class HullPoint { + Point point; + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Point Point { + get { return point; } + set { point = value; } + } + bool deleted; + + internal bool Deleted { + get { return deleted; } + set { deleted = value; } + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal HullPoint(Point point) { + this.Point = point; + } + + public override string ToString() { + return point + (Deleted ? "X" : ""); + } + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/ConvexHull/HullPoint.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/ConvexHull/HullPoint.cs.meta new file mode 100644 index 000000000..035c343d8 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/ConvexHull/HullPoint.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a24e6188dd050ce46850d17ef181852b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/ConvexHull/HullPointComparer.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/ConvexHull/HullPointComparer.cs new file mode 100644 index 000000000..bb04b603f --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/ConvexHull/HullPointComparer.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; + +namespace Microsoft.Msagl.Core.Geometry { + internal class HullPointComparer:IComparer { + Point pivot; + + /// + /// note that this function can change "deleted" member for collinear points + /// + /// + /// + /// +#if SHARPKIT //http://code.google.com/p/sharpkit/issues/detail?id=203 + //SharpKit/Colin - https://code.google.com/p/sharpkit/issues/detail?id=332 + public int Compare(HullPoint i, HullPoint j) { +#else + int IComparer.Compare(HullPoint i, HullPoint j) { +#endif + if (i == j) + return 0; + if (i == null) + return -1; + if (j == null) + return 1; + + switch (Point.GetTriangleOrientationWithIntersectionEpsilon(pivot, i.Point, j.Point)) { + case TriangleOrientation.Counterclockwise: + return -1; + case TriangleOrientation.Clockwise: + return 1; + case TriangleOrientation.Collinear: { + //because of the double point error pi and pj can be on different sizes of the pivot on the horizontal line passing through the pivot, or rather just above it + var piDelX = i.Point.X - pivot.X; + var pjDelX = j.Point.X - pivot.X; + if (piDelX > ApproximateComparer.DistanceEpsilon && pjDelX < -ApproximateComparer.DistanceEpsilon) { + return -1; + } + if (piDelX < -ApproximateComparer.DistanceEpsilon && pjDelX > ApproximateComparer.DistanceEpsilon) { + return 1; + } + + //here i and j cannot be on the different sides of the pivot because of the choice of the pivot + //delete the one that is closer to the pivot. + var pi = i.Point - pivot; + var pj = j.Point - pivot; + var iMinJ = pi.L1 - pj.L1; + if (iMinJ < 0) { + i.Deleted = true; + return -1; + } + if (iMinJ > 0) { + j.Deleted = true; + return 1; + } + + //points are the same, leave the one with the greatest hash code + if (i.GetHashCode() < j.GetHashCode()) + i.Deleted = true; + else + j.Deleted = true; + + return 0; + } + + } + throw new InvalidOperationException(); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal HullPointComparer(Point pivotPoint) { + this.pivot = pivotPoint; + } + + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/ConvexHull/HullPointComparer.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/ConvexHull/HullPointComparer.cs.meta new file mode 100644 index 000000000..4fd9c1679 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/ConvexHull/HullPointComparer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5544c1d609815f24db41713b5cee93c4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/ConvexHull/HullStack.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/ConvexHull/HullStack.cs new file mode 100644 index 000000000..e63068e44 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/ConvexHull/HullStack.cs @@ -0,0 +1,25 @@ +namespace Microsoft.Msagl.Core.Geometry { + internal class HullStack { + Point hullPoint; + + internal Point Point { + get { return hullPoint; } + set { hullPoint = value; } + } + HullStack next; + + internal HullStack Next { + get { return next; } + set { next = value; } + } + + internal HullStack(Point hullPoint) { + this.Point = hullPoint; + } + + public override string ToString() { + return hullPoint.ToString(); + } + + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/ConvexHull/HullStack.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/ConvexHull/HullStack.cs.meta new file mode 100644 index 000000000..7629add4a --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/ConvexHull/HullStack.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d5d2bc29fc3a11c408e92a50c141d04c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves.meta new file mode 100644 index 000000000..6a930dca8 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f26b9b4baf6941a4d8b4c04ce6bfc6fc +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ClosestPointOnCurve.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ClosestPointOnCurve.cs new file mode 100644 index 000000000..d695cc0f3 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ClosestPointOnCurve.cs @@ -0,0 +1,63 @@ +using System; + +namespace Microsoft.Msagl.Core.Geometry.Curves { + internal class ClosestPointOnCurve { + ClosestPointOnCurve() { } + ///// + ///// Gets the closest point on the curve to the point + ///// + ///// + ///// + ///// will return Double.MaxVal if Newton iterations fail + ///// + internal static double ClosestPoint(ICurve curve, Point a, double hint, double low, double high) { + /* + * Let F=(c(t)-a)^2. We try to bring to zero the first derivative of F, Ft. Denote it by f(t). + * Applying the Newton method we see that dt=-f(t)/der(f(t) + * The first derivative of F, f, has the form (c-a)*ct. We discarded a multiplier here. + * The second derivative has the form ct*ct+(c-a)*ctt + * The new t becomes t-dt + */ + const int numberOfIterationsMax = 5; + const int numberOfOverShootsMax = 5; + double t = hint; + + int numberOfIteration = 0; + int numberOfOvershoots = 0; + double dt; + bool abort = false; + do { + Point c = curve[t]; + Point ct = curve.Derivative(t); + Point ctt = curve.SecondDerivative(t); + + double secondDerivative = ct * ct + (c - a) * ctt; + + if (Math.Abs(secondDerivative) < ApproximateComparer.Tolerance) + return t; + + dt = (c - a) * ct / secondDerivative; + + t -= dt; + + if (t > high + ApproximateComparer.Tolerance) { + t = high; + numberOfOvershoots++; + } else if (t < low - ApproximateComparer.Tolerance) { + t = low; + numberOfOvershoots++; + } + numberOfIteration++; + } while (Math.Abs(dt) > ApproximateComparer.Tolerance &&! + (abort = (numberOfIteration >= numberOfIterationsMax || numberOfOvershoots >= numberOfOverShootsMax))); + + //may be the initial value was just fine + if (abort && (curve[hint] - a).Length < ApproximateComparer.DistanceEpsilon) + t = hint; + + return t; + + } + + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ClosestPointOnCurve.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ClosestPointOnCurve.cs.meta new file mode 100644 index 000000000..f74c97165 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ClosestPointOnCurve.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c70b0860b9cf2454a852dff5c58a0ba7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/CubicBezierSegment.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/CubicBezierSegment.cs new file mode 100644 index 000000000..6d86d942c --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/CubicBezierSegment.cs @@ -0,0 +1,630 @@ +// #region Using directives + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Microsoft.Msagl.Core.DataStructures; +using Microsoft.Msagl.Core.Layout; +using Microsoft.Msagl.DebugHelpers; +//#endregion + +namespace Microsoft.Msagl.Core.Geometry.Curves { + /// + /// Cubic Bezier Segment + /// +#if TEST_MSAGL + [Serializable] +#endif + public class CubicBezierSegment : ICurve { + + /// + /// left derivative at t + /// + /// the parameter where the derivative is calculated + /// + public Point LeftDerivative(double t) { + return Derivative(t); + } + + /// + /// right derivative at t + /// + /// the parameter where the derivative is calculated + /// + public Point RightDerivative(double t) { + return Derivative(t); + } + + + /// + /// control points + /// + Point[] b = new Point[4]; + + + /// + /// coefficients + /// + Point l, e, c; + + /// + /// get a control point + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "B")] + public Point B(int controlPointIndex) { + return b[controlPointIndex]; + } + /// + /// ToString + /// + /// + override public string ToString() { + return String.Format(CultureInfo.InvariantCulture, "(Bezie{0},{1},{2},{3})", b[0], b[1], b[2], b[3]); + } + ParallelogramNodeOverICurve pBoxNode; + /// + /// A tree of ParallelogramNodes covering the curve. + /// This tree is used in curve intersections routines. + /// + /// + public ParallelogramNodeOverICurve ParallelogramNodeOverICurve { + get { +#if PPC + lock(this){ +#endif + if (pBoxNode != null) + return pBoxNode; + return pBoxNode = ParallelogramNodeOverICurve.CreateParallelogramNodeForCurveSegment(this); +#if PPC + } +#endif + } + } + /// + /// Returns the point on the curve corresponding to parameter t + /// + /// + /// + public Point this[double t] { + get { + double t2 = t * t; + double t3 = t2 * t; + return l * t3 + e * t2 + c * t + b[0]; + } + } + + static void AdjustParamTo01(ref double u) { + if (u > 1) + u = 1; + else if (u < 0) + u = 0; + } + //throw away the segments [0,u] and [v,1] of the segment + + /// + /// Returns the trimmed curve + /// + /// + /// + /// + public ICurve Trim(double u, double v) { + + AdjustParamTo01(ref u); + AdjustParamTo01(ref v); + + if (u > v) + return Trim(v, u); + + if (u > 1.0 - ApproximateComparer.Tolerance) + return new CubicBezierSegment(b[3], b[3], b[3], b[3]); + + Point[] b1 = new Point[3]; + Point[] b2 = new Point[2]; + Point pv = Casteljau(u, b1, b2); + + //this will be the trim to [v,1] + CubicBezierSegment trimByU = new CubicBezierSegment(pv, b2[1], b1[2], b[3]); + + + //1-v is not zero here because we have handled already the case v=1 + Point pu = trimByU.Casteljau((v - u) / (1.0 - u), b1, b2); + + return new CubicBezierSegment(trimByU.b[0], b1[0], b2[0], pu); + + } + + /// + /// Not Implemented: Returns the trimmed curve, wrapping around the end if start is greater than end. + /// + /// The starting parameter + /// The ending parameter + /// The trimmed curve + public ICurve TrimWithWrap(double start, double end) { + throw new NotImplementedException(); + } + + //array for Casteljau method + internal Point Casteljau(double t, Point[] b1, Point[] b2) { + + double f = 1.0 - t; + for (int i = 0; i < 3; i++) + b1[i] = b[i] * f + b[i + 1] * t; + + for (int i = 0; i < 2; i++) + b2[i] = b1[i] * f + b1[i + 1] * t; + + return b2[0] * f + b2[1] * t; + + } + /// + /// first derivative + /// + /// + /// + public Point Derivative(double t) { + return 3 * l * t * t + 2 * e * t + c; + } + /// + /// second derivative + /// + /// + /// + public Point SecondDerivative(double t) { return 6 * l * t + 2 * e; } + + /// + /// third derivative + /// + /// + /// + public Point ThirdDerivative(double t) { return 6 * l; } + /// + /// the constructor + /// + /// + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1025:ReplaceRepetitiveArgumentsWithParamsArray"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "b")] + public CubicBezierSegment(Point b0, Point b1, Point b2, Point b3) { + + this.b[0] = b0; + this.b[1] = b1; + this.b[2] = b2; + this.b[3] = b3; + c = 3 * (b[1] - b[0]); + e = 3 * (b[2] - b[1]) - c; + l = b[3] - b[0] - c - e; + } + + /// + /// this[ParStart] + /// + public Point Start { + get { return b[0]; } + } + + /// + /// this[ParEnd] + /// + public Point End { + get { return b[3]; } + } + + + double parStart; + + /// + /// the start of the parameter domain + /// + public double ParStart { + get { return parStart; } + set { parStart = value; } + } + + double parEnd = 1; + + /// + /// the end of the parameter domain + /// + public double ParEnd { + get { return parEnd; } + set { parEnd = value; } + } + + /// + /// this[Reverse[t]]=this[ParEnd+ParStart-t] + /// + /// + public ICurve Reverse() { + return new CubicBezierSegment(b[3], b[2], b[1], b[0]); + } + + /// + /// Returns the curved moved by delta + /// + public void Translate(Point delta) { + this.b[0] += delta; + this.b[1] += delta; + this.b[2] += delta; + this.b[3] += delta; + c = 3 * (b[1] - b[0]); + e = 3 * (b[2] - b[1]) - c; + l = b[3] - b[0] - c - e; + + pBoxNode = null; + } + + /// + /// Returns the curved scaled by x and y + /// + /// + /// + /// + public ICurve ScaleFromOrigin(double xScale, double yScale) + { + return new CubicBezierSegment(Point.Scale(xScale, yScale, b[0]), Point.Scale(xScale, yScale, b[1]), Point.Scale(xScale, yScale, b[2]), Point.Scale(xScale, yScale, b[3])); + } + + /// + /// Offsets the curve in the direction of dir + /// + /// + /// + /// + public ICurve OffsetCurve(double offset, Point dir) { return null; } + + /// + /// return length of the curve segment [start,end] + /// + /// + /// + /// + public double LengthPartial(double start, double end) { + return Trim(start, end).Length; + } + /// + /// Get the length of the curve + /// + public double Length { + get { return LengthOnControlPolygon(b[0],b[1],b[2],b[3]); } + } + + /// + /// + /// + /// + /// + /// + /// + /// + static double LengthOnControlPolygon(Point b0, Point b1, Point b2, Point b3) { + var innerCordLength = (b3- b0).Length; + var controlPointPolygonLength = (b1 - b0).Length + (b2 - b1).Length + (b3 - b2).Length; + if (controlPointPolygonLength - innerCordLength > Curve.LineSegmentThreshold) { + var mb0 = (b0 + b1)/2; + var mb1 = (b1 + b2)/2; + var mb2=(b2 + b3)/2; + var mmb0 = (mb0 + mb1)/2; + var mmb1 = (mb2 + mb1)/2; + var mmmb0 = (mmb0 + mmb1)/2; +// LayoutAlgorithmSettings.ShowDebugCurves(new DebugCurve(100, 2, "blue", new CubicBezierSegment(b0, b1, b2, b3)), new DebugCurve(100, 1, "red", new CubicBezierSegment(b0, mb0, mmb0, mmmb0)), new DebugCurve(100, 1, "green", new CubicBezierSegment(mmmb0, mmb1, mb2, b3))); + return LengthOnControlPolygon(b0, mb0, mmb0, mmmb0) + LengthOnControlPolygon(mmmb0, mmb1, mb2, b3); + } + + return (controlPointPolygonLength + innerCordLength) / 2; + } + + /// + /// the segment bounding box + /// + public Rectangle BoundingBox { + get { + var ret = new Rectangle(this.b[0], this.b[1]); + ret.Add(b[2]); + ret.Add(b[3]); + return ret; + } + } + + + /// + /// Return the transformed curve + /// + /// + /// the transformed curve + public ICurve Transform(PlaneTransformation transformation) { + return new CubicBezierSegment(transformation * b[0], transformation * b[1], transformation * b[2], transformation * b[3]); + } + + /// + /// returns a parameter t such that the distance between curve[t] and targetPoint is minimal + /// and t belongs to the closed segment [low,high] + /// + /// the point to find the closest point + /// the upper bound of the parameter + /// the low bound of the parameter + /// + public double ClosestParameterWithinBounds(Point targetPoint, double low, double high) { + System.Diagnostics.Debug.Assert(high <= 1 && low >= 0); + System.Diagnostics.Debug.Assert(low <= high); + double t = (high-low) / 8; + double closest = 0; + double minDist = Double.MaxValue; + for (int i = 0; i < 9; i++) { + Point p = targetPoint - this[i * t + low]; + double d = p * p; + if (d < minDist) { + minDist = d; + closest = i * t + low; + } + } + return ClosestPointOnCurve.ClosestPoint(this, targetPoint, closest, low, high); + } + + + /// + /// clones the curve. + /// + /// the cloned curve + public ICurve Clone() { + return new CubicBezierSegment(this.b[0], this.b[1], this.b[2], this.b[3]); + } + + /// + /// the signed curvature of the segment at t + /// + /// + /// + public double Curvature(double t) { + System.Diagnostics.Debug.Assert(t >= ParStart && t <= ParEnd); + + double den = G(t); + + System.Diagnostics.Debug.Assert(Math.Abs(den) > 0.00001); + + return F(t) / den; + } + + + double F(double t) { + return Xp(t) * Ypp(t) - Yp(t) * Xpp(t); + } + + /// + /// G(t) is the denomenator of the curvature + /// + /// + /// + double G(double t) { + double xp = Xp(t); + double yp = Yp(t); + double den = (xp * xp + yp * yp); + return Math.Sqrt(den * den * den); + } + + /// + /// the first derivative of x-coord + /// + /// + /// + double Xp(double t) { + return 3 * l.X * t * t + 2 * e.X * t + c.X; + } + + /// + /// the second derivativ of y-coordinate + /// + /// + /// + double Ypp(double t) { + return 6 * l.Y * t + 2 * e.Y; + } + + /// + /// the first derivative of y-coord + /// + /// + /// + double Yp(double t) { + return 3 * l.Y * t * t + 2 * e.Y * t + c.Y; + } + + /// + /// the seconde derivative of x coord + /// + /// + /// + double Xpp(double t) { + return 6 * l.X * t + 2 * e.X; + } + /// + /// the third derivative of x coordinate + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "t")] + double Xppp(double t) { + return 6 * l.X; + } + + /// + /// the third derivative of y coordinate + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "t")] + double Yppp(double t) { + return 6 * l.Y; + } + + + /// + /// the derivative of the curvature at t + /// + /// + /// + public double CurvatureDerivative(double t) { + // we need to calculate the derivative of f/g where f=xp* ypp-yp*xpp and g=(xp*xp+yp*yp)^(3/2) + double h = G(t); + return (Fp(t) * h - Gp(t) * F(t)) / (h * h); + } + + + + + double Fp(double t) { + return Xp(t) * Yppp(t) - Yp(t) * Xppp(t); + } + + double Fpp(double t) { + return Xpp(t) * Yppp(t) // + Xp(t) * Ypppp(t)=0 + - Ypp(t) * Xppp(t);//- Yp(t) * Xpppp(t)=0 + } + + + /// + /// returns a parameter t such that the distance between curve[t] and a is minimal + /// + /// + /// + public double ClosestParameter(Point targetPoint) { + double t = 1.0 / 8; + double closest = 0; + double minDist = Double.MaxValue; + for (int i = 0; i < 9; i++) { + Point p = targetPoint - this[i * t]; + double d = p * p; + if (d < minDist) { + minDist = d; + closest = i * t; + } + } + return ClosestPointOnCurve.ClosestPoint(this, targetPoint, closest, 0, 1); + } + + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + public IEnumerable> MaximalCurvaturePoints { + get { + + List> maxCurveCandidates = new List>(); + + + int n = 8; + double d = 1.0 / n; + double prev = -1;//out of the domain + for (int i = 0; i < n; i++) { + double start = i * d; + double end = start + d; + double x; + if (RootFinder.TryToFindRoot(new CurvatureDerivative(this, start, end), start, end, (start + end) / 2, out x)) { + if (x != prev) { + prev = x; + maxCurveCandidates.Add(new Tuple(x, Curvature(x))); + } + } + } + maxCurveCandidates.Add(new Tuple(ParStart, Curvature(ParStart))); + maxCurveCandidates.Add(new Tuple(ParEnd, Curvature(ParEnd))); + + var maxCur = maxCurveCandidates.Max(l => Math.Abs(l.Item2)); + + return from v in maxCurveCandidates where Math.Abs(v.Item2) == maxCur select v; + } + + } + + #region ICurve Members + + /// + /// + /// + /// + /// + public double CurvatureSecondDerivative(double t) { + double g = G(t); + return (Qp(t) * g - 2 * Q(t) * Gp(t)) / (g * g * g); + } + + double Q(double t) { + return Fp(t) * G(t) - Gp(t) * F(t); + } + + double Qp(double t) { + return Fpp(t) * G(t) - Gpp(t) * F(t); + } + + double Gpp(double t) { + var xp = Xp(t); + var yp = Yp(t); + var xpp = Xpp(t); + var ypp = Ypp(t); + var xppp = Xppp(t); + var yppp = Yppp(t); + var u = Math.Sqrt(xp * xp + yp * yp); + var v = xp * xpp + yp * ypp; + return 3 * ((v * v) / u + u * (xpp * xpp + xp * xppp + ypp * ypp + yp * yppp)); + + } + + double Gp(double t) { + var xp = Xp(t); + var yp = Yp(t); + var xpp = Xpp(t); + var ypp = Ypp(t); + return 3 * Math.Sqrt(xp * xp + yp * yp) * (xp * xpp + yp * ypp); + } + + + #endregion + + + /// + /// + /// + /// + /// + public double GetParameterAtLength(double length) { + double low = 0; + double upper = 1; + while (upper - low > ApproximateComparer.Tolerance) { + var middle = (upper + low)/2; + int err = EvaluateError(length, middle); + if (err > 0) + upper = middle; + else if (err < 0) + low = middle; + else + return middle; + } + + return (low + upper)/2; + } + + int EvaluateError(double length, double t) { + //todo: this is a slow version! + var f = 1 - t; + var mb0 = f*b[0] + t*b[1]; + var mb1 = f*b[1] + t*b[2]; + var mb2 = f*b[2] + t*b[3]; + var mmb0 = f * mb0 + t * mb1; + var mmb1 = f * mb1 + t * mb2; + var mmmb0 = f * mmb0 + t * mmb1; + + var lengthAtT = LengthOnControlPolygon(b[0], mb0, mmb0, mmmb0); + + if (lengthAtT > length + ApproximateComparer.DistanceEpsilon) + return 1; + + if (lengthAtT < length - ApproximateComparer.DistanceEpsilon) + return -1; + + return 0; + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/CubicBezierSegment.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/CubicBezierSegment.cs.meta new file mode 100644 index 000000000..a45e1284e --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/CubicBezierSegment.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0bcfc220e0631264ba44cc8179e030cb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/CurvatureDerivative.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/CurvatureDerivative.cs new file mode 100644 index 000000000..e07af6eb7 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/CurvatureDerivative.cs @@ -0,0 +1,69 @@ +using System; + +namespace Microsoft.Msagl.Core.Geometry.Curves { + /// + /// + /// + public class CurvatureDerivative : IFunction { + ICurve curve; + double start; + double end; + /// + /// + /// + /// + /// + /// + public CurvatureDerivative(ICurve curve, double start, double end) { + this.curve = curve; + this.start = start; + this.end = end; + } + + /// + /// + /// + /// + /// + public double this[double t] { + get { return curve.CurvatureDerivative(t); } + } + /// + /// + /// + /// + /// + public double Derivative(double t) { + return curve.CurvatureSecondDerivative(t); + } + /// + /// + /// + /// + /// + public double SecondDerivative(double t) { + throw new NotImplementedException(); + } + /// + /// + /// + /// + /// + public double ThirdDerivative(double t) { + throw new NotImplementedException(); + } + /// + /// + /// + public double ParStart { + get {return start; } + } +/// +/// +/// + public double ParEnd { + get { return end; } + } + + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/CurvatureDerivative.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/CurvatureDerivative.cs.meta new file mode 100644 index 000000000..144595a67 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/CurvatureDerivative.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bb98496b3d0bc244bb7bd7746d7136d6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/Curve.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/Curve.cs new file mode 100644 index 000000000..838b82364 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/Curve.cs @@ -0,0 +1,2109 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +namespace Microsoft.Msagl.Core.Geometry.Curves { +#pragma warning disable 1587 + /// + /// Curve: keeps a sequence of connected ICurves + /// +#pragma warning restore 1587 +#if TEST_MSAGL + [Serializable] +#endif +#pragma warning disable 1591 + public partial class Curve : ICurve { +#pragma warning restore 1591 + ParallelogramInternalTreeNode pBNode; + //the parameter domain is [0,n] where n is the number of segments + readonly List segs; + + static bool IsCloseToLineSeg(double a, ref Point ap, double b, ref Point bp, ICurve s, double e) { + const double x = 1.0/3; + double p = a*x + b*(1 - x); + + Point delta = s[p] - (ap*x + bp*(1 - x)); + if (delta*delta > e) + return false; + + p = a*(1 - x) + b*x; + + delta = s[p] - (ap*(1 - x) + bp*x); + if (delta*delta > e) + return false; + + p = a*0.5 + b*0.5; + + delta = s[p] - (ap*0.5 + bp*0.5); + if (delta*delta > e) + return false; + + return true; + } + + /// + /// interpolates the curve between parameters a and side1 as by a sequence of line segments + /// + /// start parameter of the interpolation + /// start point of the interpolation + /// end parameter of the interpolation + /// end point of the interpolation + /// the interpolated curve + /// the maximal allowed distance between the curve and its inerpolation + /// + public static IList Interpolate(double startParameter, Point start, double endParameter, Point end, + ICurve curve, double epsilon) { + return Interpolate(startParameter, ref start, endParameter, ref end, curve, epsilon); + } + + internal static List Interpolate(double a, ref Point ap, double b, ref Point bp, ICurve s, + double eps) { + var r = new List(); + if (IsCloseToLineSeg(a, ref ap, b, ref bp, s, eps)) + r.Add(new LineSegment(ap, bp)); + else { + double m = 0.5*(a + b); + Point mp = s[m]; + r.AddRange(Interpolate(a, ref ap, m, ref mp, s, eps)); + r.AddRange(Interpolate(m, ref mp, b, ref bp, s, eps)); + } + + return r; + } + + /// + /// this function always produces at least two segments + /// + /// + /// + /// + /// + /// + /// + /// + internal static List Interpolate(double eps, double a, ref Point ap, double b, ref Point bp, + ICurve s) { + double m = (a + b)/2; + Point mp = s[m]; + List ret = Interpolate(a, ref ap, m, ref mp, s, eps*eps); + ret.AddRange(Interpolate(m, ref mp, b, ref bp, s, eps*eps)); + return ret; + } + + internal static List Interpolate(ICurve s, double eps) { + Point start = s.Start; + Point end = s.End; + return Interpolate(eps, s.ParStart, ref start, s.ParEnd, ref end, s); + } + + + /// + /// this[Reverse[t]]=this[ParEnd+ParStart-t] + /// + /// + public ICurve Reverse() { + var ret = new Curve(segs.Count); + for (int i = segs.Count - 1; i >= 0; i--) + ret.AddSegment(segs[i].Reverse()); + return ret; + } + + /// + /// Constructs the curve + /// + public Curve() { + segs = new List(); + } + + /// + /// Constructs a curve with an initial segment capacity. + /// + public Curve(int segmentCapacity) { + segs = new List(segmentCapacity); + } + + internal Curve(List segs) { + this.segs = segs; + ParStart = 0; + + foreach (ICurve s in segs) + ParEnd += s.ParEnd - s.ParStart; + } + + /// + /// this[ParStart] + /// + public Point Start { + get { return segs[0].Start; } + } + + /// + /// this[ParEnd] + /// + public Point End { + get { return segs[segs.Count - 1].End; } + } + + /// + /// Returns the trim curve + /// + /// + /// + /// + public ICurve Trim(double start, double end) { + AdjustStartEndEndParametersToDomain(ref start, ref end); + + int sseg; + double spar; + + GetSegmentAndParameter(start, out spar, out sseg); + + int eseg; + double epar; + + GetSegmentAndParameter(end, out epar, out eseg); + + if (sseg == eseg) + return Segments[sseg].Trim(spar, epar); + + var c = new Curve(eseg-sseg+1); + + if (spar < Segments[sseg].ParEnd) + c = c.AddSegment(Segments[sseg].Trim(spar, Segments[sseg].ParEnd)); + + for (int i = sseg + 1; i < eseg; i++) + c = c.AddSegment(Segments[i]); + + if (Segments[eseg].ParStart < epar) + c = c.AddSegment(Segments[eseg].Trim(Segments[eseg].ParStart, epar)); + + return c; + } + + void AdjustStartEndEndParametersToDomain(ref double start, ref double end) { + if (start > end) { + double t = start; + start = end; + end = t; + } + + if (start < ParStart) + start = ParStart; + + if (end > ParEnd) + end = ParEnd; + + } + + /// + /// Returns the trimmed curve, wrapping around the end if start is greater than end. + /// + /// The starting parameter + /// The ending parameter + /// The trimmed curve + public ICurve TrimWithWrap(double start, double end) { + Debug.Assert(start >= ParStart && start <= ParEnd); + Debug.Assert(end >= ParStart && end <= ParEnd); + if (start < end) + return Trim(start, end) as Curve; + + Debug.Assert(ApproximateComparer.Close(Start, End), "Curve must be closed to wrap"); + var c = new Curve(); + c.AddSegment(Trim(start, ParEnd) as Curve); + c.AddSegment(Trim(ParStart, end) as Curve); + return c; + } + + /// + /// Adds a segment to the curve + /// + /// the curve that we add a segment to + /// the new curve extended with the segment + public Curve AddSegment(ICurve curve) { + if (curve == null) + return this; //nothing happens +#if TEST_MSAGL + if (segs.Count > 0 && !ApproximateComparer.Close(End, curve.Start, 0.001)) //Layout.Show(this, s); + //Console.WriteLine((segs[segs.Count - 1].End - s.Sta rt).Length); + throw new InvalidOperationException(); //discontinuous curve +#endif + ParStart = 0; + var c = curve as Curve; + if (c == null) { + segs.Add(curve); + ParEnd += curve.ParEnd - curve.ParStart; + } + else { + IncreaseSegmentCapacity(c.Segments.Count); + foreach (ICurve cc in c.Segments) { + segs.Add(cc); + ParEnd += cc.ParEnd - cc.ParStart; + } + } + return this; + } + + /// + /// Increases the capacity of the segment list. + /// Call this before adding a bunch of segments to prevent the list from having to resize multiple times. + /// + public void IncreaseSegmentCapacity(int additionalAmount) { + int capacityNeeded = segs.Count + additionalAmount; + if (segs.Capacity < capacityNeeded) { + int capacity = Math.Max(segs.Capacity, 1); + while (capacity < capacityNeeded) capacity *= 2; + segs.Capacity = capacity; + } + } + + internal Curve AddSegs(ICurve a, ICurve b) { + Curve c = AddSegment(a); + return c.AddSegment(b); + } + + internal Curve AddSegs(ICurve a, ICurve b, ICurve c, ICurve d) { + IncreaseSegmentCapacity(4); + Curve r = AddSegment(a); + r = r.AddSegment(b); + r = r.AddSegment(c); + return r.AddSegment(d); + } + + //internal Curve AddSegs(IEnumerable segments) { + // Curve r = new Curve(this.Segs); + // foreach (ICurve segment in segments) + // r = r.AddSeg(segment); + + // return r; + //} + /// + /// Returns the list of the curve segments + /// + public IList Segments { + get { return segs; } + } + + /// + /// A tree of ParallelogramNodes covering the curve. + /// This tree is used in curve intersections routines. + /// + /// + public ParallelogramNodeOverICurve ParallelogramNodeOverICurve { + get { + lock (this) { + if (pBNode != null) + return pBNode; + + pBNode = new ParallelogramInternalTreeNode(this, ParallelogramNodeOverICurve.DefaultLeafBoxesOffset); + var parallelograms = new List(); + foreach (ICurve curveSeg in segs) { + ParallelogramNodeOverICurve pBoxNode = curveSeg.ParallelogramNodeOverICurve; + + parallelograms.Add(pBoxNode.Parallelogram); + + pBNode.AddChild(pBoxNode); + } + pBNode.Parallelogram = Parallelogram.GetParallelogramOfAGroup(parallelograms); + + return pBNode; + } + } + } + + /// + /// finds an intersection between to curves, + /// + /// + /// + /// if set to true parameters of the intersection point will be given in the curve parametrization + /// InterseciontInfo or null if there are no intersections + public static IntersectionInfo CurveCurveIntersectionOne(ICurve curve0, ICurve curve1, bool liftIntersection) { + ValidateArg.IsNotNull(curve0, "curve0"); + ValidateArg.IsNotNull(curve1, "curve1"); + Debug.Assert(curve0 != curve1, "curve0 == curve1"); +#if DEBUGGLEE +// double c0S = curve0.ParStart, c1S = curve1.ParStart; +// if (CurvesAreCloseAtParams(curve0, curve1, c0S, c1S)) { +// double mc0 = 0.5 * (curve0.ParStart + curve0.ParEnd); +// double mc1 = 0.5 * (curve1.ParStart + curve1.ParEnd); +// double c0E = curve0.ParEnd; +// if (CurvesAreCloseAtParams(curve0, curve1, mc0, mc1)) { +// double c1E = curve1.ParEnd; +// CurvesAreCloseAtParams(curve0, curve1, c0E, c1E); +// throw new InvalidOperationException(); +// } +// } +#endif + //recurse down to find all PBLeaf pairs which intesect and try to cross their segments + + IntersectionInfo ret = CurveCurveXWithParallelogramNodesOne(curve0.ParallelogramNodeOverICurve, + curve1.ParallelogramNodeOverICurve); + + if (liftIntersection && ret != null) + ret = LiftIntersectionToCurves(curve0, curve1, ret); + + + return ret; + } + + /// + /// calculates all intersections between curve0 and curve1 + /// + /// + /// + /// if set to true parameters of the intersection point will be given in the curve parametrization + /// + public static IList GetAllIntersections(ICurve curve0, ICurve curve1, bool liftIntersections) { + ValidateArg.IsNotNull(curve0, "curve0"); + ValidateArg.IsNotNull(curve1, "curve1"); + Debug.Assert(curve0 != curve1); +#if DEBUGGLEE +// var c0S = curve0.ParStart; +// var c1S = curve1.ParStart; +// var c0E = curve0.ParEnd; +// var c1E = curve1.ParEnd; +// if (CurvesAreCloseAtParams(curve0, curve1, c0S, c1S)) { +// if (CurvesAreCloseAtParams(curve0, curve1, c0E, c1E)) { +// var mc0 = 0.5*(curve0.ParStart + curve0.ParEnd); +// var mc1 = 0.5*(curve1.ParStart + curve1.ParEnd); +// if (CurvesAreCloseAtParams(curve0, curve1, mc0, mc1)) +// throw new InvalidOperationException(); +// } +// } +#endif + + var lineSeg = curve0 as LineSegment; + if (lineSeg != null) + return GetAllIntersectionsOfLineAndICurve(lineSeg, curve1, liftIntersections); + + return GetAllIntersectionsInternal(curve0, curve1, liftIntersections); + } + + + internal static IList GetAllIntersectionsInternal(ICurve curve0, ICurve curve1, + bool liftIntersections) { + //recurse down to find all PBLeaf pairs which intesect and try to cross their segments + + var intersections = new List(); + CurveCurveXWithParallelogramNodes(curve0.ParallelogramNodeOverICurve, curve1.ParallelogramNodeOverICurve, + ref intersections); + + if (liftIntersections) + for (int i = 0; i < intersections.Count; i++) + intersections[i] = LiftIntersectionToCurves(curve0, curve1, intersections[i]); + + + //fix the parameters - adjust them to the curve + return intersections; + } + + static IList GetAllIntersectionsOfLineAndICurve(LineSegment lineSeg, ICurve iCurve, + bool liftIntersections) { + var poly = iCurve as Polyline; + if (poly != null) + return GetAllIntersectionsOfLineAndPolyline(lineSeg, poly); + + var curve = iCurve as Curve; + if (curve != null) + return GetAllIntersectionsOfLineAndCurve(lineSeg, curve, liftIntersections); + + var roundedRect = iCurve as RoundedRect; + if (roundedRect != null) + return GetAllIntersectionsOfLineAndRoundedRect(lineSeg, roundedRect, liftIntersections); + + var ellipse = iCurve as Ellipse; + if (ellipse != null && ellipse.IsArc()) + return GetAllIntersectionsOfLineAndArc(lineSeg, ellipse); + + return GetAllIntersectionsInternal(lineSeg, iCurve, liftIntersections); + } + + static IList GetAllIntersectionsOfLineAndRoundedRect(LineSegment lineSeg, RoundedRect roundedRect, bool liftIntersections) { + var ret = GetAllIntersectionsOfLineAndCurve(lineSeg, roundedRect.Curve, liftIntersections); + if(liftIntersections) + foreach (var intersectionInfo in ret) { + intersectionInfo.Segment1 = roundedRect; + } + return ret; + } + + static IList GetAllIntersectionsOfLineAndCurve(LineSegment lineSeg, Curve curve, bool liftIntersections) { + var ret = new List(); + var lineParallelogram = lineSeg.ParallelogramNodeOverICurve; + var curveParallelogramRoot = curve.ParallelogramNodeOverICurve; + if (Parallelogram.Intersect(lineParallelogram.Parallelogram, curveParallelogramRoot.Parallelogram) == false) + return ret; + var parOffset = 0.0; + foreach (var seg in curve.Segments) { + var iiList = GetAllIntersections(lineSeg, seg, false); + if(liftIntersections) { + foreach (var intersectionInfo in iiList) { + intersectionInfo.Par1 += parOffset-seg.ParStart; + intersectionInfo.Segment1 = curve; + } + parOffset += seg.ParEnd - seg.ParStart; + } + foreach (var intersectionInfo in iiList) { + if(! AlreadyInside(ret, intersectionInfo)) + ret.Add(intersectionInfo); + } + } + + return ret; + } + + static bool AlreadyInside(List ret, IntersectionInfo intersectionInfo) { + for(int i=0;i GetAllIntersectionsOfLineAndArc(LineSegment lineSeg, Ellipse ellipse) { + Point lineDir = lineSeg.End - lineSeg.Start; + var ret = new List(); + double segLength = lineDir.Length; + if (segLength < ApproximateComparer.DistanceEpsilon) { + if (ApproximateComparer.Close((lineSeg.Start - ellipse.Center).Length, ellipse.AxisA.Length)) { + double angle = Point.Angle(ellipse.AxisA, lineSeg.Start - ellipse.Center); + if (ellipse.ParStart - ApproximateComparer.Tolerance <= angle) { + angle = Math.Max(angle, ellipse.ParStart); + if (angle <= ellipse.ParEnd + ApproximateComparer.Tolerance) { + angle = Math.Min(ellipse.ParEnd, angle); + ret.Add(new IntersectionInfo(0, angle, lineSeg.Start, lineSeg, ellipse)); + } + } + } + return ret; + } + + Point perp = lineDir.Rotate90Ccw()/segLength; + double segProjection = (lineSeg.Start - ellipse.Center)*perp; + Point closestPointOnLine = ellipse.Center + perp*segProjection; + + double rad = ellipse.AxisA.Length; + double absSegProj = Math.Abs(segProjection); + if (rad < absSegProj - ApproximateComparer.DistanceEpsilon) + return ret; //we don't have an intersection + lineDir = perp.Rotate90Cw(); + if (ApproximateComparer.Close(rad, absSegProj)) + TryToAddPointToLineCircleCrossing(lineSeg, ellipse, ret, closestPointOnLine, segLength, lineDir); + else { + Debug.Assert(rad > absSegProj); + double otherLeg = Math.Sqrt(rad*rad - segProjection*segProjection); + TryToAddPointToLineCircleCrossing(lineSeg, ellipse, ret, closestPointOnLine + otherLeg*lineDir, + segLength, lineDir); + TryToAddPointToLineCircleCrossing(lineSeg, ellipse, ret, closestPointOnLine - otherLeg*lineDir, + segLength, lineDir); + } + + return ret; + } + + static void TryToAddPointToLineCircleCrossing(LineSegment lineSeg, + Ellipse ellipse, List ret, Point point, double segLength, Point lineDir) { + Point ds = point - lineSeg.Start; + Point de = point - lineSeg.End; + double t = ds*lineDir; + if (t < -ApproximateComparer.DistanceEpsilon) + return; + t = Math.Max(t, 0); + if (t > segLength + ApproximateComparer.DistanceEpsilon) + return; + t = Math.Min(t, segLength); + t /= segLength; + + double angle = Point.Angle(ellipse.AxisA, point - ellipse.Center); + if (ellipse.ParStart - ApproximateComparer.Tolerance <= angle) { + angle = Math.Max(angle, ellipse.ParStart); + if (angle <= ellipse.ParEnd + ApproximateComparer.Tolerance) { + angle = Math.Min(ellipse.ParEnd, angle); + ret.Add(new IntersectionInfo(t, angle, point, lineSeg, ellipse)); + } + } + } + + static IList GetAllIntersectionsOfLineAndPolyline(LineSegment lineSeg, Polyline poly) { + var ret = new List(); + double offset = 0.0; + double par0, par1; + Point x; + PolylinePoint polyPoint = poly.StartPoint; + for (; polyPoint != null && polyPoint.Next != null; polyPoint = polyPoint.Next) { + if (CrossTwoLineSegs(lineSeg.Start, lineSeg.End, polyPoint.Point, polyPoint.Next.Point, 0, 1, 0, 1, + out par0, out par1, out x)) { + AdjustSolution(lineSeg.Start, lineSeg.End, polyPoint.Point, polyPoint.Next.Point, ref par0, ref par1, + ref x); + if (!OldIntersection(ret, ref x)) + ret.Add(new IntersectionInfo(par0, offset + par1, x, lineSeg, poly)); + } + offset++; + } + if (poly.Closed) + if (CrossTwoLineSegs(lineSeg.Start, lineSeg.End, polyPoint.Point, poly.Start, 0, 1, 0, 1, out par0, + out par1, out x)) { + AdjustSolution(lineSeg.Start, lineSeg.End, polyPoint.Point, poly.Start, ref par0, ref par1, ref x); + if (!OldIntersection(ret, ref x)) + ret.Add(new IntersectionInfo(par0, offset + par1, x, lineSeg, poly)); + } + + return ret; + } + + static void AdjustSolution(Point aStart, Point aEnd, Point bStart, Point bEnd, ref double par0, ref double par1, + ref Point x) { + //adjust the intersection if it is close to the ends of the segs + if (ApproximateComparer.CloseIntersections(x, aStart)) { + x = aStart; + par0 = 0; + } + else if (ApproximateComparer.CloseIntersections(x, aEnd)) { + x = aEnd; + par0 = 1; + } + + if (ApproximateComparer.CloseIntersections(x, bStart)) { + x = bStart; + par1 = Math.Floor(par1); + } + else if (ApproximateComparer.CloseIntersections(x, bEnd)) { + x = bEnd; + par1 = Math.Ceiling(par1); + } + } + +#if DEBUGGLEE + static bool CurvesAreCloseAtParams(ICurve c0, ICurve c1, double c0S, double c1S) { + return Close(c0[c0S], c1[c1S]) && Close(c0.Derivative(c0S), c1.Derivative(c1S)); + } +#endif + + + static IntersectionInfo CurveCurveXWithParallelogramNodesOne(ParallelogramNodeOverICurve n0, + ParallelogramNodeOverICurve n1) { + if (!Parallelogram.Intersect(n0.Parallelogram, n1.Parallelogram)) + //Console.WriteLine("Boxes {0} and {1} do not intersect", n0.Box, n1.Box); + return null; + var n0Pb = n0 as ParallelogramInternalTreeNode; + var n1Pb = n1 as ParallelogramInternalTreeNode; + if (n0Pb != null && n1Pb != null) + foreach (ParallelogramNodeOverICurve n00 in n0Pb.Children) + foreach (ParallelogramNodeOverICurve n11 in n1Pb.Children) { + IntersectionInfo x = CurveCurveXWithParallelogramNodesOne(n00, n11); + if (x != null) return x; + } + else if (n1Pb != null) + foreach (ParallelogramNodeOverICurve n in n1Pb.Children) { + IntersectionInfo x = CurveCurveXWithParallelogramNodesOne(n0, n); + if (x != null) return x; + } + else if (n0Pb != null) + foreach (ParallelogramNodeOverICurve n in n0Pb.Children) { + IntersectionInfo x = CurveCurveXWithParallelogramNodesOne(n, n1); + if (x != null) return x; + } + else + return CrossOverIntervalsOne(n0, n1); + + return null; + } + + + static void CurveCurveXWithParallelogramNodes(ParallelogramNodeOverICurve n0, ParallelogramNodeOverICurve n1, + ref List intersections) { + if (!Parallelogram.Intersect(n0.Parallelogram, n1.Parallelogram)) + //Console.WriteLine("Boxes {0} and {1} do not intersect", n0.Box, n1.Box); + return; + //if(Routing.debug) + // Testing.InternalTest.Show(n0, n1, n0.Seg, n1.Seg); + var n0Pb = n0 as ParallelogramInternalTreeNode; + var n1Pb = n1 as ParallelogramInternalTreeNode; + if (n0Pb != null && n1Pb != null) + foreach (ParallelogramNodeOverICurve n00 in n0Pb.Children) + foreach (ParallelogramNodeOverICurve n11 in n1Pb.Children) + CurveCurveXWithParallelogramNodes(n00, n11, ref intersections); + else if (n1Pb != null) + foreach (ParallelogramNodeOverICurve n in n1Pb.Children) + CurveCurveXWithParallelogramNodes(n0, n, ref intersections); + else if (n0Pb != null) + foreach (ParallelogramNodeOverICurve n in n0Pb.Children) + CurveCurveXWithParallelogramNodes(n, n1, ref intersections); + else intersections = CrossOverIntervals(n0, n1, intersections); + } + + static IntersectionInfo CrossOverIntervalsOne(ParallelogramNodeOverICurve n0, ParallelogramNodeOverICurve n1) { + //both are leafs + var l0 = n0 as ParallelogramLeaf; + var l1 = n1 as ParallelogramLeaf; + double d0 = (l0.High - l0.Low)/2; + double d1 = (l1.High - l1.Low)/2; + + for (int i = 1; i < 2; i++) { + double p0 = i*d0 + l0.Low; + for (int j = 1; j < 2; j++) { + double p1 = j*d1 + l1.Low; + double aSol, bSol; + Point x; + bool r; + if (l0.Chord == null && l1.Chord == null) + r = CrossWithinIntervalsWithGuess(n0.Seg, n1.Seg, l0.Low, l0.High, l1.Low, l1.High, + p0, p1, out aSol, out bSol, out x); + else if (l0.Chord != null && l1.Chord == null) { + r = CrossWithinIntervalsWithGuess(l0.Chord, n1.Seg, 0, 1, l1.Low, l1.High, + 0.5*i, + p1, out aSol, out bSol, out x); + if (r) + aSol = l0.Low + aSol*(l0.High - l0.Low); + } + else if (l0.Chord == null) { + r = CrossWithinIntervalsWithGuess(n0.Seg, l1.Chord, + l0.Low, l0.High, 0, 1, p0, + 0.5*j, out aSol, out bSol, out x); + if (r) + bSol = l1.Low + bSol*(l1.High - l1.Low); + } + else //if (l0.Chord != null && l1.Chord != null) + { + r = CrossWithinIntervalsWithGuess(l0.Chord, l1.Chord, + 0, 1, 0, 1, 0.5*i, + 0.5*j, out aSol, out bSol, out x); + if (r) { + bSol = l1.Low + bSol*(l1.High - l1.Low); + aSol = l0.Low + aSol*(l0.High - l0.Low); + } + } + + if (r) + return CreateIntersectionOne(l0, l1, aSol, bSol, x); + } + } + + return GoDeeperOne(l0, l1); + } + + static List CrossOverIntervals(ParallelogramNodeOverICurve n0, ParallelogramNodeOverICurve n1, + List intersections) { + //both are leafs + var l0 = n0 as ParallelogramLeaf; + var l1 = n1 as ParallelogramLeaf; + double d0 = (l0.High - l0.Low)/2; + double d1 = (l1.High - l1.Low)/2; + bool found = false; + + for (int i = 1; i < 2; i++) { + double p0 = i*d0 + l0.Low; + for (int j = 1; j < 2; j++) { + double p1 = j*d1 + l1.Low; + + + double aSol, bSol; + Point x; + + + bool r; + if (l0.Chord == null && l1.Chord == null) + r = CrossWithinIntervalsWithGuess(n0.Seg, n1.Seg, l0.Low, l0.High, l1.Low, l1.High, + p0, p1, out aSol, out bSol, out x); + else if (l0.Chord != null && l1.Chord == null) { + r = CrossWithinIntervalsWithGuess(l0.Chord, n1.Seg, 0, 1, l1.Low, l1.High, + 0.5*i, + p1, out aSol, out bSol, out x); + if (r) + aSol = l0.Low + aSol*(l0.High - l0.Low); + } + else if (l0.Chord == null) { +//&& l1.Chord != null) + r = CrossWithinIntervalsWithGuess(n0.Seg, l1.Chord, + l0.Low, l0.High, 0, 1, p0, + 0.5*j, out aSol, out bSol, out x); + if (r) + bSol = l1.Low + bSol*(l1.High - l1.Low); + } + else //if (l0.Chord != null && l1.Chord != null) + { + r = CrossWithinIntervalsWithGuess(l0.Chord, l1.Chord, + 0, 1, 0, 1, 0.5*i, + 0.5*j, out aSol, out bSol, out x); + if (r) { + bSol = l1.Low + bSol*(l1.High - l1.Low); + aSol = l0.Low + aSol*(l0.High - l0.Low); + } + } + + if (r) { + AddIntersection(l0, l1, intersections, aSol, bSol, x); + found = true; + } + } + } + + if (!found) + GoDeeper(ref intersections, l0, l1); + return intersections; + } + + static void AddIntersection(ParallelogramLeaf n0, ParallelogramLeaf n1, List intersections, + double aSol, double bSol, + Point x) { + //adjust the intersection if it is close to the ends of the segs + if (ApproximateComparer.CloseIntersections(x, n0.Seg[n0.Low])) { + x = n0.Seg[n0.Low]; + aSol = n0.Low; + } + else if (ApproximateComparer.CloseIntersections(x, n0.Seg[n0.High])) { + x = n0.Seg[n0.High]; + aSol = n0.High; + } + + if (ApproximateComparer.CloseIntersections(x, n1.Seg[n1.Low])) { + x = n1.Seg[n1.Low]; + bSol = n1.Low; + } + else if (ApproximateComparer.CloseIntersections(x, n1.Seg[n1.High])) { + x = n1.Seg[n1.High]; + bSol = n1.High; + } + + bool oldIntersection = OldIntersection(intersections, ref x); + if (!oldIntersection) { + var xx = new IntersectionInfo(aSol, bSol, x, n0.Seg, n1.Seg); + intersections.Add(xx); + } + + return; + } + + /// + /// returns true if the intersection exists already + /// + /// + /// + /// + static bool OldIntersection(List intersections, ref Point x) { + bool oldIntersection = false; + //we don't expect many intersections so it's ok just go through all of them + foreach (IntersectionInfo ii in intersections) + if ((x - ii.IntersectionPoint).Length < ApproximateComparer.DistanceEpsilon*100) + //please no close intersections + { + oldIntersection = true; + break; + } + return oldIntersection; + } + + static IntersectionInfo CreateIntersectionOne(ParallelogramLeaf n0, ParallelogramLeaf n1, + double aSol, double bSol, Point x) { + //adjust the intersection if it is close to the ends of the segs + if (ApproximateComparer.CloseIntersections(x, n0.Seg[n0.Low])) { + x = n0.Seg[n0.Low]; + aSol = n0.Low; + } + else if (ApproximateComparer.CloseIntersections(x, n0.Seg[n0.High])) { + x = n0.Seg[n0.High]; + aSol = n0.High; + } + + if (ApproximateComparer.CloseIntersections(x, n1.Seg[n1.Low])) { + x = n1.Seg[n1.Low]; + bSol = n1.Low; + } + else if (ApproximateComparer.CloseIntersections(x, n1.Seg[n1.High])) { + x = n1.Seg[n1.High]; + bSol = n1.High; + } + + return new IntersectionInfo(aSol, bSol, x, n0.Seg, n1.Seg); + } + + static IntersectionInfo LiftIntersectionToCurves( + ICurve c0, ICurve c1, double aSol, + double bSol, Point x, ICurve seg0, ICurve seg1) { + double a = LiftParameterToCurve(c0, aSol - seg0.ParStart, seg0); + double b = LiftParameterToCurve(c1, bSol - seg1.ParStart, seg1); + + return new IntersectionInfo(a, b, x, c0, c1); + } + + static IntersectionInfo DropIntersectionToSegs(IntersectionInfo xx) { + ICurve seg0; + double par0; + + if (xx.Segment0 is Curve) + (xx.Segment0 as Curve).GetSegmentAndParameter(xx.Par0, out par0, out seg0); + else { + par0 = xx.Par0; + seg0 = xx.Segment0; + } + + ICurve seg1; + double par1; + + if (xx.Segment1 is Curve) + (xx.Segment1 as Curve).GetSegmentAndParameter(xx.Par1, out par1, out seg1); + else { + par1 = xx.Par1; + seg1 = xx.Segment1; + } + + return new IntersectionInfo(par0, par1, xx.IntersectionPoint, seg0, seg1); + } + + internal static IntersectionInfo LiftIntersectionToCurves(ICurve c0, ICurve c1, IntersectionInfo xx) { + return LiftIntersectionToCurves(c0, c1, xx.Par0, xx.Par1, xx.IntersectionPoint, xx.Segment0, xx.Segment1); + } + + internal static double LiftParameterToCurve(ICurve curve, double par, ICurve seg) { + if (curve == seg) + return par; + + var c = curve as Curve; + + if (c != null) { + double offset = 0; + foreach (ICurve s in c.Segments) { + if (s == seg) + return par + offset; + offset += ParamSpan(s); + } + } + var roundedRect = curve as RoundedRect; + if (roundedRect != null) + return LiftParameterToCurve(roundedRect.Curve, par, seg); + + throw new InvalidOperationException(); //"bug in LiftParameterToCurve"); + } + + static double ParamSpan(ICurve s) { + return s.ParEnd - s.ParStart; + } + + static IntersectionInfo GoDeeperOne(ParallelogramLeaf l0, ParallelogramLeaf l1) { + double eps = ApproximateComparer.DistanceEpsilon; + // Console.WriteLine("did not find an intersection"); + if (l0.LeafBoxesOffset > eps && l1.LeafBoxesOffset > eps) { + // Console.WriteLine("going deeper on both with offset {0} {1}", l0.LeafBoxesOffset / 2, l1.LeafBoxesOffset / 2); + + + ParallelogramNodeOverICurve nn0 = ParallelogramNodeOverICurve.CreateParallelogramNodeForCurveSeg( + l0.Low, l0.High, l0.Seg, l0.LeafBoxesOffset/2); + ParallelogramNodeOverICurve nn1 = ParallelogramNodeOverICurve.CreateParallelogramNodeForCurveSeg( + l1.Low, l1.High, l1.Seg, l1.LeafBoxesOffset/2); + return CurveCurveXWithParallelogramNodesOne(nn0, nn1); + } + if (l0.LeafBoxesOffset > eps) { + // Console.WriteLine("go deeper on the left"); + ParallelogramNodeOverICurve nn0 = ParallelogramNodeOverICurve.CreateParallelogramNodeForCurveSeg( + l0.Low, l0.High, l0.Seg, l0.LeafBoxesOffset/2); + return CurveCurveXWithParallelogramNodesOne(nn0, l1); + } + if (l1.LeafBoxesOffset > eps) { + // Console.WriteLine("go deeper on the right"); + ParallelogramNodeOverICurve nn1 = ParallelogramNodeOverICurve.CreateParallelogramNodeForCurveSeg( + l1.Low, l1.High, l1.Seg, l1.LeafBoxesOffset/2); + return CurveCurveXWithParallelogramNodesOne(l0, nn1); + } + //just cross LineSegs and adjust the solutions if the segments are not straight lines + Point l0Low = l0.Seg[l0.Low]; + Point l0High = l0.Seg[l0.High]; + if (!ApproximateComparer.Close(l0Low, l0High)) { + Point l1Low = l1.Seg[l1.Low]; + Point l1High = l1.Seg[l1.High]; + if (!ApproximateComparer.Close(l1Low, l1High)) { + LineSegment ls0 = l0.Seg is LineSegment ? l0.Seg as LineSegment : new LineSegment(l0Low, l0High); + LineSegment ls1 = l1.Seg is LineSegment ? l1.Seg as LineSegment : new LineSegment(l1Low, l1High); + + double asol, bsol; + Point x; + bool r = CrossWithinIntervalsWithGuess(ls0, ls1, 0, 1, 0, 1, 0.5, 0.5, out asol, out bsol, out x); + if (r) { + AdjustParameters(l0, ls0, l1, ls1, x, ref asol, ref bsol); + return CreateIntersectionOne(l0, l1, asol, bsol, x); + } + } + } + return null; + } + + static void GoDeeper(ref List intersections, ParallelogramLeaf l0, ParallelogramLeaf l1) { + double eps = ApproximateComparer.DistanceEpsilon; + // Console.WriteLine("did not find an intersection"); + if (l0.LeafBoxesOffset > eps && l1.LeafBoxesOffset > eps) { + // Console.WriteLine("going deeper on both with offset {0} {1}", l0.LeafBoxesOffset / 2, l1.LeafBoxesOffset / 2); + + + ParallelogramNodeOverICurve nn0 = ParallelogramNodeOverICurve.CreateParallelogramNodeForCurveSeg( + l0.Low, l0.High, l0.Seg, l0.LeafBoxesOffset/2); + ParallelogramNodeOverICurve nn1 = ParallelogramNodeOverICurve.CreateParallelogramNodeForCurveSeg( + l1.Low, l1.High, l1.Seg, l1.LeafBoxesOffset/2); + CurveCurveXWithParallelogramNodes(nn0, nn1, ref intersections); + } + else if (l0.LeafBoxesOffset > eps) { + // Console.WriteLine("go deeper on the left"); + ParallelogramNodeOverICurve nn0 = ParallelogramNodeOverICurve.CreateParallelogramNodeForCurveSeg( + l0.Low, l0.High, l0.Seg, l0.LeafBoxesOffset/2); + CurveCurveXWithParallelogramNodes(nn0, l1, ref intersections); + } + else if (l1.LeafBoxesOffset > eps) { + // Console.WriteLine("go deeper on the right"); + ParallelogramNodeOverICurve nn1 = ParallelogramNodeOverICurve.CreateParallelogramNodeForCurveSeg( + l1.Low, l1.High, l1.Seg, l1.LeafBoxesOffset/2); + CurveCurveXWithParallelogramNodes(l0, nn1, ref intersections); + } + else { + //just cross LineSegs since the polylogramms are so thin + Point l0Low = l0.Seg[l0.Low]; + Point l0High = l0.Seg[l0.High]; + if (!ApproximateComparer.Close(l0Low, l0High)) { + Point l1Low = l1.Seg[l1.Low]; + Point l1High = l1.Seg[l1.High]; + if (!ApproximateComparer.Close(l1Low, l1High)) { + LineSegment ls0 = l0.Seg is LineSegment ? l0.Seg as LineSegment : new LineSegment(l0Low, l0High); + LineSegment ls1 = l1.Seg is LineSegment ? l1.Seg as LineSegment : new LineSegment(l1Low, l1High); + + double asol, bsol; + Point x; + bool r = CrossWithinIntervalsWithGuess(ls0, ls1, 0, 1, 0, 1, 0.5, 0.5, out asol, out bsol, out x); + if (r) { + AdjustParameters(l0, ls0, l1, ls1, x, ref asol, ref bsol); + AddIntersection(l0, l1, intersections, asol, bsol, x); + } + } + } + } + } + + + static void AdjustParameters(ParallelogramLeaf l0, LineSegment ls0, ParallelogramLeaf l1, LineSegment ls1, + Point x, ref double asol, ref double bsol) { + if (ls0 != l0.Seg && l0.Seg is Polyline == false) //l0.Seg is not a LineSegment and not a polyline + asol = l0.Seg.ClosestParameter(x); //we need to find the correct parameter + else + asol = l0.Low + asol*(l0.High - l0.Low); + if (ls1 != l1.Seg && l1.Seg is Polyline == false) //l1.Seg is not a LineSegment and not a polyline + bsol = l1.Seg.ClosestParameter(x); //we need to find the correct parameter + else + bsol = l1.Low + bsol*(l1.High - l1.Low); + } + + // static void ShowNodesWithCurves(Curve c0, Curve point0, ParallelogramNodeOverICurve n0, ParallelogramNodeOverICurve n1) + //{ + // System.Windows.Forms.Form f = new System.Windows.Forms.Form(); + // GViewer v = new GViewer(); + // f.SuspendLayout(); + // f.Controls.Add(v); + // Graph g = new Graph("", ""); + + // foreach (ICurve cs in c0.Segs) + // if (cs is CubicBezierSeg) + // { + // g.bezierCurves.Add(cs as CubicBezierSeg); + // } + + // foreach (ICurve cs in point0.Segs) + // if (cs is CubicBezierSeg) + // { + // g.bezierCurves.Add(cs as CubicBezierSeg); + // } + // else if (cs is Ellipse) + // { + // Ellipse el = cs as Ellipse; + // int n = 64; + // double delta = (el.ParEnd - el.ParStart) / n; + // for (int i = 0; i < n; i++) + // { + // Point s = el[el.ParStart + i * delta]; + // Point e = el[el.ParStart + (i + 1) * delta]; + // g.bezierCurves.Add(new CubicBezierSeg(s, 0.5 * (s + e), 0.5 * (s + e), e)); + // } + + + // } + + // // g.ellipses.Add(cs as Ellipse); + + // g.pBoxes.Add(n0.Box); + // g.pBoxes.Add(n1.Box); + // v.Graph = g; + // Rectangle side1 = new Rectangle(); + // for (int i = 0; i < 4; i++) + // side1.Add(n0.Box.Vertex(i)); + // for (int i = 0; i < 4; i++) + // side1.Add(n0.Box.Vertex(i)); + // g.GraphAttr.BoundingBox = side1; + + // v.Dock = System.Windows.Forms.DockStyle.Fill; + // f.Size = new Size(System.Windows.Forms.Screen.PrimaryScreen.WorkingArea.Size.Width / 2, System.Windows.Forms.Screen.PrimaryScreen.WorkingArea.Size.Height / 2); + // f.ResumeLayout(); + // f.ShowDialog(); + + //} + + //internal static void ShowBezierSegs(params CubicBezierSeg[] segs) + //{ + // System.Windows.Forms.Form f = new System.Windows.Forms.Form(); + // GViewer v = new GViewer(); + // f.SuspendLayout(); + // f.Controls.Add(v); + // Graph g = new Graph("", ""); + + // foreach (CubicBezierSeg cs in segs) + // g.bezierCurves.Add(cs); + + + // v.Graph = g; + // v.Dock = System.Windows.Forms.DockStyle.Fill; + // f.Size = System.Windows.Forms.Screen.PrimaryScreen.WorkingArea.Size; + // f.ResumeLayout(); + // f.ShowDialog(); + + //} + + static double lineSegThreshold = 0.05; + + /// + /// The distance between the start and end point of a curve segment for which we consider the segment as a line segment + /// + public static double LineSegmentThreshold { + get { return lineSegThreshold; } + set { lineSegThreshold = value; } + } + + /// + /// returns the segment correspoinding to t and the segment parameter + /// + /// + /// + /// + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "t"), + SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#"), + SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#")] + public void GetSegmentAndParameter(double t, out double par, out ICurve segment) { + double u = ParStart; //u is the sum of param domains + foreach (ICurve sg in segs) { + double domLen = sg.ParEnd - sg.ParStart; + if (t >= u && t <= u + domLen) { + par = t - u + sg.ParStart; + segment = sg; + return; + } + u += domLen; + } + segment = segs.Last(); + par = segment.ParEnd; + // throw new InvalidOperationException(); // parameter is out of domain of the curve + // //remove later + // par = 0; + // segment = null; + /*g = Math.Max(t, Math.Min(t, nOfSegments)); + + if (t == nOfSegments) + { + seg = nOfSegments - 1; + g = 1; + } + else + { + seg = (int)t; + g = t - seg; + }*/ + } + + internal void GetSegmentAndParameter(double t, out double par, out int segIndex) { + double u = 0; //u is the sum of param domains + segIndex = 0; + par = -1; + + for (int i = 0; i < segs.Count; i++) + { + var sg = segs[i]; + double domLen = sg.ParEnd - sg.ParStart; + if (t >= u && (t <= u + domLen || (i == segs.Count-1 && ApproximateComparer.Compare(t,u+domLen)<=0) )) + { + par = t - u + sg.ParStart; + return; + } + segIndex++; + u += domLen; + } + + throw new InvalidOperationException(string.Format("Check, args t:{0}, par:{1}, segIndex:{2} and u:{3}", t, par, segIndex, u)); // parameter is out of domain of the curve*/ + /*g = Math.Max(t, Math.Min(t, nOfSegments)); + + if (t == nOfSegments) + { + seg = nOfSegments - 1; + g = 1; + } + else + { + seg = (int)t; + g = t - seg; + }*/ + } + + + /// + /// Returns the point on the curve corresponding to parameter t + /// + /// + /// + public Point this[double t] { + get { + double par; + ICurve seg; + + GetSegmentAndParameter(t, out par, out seg); + + return seg[par]; + } + } + + /// + /// first derivative at t + /// + /// the parameter where the derivative is calculated + /// + public Point Derivative(double t) { + double par; + ICurve seg; + + GetSegmentAndParameter(t, out par, out seg); + return seg.Derivative(par); + } + + /// + /// second derivative + /// + /// + /// + public Point SecondDerivative(double t) { + double par; + ICurve seg; + + GetSegmentAndParameter(t, out par, out seg); + return seg.SecondDerivative(par); + } + + /// + /// third derivative + /// + /// the parameter of the derivative + /// + public Point ThirdDerivative(double t) { + double par; + ICurve seg; + + GetSegmentAndParameter(t, out par, out seg); + return seg.ThirdDerivative(par); + } + + // For curves A(s) and B(t), if you have some evidence that + // there is at most one intersection point, and you have some guess for the parameters (s0, t0)... + // You are trying to bring to (0,0) the vector( F^2 s , F^2 t ). + //F(s,t) = A(s) - B(t). To minimize F^2, + //You get the system of equations to solve for ds and dt: + //F*Fs + (F*Fss + Fs*Fs)ds + (F*Fst + Fs*Ft)dt = 0 + //F*Ft + (F*Fst + Fs*Ft)ds + (F*Ftt + Ft*Ft)dt = 0 + // + //Where F = F(si,ti), Fs and Ft are the first partials at si, ti, Fxx are the second partials, + // and s(i+1) = si+ds, t(i+1) = ti+dt. + //Of course you have to make sure that ds and dt do not take you out of your domain. This will converge if the curves have 2nd order continuity and your starting parameters are reasonable. It is not a good method for situations that are not well behaved, but it is really simple. + + internal static bool CrossWithinIntervalsWithGuess( + ICurve a, ICurve b, + double amin, double amax, + double bmin, double bmax, + double aGuess, + double bGuess, + out double aSolution, + out double bSolution, out Point x) { + bool r; + if (a is LineSegment && b is LineSegment) + if (CrossTwoLineSegs(a.Start, a.End, b.Start, b.End, amin, amax, bmin, bmax, out aSolution, + out bSolution, out x)) + return true; + //it also handles the case of almost parallel segments + Point aPoint; + Point bPoint; + r = MinDistWithinIntervals(a, + b, + amin, + amax, bmin, + bmax, + aGuess, bGuess, + out aSolution, + out bSolution, out aPoint, out bPoint); + + + x = 0.5*(aPoint + bPoint); + Point aMinusB = aPoint - bPoint; + //if side1 is false tnen the values a and side1 are meaningless + bool ret = r && aMinusB*aMinusB < ApproximateComparer.DistanceEpsilon*ApproximateComparer.DistanceEpsilon; + return ret; + } + + static bool CrossTwoLineSegs(Point aStart, Point aEnd, Point bStart, Point bEnd, double amin, double amax, + double bmin, double bmax, out double aSolution, out double bSolution, out Point x) { + Point u = aEnd - aStart; + Point v = bStart - bEnd; + Point w = bStart - aStart; + bool r = LinearSystem2.Solve(u.X, v.X, w.X, u.Y, v.Y, w.Y, out aSolution, out bSolution); + x = aStart + aSolution*u; + + if (r) { + if (aSolution < amin - ApproximateComparer.Tolerance) + return false; + + aSolution = Math.Max(aSolution, amin); + + if (aSolution > amax + ApproximateComparer.Tolerance) + return false; + + aSolution = Math.Min(aSolution, amax); + + if (bSolution < bmin - ApproximateComparer.Tolerance) + return false; + + bSolution = Math.Max(bSolution, bmin); + + if (bSolution > bmax + ApproximateComparer.Tolerance) + return false; + + bSolution = Math.Min(bSolution, bmax); + + // if(!ApproximateComparer.Close(x,B[bSolution])) + // throw new InvalidOperationException();// ("segs"); + return true; + } + + return false; + } + + /// + /// Decides if the point lies inside, outside or on the curve + /// + /// the point under test + /// a closed curve + /// the point position characteristic + public static PointLocation PointRelativeToCurveLocation(Point point, ICurve curve) { + System.Diagnostics.Debug.Assert(!Double.IsNaN(point.X) && !Double.IsNaN(point.Y)); + ValidateArg.IsNotNull(curve, "curve"); + if (!curve.BoundingBox.Contains(point)) + return PointLocation.Outside; + + double l = 2*curve.BoundingBox.Diagonal; //l should be big enough for the line to exit outside of the curve + + const double degree = Math.PI/180.0; + int inside = 0; + for (int i = 13; i < 360; i += 13) { + var lineDir = new Point(Math.Cos(i*degree), Math.Sin(i*degree)); + var ls = new LineSegment(point, point + l*lineDir); + + IList intersections = GetAllIntersections(ls, curve, true); + + + //SugiyamaLayoutSettings.Show(ls, curve); + // CurveSerializer.Serialize("cornerC:\\tmp\\ls",ls); + // CurveSerializer.Serialize("cornerC:\\tmp\\pol",curve); + if (AllIntersectionsAreGood(intersections, curve)) { + foreach (IntersectionInfo xx in intersections) + if (ApproximateComparer.Close(xx.IntersectionPoint, point)) + return PointLocation.Boundary; + bool insideThisTime = intersections.Count%2 == 1; + //to be on the safe side we need to get the same result at least twice + if (insideThisTime) + inside++; + else + inside--; + + if (inside >= 2) + return PointLocation.Inside; + if (inside <= -2) + return PointLocation.Outside; + } + } + //if all intersections are not good then we probably have the point on the boundaryCurve + + return PointLocation.Boundary; + } + + + // static bool debug; + static bool AllIntersectionsAreGood(IList intersections, ICurve polygon) { + // If this isn't a Curve, try a Polyline. + //TODO: fix this to avoid the cast + var polyCurve = polygon as Curve; + if (null == polyCurve) { + var polyLine = polygon as Polyline; + if (null != polyLine) + polyCurve = polyLine.ToCurve(); + } + if (null != polyCurve) + foreach (IntersectionInfo xx in intersections) + if (!RealCut(DropIntersectionToSegs(xx), polyCurve, false)) + return false; + return true; + } + + + /// + /// Returns true if curves do not touch in the intersection point + /// + /// + /// + /// if set to true and first curve is closed will return true + /// only when the second curve cuts the first one from the inside + /// + public static bool RealCutWithClosedCurve(IntersectionInfo xx, Curve polygon, bool onlyFromInsideCuts) { + ValidateArg.IsNotNull(xx, "xx"); + ValidateArg.IsNotNull(polygon, "polygon"); + ICurve sseg = xx.Segment0; + ICurve pseg = xx.Segment1; + double spar = xx.Par0; + double ppar = xx.Par1; + Point x = xx.IntersectionPoint; + + //normalised tangent to spline + Point ts = sseg.Derivative(spar).Normalize(); + Point pn = pseg.Derivative(ppar).Normalize().Rotate(Math.PI/2); + + if (ApproximateComparer.Close(x, pseg.End)) { + //so pseg enters the spline + ICurve exitSeg = null; + for (int i = 0; i < polygon.Segments.Count; i++) + if (polygon.Segments[i] == pseg) { + exitSeg = polygon.Segments[(i + 1)%polygon.Segments.Count]; + break; + } + + if (exitSeg == null) + throw new InvalidOperationException(); //"exitSeg==null"); + + Point tsn = ts.Rotate((Math.PI/2)); + + bool touch = (tsn*pseg.Derivative(pseg.ParEnd))*(tsn*exitSeg.Derivative(exitSeg.ParStart)) < + ApproximateComparer.Tolerance; + + return !touch; + } + + if (ApproximateComparer.Close(x, pseg.Start)) { + //so pseg exits the spline + ICurve enterSeg = null; + for (int i = 0; i < polygon.Segments.Count; i++) + if (polygon.Segments[i] == pseg) { + enterSeg = polygon.Segments[i > 0 ? (i - 1) : polygon.Segments.Count - 1]; + break; + } + + Point tsn = ts.Rotate((Math.PI/2)); + bool touch = (tsn*pseg.Derivative(pseg.ParStart))* + (tsn*enterSeg.Derivative(enterSeg.ParEnd)) < ApproximateComparer.Tolerance; + + return !touch; + } + + double d = ts*pn; + if (onlyFromInsideCuts) + return d > ApproximateComparer.DistanceEpsilon; + return Math.Abs(d) > ApproximateComparer.DistanceEpsilon; + } + + /// + /// + /// + /// + /// + /// consider a cut good only if the segment cuts the polygon from inside + /// + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "polyline")] + public static bool RealCut(IntersectionInfo xx, Curve polyline, bool onlyFromInsideCuts) { + ValidateArg.IsNotNull(xx, "xx"); + ValidateArg.IsNotNull(polyline, "polyline"); + ICurve sseg = xx.Segment0; + ICurve pseg = xx.Segment1; + double spar = xx.Par0; + double ppar = xx.Par1; + Point x = xx.IntersectionPoint; + + + //normalised tangent to spline + Point ts = sseg.Derivative(spar).Normalize(); + Point pn = pseg.Derivative(ppar).Normalize().Rotate(Math.PI/2); + + if (ApproximateComparer.Close(x, pseg.End)) { + //so pseg enters the spline + ICurve exitSeg = null; + for (int i = 0; i < polyline.Segments.Count - 1; i++) + if (polyline.Segments[i] == pseg) { + exitSeg = polyline.Segments[i + 1]; + break; + } + + if (exitSeg == null) + return false; //hit the end of the polyline + + Point tsn = ts.Rotate((Math.PI/2)); + + bool touch = (tsn*pseg.Derivative(pseg.ParEnd))*(tsn*exitSeg.Derivative(exitSeg.ParStart)) < + ApproximateComparer.Tolerance; + + return !touch; + } + + if (ApproximateComparer.Close(x, pseg.Start)) { + //so pseg exits the spline + ICurve enterSeg = null; + for (int i = polyline.segs.Count - 1; i > 0; i--) + if (polyline.Segments[i] == pseg) { + enterSeg = polyline.Segments[i - 1]; + break; + } + if (enterSeg == null) + return false; + Point tsn = ts.Rotate((Math.PI/2)); + bool touch = (tsn*pseg.Derivative(pseg.ParStart))* + (tsn*enterSeg.Derivative(enterSeg.ParEnd)) < ApproximateComparer.Tolerance; + + return !touch; + } + + double d = ts*pn; + if (onlyFromInsideCuts) + return d > ApproximateComparer.DistanceEpsilon; + return Math.Abs(d) > ApproximateComparer.DistanceEpsilon; + } + + internal static bool MinDistWithinIntervals( + ICurve a, ICurve b, double aMin, double aMax, double bMin, double bMax, + double aGuess, double bGuess, out double aSolution, out double bSolution, out Point aPoint, out Point bPoint) { + var md = new MinDistCurveCurve(a, b, aMin, aMax, bMin, bMax, aGuess, bGuess); + md.Solve(); + aSolution = md.ASolution; + aPoint = md.APoint; + bSolution = md.BSolution; + bPoint = md.BPoint; + + return md.Status; + } + +#if DEBUGCURVES + public override string ToString() + { + bool poly = true; + foreach (ICurve s in segs) + if (s is LineSeg == false) + { + poly = false; + break; + } + + string ret; + if (!poly) + { + ret = "{"; + + foreach (ICurve seg in Segs) + { + ret += seg + ","; + } + + + return ret + "}"; + } + ret = "{"; + if (segs.Count > 0) + ret += segs[0].Start.X.ToString() + "," + segs[0].Start.Y.ToString()+" "; + foreach(LineSeg s in segs) + ret += s.End.X.ToString() + "," + s.End.Y.ToString() + " "; + return ret + "}"; + } +#endif + + /// + /// Offsets the curve in the direction of dir + /// + /// + /// + /// + public ICurve OffsetCurve(double offset, Point dir) { + return null; + } + + /// + /// The bounding rectangle of the curve + /// + public Rectangle BoundingBox { + get { + if (Segments.Count == 0) + return new Rectangle(0, 0, -1, -1); +#if SHARPKIT //https://code.google.com/p/sharpkit/issues/detail?id=369 there are no structs in js + Rectangle b = Segments[0].BoundingBox.Clone(); +#else + Rectangle b = Segments[0].BoundingBox; +#endif + for (int i = 1; i < Segments.Count; i++) + b.Add(Segments[i].BoundingBox); + + return b; + } + } + + ///// + ///// //sort intersections by the a parameter + ///// + ///// + ///// + ///// + //internal static void AddRealIntersectionsToDictionaryForClosedCurve(SortedDictionary dictionary, ICurve coeff, Curve side1) { + // StopIntersect si = new StopIntersect((new XExaminer(dictionary, coeff, side1)).IntersectionExaminerForClosedCurveWithIntersectionLifting); + // Curve.GetAllIntersections(coeff, side1, si, true); + //} + + //internal static void AddRealIntersectionsWithTreeToDictionaryForClosedCurves(SortedDictionary dictionary, ICurve coeff, ParallelogramNode bound) { + // if( Parallelogram.Intersect( coeff.ParallelogramNodeOverICurve.Parallelogram, bound.Parallelogram)){ + // ParallelogramBinaryTreeNode treeNode = bound as ParallelogramBinaryTreeNode; + // if (treeNode != null) { + // AddRealIntersectionsWithTreeToDictionaryForClosedCurves(dictionary, coeff, treeNode.LeftSon); + // AddRealIntersectionsWithTreeToDictionaryForClosedCurves(dictionary, coeff, treeNode.RightSon); + // } else + // AddRealIntersectionsToDictionaryForClosedCurve(dictionary, coeff, (Curve)((ParallelogramNodeOverICurve)bound).Seg); + // } + //} + + #region ICurve Members + + /// + /// clones the curve. + /// + /// the cloned curve + public ICurve Clone() { + var c = new Curve(Segments.Count); + foreach (ICurve seg in Segments) + c.AddSegment(seg.Clone()); + return c; + } + + #endregion + + #region ICurve Members + + /// + /// + /// + /// + /// + public double GetParameterAtLength(double length) { + var parSpan = 0.0; + foreach (var seg in Segments) { + var segL = seg.Length; + if (segL >= length) + return parSpan+seg.GetParameterAtLength(length); + + length -= segL; + parSpan += seg.ParEnd - seg.ParStart; + } + return ParEnd; + } + + /// + /// returns a parameter t such that the distance between curve[t] and targetPoint is minimal + /// and t belongs to the closed segment [low,high] + /// + /// the point to find the closest point + /// the upper bound of the parameter + /// the low bound of the parameter + /// + public double ClosestParameterWithinBounds(Point targetPoint, double low, double high) { + double par = 0; + double dist = Double.MaxValue; + double offset = 0; + foreach (ICurve seg in Segments) { + if (offset > high) + break; //we are out of the [low, high] segment + double segParamSpan = ParamSpan(seg); + double segEnd = offset + segParamSpan; + if (segEnd >= low) { + //we are in business + double segLow = Math.Max(seg.ParStart, seg.ParStart + (low - offset)); + double segHigh = Math.Min(seg.ParEnd, seg.ParStart + (high - offset)); + Debug.Assert(segHigh >= segLow); + double t = seg.ClosestParameterWithinBounds(targetPoint, segLow, segHigh); + Point d = targetPoint - seg[t]; + double dd = d*d; + if (dd < dist) { + par = offset + t - seg.ParStart; + dist = dd; + } + } + offset += segParamSpan; + } + return par; + } + + /// + /// returns a parameter t such that the distance between curve[t] and a is minimal + /// + /// + /// + public double ClosestParameter(Point targetPoint) { + double par = 0; + double dist = Double.MaxValue; + double offset = 0; + foreach (ICurve c in Segments) { + double t = c.ClosestParameter(targetPoint); + Point d = targetPoint - c[t]; + double dd = d*d; + if (dd < dist) { + par = offset + t - c.ParStart; + dist = dd; + } + offset += ParamSpan(c); + } + return par; + } + + #endregion + + #region Curve concatenations + + /// + ///adds a line segment to the curve + /// + /// + /// + /// + /// + public static Curve AddLineSegment(Curve curve, Point pointA, Point pointB) { + ValidateArg.IsNotNull(curve, "curve"); + return curve.AddSegment(new LineSegment(pointA, pointB)); + } + + + //static internal void AddLineSegment(Curve c, double x, double y, Point b) { + // AddLineSegment(c, new Point(x, y), b); + //} + + /// + /// adds a line segment to the curve + /// + /// + /// + /// + /// + /// + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "x"), + SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "y")] + public static void AddLineSegment(Curve curve, double x0, double y0, double x1, double y1) { + AddLineSegment(curve, new Point(x0, y0), new Point(x1, y1)); + } + + /// + /// adds a line segment to the curve + /// + /// + /// + /// + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "y"), + SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "x"), + SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "c")] + public static void ContinueWithLineSegment(Curve c, double x, double y) { + ValidateArg.IsNotNull(c, "c"); + AddLineSegment(c, c.End, new Point(x, y)); + } + + /// + /// adds a line segment to the curve + /// + /// + /// + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "x"), + SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "c"), + SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Seg")] + public static void ContinueWithLineSegment(Curve c, Point x) { + ValidateArg.IsNotNull(c, "c"); + AddLineSegment(c, c.End, x); + } + + /// + /// + /// + /// + public static void CloseCurve(Curve curve) { + ValidateArg.IsNotNull(curve, "curve"); + ContinueWithLineSegment(curve, curve.Start); + } + + #endregion + + /// + /// left derivative at t + /// + /// the parameter where the derivative is calculated + /// + public Point LeftDerivative(double t) { + ICurve seg = TryToGetLeftSegment(t); + if (seg != null) + return seg.Derivative(seg.ParEnd); + return Derivative(t); + } + + /// + /// right derivative at t + /// + /// the parameter where the derivative is calculated + /// + public Point RightDerivative(double t) { + ICurve seg = TryToGetRightSegment(t); + if (seg != null) + return seg.Derivative(seg.ParStart); + return Derivative(t); + } + + + ICurve TryToGetLeftSegment(double t) { + if (Math.Abs(t - ParStart) < ApproximateComparer.Tolerance) { + if (Start == End) + return Segments[Segments.Count - 1]; + return null; + } + foreach (ICurve seg in Segments) { + t -= ParamSpan(seg); + if (Math.Abs(t) < ApproximateComparer.Tolerance) + return seg; + } + return null; + } + + ICurve TryToGetRightSegment(double t) { + if (Math.Abs(t - ParEnd) < ApproximateComparer.Tolerance) { + if (Start == End) + return Segments[0]; + return null; + } + + foreach (ICurve seg in Segments) { + if (Math.Abs(t) < ApproximateComparer.Tolerance) + return seg; + + t -= ParamSpan(seg); + } + return null; + } + + /// + /// gets the closest point together with its parameter + /// + /// + /// + /// + /// + [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#")] + public static double ClosestParameterWithPoint(ICurve curve, Point location, out Point pointOnCurve) { + ValidateArg.IsNotNull(curve, "curve"); + double t = curve.ClosestParameter(location); + pointOnCurve = curve[t]; + return t; + } + + /// + /// gets the point on the curve that is closest to the given point + /// + /// the curve to examine + /// the target point + /// the closest point + public static Point ClosestPoint(ICurve curve, Point location) { + ValidateArg.IsNotNull(curve, "curve"); + return curve[curve.ClosestParameter(location)]; + } + + /// + /// Tests whether the first curve is inside the second. + /// We suppose that the curves are convex and they are + /// not degenerated into a point + /// + /// + /// + /// + public static bool CurveIsInsideOther(ICurve innerCurve, ICurve outerCurve) { + ValidateArg.IsNotNull(innerCurve, "innerCurve"); + ValidateArg.IsNotNull(outerCurve, "outerCurve"); + if (!outerCurve.BoundingBox.Contains(innerCurve.BoundingBox)) return false; + IList xx = GetAllIntersections(innerCurve, outerCurve, true); + if (xx.Count == 0) return NonIntersectingCurveIsInsideOther(innerCurve, outerCurve); + if (xx.Count == 1) //it has to be a touch + return innerCurve.Start != xx[0].IntersectionPoint + ? PointRelativeToCurveLocation(innerCurve.Start, outerCurve) == PointLocation.Inside + : PointRelativeToCurveLocation(innerCurve[(innerCurve.ParStart + innerCurve.ParEnd)/2], + outerCurve) == PointLocation.Inside; + return + PointsBetweenIntersections(innerCurve, xx).All( + p => PointRelativeToCurveLocation(p, outerCurve) != PointLocation.Outside); + } + + // Return points between but not including the intersections. + internal static IEnumerable PointsBetweenIntersections(ICurve a, IList xx) { + xx.OrderBy(x => x.Par0); + for (int i = 0; i < xx.Count - 1; i++) + yield return a[(xx[i].Par0 + xx[i + 1].Par0)/2]; + //take care of the last interval + double start = xx[xx.Count - 1].Par0; + double end = xx[0].Par0; + double len = a.ParEnd - start + end - a.ParStart; + double middle = start + len/2; + if (middle > a.ParEnd) + middle = a.ParStart + middle - a.ParEnd; + yield return a[middle]; + } + + static bool NonIntersectingCurveIsInsideOther(ICurve a, ICurve b) { + ValidateArg.IsNotNull(a, "a"); + ValidateArg.IsNotNull(b, "b"); + // Due to rounding, even curves with 0 intersections may return Boundary. + for (double par = a.ParStart; par < a.ParEnd; par += 0.5) { + // continue as long as we have boundary points. + PointLocation parLoc = PointRelativeToCurveLocation(a[par], b); + if (PointLocation.Boundary != parLoc) return PointLocation.Inside == parLoc; + } + + // All points so far were on border so it is not considered inside; test the End. + return PointLocation.Outside != PointRelativeToCurveLocation(a.End, b); + } + + /// + /// Tests whether the interiors of two closed convex curves intersect + /// + /// convex closed curve + /// convex closed curve + /// + public static bool ClosedCurveInteriorsIntersect(ICurve curve1, ICurve curve2) { + ValidateArg.IsNotNull(curve1, "curve1"); + ValidateArg.IsNotNull(curve2, "curve2"); + if (!curve2.BoundingBox.Intersects(curve1.BoundingBox)) + return false; + IList xx = GetAllIntersections(curve1, curve2, true); + if (xx.Count == 0) + return NonIntersectingCurveIsInsideOther(curve1, curve2) || + NonIntersectingCurveIsInsideOther(curve2, curve1); + if (xx.Count == 1) //it is a touch + return curve1.Start != xx[0].IntersectionPoint + ? PointRelativeToCurveLocation(curve1.Start, curve2) == PointLocation.Inside + : PointRelativeToCurveLocation(curve1[(curve1.ParStart + curve1.ParEnd)/2], curve2) == + PointLocation.Inside || + curve2.Start != xx[0].IntersectionPoint + ? PointRelativeToCurveLocation(curve2.Start, curve1) == PointLocation.Inside + : PointRelativeToCurveLocation(curve2[(curve2.ParStart + curve2.ParEnd)/2], curve1) == + PointLocation.Inside; + return + PointsBetweenIntersections(curve1, xx).Any( + p => PointRelativeToCurveLocation(p, curve2) == PointLocation.Inside); + } + + #region ICurve Members + + /// + /// + /// + /// + /// + public double Curvature(double t) { + ICurve seg; + double par; + GetSegmentAndParameter(t, out par, out seg); + return seg.Curvature(par); + } + + /// + /// + /// + /// + /// + public double CurvatureDerivative(double t) { + throw new NotImplementedException(); + } + + /// + /// + /// + /// + /// + public double CurvatureSecondDerivative(double t) { + throw new NotImplementedException(); + } + + #endregion + + /// + /// + ///True if the curves intersect each other. + public static bool CurvesIntersect(ICurve curve1, ICurve curve2) { + return curve1 == curve2 || (CurveCurveIntersectionOne(curve1, curve2, false) != null); + } + + internal static CubicBezierSegment CreateBezierSeg(double kPrev, double kNext, Site a, Site b, Site c) { + Point s = kPrev*a.Point + (1 - kPrev)*b.Point; + Point e = kNext*c.Point + (1 - kNext)*b.Point; + Point t = (2.0/3.0)*b.Point; + return new CubicBezierSegment(s, s/3.0 + t, t + e/3.0, e); + } + + internal static CubicBezierSegment CreateBezierSeg(Point a, Point b, Point perp, int i) { + Point d = perp*i; + return new CubicBezierSegment(a, a + d, b + d, b); + } + + internal static bool FindCorner(Site a, out Site b, out Site c) { + c = null; // to silence the compiler + b = a.Next; + if (b.Next == null) + return false; //no corner has been found + c = b.Next; + return c != null; + } + + internal static ICurve TrimEdgeSplineWithNodeBoundaries(ICurve sourceBoundary, + ICurve targetBoundary, ICurve spline, + bool narrowestInterval) { + + var start = spline.ParStart; + var end = spline.ParEnd; + if (sourceBoundary != null) + FindNewStart(spline, ref start, sourceBoundary, narrowestInterval); + if (targetBoundary != null) + FindNewEnd(spline, targetBoundary, narrowestInterval, ref end); + + double st = Math.Min(start, end); + double en = Math.Max(start, end); + return st < en ? spline.Trim(st, en) : spline; + } + + static void FindNewEnd(ICurve spline, ICurve targetBoundary, bool narrowestInterval, ref double end) { + //SugiyamaLayoutSettings.Show(c, spline); + IList intersections = GetAllIntersections(spline, targetBoundary, true); + if (intersections.Count == 0) { + end = spline.ParEnd; + return; + } + if (narrowestInterval) { + end = spline.ParEnd; + foreach (IntersectionInfo xx in intersections) + if (xx.Par0 < end) + end = xx.Par0; + } + else { + //looking for the last intersection + end = spline.ParStart; + foreach (IntersectionInfo xx in intersections) + if (xx.Par0 > end) + end = xx.Par0; + } + } + + static void FindNewStart(ICurve spline, ref double start, ICurve sourceBoundary, bool narrowestInterval) { + IList intersections = GetAllIntersections(spline, sourceBoundary, true); + if (intersections.Count == 0) { + start = spline.ParStart; + return; + } + if (narrowestInterval) { + start = spline.ParStart; + foreach (IntersectionInfo xx in intersections) + if (xx.Par0 > start) + start = xx.Par0; + } + else { + start = spline.ParEnd; + foreach (IntersectionInfo xx in intersections) + if (xx.Par0 < start) + start = xx.Par0; + } + } + + /// + /// + /// + /// + /// + public static Polyline PolylineAroundClosedCurve(ICurve curve) { + Polyline ret; + var ellipse = curve as Ellipse; + if (ellipse != null) + ret = RefineEllipse(ellipse); + else { + var poly = curve as Polyline; + if (poly != null) + return poly; + var c = curve as Curve; + if (c != null && AllSegsAreLines(c)) { + ret = new Polyline(); + foreach (LineSegment ls in c.Segments) + ret.AddPoint(ls.Start); + ret.Closed = true; + if (!ret.IsClockwise()) + ret = (Polyline) ret.Reverse(); + } + else + ret = StandardRectBoundary(curve); + } + return ret; + } + + static bool AllSegsAreLines(Curve c) { + foreach (ICurve s in c.Segments) + if (!(s is LineSegment)) + return false; + return true; + } + + /// + /// this code only works for the standard ellipse + /// + /// + /// + static Polyline RefineEllipse(Ellipse ellipse) { + Polyline rect = StandardRectBoundary(ellipse); + var dict = new SortedDictionary(); + double a = Math.PI/4; + double w = ellipse.BoundingBox.Width; + double h = ellipse.BoundingBox.Height; + double l = Math.Sqrt(w*w + h*h); + for (int i = 0; i < 4; i++) { + double t = a + i*Math.PI/2; // parameter + Point p = ellipse[t]; //point on the ellipse + Point tan = l*(ellipse.Derivative(t).Normalize()); //make it long enough + + var ls = new LineSegment(p - tan, p + tan); + //System.Diagnostics.Debug.Assert(ls.Start.X > -10000); + foreach (IntersectionInfo ix in GetAllIntersections(rect, ls, true)) + dict[ix.Par0] = ix.IntersectionPoint; + } + + Debug.Assert(dict.Count > 0); + return new Polyline(dict.Values) {Closed = true}; + } + + internal static Polyline StandardRectBoundary(ICurve curve) { + Rectangle bbox = curve.BoundingBox; + return bbox.Perimeter(); + } + + /// + /// Create a closed Polyline from a rectangle + /// + /// + public static Polyline PolyFromBox(Rectangle rectangle) { + var p = new Polyline(); + p.AddPoint(rectangle.LeftTop); + p.AddPoint(rectangle.RightTop); + p.AddPoint(rectangle.RightBottom); + p.AddPoint(rectangle.LeftBottom); + p.Closed = true; + return p; + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/Curve.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/Curve.cs.meta new file mode 100644 index 000000000..462e62121 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/Curve.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 57f594541b890cb47891b3a90e1b5045 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/CurveFactory.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/CurveFactory.cs new file mode 100644 index 000000000..f8504c445 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/CurveFactory.cs @@ -0,0 +1,397 @@ +using System; +using System.Collections.Generic; +using Microsoft.Msagl.Core.Layout; + +namespace Microsoft.Msagl.Core.Geometry.Curves { + /// + /// the helper class to create curves + /// + public sealed class CurveFactory { + CurveFactory() { } + /// + /// Creates an ellipse by the length of axes and the center + /// + /// + /// + /// + /// + static public ICurve CreateEllipse(double radiusInXDirection, double radiusInYDirection, Point center) { + return new Ellipse(radiusInXDirection, radiusInYDirection, center); + } + + /// + /// Creates an ellipse by the length of axes and the center + /// + /// + /// + /// + static public ICurve CreateCircle(double radius, Point center) { + return new Ellipse(radius, radius, center); + } + + /// + /// Create a rectangle with smoothed corners + /// + /// the rectangle width + /// the rectangle height + /// the length of the x axis of the corner smoothing ellipse + /// the length of the y axis of the corner smoothing ellipse + /// the rectangle center + /// + static public ICurve CreateRectangleWithRoundedCorners(double width, double height, double radiusInXDirection, double radiusInYDirection, Point center) { + var box = new Rectangle(center.X - width / 2, center.Y - height / 2, center.X + width / 2, center.Y + height / 2); + return new RoundedRect(box, radiusInXDirection, radiusInYDirection); + } + + /// + /// Create in the specified curve, a rectangle with smoothed corners + /// + /// + /// the rectangle width + /// the rectangle height + /// the length of the x axis of the corner smoothing ellipse + /// the length of the y axis of the corner smoothing ellipse + /// the rectangle center + /// + static internal void CreateRectangleWithRoundedCorners(Curve c, double width, double height, double radiusInXDirection, double radiusInYDirection, Point center) { + if (radiusInXDirection == 0 || radiusInYDirection == 0) { + CreateRectangle(c, width, height, center); + return; + } + double w = width / 2; + if (radiusInXDirection > w / 2) + radiusInXDirection = w / 2; + double h = height / 2; + if (radiusInYDirection > h / 2) + radiusInYDirection = h / 2; + double x = center.X; + double y = center.Y; + double ox = w - radiusInXDirection; + double oy = h - radiusInYDirection; + double top = y + h; + double bottom = y - h; + double left = x - w; + double right = x + w; + //ellipse's axises + Point a = new Point(radiusInXDirection, 0); + Point b = new Point(0, radiusInYDirection); + + c.IncreaseSegmentCapacity(8); + + if (ox > 0) + c.AddSegment(new LineSegment(new Point(x - ox, bottom), new Point(x + ox, bottom))); + c.AddSegment(new Ellipse(1.5 * Math.PI, 2 * Math.PI, a, b, x + ox, y - oy)); + if (oy > 0) + c.AddSegment(new LineSegment(new Point(right, y - oy), new Point(right, y + oy))); + c.AddSegment(new Ellipse(0, 0.5 * Math.PI, a, b, x + ox, y + oy)); + if (ox > 0) + c.AddSegment(new LineSegment(new Point(x + ox, top), new Point(x - ox, top))); + c.AddSegment(new Ellipse(0.5 * Math.PI, Math.PI, a, b, x - ox, y + oy)); + if (oy > 0) + c.AddSegment(new LineSegment(new Point(left, y + oy), new Point(left, y - oy))); + c.AddSegment(new Ellipse(Math.PI, 1.5 * Math.PI, a, b, x - ox, y - oy)); + } + + /// + /// create a box of the given width and height at center. + /// + /// + /// + /// + /// + static public ICurve CreateRectangle(double width, double height, Point center) { + double w = width / 2; + double h = height / 2; + double x = center.X; + double y = center.Y; + Curve c = new Curve(); + Point[] p = new Point[] { new Point(x - w, y - h), new Point(x + w, y - h), new Point(x + w, y + h), new Point(x - w, y + h) }; + c.AddSegs(new LineSegment(p[0], p[1]), new LineSegment(p[1], p[2]), new LineSegment(p[2], p[3]), new LineSegment(p[3], p[0])); + return c; + } + + /// + /// create a box of the given width and height at center. + /// + /// + /// + /// + /// + /// + static internal void CreateRectangle(Curve c, double width, double height, Point center) { + double w = width / 2; + double h = height / 2; + double x = center.X; + double y = center.Y; + Point[] p = new Point[] { new Point(x - w, y - h), new Point(x + w, y - h), new Point(x + w, y + h), new Point(x - w, y + h) }; + c.AddSegs(new LineSegment(p[0], p[1]), new LineSegment(p[1], p[2]), new LineSegment(p[2], p[3]), new LineSegment(p[3], p[0])); + } + + /// + /// Create a polyline curve for the given rectangle + /// + /// + /// + static public ICurve CreateRectangle(Rectangle rectangle) { + return CreateRectangle(rectangle.Width, rectangle.Height, rectangle.Center); + } + + /// + /// Creates a curve resembling a house large enough to inscribe within it a rectangle of the + /// given width and height at center. + /// + /// the house width - the rectangle width + /// the height of the inscribed rectangle; the house will be half-again higher + /// the center of the inscribed rectangle + /// + static public ICurve CreateHouse(double width, double height, Point center) { + double w = width / 2; + double h = height / 2; + double x = center.X; + double y = center.Y; + Curve c = new Curve(4); + Curve.AddLineSegment(c, x - w, y - h, x + w, y - h); + Curve.ContinueWithLineSegment(c, x + w, y + h); + Curve.ContinueWithLineSegment(c, x, y + 2 * h); + Curve.ContinueWithLineSegment(c, x - w, y + h); + Curve.CloseCurve(c); + return c; + } + + /// + /// Creates curve resembling a house within the rectangle formed by width and height at center + /// (if the rectangle is a square, the house has the shape of home plate in baseball). + /// + /// the bounding rectangle width + /// the bounding rectangle height + /// the bounding rectangle center + /// + static public ICurve CreateInteriorHouse(double width, double height, Point center) { + double w = width / 2; + double h = height / 2; + double x = center.X; + double y = center.Y; + Curve c = new Curve(4); + Curve.AddLineSegment(c, x - w, y - h, x + w, y - h); + Curve.ContinueWithLineSegment(c, x + w, y); + Curve.ContinueWithLineSegment(c, x, y + h); + Curve.ContinueWithLineSegment(c, x - w, y); + Curve.CloseCurve(c); + return c; + } + + /// + /// Creates a curve resembling an inverted house + /// + /// the house width + /// the house heigth + /// the house center + /// + static public ICurve CreateInvertedHouse(double width, double height, Point center) { + ICurve shape = CreateHouse(width, height, center); + return RotateCurveAroundCenterByDegree(shape, center, 180.0); + } + + /// + /// Creates a curve resembling a diamond large enough to inscribe within it a rectangle of the + /// given width and height at center. + /// + /// the width of the inscribed rectangle + /// the height of the inscribed rectangle + /// the diamond center + /// + static public ICurve CreateDiamond(double width, double height, Point center) { + double w = width; + double h = height; + double x = center.X; + double y = center.Y; + Curve c = new Curve(); + Point[] p = new Point[] { new Point(x, y - h), new Point(x + w, y), new Point(x, y + h), new Point(x - w, y) }; + c.AddSegs(new LineSegment(p[0], p[1]), new LineSegment(p[1], p[2]), new LineSegment(p[2], p[3]), new LineSegment(p[3], p[0])); + return c; + } + + /// + /// Creates a curve resembling a diamond within the rectangle formed by width and height at center. + /// + /// the bounding rectangle width + /// the bounding rectangle height + /// the bounding rectangle center + /// + static public ICurve CreateInteriorDiamond(double width, double height, Point center) { + return CreateDiamond(width / 2, height / 2, center); + } + + // This adds the padding to the edges around the inscribed rectangle of an octagon. + static double octagonPad = 1.0 / 4; + + /// + /// Creates a curve of the form of an octagon large enough to inscribe a rectangle + /// of width and height around center. + /// + /// the inscribed rectangle width + /// the inscribed rectangle height + /// the inscribed rectangle (and octagon) center + /// + public static Polyline CreateOctagon(double width, double height, Point center) { + double w = width / 2; + double h = height / 2; + Point[] ps = new Point[8]; + + // Pad out horizontally + ps[0] = new Point(w + (octagonPad * w), h - (h * octagonPad)); + ps[3] = new Point(-ps[0].X, ps[0].Y); + ps[4] = new Point(ps[3].X, -ps[3].Y); + ps[7] = new Point(ps[0].X, -ps[0].Y); + + // Pad out vertically + ps[1] = new Point(w - (w * octagonPad), h + (h * octagonPad)); + ps[2] = new Point(-ps[1].X, ps[1].Y); + ps[6] = new Point(ps[1].X, -ps[1].Y); + ps[5] = new Point(ps[2].X, -ps[2].Y); + + for (int i = 0; i < 8; i++) { + ps[i] += center; + } + + Polyline polyline = new Polyline(ps) { Closed = true }; + return polyline; + } + + /// + /// Creates a curve of the form of an hexagon large enough to inscribe a rectangle + /// of width and height around center. + /// + /// the inscribed rectangle width + /// the inscribed rectangle height + /// the inscribed rectangle (and hexagon) center + /// + public static ICurve CreateHexagon(double width, double height, Point center) { + var h = height / 2; + var w = width / 2; + var x = center.X; + var y = center.Y; + return new Polyline(new[]{ + new Point(-w + x, - h + y), + new Point( w + x, - h + y), + new Point( w + h + x, 0 + y), + new Point( w + x, h + y), + new Point(-w + x, h + y), + new Point(-w - h + x, 0 + y), + }) { Closed = true }; + } + + /// + /// Creates a curve in the form of an octagon within the rectangle formed by width and height at center. + /// + /// the bounding rectangle width + /// the bounding rectangle height + /// the bounding rectangle center + /// + public static ICurve CreateInteriorOctagon(double width, double height, Point center) { + return CreateOctagon(width / (1.0 + octagonPad), height / (1.0 + octagonPad), center); + } + + /// + /// testing, don't use + /// + /// + /// + /// + public static ICurve CreateTestShape(double width, double height) { + int mult = 1; + double w = width * 3; + double h = height * 3; + Curve curve = new Curve(9); + Curve.AddLineSegment(curve, -w, -h, 0, -h / 2); + Curve.ContinueWithLineSegment(curve, w / 2, -0.75 * h); + Curve.ContinueWithLineSegment(curve, w, -h); + Curve.ContinueWithLineSegment(curve, 0.75 * w, -h / 2); + Curve.ContinueWithLineSegment(curve, w / 2, 0); + Curve.ContinueWithLineSegment(curve, w, h); + + Curve.ContinueWithLineSegment(curve, 0, h / 2); + Curve.ContinueWithLineSegment(curve, -w, mult * h); + Curve.ContinueWithLineSegment(curve, -w / 3, 0); + Curve.CloseCurve(curve); + return curve; + } + + /// + /// create a triangle inside the box formed by width and height. + /// + /// + /// + /// + /// + static public ICurve CreateInteriorTriangle(double width, double height, Point center) { + double w = width / 2; + double h = height / 2; + double x = center.X; + double y = center.Y; + Curve c = new Curve(3); + Point[] p = new Point[] { new Point(x - w, y - h), new Point(x + w, y - h), new Point(x, y + h) }; + c.AddSegment(new LineSegment(p[0], p[1])); + c.AddSegment(new LineSegment(p[1], p[2])); + c.AddSegment(new LineSegment(p[2], p[0])); + return c; + } + + /// + /// Rotate a curve around a given point using radians + /// + /// + /// + /// + /// + public static ICurve RotateCurveAroundCenterByRadian(ICurve curve, Point center, double angle) { + ValidateArg.IsNotNull(curve, "curve"); + var c = Math.Cos(angle); + var s = Math.Sin(angle); + var transform = new PlaneTransformation(1, 0, center.X, 0, 1, center.Y) * new PlaneTransformation(c, -s, 0, s, c, 0) * new PlaneTransformation(1, 0, -center.X, 0, 1, -center.Y); + return curve.Transform(transform); + } + + /// + /// Rotate a curve around a given point using degrees + /// + /// + /// + /// + /// + public static ICurve RotateCurveAroundCenterByDegree(ICurve curve, Point center, double degree) { + return RotateCurveAroundCenterByRadian(curve, center, degree * (Math.PI / 180.0)); + } + + /// + /// + /// + /// + /// + public static ICurve CreateStar(double width, Point center) { + const double a = Math.PI * 2 / 5; + var r2 = width / (2 * Math.Sin(a)); + var r = r2 / 2; + return new Polyline(StarPoints(r, r2, center, a)) { Closed = true }; + + } + + static IEnumerable StarPoints(double r, double r2, Point center, double a) { + var ang = Math.PI / 2; + var anghalf = a / 2; + for (int i = 0; i < 5; i++) { + yield return center + r2 * new Point(Math.Cos(ang), Math.Sin(ang)); + yield return center + r * new Point(Math.Cos(ang + anghalf), Math.Sin(ang + anghalf)); + ang += a; + } + } + + internal static Polyline CreateRegularPolygon(int n, Point center, double rad) { + var pt = new Point[n]; + double a = 2 * Math.PI / n; + for (int i = 0; i < n; i++) + pt[i] = rad * (new Point(Math.Cos(i * a), Math.Sin(i * a))) + center; + return new Polyline(pt) { Closed = true }; + } + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/CurveFactory.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/CurveFactory.cs.meta new file mode 100644 index 000000000..2ea39d739 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/CurveFactory.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0683753b5ef800a419a9cff7fda0aa34 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/CurveLengthAndParams.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/CurveLengthAndParams.cs new file mode 100644 index 000000000..3768d46c5 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/CurveLengthAndParams.cs @@ -0,0 +1,128 @@ +namespace Microsoft.Msagl.Core.Geometry.Curves { + public partial class Curve { + + double parStart; + + /// + /// the start of the parameter domain + /// + public double ParStart { + get { return parStart; } + set { parStart = value; } + } + + double parEnd; + + /// + /// the end of the parameter domain + /// + public double ParEnd { + get { return parEnd; } + set { parEnd = value; } + } + + /// + /// Returns the curved moved by delta + /// + public void Translate(Point delta) + { + lock (this) + { + foreach (ICurve s in segs) + s.Translate(delta); + + pBNode = null; + } + } + + /// + /// Returns the curved scaled by x and y + /// + /// + /// + /// + public ICurve ScaleFromOrigin(double xScale, double yScale) + { + Curve c = new Curve(segs.Count); + foreach (ICurve s in segs) + c.AddSegment(s.ScaleFromOrigin(xScale, yScale)); + + return c; + } + + /// + /// Return the transformed curve + /// + /// + /// the transformed curve + public ICurve Transform(PlaneTransformation transformation) { + Curve c = new Curve(segs.Count); + foreach (ICurve s in segs) + c.AddSegment(s.Transform(transformation)); + + return c; + } + + /// + /// return length of the curve segment [start,end] + /// + /// + /// + /// + public double LengthPartial(double start, double end) { + double s, e; + int i, j; + + AdjustStartEndEndParametersToDomain(ref start, ref end); + this.GetSegmentAndParameter(start, out s, out i); + this.GetSegmentAndParameter(end, out e, out j); + + ICurve seg = this.segs[i]; + double ret = seg.LengthPartial(s, seg.ParEnd); + for (int k = i + 1; k < j; k++) + ret += segs[k].Length; + + seg = segs[j]; + return ret + seg.LengthPartial(seg.ParStart, e); + + } + + /// + /// An approximate length of the curve calculated using the interpolation + /// + /// + /// + static public double LengthWithInterpolation(ICurve curve) { + double ret = 0; + foreach (LineSegment ls in Interpolate(curve, lineSegThreshold)) + ret += ls.Length; + return ret; + } + + /// + /// An approximate length of the curve calculated using the interpolation + /// + /// + /// + /// + static public double LengthWithInterpolationAndThreshold(ICurve curve, double threshold) + { + double ret = 0; + foreach (LineSegment ls in Interpolate(curve, threshold)) + ret += ls.Length; + return ret; + } + + /// + /// Get the length of the curve + /// + public double Length { + get { + double ret = 0; + foreach (ICurve ic in segs) + ret += ic.Length; + return ret; + } + } + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/CurveLengthAndParams.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/CurveLengthAndParams.cs.meta new file mode 100644 index 000000000..ef31cedfd --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/CurveLengthAndParams.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a9cdecaa81a099a428117c0723547bda +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/CurveTangent.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/CurveTangent.cs new file mode 100644 index 000000000..f304e1d19 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/CurveTangent.cs @@ -0,0 +1,21 @@ + +namespace Microsoft.Msagl.Core.Geometry.Curves { + /// + /// used to calculate the around curve + /// + internal class CurveTangent { + internal Point touchPoint; + + internal Point direction; + + /// + /// + /// + /// + /// + internal CurveTangent(Point touch, Point direction) { + this.touchPoint = touch; + this.direction = direction; + } + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/CurveTangent.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/CurveTangent.cs.meta new file mode 100644 index 000000000..4419bf61e --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/CurveTangent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8165017438605044f82f595067f26f18 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/Ellipse.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/Ellipse.cs new file mode 100644 index 000000000..90b157f2f --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/Ellipse.cs @@ -0,0 +1,621 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace Microsoft.Msagl.Core.Geometry.Curves{ + /// + /// A class representing an ellipse. + /// +#if TEST_MSAGL + [Serializable] +#endif + public class Ellipse : ICurve{ +#if TEST_MSAGL + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.String.Format(System.String,System.Object[])")] + public override string ToString() { + return String.Format("{0} {1} from {2} to {3} a0={4} a1={5}", Start, End, ParStart, ParEnd, AxisA, AxisB); + } +#endif + Rectangle box; + + ParallelogramNodeOverICurve parallelogramNodeOverICurve; + + /// + /// offsets the curve in the given direction + /// + /// the width of the offset + /// the direction of the offset + /// + public ICurve OffsetCurve(double offset, Point dir){ + //is dir inside or outside + Point d = dir - center; + double angle = Point.Angle(aAxis, d); + Point s = aAxis*Math.Cos(angle) + bAxis*Math.Sin(angle); + if(s.Length < d.Length){ + double al = aAxis.Length; + double bl = bAxis.Length; + return new Ellipse((al + offset)*aAxis.Normalize(), (bl + offset)*bAxis.Normalize(), center); + } + { + double al = aAxis.Length; + double bl = bAxis.Length; +#if DEBUGCURVES + if (al < offset || bl < offset) + throw new Exception("wrong parameter for ellipse offset"); +#endif + return new Ellipse((al - offset)*aAxis.Normalize(), (bl - offset)*bAxis.Normalize(), center); + } + } + + /// + /// Reverse the ellipe: not implemented. + /// + /// returns the reversed curve + public ICurve Reverse(){ + return null; // throw new Exception("not implemented"); + } + + /// + /// Returns the start point of the curve + /// + public Point Start{ + get { return this[ParStart]; } + } + + /// + /// Returns the end point of the curve + /// + public Point End{ + get { return this[ParEnd]; } + } + + /// + /// Trims the curve + /// + /// the trim start parameter + /// the trim end parameter + /// + public ICurve Trim(double start, double end){ + Debug.Assert(start<=end); + Debug.Assert(start>=ParStart-ApproximateComparer.Tolerance); + Debug.Assert(end<=ParEnd+ApproximateComparer.Tolerance); + + return new Ellipse(Math.Max(start, ParStart), Math.Min(end,ParEnd), AxisA, AxisB, center); + } + + /// + /// Not Implemented: Returns the trimmed curve, wrapping around the end if start is greater than end. + /// + /// The starting parameter + /// The ending parameter + /// The trimmed curve + public ICurve TrimWithWrap(double start, double end) + { + throw new NotImplementedException(); + } + + //half x axes + Point aAxis; + + /// + /// the X axis of the ellipse + /// + public Point AxisA{ + get { return aAxis; } + set { aAxis = value; } + } + + Point bAxis; + + /// + /// the Y axis of the ellipse + /// + public Point AxisB{ + get { return bAxis; } + set { bAxis = value; } + } + + Point center; + + /// + /// the center of the ellipse + /// + public Point Center{ + get { return center; } + set { center = value; } + } + + /// + /// The bounding box of the ellipse + /// + public Rectangle BoundingBox{ + get { return box; } + } + + /// + /// Returns the point on the curve corresponding to parameter t + /// + /// the parameter of the derivative + /// + public Point this[double t]{ + get { return center + Math.Cos(t)*aAxis + Math.Sin(t)*bAxis; } + } + + /// + /// first derivative + /// + /// the p + /// + public Point Derivative(double t){ + return -Math.Sin(t)*aAxis + Math.Cos(t)*bAxis; + } + + /// + /// second derivative + /// + /// + /// + public Point SecondDerivative(double t){ + return -Math.Cos(t)*aAxis - Math.Sin(t)*bAxis; + } + + /// + /// third derivative + /// + /// + /// + public Point ThirdDerivative(double t){ + return Math.Sin(t)*aAxis - Math.Cos(t)*bAxis; + } + + /// + /// a tree of ParallelogramNodes covering the edge + /// + /// + public ParallelogramNodeOverICurve ParallelogramNodeOverICurve{ + get{ +#if PPC + lock(this){ +#endif + if(parallelogramNodeOverICurve != null) + + return parallelogramNodeOverICurve; + return parallelogramNodeOverICurve = CreateParallelogramNodeForCurveSeg(this); +#if PPC + } +#endif + } + } + + + static ParallelogramNodeOverICurve CreateNodeWithSegmentSplit(double start, double end, Ellipse seg, double eps){ + var pBNode = new ParallelogramInternalTreeNode(seg, eps); + pBNode.AddChild(CreateParallelogramNodeForCurveSeg(start, 0.5*(start + end), seg, eps)); + pBNode.AddChild(CreateParallelogramNodeForCurveSeg(0.5*(start + end), end, seg, eps)); + var boxes = new List(); + boxes.Add(pBNode.Children[0].Parallelogram); + boxes.Add(pBNode.Children[1].Parallelogram); + pBNode.Parallelogram = Parallelogram.GetParallelogramOfAGroup(boxes); + return pBNode; + } + + internal static ParallelogramNodeOverICurve CreateParallelogramNodeForCurveSeg(double start, double end, + Ellipse seg, double eps){ + bool closedSeg = (start == seg.ParStart && end == seg.ParEnd && ApproximateComparer.Close(seg.Start, seg.End)); + if(closedSeg) + return CreateNodeWithSegmentSplit(start, end, seg, eps); + + Point s = seg[start]; + Point e = seg[end]; + Point w = e - s; + Point middle = seg[(start + end) / 2]; + + if (ParallelogramNodeOverICurve.DistToSegm(middle, s, e) <= ApproximateComparer.IntersectionEpsilon && + w * w < Curve.LineSegmentThreshold * Curve.LineSegmentThreshold && end - start < Curve.LineSegmentThreshold) { + var ls = new LineSegment(s, e); + var leaf = ls.ParallelogramNodeOverICurve as ParallelogramLeaf; + leaf.Low = start; + leaf.High = end; + leaf.Seg = seg; + leaf.Chord = ls; + return leaf; + } + + bool we = WithinEpsilon(seg, start, end, eps); + var box = new Parallelogram(); + + if(we && CreateParallelogramOnSubSeg(start, end, seg, ref box)){ + return new ParallelogramLeaf(start, end, box, seg, eps); + } else{ + return CreateNodeWithSegmentSplit(start, end, seg, eps); + } + } + + internal static bool CreateParallelogramOnSubSeg(double start, double end, Ellipse seg, ref Parallelogram box){ + Point tan1 = seg.Derivative(start); + + Point tan2 = seg.Derivative(end); + Point tan2Perp = Point.P(-tan2.Y, tan2.X); + + Point corner = seg[start]; + + Point e = seg[end]; + + Point p = e - corner; + + double numerator = p*tan2Perp; + double denumerator = (tan1*tan2Perp); + double x; // = (p * tan2Perp) / (tan1 * tan2Perp); + if(Math.Abs(numerator) < ApproximateComparer.DistanceEpsilon) + x = 0; + else if(Math.Abs(denumerator) < ApproximateComparer.DistanceEpsilon){ + //it is degenerated; adjacent sides are parallel, but + //since p * tan2Perp is big it does not contain e + return false; + } else x = numerator/denumerator; + + tan1 *= x; + + box = new Parallelogram(corner, tan1, e - corner - tan1); +#if DEBUGCURVES + if (!box.Contains(seg[end])) + { + + throw new InvalidOperationException();//"the box does not contain the end of the segment"); + } +#endif + + return true; + } + + static bool WithinEpsilon(Ellipse seg, double start, double end, double eps){ + int n = 3; //hack !!!! but maybe can be proven for Bezier curves and other regular curves + double d = (end - start)/n; + Point s = seg[start]; + Point e = seg[end]; + + double d0 = ParallelogramNodeOverICurve.DistToSegm(seg[start + d], s, e); + if(d0 > eps) + return false; + + double d1 = ParallelogramNodeOverICurve.DistToSegm(seg[start + d*(n - 1)], s, e); + //double d1d1 = seg.d1(start) * seg.d1(end); + + return d1 <= eps; // && d1d1 > 0; + } + + static ParallelogramNodeOverICurve CreateParallelogramNodeForCurveSeg(Ellipse seg) { + return CreateParallelogramNodeForCurveSeg(seg.ParStart, seg.ParEnd, seg, + ParallelogramNodeOverICurve.DefaultLeafBoxesOffset); + } + + double parStart; + + /// + /// the start of the parameter domain + /// + public double ParStart{ + get { return parStart; } + set { parStart = value; } + } + + double parEnd; + + /// + /// the end of the parameter domain + /// + public double ParEnd{ + get { return parEnd; } + set { parEnd = value; } + } + + /// + /// The point on the ellipse corresponding to the parameter t is calculated by + /// the formula center + cos(t)*axis0 + sin(t) * axis1. + /// To get an ellipse rotating clockwise use, for example, + /// axis0=(-1,0) and axis1=(0,1) + /// start angle in radians + /// end angle in radians + /// x radius + /// y radius + /// the ellipse center + /// + public Ellipse(double parStart, double parEnd, Point axis0, Point axis1, Point center){ + Debug.Assert(parStart<=parEnd); + ParStart = parStart; + ParEnd = parEnd; + AxisA = axis0; + AxisB = axis1; + this.center = center; + SetBoundingBox(); + } + + void SetBoundingBox() { + if (ApproximateComparer.Close(ParStart, 0) && ApproximateComparer.Close(ParEnd, Math.PI * 2)) + box = FullBox(); + else { + //the idea is that the box of an arc staying in one quadrant is just the box of the start and the end point of the arc + box = new Rectangle(Start, End); + //now Start and End are in the box, we need just add all k*P/2 that are in between + double t; + for (int i = (int)Math.Ceiling(ParStart / (Math.PI / 2)); (t = i * Math.PI / 2) < ParEnd; i++) + if (t > parStart) + box.Add(this[t]); + + } + } + + + /// + /// The point on the ellipse corresponding to the parameter t is calculated by + /// the formula center + cos(t)*axis0 + sin(t) * axis1. + /// To get an ellipse rotating clockwise use, for example, + /// axis0=(-1,0) and axis1=(0,1) + /// + /// start angle in radians + /// end angle in radians + /// the x axis + /// the y axis + /// x coordinate of the center + /// y coordinate of the center + public Ellipse(double parStart, double parEnd, Point axis0, Point axis1, double centerX, double centerY) + : this(parStart, parEnd, axis0, axis1, new Point(centerX, centerY)){ + } + + /// + /// Construct a full ellipse by two axes + /// + /// an axis + /// an axis + /// + public Ellipse(Point axis0, Point axis1, Point center) + : this(0, Math.PI*2, axis0, axis1, center){ + } + + + /// + /// Constructs a full ellipse with axes aligned to X and Y directions + /// + /// the length of the X axis + /// the length of the Y axis + /// + public Ellipse(double axisA, double axisB, Point center) + : this(0, Math.PI*2, new Point(axisA, 0), new Point(0, axisB), center){ + } + + /// + /// Moves the ellipse to the delta vector + /// + public void Translate(Point delta) + { + this.center += delta; + this.box = new Rectangle(center + bAxis + aAxis, center - bAxis - aAxis); + parallelogramNodeOverICurve = null; + } + + /// + /// Scales the ellipse by x and by y + /// + /// + /// + /// the moved ellipse + public ICurve ScaleFromOrigin(double xScale, double yScale) + { + return new Ellipse(parStart, parEnd, aAxis * xScale, bAxis * yScale, Point.Scale(xScale, yScale, center)); + } + + /// + /// + /// + /// + /// + public double GetParameterAtLength(double length) { + //todo: slow version! + const double eps = 0.001; + + var l = ParStart; + var u = ParEnd; + var lenplus = length + eps; + var lenminsu = length - eps; + while (u - l > ApproximateComparer.DistanceEpsilon) { + var m = 0.5*(u + l); + var len = LengthPartial(ParStart, m); + if(len>lenplus) + u=m; + else if (len < lenminsu) + l = m; + else return m; + } + return (u + l)/2; + } + + /// + /// Transforms the ellipse + /// + /// + /// the transformed ellipse + public ICurve Transform(PlaneTransformation transformation){ + if(transformation != null){ + Point ap = transformation*aAxis - transformation.Offset; + Point bp = transformation*bAxis - transformation.Offset; + return new Ellipse(parStart, parEnd, ap, bp, transformation*center); + } + return this; + } + + /// + /// returns a parameter t such that the distance between curve[t] and targetPoint is minimal + /// and t belongs to the closed segment [low,high] + /// + /// the point to find the closest point + /// the upper bound of the parameter + /// the low bound of the parameter + /// + public double ClosestParameterWithinBounds(Point targetPoint, double low, double high) { + const int numberOfTestPoints = 8; + double t = (high - low) / (numberOfTestPoints + 1); + double closest = low; + double minDist = Double.MaxValue; + for (int i = 0; i <= numberOfTestPoints; i++) { + double par = low + i * t; + Point p = targetPoint - this[par]; + double d = p * p; + if (d < minDist) { + minDist = d; + closest = par; + } + } + if (closest == 0 && high == Math.PI*2) + low = -Math.PI; + double ret = ClosestPointOnCurve.ClosestPoint(this, targetPoint, closest, low, high); + if (ret < 0) + ret += 2 * Math.PI; + return ret; + } + + /// + /// return length of the curve segment [start,end] : not implemented + /// + /// + /// + /// + public double LengthPartial(double start, double end) { + return Curve.LengthWithInterpolationAndThreshold(Trim(start, end), Curve.LineSegmentThreshold/100); + } + + /// + /// Return the length of the ellipse curve: not implemented + /// + public double Length{ + get { + return Curve.LengthWithInterpolation(this); + } + } + + + /// + /// clones the curve. + /// + /// the cloned curve + public ICurve Clone(){ + return new Ellipse(parStart, parEnd, aAxis, bAxis, center); + } + + #region ICurve Members + + /// + /// returns a parameter t such that the distance between curve[t] and a is minimal + /// + /// + /// the parameter of the closest point + public double ClosestParameter(Point targetPoint){ + double savedParStart = 0; + const int numberOfTestPoints = 8; + double t = (ParEnd - ParStart)/(numberOfTestPoints + 1); + double closest = ParStart; + double minDist = Double.MaxValue; + for (int i = 0; i <= numberOfTestPoints; i++){ + double par = ParStart + i*t; + Point p = targetPoint - this[par]; + double d = p*p; + if(d < minDist){ + minDist = d; + closest = par; + } + } + bool parStartWasChanged = false; + if(closest == 0 && ParEnd == Math.PI*2){ + parStartWasChanged = true; + savedParStart = ParStart; + ParStart = -Math.PI; + } + double ret = ClosestPointOnCurve.ClosestPoint(this, targetPoint, closest, ParStart, ParEnd); + if(ret<0) + ret += 2*Math.PI; + if (parStartWasChanged) + ParStart = savedParStart; + return ret; + } + + /// + /// left derivative at t + /// + /// the parameter where the derivative is calculated + /// + public Point LeftDerivative(double t){ + return Derivative(t); + } + + /// + /// right derivative at t + /// + /// the parameter where the derivative is calculated + /// + public Point RightDerivative(double t){ + return Derivative(t); + } + + #endregion + + #region ICurve Members + + /// + /// + /// + /// + /// + public double Curvature(double t){ + throw new NotImplementedException(); + } + + /// + /// + /// + /// + /// + public double CurvatureDerivative(double t){ + throw new NotImplementedException(); + } + + + /// + /// + /// + /// + /// + public double CurvatureSecondDerivative(double t){ + throw new NotImplementedException(); + } + + #endregion + /// + /// returns true if the ellipse goes counterclockwise + /// + /// + public bool OrientedCounterclockwise(){ + return AxisA.X*AxisB.Y - AxisB.X*AxisA.Y > 0; + } + + + /// + ///returns the box of the ellipse that this ellipse is a part of + /// + /// + public Rectangle FullBox() { + var del=AxisA + AxisB; + return new Rectangle(center + del, center - del); + } + + /// + ///is it a proper circle? + /// + public bool IsArc() { + return AxisA.X == AxisB.Y && AxisA.Y == -AxisB.X; + } + + } +} \ No newline at end of file diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/Ellipse.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/Ellipse.cs.meta new file mode 100644 index 000000000..7ccfaebc2 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/Ellipse.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c5c6926f69ff27d44ac54fa374927c6f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ICurve.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ICurve.cs new file mode 100644 index 000000000..7e49cb7f6 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ICurve.cs @@ -0,0 +1,217 @@ +namespace Microsoft.Msagl.Core.Geometry.Curves { + /// + /// The interface for curves: instances of ICurve inside of GLEE + /// are BSpline,Curve,LineSeg, Ellipse,CubicBezierSeg and ArrowTipCurve. + /// + public interface ICurve { + /// + /// Returns the point on the curve corresponding to parameter t + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1043:UseIntegralOrStringArgumentForIndexers")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "t")] + Point this[double t] { get; } + /// + /// first derivative at t + /// + /// the parameter where the derivative is calculated + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "t")] + Point Derivative(double t); + /// + /// second derivative + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "t")] + Point SecondDerivative(double t); + /// + /// third derivative + /// + /// the parameter of the derivative + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "t")] + Point ThirdDerivative(double t); + + /// + /// A tree of ParallelogramNodes covering the curve. + /// This tree is used in curve intersections routines. + /// + /// + ParallelogramNodeOverICurve ParallelogramNodeOverICurve { get; } + + /// + /// XY bounding box of the curve + /// + Rectangle BoundingBox { get;} + + /// + /// the start of the parameter domain + /// + double ParStart { get;} + + /// + /// the end of the parameter domain + /// + double ParEnd { get;} + + /// + /// Returns the trim curve between start and end, without wrap + /// + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "End")] + ICurve Trim(double start, double end); + + /// + /// Returns the trim curve between start and end, with wrap, if supported by the implementing class. + /// + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "End")] + ICurve TrimWithWrap(double start, double end); + + /// + /// Moves the curve by the delta. + /// + void Translate(Point delta); + + /// + /// Returns the curved with all points scaled from the original by x and y + /// + /// + ICurve ScaleFromOrigin(double xScale, double yScale); + + /// + /// this[ParStart] + /// + Point Start { get;} + + /// + /// this[ParEnd] + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "End")] + Point End { get; } + + /// + /// this[Reverse[t]]=this[ParEnd+ParStart-t] + /// + /// + ICurve Reverse(); + + + + /// + /// Offsets the curve in the direction of dir + /// + /// + /// + /// + + + + ICurve OffsetCurve(double offset, Point dir); + + /// + /// return length of the curve segment [start,end] + /// + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "End")] + double LengthPartial(double start, double end); + + /// + /// Get the length of the curve + /// + double Length { get;} + + + /// + /// gets the parameter at a specific length from the start along the curve + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", + "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "t")] + double GetParameterAtLength(double length); + + /// + /// Return the transformed curve + /// + /// + /// the transformed curve + ICurve Transform(PlaneTransformation transformation); + + + /// + /// returns a parameter t such that the distance between curve[t] and targetPoint is minimal + /// and t belongs to the closed segment [low,high] + /// + /// the point to find the closest point + /// the upper bound of the parameter + /// the low bound of the parameter + /// + double ClosestParameterWithinBounds(Point targetPoint, double low, double high); + + /// + /// returns a parameter t such that the distance between curve[t] and a is minimal + /// + /// + /// + double ClosestParameter(Point targetPoint); +/// +/// clones the curve. +/// +/// the cloned curve + ICurve Clone(); + + + /// + /// The left derivative at t. + /// + /// the parameter where the derivative is calculated + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "t")] + Point LeftDerivative(double t); + + + /// + /// the right derivative at t + /// + /// the parameter where the derivative is calculated + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "t")] + Point RightDerivative(double t); + + + /// + /// the signed curvature of the segment at t + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "t")] + double Curvature(double t); + /// + /// the derivative of the curvature at t + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "t")] + double CurvatureDerivative(double t); + + + /// + /// the derivative of CurvatureDerivative + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "t")] + double CurvatureSecondDerivative(double t); + + + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ICurve.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ICurve.cs.meta new file mode 100644 index 000000000..62fa842d3 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ICurve.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 87007c1367b6da648814f9b9bd5f761c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/IFunction.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/IFunction.cs new file mode 100644 index 000000000..a126c4477 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/IFunction.cs @@ -0,0 +1,46 @@ + +namespace Microsoft.Msagl.Core.Geometry.Curves { + /// + /// + /// + public interface IFunction { + /// + /// Returns the falue on the curve corresponding to parameter t + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1043:UseIntegralOrStringArgumentForIndexers")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "t")] + double this[double t] { get; } + /// + /// first derivative at t + /// + /// the parameter where the derivative is calculated + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "t")] + double Derivative(double t); + /// + /// second derivative + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "t")] + double SecondDerivative(double t); + /// + /// third derivative + /// + /// the parameter of the derivative + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "t")] + double ThirdDerivative(double t); + + /// + /// the domain of the function is the [ParStart, ParEnd] segment + /// + double ParStart { get; } + /// + /// the domain of the function is the [ParStart, ParEnd] segment + /// + double ParEnd { get; } + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/IFunction.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/IFunction.cs.meta new file mode 100644 index 000000000..503b987bd --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/IFunction.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 65427237fd9e6174988919a03f663052 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/IntersectionInfo.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/IntersectionInfo.cs new file mode 100644 index 000000000..49580b92b --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/IntersectionInfo.cs @@ -0,0 +1,89 @@ +using System; +using System.Globalization; + +namespace Microsoft.Msagl.Core.Geometry.Curves { + /// + /// Contains the result of the intersection of two ICurves. + /// + public class IntersectionInfo { + + + /* The following conditions should hold: + * X=seg0[par0]=seg1[par1] + */ + + double par0, par1; + Point x; + + ICurve seg0, seg1; + /// + /// the parameter on the first curve + /// + public double Par0 { + get { return par0; } + set { par0 = value; } + } + /// + /// the parameter on the second curve + /// + public double Par1 { + get { return par1; } + set { par1 = value; } + } + + /// + /// the intersection point + /// + public Point IntersectionPoint { + get { return x; } + set { x = value; } + } + +/// +/// the segment of the first curve where the intersection point belongs +/// + public ICurve Segment0 { + get { return seg0; } + set { seg0=value; } + } + + /// + /// the segment of the second curve where the intersection point belongs + /// + public ICurve Segment1 { + get { return seg1; } + set { seg1 = value; } + } + + /// + /// the constructor + /// + /// + /// + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.String.Format(System.String,System.Object,System.Object,System.Object)")] + internal IntersectionInfo(double pr0, double pr1, Point x, ICurve s0, ICurve s1) { + par0 = pr0; + par1 = pr1; + this.x = x; + seg0 = s0; + seg1 = s1; +#if DETAILED_DEBUG + System.Diagnostics.Debug.Assert(ApproximateComparer.Close(x, s0[pr0], ApproximateComparer.IntersectionEpsilon*10), + string.Format("intersection not at curve[param]; x = {0}, s0[pr0] = {1}, diff = {2}", x, s0[pr0], x - s0[pr0])); + System.Diagnostics.Debug.Assert(ApproximateComparer.Close(x, s1[pr1], ApproximateComparer.IntersectionEpsilon*10), + string.Format("intersection not at curve[param]; x = {0}, s1[pr1] = {1}, diff = {2}", x, s1[pr1], x - s1[pr1])); +#endif + } + + /// + /// + /// + /// + public override string ToString() { + return String.Format(CultureInfo.InvariantCulture, "XX({0} {1} {2})", par0, par1, x); + } + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/IntersectionInfo.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/IntersectionInfo.cs.meta new file mode 100644 index 000000000..b96a3fbb7 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/IntersectionInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ab381fde1cfb1ba44abc25ef2f5d0888 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/LineSegment.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/LineSegment.cs new file mode 100644 index 000000000..818c76cce --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/LineSegment.cs @@ -0,0 +1,518 @@ +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.Msagl.Core.Geometry.Curves { + /// + /// Line segment + /// +#if TEST_MSAGL + [Serializable] +#endif + [DebuggerDisplay("[({a.X} {a.Y}),({b.X} {b.Y})]")] + public class LineSegment : ICurve { + + Point a;//the line goes from a to side1 + + /// + /// Offsets the curve in the direction of dir + /// + /// + /// + /// + public ICurve OffsetCurve(double offset, Point dir) { return null; } + /// + /// the line start point + /// + public Point Start { + get { return a; } + set { a = value; } + } + Point b; + /// + /// the line end point + /// + public Point End { + get { return b; } + set { b = value; } + } + + + /// + /// the start parameter + /// + public double ParStart { + get { return 0; } + } + + /// + /// the end parameter + /// + public double ParEnd { + get { return 1; } + } + + /// + /// Returns the trim curve + /// + /// + /// + /// + public ICurve Trim(double start, double end) { + + if (start > end) + throw new InvalidOperationException();//"wrong params in trimming"); + + Point p1 = start <= ParStart ? Start : (1 - start) * a + start * b; + Point p2 = end >= ParEnd ? End : (1 - end) * a + end * b; + if (ApproximateComparer.Close(start, end)) { + return null; + } + return new LineSegment(p1, p2); + } + + /// + /// Not Implemented: Returns the trimmed curve, wrapping around the end if start is greater than end. + /// + /// The starting parameter + /// The ending parameter + /// The trimmed curve + public ICurve TrimWithWrap(double start, double end) { + throw new NotImplementedException(); + } + + /// + /// A tree of ParallelogramNodes covering the curve. + /// This tree is used in curve intersections routines. + /// + /// + public ParallelogramNodeOverICurve ParallelogramNodeOverICurve { + get { + Point side = 0.5 * (b - a); + + return new ParallelogramLeaf(0, 1, new Parallelogram(a, side, side), this, 0); + } + } + + internal Point Normal { + get { + Point t = a - b; + t /= t.Length; + return new Point(-t.Y, t.X); + } + } + /// + /// construct a line segment + /// + /// + /// + public LineSegment(Point start, Point end) { +#if SHARPKIT + a = start.Clone(); + b = end.Clone(); +#else + a = start; + b = end; +#endif + } + + /// + /// constructs a line segment + /// + /// the first point + /// x-coordinate of the second point + /// y-coordinate of the second point + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "a"), SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "x"), SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "y")] + public LineSegment(Point a, double x, double y) : this(a, new Point(x, y)) { } + + /// + /// constructs a line segment + /// + /// x-coordinate of the first point + /// y-coordinate of the first point + /// x-coordinate of the second point + /// y-coordinate of the second point + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "y"), SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "x")] + public LineSegment(double x0, double y0, double x1, double y1) : this(new Point(x0, y0), new Point(x1, y1)) { } + + /// + /// Returns the point on the curve corresponding to parameter t + /// + /// + /// + public Point this[double t] { + get { return a + (b - a) * t; } + } + + /// + /// first derivative at t + /// + /// the parameter where the derivative is calculated + /// + public Point Derivative(double t) { + return b - a; + } + + /// + /// second derivative + /// + /// + /// + public Point SecondDerivative(double t) { + return new Point(0, 0); + } + + /// + /// third derivative + /// + /// the parameter of the derivative + /// + public Point ThirdDerivative(double t) { + return new Point(0, 0); + } + + /// + /// This is the rounded form; DebuggerDisplay shows the unrounded form. + /// + /// + public override string ToString() { + return "{" + a + "," + b + "}"; + } + + /// + /// this[Reverse[t]]=this[ParEnd+ParStart-t] + /// + /// + + public ICurve Reverse() { + return new LineSegment(b, a); + } + + /* + static internal IntersectionInfo Cross(LineSeg coeff, LineSeg side1){ + IntersectionInfo xx=CrossTwoLines(coeff.Start, coeff.End-coeff.Start,side1.Start, side1.End-side1.Start); + if (xx == null) + { + //parallel segs + Point adir=coeff.d1(0); + Point bdir=side1.d1(0); + + if (adir.Length > bdir.Length) + { + if (adir.Length > Curve.DistEps) + { + adir = adir.Normalize(); + if(Math.Abs((coeff-side1)*adir1){ + if (ApproximateComparer.Close(coeff.End, xx.X)) + { + xx.X = coeff.End; + xx.Par0 = 1; + } + else + return null; + } + else if(xx.Par0<0){ + if(ApproximateComparer.Close(coeff.Start,xx.X)){ + xx.X=coeff.Start; + xx.Par0=1; + } + else + return null; + } + + if (xx.Par1 > 1) + { + if (ApproximateComparer.Close(side1.End, xx.X)) + { + xx.X = coeff.End; + xx.Par1 = 1; + } + else + return null; + } + else if (xx.Par1 < 0) + { + if (ApproximateComparer.Close(side1.Start, xx.X)) + { + xx.X = coeff.Start; + xx.Par1 = 1; + } + else + return null; + } + + return xx; + } + * */ + + /// + /// Returns the curved moved by delta + /// + public void Translate(Point delta) + { + a += delta; + b += delta; + } + + /// + /// Scale (multiply) from zero by x and y + /// + /// + /// + /// scaled copy + public ICurve ScaleFromOrigin(double xScale, double yScale) + { + return new LineSegment(Point.Scale(xScale, yScale, a), Point.Scale(xScale, yScale, b)); + } + + /// + /// gets the parameter at a specific length from the start along the curve + /// + /// + /// + public double GetParameterAtLength(double length) { + var len = (b - a).Length; + if (len < ApproximateComparer.Tolerance) + return 0; + var t = length/len; + return t > 1 ? 1 : (t < 0 ? 0 : t); + } + + /// + /// Return the transformed curve + /// + /// + /// the transformed curve + public ICurve Transform(PlaneTransformation transformation) { + return new LineSegment(transformation * a, transformation * b); + } + + /// + /// returns a parameter t such that the distance between curve[t] and targetPoint is minimal + /// and t belongs to the closed segment [low,high] + /// + /// the point to find the closest point + /// the upper bound of the parameter + /// the low bound of the parameter + /// + public double ClosestParameterWithinBounds(Point targetPoint, double low, double high) { + var t = ClosestParameter(targetPoint); + if (t < low) + t = low; + if(t>high) + t=high; + return t; + } + + /// + /// return length of the curve segment [start,end] + /// + /// + /// + /// + public double LengthPartial(double start, double end) { + return (this[end] - this[start]).Length; + } + + /// + /// Get the length of the curve + /// + public double Length { + get { + return (a - b).Length; + } + } + /// + /// The bounding box of the line + /// + public Rectangle BoundingBox { + get { + return new Rectangle(Start, End); + } + } + + + + /// + /// clones the curve. + /// + /// the cloned curve + + public ICurve Clone() { + return new LineSegment(this.a, this.b); + } + + /// + /// returns a parameter t such that the distance between curve[t] and a is minimal + /// + /// + /// + public double ClosestParameter(Point targetPoint) { + return Point.ClosestParameterOnLineSegment(targetPoint, Start, End); + } + /// + /// left derivative at t + /// + /// the parameter where the derivative is calculated + /// + public Point LeftDerivative(double t) { + return Derivative(t); + } + + /// + /// right derivative at t + /// + /// the parameter where the derivative is calculated + /// + public Point RightDerivative(double t) { + return Derivative(t); + } + /// + /// returns true if segments are not parallel and are intesecting + /// + /// + /// + /// + /// + /// + /// + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "x"), SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "d"), SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "c"), SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "b"), SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "a"), SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "4#")] + public static bool Intersect(Point a, Point b, Point c, Point d, out Point x) { + if (!Point.LineLineIntersection(a, b, c, d, out x)) + return false; + return XIsBetweenPoints(ref a, ref b, ref x) && XIsBetweenPoints(ref c, ref d, ref x); + } + + static bool XIsBetweenPoints(ref Point a, ref Point b, ref Point x) { + return (a - x) * (b - x) <= ApproximateComparer.DistanceEpsilon; + } + +#region ICurve Members + + /// + /// + /// + /// + /// + public double Curvature(double t) { + return 0; + } + /// + /// + /// + /// + /// + public double CurvatureDerivative(double t) { + return 0; + } + + + /// + /// + /// + /// + /// + public double CurvatureSecondDerivative(double t) { + throw new NotImplementedException(); + } + +#endregion + + /// + /// [a,b] and [c,d] are the segments. u and v are the corresponding closest point params + /// see http://www.geometrictools.com/Documentation/DistanceLine3Line3.pdf + /// + /// + /// + /// + /// + /// + /// + internal static double MinDistBetweenLineSegments(Point a, Point b, Point c, Point d, out double parab, out double parcd) { + Point u = b - a; + Point v = d - c; + Point w = a - c; + + double D = Point.CrossProduct(u, v); + + double uu = u * u; // always >= 0 + double uv = u * v; + double vv = v * v; // always >= 0 + double uw = u * w; + double vw = v * w; + double sN, tN; + double absD = Math.Abs(D); + double sD = absD, tD = absD; + + // compute the line parameters of the two closest points + if (absD < ApproximateComparer.Tolerance) { // the lines are almost parallel + sN = 0.0; // force using point a on segment [a..b] + sD = 1.0; // to prevent possible division by 0.0 later + tN = vw; + tD = vv; + } + else { // get the closest points on the infinite lines + sN = Point.CrossProduct(v, w); + tN = Point.CrossProduct(u, w); + if (D < 0) { + sN = -sN; + tN = -tN; + } + + if (sN < 0.0) { // parab < 0 => the s=0 edge is visible + sN = 0.0; + tN = vw; + tD = vv; + } + else if (sN > sD) { // parab > 1 => the s=1 edge is visible + sN = sD = 1; + tN = vw + uv; + tD = vv; + } + } + + if (tN < 0.0) { // tc < 0 => the t=0 edge is visible + tN = 0.0; + // recompute parab for this edge + if (-uw < 0.0) + sN = 0.0; + else if (-uw > uu) + sN = sD; + else { + sN = -uw; + sD = uu; + } + } + else if (tN > tD) { // tc > 1 => the t=1 edge is visible + tN = tD = 1; + // recompute parab for this edge + if ((-uw + uv) < 0.0) + sN = 0; + else if ((-uw + uv) > uu) + sN = sD; + else { + sN = (-uw + uv); + sD = uu; + } + } + + // finally do the division to get parameters + parab = (Math.Abs(sN) < ApproximateComparer.Tolerance ? 0.0 : sN / sD); + parcd = (Math.Abs(tN) < ApproximateComparer.Tolerance ? 0.0 : tN / tD); + + // get the difference of the two closest points + Point dP = w + (parab * u) - (parcd * v); + + return dP.Length; // return the closest distance + } + } +} + diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/LineSegment.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/LineSegment.cs.meta new file mode 100644 index 000000000..1bcd7689e --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/LineSegment.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 58ccf7a3701aeea4d9dbc54d63e11731 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/LinearSystem2.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/LinearSystem2.cs new file mode 100644 index 000000000..eeb5decff --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/LinearSystem2.cs @@ -0,0 +1,31 @@ +using System; + +namespace Microsoft.Msagl.Core.Geometry.Curves { + /// + /// solves a linear system of two equations with to unknown variables + /// + internal class LinearSystem2 { + LinearSystem2() { } + static double eps = 1.0E-8; + + internal static double Eps { + get { return LinearSystem2.eps; } + // set { LinearSystem2.eps = value; } + } + + internal static bool Solve(double a00, double a01, double b0, double a10, double a11, double b1, out double x, out double y) { + double d = a00 * a11 - a10 * a01; + + if (Math.Abs(d) < Eps) { + x = y = 0; //to avoid the compiler bug + return false; + } + + x = (b0 * a11 - b1 * a01) / d; + y = (a00 * b1 - a10 * b0) / d; + + return true; + + } + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/LinearSystem2.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/LinearSystem2.cs.meta new file mode 100644 index 000000000..d8ebdb40d --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/LinearSystem2.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b5c55970eec189a468d9c4b82fdc5a50 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/MinDistCurveCurve.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/MinDistCurveCurve.cs new file mode 100644 index 000000000..8fec1efc4 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/MinDistCurveCurve.cs @@ -0,0 +1,357 @@ +using System; + +namespace Microsoft.Msagl.Core.Geometry.Curves { + //For curves A(s) and B(t) with some guess for the parameters (s0, t0) we + //are trying to bring to (0,0) the vector(Fs,Ft). + //where F(s,t) = (A(s) - B(t))^2. To minimize F^2, + //you get the system of equations to solve for ds and dt: + //Fs + Fss*ds + Fst*dt = 0 + //Ft + Fst*ds + Ftt*dt = 0 + // + //Where F = F(si,ti), Fs and Ft are the first partials at si, ti, Fxx are the second partials, + // and s(i+1) = si+ds, t(i+1) = ti+dt. + //Of course you have to make sure that ds and dt do not take you out of your domain. + //This will converge if the curves have 2nd order continuity + //and we are starting parameters are reasonable. + //It is not a good method for situations that are not well behaved, but it is really simple. +/// +/// Implements the minimal distance between curves functionality +/// +#if TEST_MSAGL + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Dist")] + public +#else + internal +#endif + class MinDistCurveCurve { + + ICurve curveA; + ICurve curveB; + double aMin; + double aMax; + double bMin; double bMax; + double aGuess; double bGuess; + double aSolution; + internal double ASolution { + get { return aSolution; } + } + double bSolution; + internal double BSolution { + get { return bSolution; } + } + + Point aPoint; + internal Point APoint { get { return aPoint; } } + + Point bPoint; + internal Point BPoint { get { return bPoint; } } + bool status; + internal bool Status { get { return status; } } + double si; + double ti; + + Point a, b, a_b, ad, bd, add, bdd; + + void InitValues() { + a = curveA[si]; + b = curveB[ti]; + a_b = a - b; + ad = curveA.Derivative(si); + add = curveA.SecondDerivative(si); + bd = curveB.Derivative(ti); + bdd = curveB.SecondDerivative(ti); + } +/// +/// constructor +/// +/// first curve +/// second curve +/// the first curve minimal parameter + /// the first curve maximal parameter + /// the second curve minimal parameter + /// the first curve maximal parameter +/// +/// + public MinDistCurveCurve(ICurve curveAPar, + ICurve curveBPar, + double lowBound0, + double upperBound0, + double lowBound1, + double upperBound1, + double guess0, + double guess1) { + this.curveA = curveAPar; + this.curveB = curveBPar; + this.aMin = lowBound0; + this.bMin = lowBound1; + this.aMax = upperBound0; + this.bMax = upperBound1; + this.aGuess = guess0; + this.bGuess = guess1; + this.si = guess0; + this.ti = guess1; + } + + + //we ignore the mulitplier 2 here fore efficiency reasons + double Fs { + get { + return /*2**/a_b * ad; + } + } + + double Fss { + get { + return /*2**/(a_b * add + ad * ad); + } + } + + double Fst //equals to Fts + { + get { + return - /*2**/bd * ad; + } + } + + double Ftt { + get { + return /*2**/(-a_b * bdd + bd * bd); + } + } + + + double Ft { + get { + return -/*2**/a_b * bd; + } + } + + + + /// + /// xy - the first row + /// uw - the second row + /// + /// + /// + /// + /// + internal static double Delta(double x, double y, double u, double w) { + return x * w - u * y; + } + //Fs + Fss*ds + Fst*dt = 0 + //Ft + Fst*ds + Ftt*dt = 0 + internal void Solve() { + + int numberOfBoundaryCrossings = 0; + const int maxNumberOfBoundaryCrossings = 10; + int numberOfTotalReps = 0; + const int maxNumberOfTotalReps = 100; + + bool abort = false; + + InitValues(); + + if (curveA is LineSegment && curveB is LineSegment ) { + Point bd1 = curveB.Derivative(0); + bd1 /= bd1.Length; + Point an = (curveA as LineSegment).Normal; + + double del = Math.Abs(an * bd1); + + + if (Math.Abs(del) < ApproximateComparer.DistanceEpsilon || Delta(Fss, Fst, Fst, Ftt) < ApproximateComparer.Tolerance) { + status = true; + ParallelLineSegLineSegMinDist(); + return; + } + } + + double ds, dt; + do { + + //hopefully it will be inlined by the compiler + double delta = Delta(Fss, Fst, Fst, Ftt); + if (Math.Abs(delta) < ApproximateComparer.Tolerance) { + status = false; + abort = true; + break; + } + + ds = Delta(-Fs, Fst, -Ft, Ftt) / delta; + dt = Delta(Fss, -Fs, Fst, -Ft) / delta; + + + double nsi = si + ds; + double nti = ti + dt; + + bool bc; + + if (nsi > aMax + ApproximateComparer.DistanceEpsilon || nsi < aMin - ApproximateComparer.DistanceEpsilon || nti > bMax + ApproximateComparer.DistanceEpsilon || nti < bMin - ApproximateComparer.DistanceEpsilon) { + numberOfBoundaryCrossings++; + ChopDsDt(ref ds, ref dt); + si += ds; + ti += dt; + bc = true; + } else { + bc = false; + si = nsi; + ti = nti; + if (si > aMax) + si = aMax; + else if (si < aMin) + si = aMin; + + if (ti > bMax) + ti = bMax; + else if (ti < bMin) + ti = bMin; + } + + InitValues(); + + numberOfTotalReps++; + + abort = numberOfBoundaryCrossings >= maxNumberOfBoundaryCrossings || + numberOfTotalReps >= maxNumberOfTotalReps || (ds == 0 && dt == 0 && bc); + + } while ((Math.Abs(ds) >= ApproximateComparer.Tolerance || Math.Abs(dt) >= ApproximateComparer.Tolerance) && !abort); + + if (abort) { + //may be the initial values were just OK + Point t = curveA[aGuess] - curveB[bGuess]; + if (t * t < ApproximateComparer.DistanceEpsilon * ApproximateComparer.DistanceEpsilon) { + aSolution = aGuess; + bSolution = bGuess; + aPoint = curveA[aGuess]; + bPoint = curveB[bGuess]; + status = true; + return; + + } + } + + + aSolution = si; + bSolution = ti; + aPoint = a; + bPoint = b; + status = !abort; + + } + + + + void ChopDsDt(ref double ds, ref double dt) { + if (ds != 0 && dt != 0) { + double k1 = 1; //we are looking for a chopped vector of the form k(ds,dt) + + if (si + ds > aMax) //we have si+k*ds=aMax + k1 = (aMax - si) / ds; + else if (si + ds < aMin) + k1 = (aMin - si) / ds; + + double k2 = 1; + + if (ti + dt > bMax) //we need to have ti+k*dt=bMax or ti+k*dt=bMin + k2 = (bMax - ti) / dt; + else if (ti + dt < bMin) + k2 = (bMin - ti) / dt; + + double k = Math.Min(k1, k2); + ds *= k; + dt *= k; + + } else if (ds == 0) { + if (ti + dt > bMax) + dt = bMax - ti; + else if (ti + dt < bMin) + dt = bMin - ti; + } else { //dt==0) + if (si + ds > aMax) + ds = aMax - si; + else if (si + ds < aMin) + ds = aMin - si; + } + } + + void ParallelLineSegLineSegMinDist() { + LineSegment l0 = curveA as LineSegment; + LineSegment l1 = curveB as LineSegment; + + Point v0 = l0.Start; + Point v1 = l0.End; + Point v2 = l1.Start; + Point v3 = l1.End; + + Point d0 = v1 - v0; + + double nd0 = d0.Length; + + double r0 = 0, r1, r2, r3; + + if (nd0 > ApproximateComparer.DistanceEpsilon) { + //v0 becomes the zero point + d0 /= nd0; + r1 = d0 * (v1 - v0); + r2 = d0 * (v2 - v0); + r3 = d0 * (v3 - v0); + + bool swapped = false; + if (r2 > r3) { + swapped = true; + double t = r2; + r2 = r3; + r3 = t; + } + + if (r3 < r0) { + aSolution = 0; + bSolution = swapped ? 0 : 1; + } else if (r2 > r1) { + aSolution = 1; + bSolution = swapped ? 1 : 0; + + } else { + double r = Math.Min(r1, r3); + aSolution = r / (r1 - r0); + bSolution = (r - r2) / (r3 - r2); + if (swapped) + bSolution = 1 - bSolution; + + } + } else { + Point d1 = v3 - v2; + double nd1 = d1.Length; + if (nd1 > ApproximateComparer.DistanceEpsilon) { + //v2 becomes the zero point + d1 /= nd1; + r0 = 0; //v2 position + r1 = d1 * (v3 - v2);//v3 position + r2 = d1 * (v0 - v2);//v0 position - here v0 and v1 are indistinguishable + + + if (r2 < r0) { + bSolution = 0; + aSolution = 1; + } else if (r2 > r1) { + bSolution = 1; + aSolution = 0; + + } else { + double r = Math.Min(r1, r2); + bSolution = r / (r1 - r0); + aSolution = 0; + } + + } else { + aSolution = 0; + bSolution = 0; + } + } + aPoint = curveA[aSolution]; + bPoint = curveB[bSolution]; + } + } + +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/MinDistCurveCurve.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/MinDistCurveCurve.cs.meta new file mode 100644 index 000000000..2aebbafd4 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/MinDistCurveCurve.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 769a03cab788c094b9cd2195e07c0e8c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/Parallelogram.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/Parallelogram.cs new file mode 100644 index 000000000..642ba080c --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/Parallelogram.cs @@ -0,0 +1,461 @@ +// #region Using directives + +using System; +using System.Collections.Generic; +using System.Globalization; +//#endregion + +namespace Microsoft.Msagl.Core.Geometry.Curves { + + /// + /// Parallelogram vertex type. + /// The clockwise order of vertices is Corner, A, OtherCorner,B + /// + + + public enum VertexId { + /// + /// the basic corner vertex + /// + Corner = 0, + /// + /// a vertex adjacent to the basic corner + /// + VertexA, + /// + /// the corner opposite to the basic corner + /// + OtherCorner, + /// + /// another vertex adjacent to the basic corner + /// + VertexB + } + + /// + /// It is a parallelogram with the vertices corner, corner+coeff,corner+coeff+side1 and corner+side1. + /// Parallelograms are used by GLEE in curve intersections routines. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes") +#if TEST_MSAGL + , Serializable +#endif + ] + //parallelograms are not stored in dictionaries + public struct Parallelogram { + + bool isSeg; + + Point corner; + Point a;//a side exiting from the corner + Point b;//another side exiting from the corner + Point otherCorner; + Point aPlusCorner; + Point bPlusCorner; + +#if SHARPKIT //https://code.google.com/p/sharpkit/issues/detail?id=371 + // SharpKit/Colin: this to support the lack of int casting to enums + private IEnumerable AllVertices + { + get + { + yield return corner; + yield return aPlusCorner; + yield return otherCorner; + yield return bPlusCorner; + } + } + + private IEnumerable OtherVertices + { + get + { + yield return aPlusCorner; + yield return otherCorner; + yield return bPlusCorner; + } + } +#endif + + internal Point Corner { + get { return corner; } + } + + internal Point OtherCorner { + get { return otherCorner; } + } + + Point aRot;//a rotated on 90 degrees towards side1 + Point bRot; //side1 rotated on 90 degrees towards coeff + + double abRot; //the scalar product coeff*bRot + double baRot; //the scalar product side1*aRot; +/// +/// to string +/// +/// + override public string ToString() { + return String.Format(CultureInfo.InvariantCulture, "({0},{1},{2},{3})", Vertex(0), Vertex(VertexId.VertexA), + Vertex(VertexId.OtherCorner), Vertex(VertexId.VertexB)); + } + + /// + /// Constructs the parallelogram by the corner and two sides. + /// + /// the corner + /// a side + /// another side + public Parallelogram(Point corner, Point sideA, Point sideB) { + + this.corner = corner; + this.a = sideA; + this.b = sideB; + + this.aRot = new Point(-sideA.Y, sideA.X); + if (aRot.Length > 0.5) + aRot = aRot.Normalize(); + + this.bRot = new Point(-sideB.Y, sideB.X); + if (bRot.Length > 0.5) + bRot = bRot.Normalize(); + + abRot = sideA * bRot; + + baRot = sideB * aRot; + + + if (abRot < 0) { + abRot = -abRot; + bRot = -bRot; + } + + if (baRot < 0) { + baRot = -baRot; + aRot = -aRot; + } + + + isSeg = (sideA - sideB).Length < ApproximateComparer.DistanceEpsilon; + + aPlusCorner = sideA + corner; + otherCorner = sideB +aPlusCorner; + bPlusCorner = sideB + corner; + } + /// + /// Return true if the parallelogram contains the point + /// + /// + /// + public bool Contains(Point point) { + Point g = point - corner; + double e = ApproximateComparer.DistanceEpsilon; + + double gbRot = g * bRot; + if (gbRot > abRot + e || gbRot < -e) + return false; + + double gaRot = g * aRot; + return gaRot <= baRot + e && gaRot >= -e; + } + + +/// +/// return the area of the parallelogram +/// + public double Area { get { return Math.Abs(a.X * b.Y - a.Y * b.X); } } + + /// + /// Return the correspoingin vertex of the parallelogram + /// + /// vertex kind + /// vertex value + public Point Vertex(VertexId vertexPar) { + switch (vertexPar) { + case VertexId.Corner: return corner; + case VertexId.VertexA: return aPlusCorner; + case VertexId.OtherCorner: return OtherCorner; + case VertexId.VertexB: return bPlusCorner; + default: + throw new InvalidOperationException(); + } + } + +/// +/// returns true if parallelograms intersect +/// + /// + /// +/// + static public bool Intersect(Parallelogram parallelogram0, Parallelogram parallelogram1) { + //my hunch is that p0 and p1 do not intersect if and only if + //they can be separated with one of the parallelogram sides in case when at least one of them + + + bool ret = !(separByA(ref parallelogram0, ref parallelogram1) || separByA(ref parallelogram1, ref parallelogram0) || + separByB(ref parallelogram0, ref parallelogram1) || separByB(ref parallelogram1, ref parallelogram0)); + + if (ret == false) + return false; + + if (!(parallelogram0.isSeg && parallelogram1.isSeg)) + return true; + + + if (!Point.ParallelWithinEpsilon(parallelogram0.OtherCorner - parallelogram0.Corner, + parallelogram1.OtherCorner - parallelogram1.Corner, 1.0E-5)) + return true; + + //here we know that the segs are parallel + return ParallelSegsIntersect(ref parallelogram1, ref parallelogram0); + + } + + + static bool ParallelSegsIntersect(ref Parallelogram p0, ref Parallelogram p1) { + + Point v0 = p0.Corner; + Point v1 = p0.OtherCorner; + + Point v2 = p1.Corner; + Point v3 = p1.OtherCorner; + + + Point d = v1 - v0; + + //let us imagine that v0 is at zero + + double r0 = 0; // position of v0 + + //offset of v1 + double r1 = d * d; + + //offset of v2 + double r2 = (v2 - v0) * d; + + //offset of v3 + double r3 = (v3 - v0) * d; + + // we need to check if [r0,r1] intersects [r2,r3] + + if (r2 > r3) { + double t = r2; + r2 = r3; + r3 = t; + } + + return !(r3 < r0 - ApproximateComparer.DistanceEpsilon || r2 > r1 + ApproximateComparer.DistanceEpsilon); + + } + + static double mult(ref Point a, ref Point b) { + return a.X * b.X + a.Y * b.Y; + } + + static bool separByA(ref Parallelogram p0, ref Parallelogram p1) { + + double eps = ApproximateComparer.DistanceEpsilon; + Point t = new Point(p1.corner.X - p0.corner.X, p1.corner.Y - p0.corner.Y); + double p1a = mult(ref t , ref p0.aRot); + + if (p1a > p0.baRot + eps) { + t.X = p1.aPlusCorner.X - p0.corner.X; + t.Y = p1.aPlusCorner.Y - p0.corner.Y; + if (mult(ref t, ref p0.aRot) <= p0.baRot + eps) + return false; + + t.X = p1.bPlusCorner.X - p0.corner.X; + t.Y = p1.bPlusCorner.Y - p0.corner.Y; + if (mult(ref t, ref p0.aRot) <= p0.baRot + eps) + return false; + + t.X = p1.otherCorner.X - p0.corner.X; + t.Y = p1.otherCorner.Y - p0.corner.Y; + if (mult(ref t, ref p0.aRot) <= p0.baRot + eps) + return false; + + return true; + } else if (p1a < -eps) { + t.X = p1.aPlusCorner.X - p0.corner.X; + t.Y = p1.aPlusCorner.Y - p0.corner.Y; + if (mult(ref t, ref p0.aRot) >= - eps) + return false; + + t.X = p1.bPlusCorner.X - p0.corner.X; + t.Y = p1.bPlusCorner.Y - p0.corner.Y; + if (mult(ref t, ref p0.aRot) >= -eps) + return false; + + t.X = p1.otherCorner.X - p0.corner.X; + t.Y = p1.otherCorner.Y - p0.corner.Y; + if (mult(ref t, ref p0.aRot) >= -eps) + return false; + + return true; + } + + return false;/* + double eps = Curve.DistEps; + + double p1a = (p1.Corner - p0.Corner) * p0.aRot; + + if (p1a > p0.baRot + eps) { + for (int i = 1; i < 4; i++) { + if ((p1.Vertex((Parallelogram.VertexId)i) - p0.corner) * p0.aRot <= p0.baRot + eps) + return false; + } + return true; + } else if (p1a < -eps) { + for (int i = 1; i < 4; i++) { + + double delta = (p1.Vertex((Parallelogram.VertexId)i) - p0.corner) * p0.aRot; + + if (delta >= -eps) { + return false; + } + } + return true; + } + + return false;*/ + } + + + static bool separByB(ref Parallelogram p0, ref Parallelogram p1) { + double eps = ApproximateComparer.DistanceEpsilon; + double p1a = (p1.Vertex(0) - p0.corner) * p0.bRot; + + if (p1a > p0.abRot + eps) { +#if SHARPKIT //https://code.google.com/p/sharpkit/issues/detail?id=371 + //SharpKit/Colin - can't cast ints to enum + foreach (var i in p1.OtherVertices) + if ((i - p0.corner)*p0.bRot <= p0.abRot + eps) + return false; +#else + for (int i = 1; i < 4; i++) { + if ((p1.Vertex((VertexId)i) - p0.corner) * p0.bRot <= p0.abRot + eps) + return false; + } +#endif + + return true; + } else if (p1a < -eps) { +#if SHARPKIT //https://code.google.com/p/sharpkit/issues/detail?id=371 + //SharpKit/Colin - can't cast ints to enum + foreach (var i in p1.OtherVertices) + if ((i - p0.corner) * p0.bRot >= -eps) + return false; +#else + for (int i = 1; i < 4; i++) { + if ((p1.Vertex((VertexId)i) - p0.corner) * p0.bRot >= -eps) + return false; + } +#endif + return true; + } + return false; + } + /// + /// create a Parallelogram over a group + /// + /// group of boxes + /// xy box + internal static Parallelogram GetParallelogramOfAGroup(List boxes) { + + double minX = 0, maxX = 0, minY = 0, maxY = 0;//have to int - getting compiler error - bug + bool firstTime = true; + foreach (Parallelogram b in boxes) { +#if SHARPKIT //https://code.google.com/p/sharpkit/issues/detail?id=371 + // SharpKit/Colin: can't cast ints to enums + foreach (Point v in b.AllVertices) { +#else + for (int i = 0; i < 4; i++) { + Point v = b.Vertex((VertexId)i); +#endif + double x = v.X; + double y = v.Y; + if (firstTime) { + firstTime = false; + minX = maxX = x; + minY = maxY = y; + } else { + if (x < minX) { + minX = x; + } else if (x > maxX) { + maxX = x; + } + if (y < minY) { + minY = y; + } else if (y > maxY) { + maxY = y; + } + } + } + + } + return new Parallelogram(new Point(minX, minY), new Point(0, maxY - minY), new Point(maxX - minX, 0)); + } + + internal Parallelogram(Parallelogram box0, Parallelogram box1) { + Point v = box0.Corner; + double minX, maxX, minY, maxY; + minX = maxX = v.X; + minY = maxY = v.Y; + + + PumpMinMax(ref minX, ref maxX, ref minY, ref maxY, ref box0.aPlusCorner); + PumpMinMax(ref minX, ref maxX, ref minY, ref maxY, ref box0.otherCorner); + PumpMinMax(ref minX, ref maxX, ref minY, ref maxY, ref box0.bPlusCorner); + + PumpMinMax(ref minX, ref maxX, ref minY, ref maxY, ref box1.corner); + PumpMinMax(ref minX, ref maxX, ref minY, ref maxY, ref box1.aPlusCorner); + PumpMinMax(ref minX, ref maxX, ref minY, ref maxY, ref box1.otherCorner); + PumpMinMax(ref minX, ref maxX, ref minY, ref maxY, ref box1.bPlusCorner); + + this.corner = new Point(minX, minY); + this.a = new Point(0, maxY - minY); + this.b = new Point(maxX - minX, 0); + + aPlusCorner = a + corner; + otherCorner = b + aPlusCorner; + bPlusCorner = b + corner; + + this.aRot = new Point(-this.a.Y, this.a.X); + if (aRot.Length > 0.5) + aRot = aRot.Normalize(); + + this.bRot = new Point(-this.b.Y, this.b.X); + if (bRot.Length > 0.5) + bRot = bRot.Normalize(); + + abRot = this.a * bRot; + baRot = this.b * aRot; + + + if (abRot < 0) { + abRot = -abRot; + bRot = -bRot; + } + + if (baRot < 0) { + baRot = -baRot; + aRot = -aRot; + } + + isSeg = (this.a - this.b).Length < ApproximateComparer.DistanceEpsilon; + } + + static void PumpMinMax(ref double minX, ref double maxX, ref double minY, ref double maxY, + ref Point p) { + if (p.X < minX) { + minX = p.X; + } else if (p.X > maxX) { + maxX = p.X; + } + if (p.Y < minY) { + minY = p.Y; + } else if (p.Y > maxY) { + maxY = p.Y; + } + } + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/Parallelogram.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/Parallelogram.cs.meta new file mode 100644 index 000000000..2b532a52e --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/Parallelogram.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8f080cd400c38514bbbece3bca6c2bb7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ParallelogramBinaryTreeNode.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ParallelogramBinaryTreeNode.cs new file mode 100644 index 000000000..8921bc327 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ParallelogramBinaryTreeNode.cs @@ -0,0 +1,27 @@ +namespace Microsoft.Msagl.Core.Geometry.Curves { + /// + /// Keeps left and right sons of the node. Is used in curve intersections routines. + /// + internal class ParallelogramBinaryTreeNode:ParallelogramNode { + + ParallelogramNode leftSon; + public ParallelogramNode LeftSon { + get { + return leftSon; + } + set { + leftSon = value; + } + } + ParallelogramNode rightSon; + + public ParallelogramNode RightSon { + get { + return rightSon; + } + set { + rightSon = value; + } + } + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ParallelogramBinaryTreeNode.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ParallelogramBinaryTreeNode.cs.meta new file mode 100644 index 000000000..25b9e1484 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ParallelogramBinaryTreeNode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 32e489400e6548f48b2083d3fb5089b0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ParallelogramInternalTreeNode.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ParallelogramInternalTreeNode.cs new file mode 100644 index 000000000..123c161ee --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ParallelogramInternalTreeNode.cs @@ -0,0 +1,41 @@ +#region Using directives + +using System; +using System.Collections.Generic; + +#endregion + +namespace Microsoft.Msagl.Core.Geometry.Curves +{ + + /// + /// An internal node of ParallelogramNodeOverICurve hierarchy. + /// Is used in curve intersections routines. + /// +#if TEST_MSAGL + [Serializable] +#endif + internal class ParallelogramInternalTreeNode : ParallelogramNodeOverICurve + { + List children = new List(); + + internal ParallelogramInternalTreeNode(ICurve seg, double leafBoxesOffset):base(seg,leafBoxesOffset) + { + } + + /// + /// children of the node + /// + /// + internal List Children + { + get { return children; } + } + + internal void AddChild(ParallelogramNodeOverICurve node) + { + children.Add(node); + } + } + +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ParallelogramInternalTreeNode.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ParallelogramInternalTreeNode.cs.meta new file mode 100644 index 000000000..93bb13e53 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ParallelogramInternalTreeNode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3aa68f74e4ef5794f93ff1829db71dc0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ParallelogramLeaf.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ParallelogramLeaf.cs new file mode 100644 index 000000000..39386df51 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ParallelogramLeaf.cs @@ -0,0 +1,54 @@ +using System; + +namespace Microsoft.Msagl.Core.Geometry.Curves { + + /// + /// A leaf of the ParallelogramNodeOverICurve hierarchy. + /// Is used in curve intersectons routine. + /// +#if TEST_MSAGL + [Serializable] +#endif + internal class ParallelogramLeaf : ParallelogramNodeOverICurve { + double low; + + internal double Low { + get { + return low; + } + set { + low = value; + } + } + double high; + + internal double High { + get { + return high; + } + set { + high = value; + } + } + + internal ParallelogramLeaf(double low, double high, Parallelogram box, ICurve seg, double leafBoxesOffset) + : base(seg, leafBoxesOffset) { + this.low = low; + this.high = high; + this.Parallelogram = box; + } + + + + LineSegment chord; + + internal LineSegment Chord { + get { return chord; } + set { + chord = value; + if (!ApproximateComparer.Close(Seg[low], chord.Start)) + throw new InvalidOperationException(); + } + } + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ParallelogramLeaf.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ParallelogramLeaf.cs.meta new file mode 100644 index 000000000..89c77f11d --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ParallelogramLeaf.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4c3631e140b119c42a768dcdad7318a8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ParallelogramNode.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ParallelogramNode.cs new file mode 100644 index 000000000..04713fd24 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ParallelogramNode.cs @@ -0,0 +1,31 @@ +#region Using directives + + + +#endregion + +using System; + +namespace Microsoft.Msagl.Core.Geometry.Curves { + /// + /// Represents a node containing a parallelogram. + /// Is used in curve intersections routines. + /// +#if TEST_MSAGL + [Serializable] +#endif + abstract public class ParallelogramNode { + Parallelogram parallelogram; + /// + /// gets or sets the parallelogram of the node + /// + public Parallelogram Parallelogram { + get { + return parallelogram; + } + set { + parallelogram = value; + } + } + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ParallelogramNode.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ParallelogramNode.cs.meta new file mode 100644 index 000000000..bc81bb6f8 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ParallelogramNode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9258e849b5418a249ac14eb2cbe49c62 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ParallelogramNodeOverICurve.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ParallelogramNodeOverICurve.cs new file mode 100644 index 000000000..3038c2109 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ParallelogramNodeOverICurve.cs @@ -0,0 +1,218 @@ +// //#region Using directives + +using System; +using System.Collections.Generic; + +//#endregion + +namespace Microsoft.Msagl.Core.Geometry.Curves { + + /// + /// Serves to hold a Parallelogram and a ICurve, + /// and is used in curve intersections routines. + /// The node can be a top of the hierarchy if its sons are non-nulls. + /// The sons are either both nulls or both non-nulls + /// + +#if TEST_MSAGL + [Serializable] +#endif + abstract public class ParallelogramNodeOverICurve : ParallelogramNode { + + ICurve seg; + /// + /// The segment bounded by the parallelogram + /// + internal ICurve Seg { + get { + return seg; + } + set { + seg = value; + } + } + + double leafBoxesOffset = DefaultLeafBoxesOffset; + + static internal double DefaultLeafBoxesOffset = 0.5; + /// + /// The leafs of this node are as tight as the offset + /// + /// + internal double LeafBoxesOffset { + get { + return leafBoxesOffset; + } + } + + + + + internal ParallelogramNodeOverICurve(ICurve s, double leafBoxesOffset) { + seg = s; + this.leafBoxesOffset = leafBoxesOffset; + } + +/// +/// +/// +/// +/// + static public ParallelogramNodeOverICurve CreateParallelogramNodeForCurveSegment(ICurve segment) { + ValidateArg.IsNotNull(segment, "segment"); + return CreateParallelogramNodeForCurveSeg(segment.ParStart, segment.ParEnd, segment, DefaultLeafBoxesOffset); + } + + static bool WithinEpsilon(ICurve seg, double start, double end, double eps) { + if (seg is LineSegment) + return true; + + int n = 3; //hack !!!! but maybe can be proven for Bezier curves and other regular curves + double d = (end - start) / n; + Point s = seg[start]; + Point e = seg[end]; + + double d0 = DistToSegm(seg[start + d], s, e); + double d1 = DistToSegm(seg[start + d * (n-1)], s, e); + //double d1d1 = seg.d1(start) * seg.d1(end); + + return d0 < eps + && + d1 < eps;// && d1d1 > 0; + + } + + internal static double DistToSegm(Point p, Point s, Point e) { + + Point l = e - s; + if (l.Length < ApproximateComparer.IntersectionEpsilon) + return (p - (0.5f * (s + e))).Length; + Point perp = new Point(-l.Y, l.X); + perp = perp * (1.0f / perp.Length); + return Math.Abs((p - s) * perp); + + } + + + + /// + /// Creates a bounding parallelogram on a curve segment + /// We suppose here that the segment is convex or concave from start to end, + /// that is the region bounded by the straight segment seg[start], seg[end] and the curve seg is convex + /// + internal static bool CreateParallelogramOnSubSeg(double start, double end, ICurve seg, ref Parallelogram box, + Point startPoint, Point endPoint) { + + if (seg is CubicBezierSegment) + return CreateParallelogramOnSubSegOnBezierSeg(start, end, seg, ref box); + + Point tan1 = seg.Derivative(start); + + Point tan2 = seg.Derivative(end); + Point tan2Perp = Point.P(-tan2.Y, tan2.X); + + + + Point p = endPoint - startPoint; + + double numerator = p * tan2Perp; + double denumerator = (tan1 * tan2Perp); + double x;// = (p * tan2Perp) / (tan1 * tan2Perp); + if (Math.Abs(numerator) < ApproximateComparer.DistanceEpsilon) + x = 0; + else if (Math.Abs(denumerator) < ApproximateComparer.DistanceEpsilon) { + //it is degenerated; adjacent sides are parallel, but + //since p * tan2Perp is big it does not contain e + return false; + } else x = numerator / denumerator; + + tan1 *= x; + + box = new Parallelogram(startPoint, tan1, endPoint - startPoint - tan1); +#if DEBUGCURVES + if (!box.Contains(seg[end])) + { + + throw new InvalidOperationException();//"the box does not contain the end of the segment"); + } +#endif + + double delta = (end - start) / 64; + for (int i = 1; i < 64; i++) { + if (!box.Contains(seg[start + delta * i])) + return false; + } + return true; + + + } + delegate Point B(int i); + internal static bool CreateParallelogramOnSubSegOnBezierSeg(double start, double end, ICurve seg, ref Parallelogram box) { + + CubicBezierSegment trimSeg = seg.Trim(start, end) as CubicBezierSegment; + + + B b = trimSeg.B; + Point a = b(1) - b(0); + + box = new Parallelogram(b(0), a, b(3) - b(0)); + + if (box.Contains(b(2))) + return true; + + box = new Parallelogram(b(3), b(2) - b(3), b(0) - b(3)); + + if (box.Contains(b(1))) + return true; + + return false; + } + + + internal static ParallelogramNodeOverICurve CreateParallelogramNodeForCurveSeg(double start, double end, ICurve seg, double eps) { + + bool closedSeg = (start == seg.ParStart && end == seg.ParEnd && ApproximateComparer.Close(seg.Start, seg.End)); + if (closedSeg) + return CreateNodeWithSegmentSplit(start, end, seg, eps); + + Point s = seg[start]; + Point e = seg[end]; + Point w = e - s; + Point middle = seg[(start + end)/2]; + if ( + DistToSegm(middle, s, e) <= ApproximateComparer.IntersectionEpsilon + && w*w < Curve.LineSegmentThreshold*Curve.LineSegmentThreshold + && end - start < Curve.LineSegmentThreshold) { + var ls = new LineSegment(s, e); + var leaf = ls.ParallelogramNodeOverICurve as ParallelogramLeaf; + leaf.Low = start; + leaf.High = end; + leaf.Seg = seg; + leaf.Chord = ls; + return leaf; + } + + bool we = WithinEpsilon(seg, start, end, eps); + Parallelogram box = new Parallelogram(); + + if (we && CreateParallelogramOnSubSeg(start, end, seg, ref box, s, e)) + return new ParallelogramLeaf(start, end, box, seg, eps); + + return CreateNodeWithSegmentSplit(start, end, seg, eps); + + } + + static ParallelogramNodeOverICurve CreateNodeWithSegmentSplit(double start, double end, ICurve seg, double eps) { + ParallelogramInternalTreeNode pBNode = new ParallelogramInternalTreeNode(seg, eps); + pBNode.AddChild(CreateParallelogramNodeForCurveSeg(start, 0.5 * (start + end), seg, eps)); + pBNode.AddChild(CreateParallelogramNodeForCurveSeg(0.5 * (start + end), end, seg, eps)); + List boxes = new List(); + boxes.Add(pBNode.Children[0].Parallelogram); + boxes.Add(pBNode.Children[1].Parallelogram); + pBNode.Parallelogram = Parallelogram.GetParallelogramOfAGroup(boxes); + return pBNode; + + } + } + +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ParallelogramNodeOverICurve.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ParallelogramNodeOverICurve.cs.meta new file mode 100644 index 000000000..3d0c6f968 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/ParallelogramNodeOverICurve.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cf0f7ac8cff442e40bec6be58bdbecc0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/PenetrationDepth.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/PenetrationDepth.cs new file mode 100644 index 000000000..a01cba03e --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/PenetrationDepth.cs @@ -0,0 +1,202 @@ +using System; +using System.Collections.Generic; + +namespace Microsoft.Msagl.Core.Geometry.Curves { + /// + /// Works for convex curves by calculating a value which might be slightly greater than the exact penetration depth + /// It is presize on convex polygons. + /// + static public class PenetrationDepth { + + /// + /// Calculates a vector of minimal length by which to move the first curve to avoid intersections of interiors. + /// It is presize on polylines and will be slightly longer on curves. + /// + /// + /// + /// the shortest vector d such that d+curve0 does not intersect the interiou curve1 + static public Point PenetrationVector(ICurve curve0, ICurve curve1) { + return PenetrationDepthForPolylines(PolylineAroundClosedCurve(curve0), PolylineAroundClosedCurve(curve1)); + } + /// + /// returns a polyline around the curve + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Polyline")] + public static Polyline PolylineAroundClosedCurve(ICurve curve) { + Polyline poly = new Polyline(); + foreach (Point point in PointsOnAroundPolyline(curve)) + poly.AddPoint(point); + if (Point.GetTriangleOrientation(poly.StartPoint.Point, poly.StartPoint.Next.Point, poly.StartPoint.Next.Next.Point) == TriangleOrientation.Counterclockwise) + poly = (Polyline)poly.Reverse(); + poly.Closed = true; + return poly; + } + + + static IEnumerable PointsOnAroundPolyline(ICurve curve) { + bool firstSide = true; + CurveTangent prevTangent = null; + CurveTangent firstCurveTangent = null; + foreach (CurveTangent curveTangent in TangentsAroundCurve(curve)) { + if (firstSide) { + firstSide = false; + firstCurveTangent = prevTangent = curveTangent; + } else { + if (!TangentsAreParallel(prevTangent, curveTangent)) + yield return TangentIntersection(prevTangent, curveTangent); + prevTangent = curveTangent; + } + } + yield return TangentIntersection(firstCurveTangent, prevTangent); + + } + + + + static IEnumerable TangentsAroundCurve(ICurve iCurve) { + Curve c = iCurve as Curve; + if (c != null) { + foreach (ICurve seg in c.Segments) + foreach (CurveTangent ct in TangentsAroundCurve(seg)) + yield return ct; + } else { + LineSegment ls = iCurve as LineSegment; + if (ls != null) + yield return new CurveTangent(ls.Start, ls.Derivative(0)); + else { + Ellipse ellipse = iCurve as Ellipse; + if (ellipse != null) { + foreach (CurveTangent ct in TangentsOfEllipse(ellipse)) + yield return ct; + } else { + CubicBezierSegment bez = iCurve as CubicBezierSegment; + if (bez != null) + foreach (CurveTangent ct in TangentsOfBezier(bez)) + yield return ct; + } + } + + } + } + + static IEnumerable TangentsOfBezier(CubicBezierSegment bez) { + const int numOfTangents = 8; + double span = (bez.ParEnd - bez.ParStart) / numOfTangents; + for (int i = 0; i < numOfTangents; i++) + yield return TangentOnICurve(span / 2 + bez.ParStart + span * i, bez); + } + + static CurveTangent TangentOnICurve(double p, ICurve iCurve) { + return new CurveTangent(iCurve[p], iCurve.Derivative(p)); + } + + static IEnumerable TangentsOfEllipse(Ellipse ellipse) { + const double angle=Math.PI/3; +#if SHARPKIT //https://github.com/SharpKit/SharpKit/issues/4 integer rounding issue + int numOfTangents =((int) Math.Ceiling((ellipse.ParEnd - ellipse.ParStart) / angle)) + 1; +#else + int numOfTangents =(int) Math.Ceiling((ellipse.ParEnd - ellipse.ParStart) / angle) + 1; +#endif + double span = (ellipse.ParEnd - ellipse.ParStart) / numOfTangents; + for (int i = 0; i < numOfTangents; i++) + yield return TangentOnICurve(span / 2 + ellipse.ParStart + span * i, ellipse); + } + + static Point TangentIntersection(CurveTangent tangentA, CurveTangent tangentB) { + Point x; + Point.LineLineIntersection(tangentA.touchPoint, tangentA.touchPoint+tangentA.direction, tangentB.touchPoint, tangentB.touchPoint+tangentB.direction, out x); + return x; + } + + static bool TangentsAreParallel(CurveTangent a, CurveTangent b) { + return Math.Abs(a.direction.X * b.direction.Y - a.direction.Y * b.direction.X) < ApproximateComparer.DistanceEpsilon; + } + + /// + /// Calculates a vector of minimal length by which to move the first polyline to avoid intersection of interiors + /// + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Polylines")] + static public Point PenetrationDepthForPolylines(Polyline poly0, Polyline poly1) { + ValidateArg.IsNotNull(poly0, "poly0"); + ValidateArg.IsNotNull(poly1, "poly1"); + System.Diagnostics.Debug.Assert( + Point.GetTriangleOrientation(poly0[0], poly0[1], poly0[2]) == TriangleOrientation.Clockwise + && + Point.GetTriangleOrientation(poly1[0], poly1[1], poly1[2]) == TriangleOrientation.Clockwise); + + poly0 = FlipPolyline(poly0); + PolylinePoint p0 = GetMostLeftLow(poly0); + PolylinePoint p1 = GetMostLeftLow(poly1); + Polyline minkovski = Merge(p0, p1); + Point origin = new Point(); + PolylinePoint pp = minkovski.StartPoint; + double d = double.PositiveInfinity; + Point ret = new Point(); + do { + PolylinePoint pn = pp.Polyline.Next(pp); + if (Point.GetTriangleOrientation(origin, pp.Point, pn.Point) != TriangleOrientation.Clockwise) { + return new Point(); + } + double t; + double dist = Point.DistToLineSegment(origin, pp.Point, pn.Point, out t); + if (dist < d) { + d = dist; + ret = (1 - t) * pp.Point + t * pn.Point; + } + pp = pn; + } while (pp != minkovski.StartPoint); + return ret; + } + + static Polyline Merge(PolylinePoint p0, PolylinePoint p1) { + PolylinePoint s0 = p0; + PolylinePoint s1 = p1; + + Polyline ret = new Polyline(); + while (true) { + ret.AddPoint(p0.Point + p1.Point); + PickNextVertex(ref p0, ref p1); + if (p0 == s0 && p1 == s1) + break; + + } + ret.Closed = true; + return ret; + } + + static void PickNextVertex(ref PolylinePoint p0, ref PolylinePoint p1) { + Point d = p1.Point - p0.Point; + TriangleOrientation orient = Point.GetTriangleOrientation(p1.Point, p1.Polyline.Next(p1).Point, p0.Polyline.Next(p0).Point + d); + if (orient == TriangleOrientation.Counterclockwise) + p0 = p0.Polyline.Next(p0); + else + p1 = p1.Polyline.Next(p1); + } + + static Polyline FlipPolyline(Polyline poly) { + Polyline ret = new Polyline(); + for (PolylinePoint pp = poly.StartPoint; pp != null; pp = pp.Next) + ret.AddPoint( -pp.Point); + ret.Closed = true; + return ret; + } + + static PolylinePoint GetMostLeftLow(Polyline poly) { + PolylinePoint ret = poly.StartPoint; + for (PolylinePoint p = ret.Next; p != null; p = p.Next) + if (p.Point.X < ret.Point.X) + ret = p; + else if (p.Point.X == ret.Point.X) + if (p.Point.Y < ret.Point.Y) + ret = p; + + return ret; + } + + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/PenetrationDepth.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/PenetrationDepth.cs.meta new file mode 100644 index 000000000..e443d2a5d --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/PenetrationDepth.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 09658b7bab8ae554f9d5087e96f60526 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/PlaneTransformation.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/PlaneTransformation.cs new file mode 100644 index 000000000..577e6bc36 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/PlaneTransformation.cs @@ -0,0 +1,277 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.Msagl.Core.Geometry.Curves { + /// + /// 2 by 3 matrix of plane affine transformations + /// +#if TEST_MSAGL + [Serializable] +#endif + public class PlaneTransformation { + /// + /// hash function + /// + /// + public override int GetHashCode() { + return (elements != null ? elements.GetHashCode() : 0); + } + + /// + /// Determines whether the specified is equal to the current . + /// + /// + /// true if the specified is equal to the current ; otherwise, false. + /// + /// The object to compare with the current object. 2 + public override bool Equals(object obj) { + var t = obj as PlaneTransformation; + return t != null && t == this; + } + + readonly double[][] elements = new double[2][]; + + + /// + /// the matrix elements + /// + internal double[][] Elements { + get { return elements; } + } + /// + /// i,j th element + /// + /// + /// + /// + [SuppressMessage("Microsoft.Design", "CA1023:IndexersShouldNotBeMultidimensional")] + public double this[int rowIndex, int columnIndex] { + get { return Elements[rowIndex][columnIndex]; } + set { Elements[rowIndex][columnIndex] = value; } + } + + /// + /// constructs the an identity transformation + /// + public PlaneTransformation() { + elements[0] = new double[3]; + elements[1] = new double[3]; + this[0, 0] = this[1, 1] = 1; //create a unit transform + } + + /// + /// first row, second row + /// + /// 0,0 + /// 0,1 + /// 0,2 + /// 1,0 + /// 1,1 + /// 1,2 + [SuppressMessage("Microsoft.Design", "CA1025:ReplaceRepetitiveArgumentsWithParamsArray")] + public PlaneTransformation(double matrixElement00, double matrixElement01, double matrixElement02, + double matrixElement10, double matrixElement11, double matrixElement12) { + elements[0] = new double[3]; + elements[1] = new double[3]; + elements[0][0] = matrixElement00; elements[0][1] = matrixElement01; elements[0][2] = matrixElement02; + elements[1][0] = matrixElement10; elements[1][1] = matrixElement11; elements[1][2] = matrixElement12; + } + + /// + /// the matrix by point multiplication + /// + /// + /// + /// + static public Point operator *(PlaneTransformation transformation, Point point) { + return Multiply(transformation, point); + } + /// + /// Point by matrix multiplication + /// + /// + /// + /// + static public Point Multiply(PlaneTransformation transformation, Point point) { + if (transformation != null) + return new Point(transformation[0, 0] * point.X + transformation[0, 1] * point.Y + transformation[0, 2], transformation[1, 0] * point.X + transformation[1, 1] * point.Y + transformation[1, 2]); + return new Point(); + } + /// + /// matrix matrix multiplication + /// + /// ][ + /// + /// + static public PlaneTransformation operator *(PlaneTransformation transformation, PlaneTransformation transformation0) { + return Multiply(transformation, transformation0); + } + + /// + /// matrix matrix multiplication + /// + /// + /// + /// + static public PlaneTransformation Multiply(PlaneTransformation a, PlaneTransformation b) { + if (a != null && b != null) + return new PlaneTransformation( + a[0, 0] * b[0, 0] + a[0, 1] * b[1, 0], a[0, 0] * b[0, 1] + a[0, 1] * b[1, 1], a[0, 0] * b[0, 2] + a[0, 1] * b[1, 2] + a[0, 2], + a[1, 0] * b[0, 0] + a[1, 1] * b[1, 0], a[1, 0] * b[0, 1] + a[1, 1] * b[1, 1], a[1, 0] * b[0, 2] + a[1, 1] * b[1, 2] + a[1, 2]); + return null; + } + + /// + /// matrix divided by matrix + /// + /// + /// + /// + static public PlaneTransformation operator /(PlaneTransformation transform0, PlaneTransformation transform1) { + if (transform0 != null && transform1 != null) + return transform0 * transform1.Inverse; + return null; + + } + + /// + /// Divid matrix by a matrix + /// + /// + /// + /// + static public PlaneTransformation Divide(PlaneTransformation transformation0, PlaneTransformation transformation1) { + return transformation0 / transformation1; + } + +/// +/// returns the inversed matrix +/// + public PlaneTransformation Inverse { + get { + double det = Elements[0][0] * Elements[1][1] - Elements[1][0] * Elements[0][1]; +// if (ApproximateComparer.Close(det, 0)) +// throw new InvalidOperationException();//"trying to reverse a singular matrix"); + + double a00 = Elements[1][1] / det; + double a01 = -Elements[0][1] / det; + double a10 = -Elements[1][0] / det; + double a11 = Elements[0][0] / det; + double a02 = -a00 * Elements[0][2] - a01 * Elements[1][2]; + double a12 = -a10 * Elements[0][2] - a11 * Elements[1][2]; + + + return new PlaneTransformation(a00, a01, a02, a10, a11, a12); + } + } + + /// + /// unit matrix + /// + static public PlaneTransformation UnitTransformation { + get { return new PlaneTransformation(1, 0, 0, 0, 1, 0); } + } + + /// + /// Rotation matrix + /// + /// the angle of rotation + /// + static public PlaneTransformation Rotation(double angle) { + double cos = Math.Cos(angle); + double sin = Math.Sin(angle); + return new PlaneTransformation(cos, -sin, 0, sin, cos, 0); + } + + /// + /// checks if the matrix is an identity one + /// + public bool IsIdentity { + get { + return + ApproximateComparer.Close(Elements[0][0], 1) && + ApproximateComparer.Close(Elements[0][1], 0) && + ApproximateComparer.Close(Elements[0][2], 0) && + ApproximateComparer.Close(Elements[1][0], 0) && + ApproximateComparer.Close(Elements[1][1], 1) && + ApproximateComparer.Close(Elements[1][2], 0); + } + } + + /// + /// returns the point of the matrix offset + /// + public Point Offset { + get { + return new Point(Elements[0][2], Elements[1][2]); + } + } + + /// + /// + /// + /// + /// + /// + public static bool operator ==(PlaneTransformation a, PlaneTransformation b) { + if ((object)a == null &&(object)b == null) + return true; + if ((object)a == null) + return false; + if ((object)b == null) + return false; + for (int i = 0; i < 2; i++) + for (int j = 0; j < 3;j++ ) + if (a[i,j] != b[i,j]) + return false; + return true; + } + + /// + /// the inequality operator + /// + /// + /// + /// + public static bool operator !=(PlaneTransformation a, PlaneTransformation b) { + return !(a == b); + } + + /// + /// clones the transform + /// + /// + public PlaneTransformation Clone() { + return new PlaneTransformation(this[0, 0], this[0, 1], this[0, 2], this[1, 0], this[1, 1], this[1, 2]); + } + /// + /// + /// + /// + /// + /// + public static PlaneTransformation ScaleAroundCenterTransformation(double scale, Point center) { + /*var toOrigin = new PlaneTransformation(1, 0, -center.X, 0, 1, -center.Y); + var scaleTr = new PlaneTransformation(scale, 0, 0, + 0, scale, 0); + var toCenter = new PlaneTransformation(1, 0, center.X, 0, 1, center.Y); + var t = toCenter*scaleTr*toOrigin; + return t;*/ + var d = 1 - scale; + return new PlaneTransformation(scale, 0, d * center.X, 0, scale, d * center.Y); + } + + public static PlaneTransformation ScaleAroundCenterTransformation(double xScale, double yScale, Point center) + { + /*var toOrigin = new PlaneTransformation(1, 0, -center.X, 0, 1, -center.Y); + var scaleTr = new PlaneTransformation(scale, 0, 0, + 0, scale, 0); + var toCenter = new PlaneTransformation(1, 0, center.X, 0, 1, center.Y); + var t = toCenter*scaleTr*toOrigin; + return t;*/ + var dX = 1 - xScale; + var dY = 1 - yScale; + return new PlaneTransformation(xScale, 0, dX * center.X, 0, yScale, dY * center.Y); + } + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/PlaneTransformation.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/PlaneTransformation.cs.meta new file mode 100644 index 000000000..43e9002ba --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/PlaneTransformation.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f3f314f8f6451f1499062ec5b3cddec7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/PointLocation.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/PointLocation.cs new file mode 100644 index 000000000..968111207 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/PointLocation.cs @@ -0,0 +1,20 @@ +namespace Microsoft.Msagl.Core.Geometry.Curves { + + /// + /// Point positions relative to a closed curve enumeration + /// + public enum PointLocation { + /// + /// The point is outside of the curve + /// + Outside = 0, + /// + /// The point is on the curve boundary + /// + Boundary = 1, + /// + /// The point is inside of the curve + /// + Inside = 2 + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/PointLocation.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/PointLocation.cs.meta new file mode 100644 index 000000000..ed12c1e38 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/PointLocation.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c50f2fda6af34144c8c004c85d7d4f17 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/Polyline.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/Polyline.cs new file mode 100644 index 000000000..86e0e3961 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/Polyline.cs @@ -0,0 +1,853 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; + +namespace Microsoft.Msagl.Core.Geometry.Curves { + /// + /// class representing a polyline + /// + [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix"), + SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Polyline")] +#if TEST_MSAGL + [Serializable] +#endif + public class Polyline : ICurve, IEnumerable { + bool needToInit = true; + + /// + /// + /// + internal void RequireInit() { + needToInit = true; + } + + bool NeedToInit { + get { + return needToInit; + } + set { + needToInit = value; + } + } + + /// + /// + /// + public IEnumerable PolylinePoints { + get { + PolylinePoint p = StartPoint; + while (p != null) { + yield return p; + p = p.Next; + } + } + } +#if DEBUG && TEST_MSAGL + /// + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.String.Format(System.String,System.Object,System.Object,System.Object)")] + public override string ToString() { + return String.Format("{0},{1},count={2}", Start,End, Count); + } +#endif + internal Curve ToCurve() { + var c = new Curve(); + Curve.AddLineSegment(c, StartPoint.Point, StartPoint.Next.Point); + PolylinePoint p = StartPoint.Next; + while ((p = p.Next) != null) + Curve.ContinueWithLineSegment(c, p.Point); + if (Closed) + Curve.ContinueWithLineSegment(c, StartPoint.Point); + return c; + } + + + ParallelogramInternalTreeNode pBNode; + + PolylinePoint startPoint; + + /// + /// + /// + public PolylinePoint StartPoint { + get { return startPoint; } + set { + RequireInit(); + startPoint = value; + } + } + + PolylinePoint endPoint; + + /// + /// + /// + public PolylinePoint EndPoint { + get { return endPoint; } + set { + RequireInit(); + endPoint = value; + } + } + + int count; + + internal int Count { + get { + if (needToInit) + Init(); + return count; + } + } + + bool closed; + + /// + /// + /// + public bool Closed { + get { return closed; } + set { + if (closed != value) { + closed = value; + RequireInit(); + } + } + } + + #region ICurve Members + + /// + /// the value of the curve at the parameter + /// + /// + /// + public Point this[double t] { + get { + Point a, b; + if (NeedToInit) + Init(); + GetAdjustedParamAndStartEndPoints(ref t, out a, out b); + return (1 - t)*a + t*b; + } + } + + void GetAdjustedParamAndStartEndPoints(ref double t, out Point a, out Point b) { + Debug.Assert(t >= -ApproximateComparer.Tolerance); + Debug.Assert(StartPoint != null); + PolylinePoint s = StartPoint; + + while (s.Next != null) { + if (t <= 1) { + a = s.Point; + b = s.Next.Point; + return; + } + s = s.Next; + t -= 1; + } + + if (Closed) { + if (t <= 1) { + a = EndPoint.Point; + b = StartPoint.Point; + return; + } + } + + throw new InvalidOperationException(); //"out of the parameter domain"); + } + + + /// + /// first derivative at t + /// + /// the parameter where the derivative is calculated + /// + public Point Derivative(double t) { + Point a, b; + if (NeedToInit) + Init(); + GetAdjustedParamAndStartEndPoints(ref t, out a, out b); + return b - a; + } + + + /// + /// left derivative at t + /// + /// the parameter where the derivative is calculated + /// + public Point LeftDerivative(double t) { + if(NeedToInit) + Init(); + PolylinePoint pp = TryToGetPolylinePointCorrespondingToT(t); + if (pp == null) + return Derivative(t); + PolylinePoint prev = TryToGetPrevPointToPolylinePoint(pp); + if (prev != null) + return pp.Point - prev.Point; + return pp.Next.Point - pp.Point; + } + + /// + /// right derivative at t + /// + /// the parameter where the derivative is calculated + /// + public Point RightDerivative(double t) { + if(NeedToInit) + Init(); + var pp = TryToGetPolylinePointCorrespondingToT(t); + if (pp == null) + return Derivative(t); + PolylinePoint next = TryToGetNextPointToPolylinePoint(pp); + if (next != null) + return next.Point - pp.Point; + return pp.Point - pp.Prev.Point; + } + + + PolylinePoint TryToGetPolylinePointCorrespondingToT(double t) { + for (PolylinePoint p = StartPoint; p != null; p = p.Next, t--) + if (Math.Abs(t) < ApproximateComparer.Tolerance) + return p; + return null; + } + + PolylinePoint TryToGetPrevPointToPolylinePoint(PolylinePoint p) { + if (p != StartPoint) + return p.Prev; + + if (!Closed) + return null; + + return EndPoint; + } + + + PolylinePoint TryToGetNextPointToPolylinePoint(PolylinePoint p) { + if (p != EndPoint) + return p.Next; + + if (!Closed) + return null; + + return StartPoint; + } + + + /// + /// second derivative + /// + /// + /// + public Point SecondDerivative(double t) { + return new Point(); + } + + /// + /// third derivative + /// + /// the parameter of the derivative + /// + public Point ThirdDerivative(double t) + { + return new Point(); + } + + + /// + /// A tree of ParallelogramNodes covering the curve. + /// This tree is used in curve intersections routines. + /// + /// + public ParallelogramNodeOverICurve ParallelogramNodeOverICurve { + get { +#if PPC + lock(this){ +#endif + if(NeedToInit) + Init(); + return pBNode; + + + +#if PPC + } +#endif + + } + } + + static Parallelogram ParallelogramOfLineSeg(Point a, Point b) { + Point side = 0.5*(b - a); + return new Parallelogram(a, side, side); + } + + Rectangle boundingBox = Rectangle.CreateAnEmptyBox(); + + /// + /// bounding box of the polyline + /// + public Rectangle BoundingBox { + get { +#if PPC + lock(this){ +#endif + if (NeedToInit) + Init(); + + return boundingBox; +#if PPC + } +#endif + } + } + + void Init() { + + boundingBox = new Rectangle(StartPoint.Point); + count = 1; + foreach (Point p in this.Skip(1)) { + boundingBox.Add(p); + count++; + } + + CalculatePbNode(); + + NeedToInit = false; + } + + void CalculatePbNode() { + pBNode = new ParallelogramInternalTreeNode(this, ParallelogramNodeOverICurve.DefaultLeafBoxesOffset); + var parallelograms = new List(); + PolylinePoint pp = StartPoint; + int offset = 0; + while (pp.Next != null) { + Parallelogram parallelogram = ParallelogramOfLineSeg(pp.Point, pp.Next.Point); + parallelograms.Add(parallelogram); + pBNode.AddChild(new ParallelogramLeaf(offset, offset + 1, parallelogram, this, 0)); + pp = pp.Next; + offset++; + } + + if (Closed) { + Parallelogram parallelogram = ParallelogramOfLineSeg(EndPoint.Point, StartPoint.Point); + parallelograms.Add(parallelogram); + pBNode.AddChild(new ParallelogramLeaf(offset, offset + 1, parallelogram, this, 0)); + } + + pBNode.Parallelogram = Parallelogram.GetParallelogramOfAGroup(parallelograms); + } + + /// + /// the start of the parameter domain + /// + public double ParStart { + get { return 0; } + } + + /// + /// the end of the parameter domain + /// + public double ParEnd { + get { return Closed ? Count : Count - 1; } + } + + /// + /// Returns the trimmed polyline. Does not change this polyline. Reversed start and end if start is less than end. + /// + /// + /// + /// + public ICurve Trim(double start, double end) { + //this is a very lazy version! + Curve curve = ToCurve(); + curve = (Curve) curve.Trim(start, end); + + return PolylineFromCurve(curve); + } + + /// + /// Returns the trimmed polyline, wrapping around the end if start is greater than end. + /// + /// + /// + /// + public ICurve TrimWithWrap(double start, double end) { + Debug.Assert((start < end) || this.Closed, "Polyline must be closed to wrap"); + + //this is a very lazy version! + var curve = (Curve)this.ToCurve().TrimWithWrap(start, end); + return PolylineFromCurve(curve); + } + + internal static Polyline PolylineFromCurve(Curve curve) { + var ret = new Polyline(); + ret.AddPoint(curve.Start); + foreach (var ls in curve.Segments) + ret.AddPoint(ls.End); + ret.Closed = curve.Start == curve.End; + return ret; + } + + /// + /// Returns the curved moved by delta + /// + public void Translate(Point delta) + { + PolylinePoint polyPoint = StartPoint; + while (polyPoint != null) + { + polyPoint.Point += delta; + polyPoint = polyPoint.Next; + } + + RequireInit(); + } + + /// + /// Returns the curved with all points scaled from the original by x and y + /// + /// + /// + /// + public ICurve ScaleFromOrigin(double xScale, double yScale) + { + var ret = new Polyline(); + PolylinePoint polyPoint = StartPoint; + while (polyPoint != null) + { + ret.AddPoint(Point.Scale(xScale, yScale, polyPoint.Point)); + polyPoint = polyPoint.Next; + } + ret.Closed = Closed; + return ret; + } + + internal void AddPoint(double x, double y) { + AddPoint(new Point(x, y)); + } + + internal void PrependPoint(Point p) { + Debug.Assert(EndPoint == null || !ApproximateComparer.Close(p, EndPoint.Point)); + var pp = new PolylinePoint(p) {Polyline = this}; + if (StartPoint != null) { + if (!ApproximateComparer.Close(p, StartPoint.Point)) + { + StartPoint.Prev = pp; + pp.Next = StartPoint; + StartPoint = pp; + } + } else { + StartPoint = EndPoint = pp; + } + RequireInit(); + } + + /// + ///adds a point to the polyline + /// + /// + public void AddPoint(Point point) { + var pp = new PolylinePoint(point) {Polyline = this}; + if (EndPoint != null) { + // if (!ApproximateComparer.Close(point, EndPoint.Point)) { + EndPoint.Next = pp; + pp.Prev = EndPoint; + EndPoint = pp; + // } + } else { + StartPoint = EndPoint = pp; + } + RequireInit(); + } + + /// + /// this[ParStart] + /// + public Point Start + { + get { return StartPoint.Point; } + } + + /// + /// this[ParEnd] + /// + public Point End + { + get { return EndPoint.Point; } + } + + /// + /// this[Reverse[t]]=this[ParEnd+ParStart-t] + /// + /// + public ICurve Reverse() + { + return ReversePolyline(); + } + + /// + /// Offsets the curve in the direction of dir + /// + /// + /// + /// + public ICurve OffsetCurve(double offset, Point dir) + { + throw new NotImplementedException(); + } + + /// + /// return length of the curve segment [start,end] + /// + /// + /// + /// + public double LengthPartial(double start, double end) + { + throw new NotImplementedException(); + } + + /// + /// Get the length of the curve + /// + public double Length + { + get { + double ret = 0; + if (StartPoint != null && StartPoint.Next != null) { + PolylinePoint p = StartPoint.Next; + do { + ret += (p.Point - p.Prev.Point).Length; + p = p.Next; + } while (p != null); + } + return ret; + } + } + + + /// + /// + /// + /// + /// + /// + public double GetParameterAtLength(double length) { + throw new NotImplementedException(); + } + + /// + /// returns the transformed polyline + /// + /// + /// + public ICurve Transform(PlaneTransformation transformation) { + if (transformation == null) + return this; + var poly = new Polyline {Closed = Closed}; + foreach (var p in this) + { + poly.AddPoint(transformation*p); + } + return poly; + } + + /// + /// returns a parameter t such that the distance between curve[t] and targetPoint is minimal + /// and t belongs to the closed segment [low,high] + /// + /// the point to find the closest point + /// the upper bound of the parameter + /// the low bound of the parameter + /// + public double ClosestParameterWithinBounds(Point targetPoint, double low, double high) { + double ret = 0; + double dist = Double.MaxValue; + int offset = 0; + PolylinePoint pp = StartPoint; + while (pp.Next != null) { + if (offset <= high && offset + 1 >= low) { + var lowLocal = Math.Max(0, low - offset); + var highLocal = Math.Min(1, high - offset); + var ls = new LineSegment(pp.Point, pp.Next.Point); + double t = ls.ClosestParameterWithinBounds(targetPoint, lowLocal, highLocal); + Point delta = ls[t] - targetPoint; + double newDist = delta*delta; + if (newDist < dist) { + dist = newDist; + ret = t + offset; + } + } + pp = pp.Next; + offset++; + } + + if (Closed) { + if (offset <= high && offset + 1 >= low) { + var lowLocal = Math.Max(0, low - offset); + var highLocal = Math.Min(1, high - offset); + var ls = new LineSegment(EndPoint.Point, StartPoint.Point); + double t = ls.ClosestParameterWithinBounds(targetPoint, lowLocal, highLocal); + Point delta = ls[t] - targetPoint; + double newDist = delta*delta; + if (newDist < dist) + ret = t + offset; + } + } + return ret; + } + + /// + /// gets the parameter of the closest point + /// + /// + /// + public double ClosestParameter(Point targetPoint) { + double ret = 0; + double dist = Double.MaxValue; + int offset = 0; + PolylinePoint pp = StartPoint; + while (pp.Next != null) { + var ls = new LineSegment(pp.Point, pp.Next.Point); + double t = ls.ClosestParameter(targetPoint); + Point delta = ls[t] - targetPoint; + double newDist = delta*delta; + if (newDist < dist) { + dist = newDist; + ret = t + offset; + } + pp = pp.Next; + offset++; + } + + if (Closed) { + var ls = new LineSegment(EndPoint.Point, StartPoint.Point); + double t = ls.ClosestParameter(targetPoint); + Point delta = ls[t] - targetPoint; + double newDist = delta*delta; + if (newDist < dist) + ret = t + offset; + } + return ret; + } + + /// + /// clones the curve. + /// + /// the cloned curve + ICurve ICurve.Clone() { + var ret = new Polyline(); + foreach (Point p in this) + ret.AddPoint(p); + ret.Closed = Closed; + return ret; + } + + #endregion + + #region IEnumerable Members + +#if SHARPKIT //https://code.google.com/p/sharpkit/issues/detail?id=332 + public IEnumerator GetEnumerator() + { +#else + IEnumerator IEnumerable.GetEnumerator() { +#endif + return new PolylineIterator(this); + } + + #endregion + + #region IEnumerable Members + + IEnumerator IEnumerable.GetEnumerator() { + return new PolylineIterator(this); + } + + #endregion + + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Polyline ReversePolyline() { + var ret = new Polyline(); + PolylinePoint pp = EndPoint; + while (pp.Prev != null) { + ret.AddPoint(pp.Point); + pp = pp.Prev; + } + ret.AddPoint(StartPoint.Point); + ret.Closed = Closed; + return ret; + } + + internal PolylinePoint Next(PolylinePoint a) { + return a.Next ?? (Closed ? StartPoint : null); + } + + internal PolylinePoint Prev(PolylinePoint a) { + return a.Prev ?? (Closed ? EndPoint : null); + } + + /// + /// creates a polyline from a point enumeration + /// + /// + public Polyline(IEnumerable points) { + ValidateArg.IsNotNull(points, "points"); + foreach (var p in points) + AddPoint(p); + } + + /// + /// creating a polyline from two points + /// + /// + /// + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "b"), + SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "a")] + public Polyline(Point a, Point b) { + AddPoint(a); + AddPoint(b); + } + + + /// + /// an empty constructor + /// + public Polyline() {} + + /// + /// + /// +#if SHARPKIT //http://code.google.com/p/sharpkit/issues/detail?id=339 + [SharpKit.JavaScript.JsMethod(NativeParams = false)] +#endif + public Polyline(params Point[] points) : this((IEnumerable)points) { } + + /// + /// true in general for convex polylines + /// + /// + internal bool IsClockwise() { + return Point.GetTriangleOrientation(StartPoint.Point, StartPoint.Next.Point, StartPoint.Next.Next.Point) == + TriangleOrientation.Clockwise; + } + + internal void RemoveStartPoint() { + PolylinePoint p = StartPoint.Next; + p.Prev = null; + StartPoint = p; + RequireInit(); + } + + internal void RemoveEndPoint() { + PolylinePoint p = EndPoint.Prev; + p.Next = null; + EndPoint = p; + RequireInit(); + } + + /// + /// Returns the point location value. The assumption is that the polyline goes clockwise and is closed and convex. + /// + /// Point to find. + /// if the point belongs to the boundary then witness is + /// the first point of the boundary segment containing p + /// + internal PointLocation GetPointLocation(Point point, out PolylinePoint witness) { + Debug.Assert(Closed && IsClockwise()); + witness = null; + + foreach (PolylinePoint polyPoint in PolylinePoints) { + PolylinePoint secondPoint = Next(polyPoint); + TriangleOrientation triangleOrientation = Point.GetTriangleOrientation(point, polyPoint.Point, + secondPoint.Point); + if (triangleOrientation == TriangleOrientation.Counterclockwise) + return PointLocation.Outside; + if (triangleOrientation == TriangleOrientation.Collinear) + if ((point - polyPoint.Point)*(secondPoint.Point - point) >= 0) { + witness = polyPoint; + return PointLocation.Boundary; + } + } + + return PointLocation.Inside; + } + + /// + /// Returns the point location value and the edge containing it if it belongs to a boundary. + /// The assumption is that the polyline goes clockwise and is closed and convex. + /// + /// Point to find + /// The starting point of the boundary hit, if any + /// The ending point of the boundary hit, if any + /// + [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#")] + [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#")] + public PointLocation GetPointLocation(Point point, out Point edgeStart, out Point edgeEnd) { + PolylinePoint start; + edgeStart = new Point(); + edgeEnd = new Point(); + PointLocation loc = GetPointLocation(point, out start); + if (PointLocation.Boundary == loc) { + edgeStart = start.Point; + edgeEnd = start.NextOnPolyline.Point; + } + return loc; + } + + /// + /// shift the given polyline by delta + /// + /// + public void Shift(Point delta) { + for (PolylinePoint pp = StartPoint; pp != null; pp = pp.Next) + pp.Point += delta; + } + + #region ICurve Members + + /// + /// + /// + /// + /// + public double Curvature(double t) { + throw new NotImplementedException(); + } + + /// + /// + /// + /// + /// + public double CurvatureDerivative(double t) { + throw new NotImplementedException(); + } + + /// + /// + /// + /// + /// + public double CurvatureSecondDerivative(double t) { + throw new NotImplementedException(); + } + + #endregion + + internal void AddRangeOfPoints(IEnumerable points){ + foreach (var point in points) + AddPoint(point); + } + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/Polyline.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/Polyline.cs.meta new file mode 100644 index 000000000..114314373 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/Polyline.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6aeac8d134fdeed4485d691ee7f1a0d5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/PolylineIterator.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/PolylineIterator.cs new file mode 100644 index 000000000..70715cfbc --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/PolylineIterator.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; + +namespace Microsoft.Msagl.Core.Geometry.Curves +{ + internal class PolylineIterator:IEnumerator + { + Polyline polyline; + + PolylinePoint currentPolyPoint; + + internal PolylineIterator(Polyline poly) + { + this.polyline = poly; + } + + #region IEnumerator Members + +#if SHARPKIT //https://code.google.com/p/sharpkit/issues/detail?id=332 + public Point Current +#else + Point IEnumerator.Current +#endif + { + get { return currentPolyPoint.Point; } + } + + #endregion + + #region IDisposable Members + +#if !SHARPKIT //https://code.google.com/p/sharpkit/issues/detail?id=71 + public void Dispose() +#else + void IDisposable.Dispose() +#endif + { + GC.SuppressFinalize(this); + } + + #endregion + + #region IEnumerator Members + + object System.Collections.IEnumerator.Current + { + get { return currentPolyPoint.Point; } + } + +#if SHARPKIT //https://code.google.com/p/sharpkit/issues/detail?id=332&thanks=332 + public bool MoveNext() +#else + bool System.Collections.IEnumerator.MoveNext() +#endif + { + if (currentPolyPoint == null) + { + currentPolyPoint = polyline.StartPoint; + return currentPolyPoint != null; + } + if(currentPolyPoint==polyline.EndPoint) + return false; + currentPolyPoint = currentPolyPoint.Next; + return true; + } + + void System.Collections.IEnumerator.Reset() + { + currentPolyPoint = null; + } + + #endregion + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/PolylineIterator.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/PolylineIterator.cs.meta new file mode 100644 index 000000000..4f000e04e --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/PolylineIterator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8da775da230205646a3cf7dd0c5ab3bd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/PolylinePoint.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/PolylinePoint.cs new file mode 100644 index 000000000..e0ce3d5cd --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/PolylinePoint.cs @@ -0,0 +1,109 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.Msagl.Core.Geometry.Curves { + /// + /// + /// +#if TEST_MSAGL + [Serializable] +#endif + public class PolylinePoint { + /// + /// + /// + Point point; + + /// + /// + /// + public Point Point { + get { return point; } + set { +#if SHARPIT + point = value.Clone(); +#else + point = value; +#endif + if (Polyline != null) + Polyline.RequireInit(); + } + } + + + PolylinePoint next; + + /// + /// + /// + public PolylinePoint Next { + get { return next; } + set { + next = value; + if (Polyline != null) + Polyline.RequireInit(); + } + } + + PolylinePoint prev; + + /// + /// + /// + public PolylinePoint Prev { + get { return prev; } + set { + prev = value; + if (Polyline != null) + Polyline.RequireInit(); + } + } + + internal PolylinePoint() { + } + + /// + /// + /// + public PolylinePoint(Point p) { +#if SHARKPIT + Point = p.Clone(); +#else + Point = p; +#endif + } + + Polyline polyline; + + /// + /// + /// + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + public Polyline Polyline { + get { return polyline; } + set { polyline = value; } + } + + /// + /// + /// + public override string ToString() { + return point.ToString(); + } + + /// + /// + /// + public PolylinePoint NextOnPolyline { + get { return Polyline.Next(this); } + } + + /// + /// + /// + public PolylinePoint PrevOnPolyline { + get { return Polyline.Prev(this); } + } + + } +} \ No newline at end of file diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/PolylinePoint.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/PolylinePoint.cs.meta new file mode 100644 index 000000000..e965b0f27 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/PolylinePoint.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 53f69bf263399ef4586f501982e256b5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/RootFinder.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/RootFinder.cs new file mode 100644 index 000000000..6aa5bbdb9 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/RootFinder.cs @@ -0,0 +1,74 @@ +using System; + +namespace Microsoft.Msagl.Core.Geometry.Curves { + /// + /// looking for a root of a function on a given segment + /// + static public class RootFinder { + + + /// + /// implements the Newton method + /// + /// + /// + /// + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")] + public static bool TryToFindRoot(IFunction f, double start, double end, double guess, out double x) { + ValidateArg.IsNotNull(f, "f"); + System.Diagnostics.Debug.Assert(start >= f.ParStart && end <= f.ParEnd); + System.Diagnostics.Debug.Assert(start <= guess && end >= guess); + int numberOfBoundaryCrossings = 0; + const int maxNumberOfBoundaryCrossings = 10; + int numberOfTotalReps = 0; + const int maxNumberOfTotalReps = 100; + x = guess; + + double dx; + bool abort = false; + do { + + var fp = f.Derivative(x); + if (Math.Abs(fp) < ApproximateComparer.Tolerance) { + abort = true; + break; + } + + dx = -f[x] / fp; + x += dx; + if (x < start - ApproximateComparer.DistanceEpsilon) { + x = start; + numberOfBoundaryCrossings++; + } else if (x > end + ApproximateComparer.DistanceEpsilon) { + x = end; + numberOfBoundaryCrossings++; + } + + numberOfTotalReps++; + + abort = numberOfBoundaryCrossings >= maxNumberOfBoundaryCrossings || + numberOfTotalReps >= maxNumberOfTotalReps || dx == 0; + + } while (Math.Abs(dx) >= ApproximateComparer.Tolerance && !abort); + + if (abort) { + //may be the initial guess was just OK + if (Math.Abs(f[guess]) < ApproximateComparer.DistanceEpsilon) { + x = guess; + return true; + } + return false; + } + if (x < start) + x = start; + else if (x > end ) + x = end; + + return true; + + } + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/RootFinder.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/RootFinder.cs.meta new file mode 100644 index 000000000..af5af7944 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/RootFinder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: edadf8298ec711740b828aac399cd612 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/RoundedRect.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/RoundedRect.cs new file mode 100644 index 000000000..850518b84 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/RoundedRect.cs @@ -0,0 +1,331 @@ +using System; +using System.Diagnostics; + +namespace Microsoft.Msagl.Core.Geometry.Curves { + /// + /// A rectanglular curve with rounded corners + /// +#if TEST_MSAGL + [Serializable] +#endif + public class RoundedRect : ICurve { + /// + /// + Curve curve; + /// + /// underlying curve + /// + public Curve Curve + { + get { return curve; } + } + + /// + /// The horizontal radius of the corner ellipse segments + /// + public double RadiusX { get; set; } + + /// + /// The vertical radius of the corner ellipse segments + /// + public double RadiusY { get; set; } + + RoundedRect() + { + } + + /// + /// Create a rounded rectangle geometry + /// + /// rounded rectangle will fit these bounds + /// horizontal radius of the corner ellipse segments + /// vertical radius of the corner ellipse segments + public RoundedRect(Rectangle bounds, double radiusX, double radiusY) { + RadiusX = radiusX; + RadiusY = radiusY; + curve = new Curve(8); + CurveFactory.CreateRectangleWithRoundedCorners(curve, + bounds.Width, bounds.Height, radiusX, radiusY, bounds.Center); + } + + /// + /// Create a new RoundedRect with the same CornerRadius inside the target bounds + /// + /// target bounds + /// new RoundedRect with the same CornerRadius inside the target bounds + public ICurve FitTo(Rectangle target) { + if (ApproximateComparer.Close(target, this.BoundingBox, ApproximateComparer.UserDefinedTolerance)) + { + return this.Clone(); + } + + return new RoundedRect(target, RadiusX, RadiusY); + } + + /// + /// Returns the point on the curve corresponding to parameter t + /// + /// + /// + public Point this[double t] { + get { return curve[t]; } + } + + /// + /// first derivative at t + /// + /// the parameter where the derivative is calculated + /// + public Point Derivative(double t) { + return curve.Derivative(t); + } + + /// + /// second derivative + /// + /// + /// + public Point SecondDerivative(double t) { + return curve.SecondDerivative(t); + } + + /// + /// third derivative + /// + /// the parameter of the derivative + /// + public Point ThirdDerivative(double t) { + return curve.ThirdDerivative(t); + } + + /// + /// A tree of ParallelogramNodes covering the curve. + /// This tree is used in curve intersections routines. + /// + /// + public ParallelogramNodeOverICurve ParallelogramNodeOverICurve { + get { return curve.ParallelogramNodeOverICurve; } + } + + bool dirtyBounds = true; + Rectangle cachedBounds; + + /// + /// XY bounding box of the curve + /// + public Rectangle BoundingBox { + get + { + if (dirtyBounds) + { + cachedBounds = curve.BoundingBox; + dirtyBounds = false; + } + return cachedBounds; + } + } + + /// + /// the start of the parameter domain + /// + public double ParStart { + get { return curve.ParStart; } + } + + /// + /// the end of the parameter domain + /// + public double ParEnd { + get { return curve.ParEnd; } + } + + /// + /// returns the trim curve + /// + /// + /// + /// + public ICurve Trim(double start, double end) { + return curve.Trim(start, end); + } + + /// + /// Returns the trimmed curve, wrapping around the end if start is greater than end. + /// + /// The starting parameter + /// The ending parameter + /// The trimmed curve + public ICurve TrimWithWrap(double start, double end) { + return curve.TrimWithWrap(start, end); + } + + /// + /// Returns the curved moved by delta + /// + public void Translate(Point delta) { + this.curve.Translate(delta); + dirtyBounds = true; + } + + /// + /// Returns the curved with all points scaled from the original by x and y + /// + /// + public ICurve ScaleFromOrigin(double xScale, double yScale) { + var bounds = curve.BoundingBox; + var lt = Point.Scale(xScale, yScale, bounds.LeftTop); + var rb = Point.Scale(xScale, yScale, bounds.RightBottom); + return new RoundedRect(new Rectangle(lt, rb), RadiusX, RadiusY); + } + + /// + /// this[ParStart] + /// + public Point Start { + get { return curve.Start; } + } + + /// + /// this[ParEnd] + /// + public Point End { + get { return curve.End; } + } + + /// + /// this[Reverse[t]]=this[ParEnd+ParStart-t] - not implemented + /// + /// + public ICurve Reverse() { + throw new NotImplementedException(); + } + + /// + /// Offsets the curve in the direction of dir + /// + /// + /// + /// + public ICurve OffsetCurve(double offset, Point dir) { + return curve.OffsetCurve(offset, dir); + } + + /// + /// return length of the curve segment [start,end] + /// + /// + /// + /// + public double LengthPartial(double start, double end) { + return curve.LengthPartial(start, end); + } + + /// + /// Get the length of the curve + /// + public double Length { + get { return curve.Length; } + } + + + /// + /// + /// + /// + /// + public double GetParameterAtLength(double length) { + return curve.GetParameterAtLength(length); + } + + /// + /// Return the transformed curve + /// + /// + /// the transformed curve + public ICurve Transform(PlaneTransformation transformation) { + var bounds = curve.BoundingBox; + var lt = transformation * bounds.LeftTop; + var rb = transformation * bounds.RightBottom; + var transBounds = new Rectangle(lt, rb); + return new RoundedRect(transBounds, RadiusX, RadiusY); + } + + /// + /// returns a parameter t such that the distance between curve[t] and targetPoint is minimal + /// and t belongs to the closed segment [low,high] + /// + /// the point to find the closest point + /// the upper bound of the parameter + /// the low bound of the parameter + /// + public double ClosestParameterWithinBounds(Point targetPoint, double low, double high) { + return this.Curve.ClosestParameterWithinBounds( targetPoint, low, high); + } + + /// + /// returns a parameter t such that the distance between curve[t] and a is minimal + /// + /// + /// + public double ClosestParameter(Point targetPoint) { + return curve.ClosestParameter(targetPoint); + } + + /// + /// clones the curve. + /// + /// the cloned curve + public ICurve Clone() { + RoundedRect newRect = new RoundedRect(); + newRect.RadiusX = this.RadiusX; + newRect.RadiusY = this.RadiusY; + newRect.curve = (Curve)this.curve.Clone(); + return newRect; + } + + /// + /// The left derivative at t. + /// + /// the parameter where the derivative is calculated + /// + public Point LeftDerivative(double t) { + return curve.LeftDerivative(t); + } + + /// + /// the right derivative at t + /// + /// the parameter where the derivative is calculated + /// + public Point RightDerivative(double t) { + return curve.RightDerivative(t); + } + + /// + /// the signed curvature of the segment at t + /// + /// + /// + public double Curvature(double t) { + return curve.Curvature(t); + } + + /// + /// the derivative of the curvature at t + /// + /// + /// + public double CurvatureDerivative(double t) { + return curve.CurvatureDerivative(t); + } + + /// + /// the derivative of CurvatureDerivative + /// + /// + /// + public double CurvatureSecondDerivative(double t) { + return curve.CurvatureSecondDerivative(t); + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/RoundedRect.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/RoundedRect.cs.meta new file mode 100644 index 000000000..087f240b6 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Curves/RoundedRect.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f51ea6c96087f90428d5d385d290e20f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Direction.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Direction.cs new file mode 100644 index 000000000..971919efa --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Direction.cs @@ -0,0 +1,30 @@ +using System; + +namespace Microsoft.Msagl.Core.Geometry { + /// + /// enumerates the compass directions + /// + [Flags] + public enum Directions { + /// + /// no direction defined + /// + None=0, + /// + /// North + /// + North=1, + /// + /// East + /// + East=2, + /// + /// South + /// + South=4, + /// + /// West + /// + West=8 + } +} \ No newline at end of file diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Direction.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Direction.cs.meta new file mode 100644 index 000000000..9cbae42da --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Direction.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0c5d50f0fd80c5d4aaa44eff021057ae +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/MinEnclosingDisc.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/MinEnclosingDisc.meta new file mode 100644 index 000000000..fbcb8fdf3 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/MinEnclosingDisc.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1567181559c5f9f4e9ebea9dbd3d082b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/MinEnclosingDisc/Disc.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/MinEnclosingDisc/Disc.cs new file mode 100644 index 000000000..87c7d0272 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/MinEnclosingDisc/Disc.cs @@ -0,0 +1,202 @@ +using System; +using System.Linq; +using System.Diagnostics; + +namespace Microsoft.Msagl.Core.Geometry +{ + /// + /// Disc for use in Minimum Enclosing Disc computation + /// + public class Disc + { + /// + /// disc centre + /// + Point c; + /// + /// disc centre + /// + public Point Center + { + get { return c; } + } + /// + /// radius + /// + double r; + /// + /// Radius of disc + /// + public double Radius + { + get { return r; } + } + double r2; + /// + /// squared distance from the centre of this disc to point + /// + /// + /// + public double Distance2(Point point) + { + double dx = c.X - point.X, dy = c.Y - point.Y; + return dx * dx + dy * dy; + } + /// + /// Test if point is contained in this disc + /// + /// + /// + public bool Contains(Point point) + { + return Distance2(point) - 1e-7 <= r2; + } + /// + /// test if all specified points (apart from the except list) are contained in this disc + /// + /// points to test for containment + /// short list of exclusions + /// true if all points are contained in the disc + public bool Contains(Point[] points, int[] except) + { + ValidateArg.IsNotNull(points, "points"); + for (int i = 0; i < points.Length; ++i) + { + if (!except.Contains(i) && !Contains(points[i])) + { + return false; + } + } + return true; + } + /// + /// create a zero radius disc centred at center + /// + /// center of disc + public Disc(Point center) + { + this.c = center; + r2 = r = 0; + } + /// + /// find the point mid-way between two points + /// + /// + /// + static Point midPoint(Point startPoint, Point endPoint) + { + return new Point((endPoint.X + startPoint.X) / 2.0, (endPoint.Y + startPoint.Y) / 2.0); + } + /// + /// Create the smallest possible disc with the specified points on the boundary + /// + /// + /// + public Disc(Point firstBoundaryPoint, Point secondBoundaryPoint) + { + c = midPoint(firstBoundaryPoint, secondBoundaryPoint); + r2 = Distance2(firstBoundaryPoint); + r = Math.Sqrt(r2); + Debug.Assert(OnBoundary(firstBoundaryPoint)); + Debug.Assert(OnBoundary(secondBoundaryPoint)); + } + /// + /// test if a point lies on (within a small delta of) the boundary of this disc + /// + /// + /// + public bool OnBoundary(Point point) + { + double d = Distance2(point); + return Math.Abs(d - r2) / (d + r2) < 1e-5; + } + /// + /// computes the centre of the disc with the 3 specified points on the boundary + /// + /// + /// + /// + /// + static Point centre(Point p1, Point p2, Point p3) + { + Debug.Assert(p2.X != p1.X); + Debug.Assert(p3.X != p2.X); + double ma, mb; + ma = (p2.Y - p1.Y) / (p2.X - p1.X); + mb = (p3.Y - p2.Y) / (p3.X - p2.X); + Debug.Assert(mb != ma); // collinear points not allowed + Point c = new Point(); + c.X = ma * mb * (p1.Y - p3.Y) + mb * (p1.X + p2.X) - ma * (p2.X + p3.X); + c.X /= 2.0 * (mb - ma); + if (Math.Abs(ma) > Math.Abs(mb)) + { + c.Y = (p1.Y + p2.Y) / 2.0 - (c.X - (p1.X + p2.X) / 2.0) / ma; + } + else + { + c.Y = (p2.Y + p3.Y) / 2.0 - (c.X - (p2.X + p3.X) / 2.0) / mb; + } + return c; + } + /// + /// if the area of the triangle formed by the 3 points is 0 then the points are collinear + /// + /// + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "p")] + public static bool Collinear(Point p1, Point p2, Point p3) + { + return p1.X * (p2.Y - p3.Y) + p2.X * (p3.Y - p1.Y) + p3.X * (p1.Y - p2.Y) == 0; + } + /// + /// Create a disc with the specified points on the boundary + /// + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "p")] + public Disc(Point p1, Point p2, Point p3) + { + if (Collinear(p1, p2, p3)) + { + Point LL = new Point( + Math.Min(p1.X, Math.Min(p2.X, p3.X)), + Math.Min(p1.Y, Math.Max(p2.Y, p3.Y))), + UR = new Point( + Math.Max(p1.X, Math.Max(p2.X, p3.X)), + Math.Max(p1.Y, Math.Max(p2.Y, p3.Y))); + c = midPoint(LL, UR); + r2 = Distance2(UR); + r = Math.Sqrt(r2); + } + else + { + double dx12 = p2.X - p1.X, dx23 = p3.X - p2.X, dx13 = p3.X - p1.X; + if (dx12 != 0) + { + if (dx23 != 0) + { + c = centre(p1, p2, p3); + } + else + { + Debug.Assert(dx13 != 0); + c = centre(p2, p1, p3); + } + } + else + { + Debug.Assert(dx23 != 0); // because points are not collinear + c = centre(p2, p3, p1); + } + r2 = Distance2(p1); + r = Math.Sqrt(r2); + Debug.Assert(OnBoundary(p1)); + Debug.Assert(OnBoundary(p2)); + Debug.Assert(OnBoundary(p3)); + } + } + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/MinEnclosingDisc/Disc.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/MinEnclosingDisc/Disc.cs.meta new file mode 100644 index 000000000..68ad38918 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/MinEnclosingDisc/Disc.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c1f8d443c79a58340ad87dae5ee581e0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/MinEnclosingDisc/MinimumEnclosingDisc.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/MinEnclosingDisc/MinimumEnclosingDisc.cs new file mode 100644 index 000000000..42150beeb --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/MinEnclosingDisc/MinimumEnclosingDisc.cs @@ -0,0 +1,178 @@ +using System.Collections.Generic; +using System.Diagnostics; + +namespace Microsoft.Msagl.Core.Geometry +{ + /// + /// implementation of the "MoveToFront" method for computing the minimum enclosing disc of a collection of points. + /// Runs in time linear in the number of points. After Welzl'1991. + /// + public class MoveToFront { + LinkedList L; + Point[] ps; + /// + /// minimum enclosing disc + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")] + public Disc disc; + /// + /// list of 2 or 3 points lying on the boundary + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists")] + public List boundary; + /// + /// Constructs the minimum enclosing disc for the specified points + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "ps")] + public MoveToFront(Point[] ps) + { + ValidateArg.IsNotNull(ps, "ps"); + this.ps = ps; + L = new LinkedList(); + for (int i = 0; i < ps.Length; ++i) + { + L.AddLast(i); + } + MinDisc md = mtf_md(null, new List()); + disc = md.disc; + boundary = md.boundary; + } + class MinDisc + { + public Disc disc; + public List boundary; + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1805:DoNotInitializeUnnecessarily")] + public MinDisc(Point[] ps, List b) + { + this.boundary = b; + Debug.Assert(b.Count <= 3); + switch(b.Count) { + case 0: + disc = null; + break; + case 1: + disc = new Disc(ps[b[0]]); + break; + case 2: + disc = new Disc(ps[b[0]], ps[b[1]]); + break; + case 3: + disc = new Disc(ps[b[0]], ps[b[1]], ps[b[2]]); + break; + } + } + public bool contains(Point p) + { + if (disc == null) + { + return false; + } + return disc.Contains(p); + } + } +#if DEBUG + bool collinear3(List b) + { + if (b.Count == 3) + { + return Disc.Collinear(ps[b[0]], ps[b[1]], ps[b[2]]); + } + return false; + } +#endif + MinDisc mtf_md(LinkedListNode lPtr, List b) + { + Debug.Assert(b.Count <= 3); + MinDisc md = new MinDisc(ps,b); + if (b.Count == 3) + { + return md; + } + LinkedListNode lnode = L.First; + while(lnode!=null&&lnode!=lPtr) + { + LinkedListNode lnext = lnode.Next; + int p = lnode.Value; + if (!md.contains(ps[p])) + { + List _b = new List(b); + _b.Add(p); +#if DEBUG + Debug.Assert(!collinear3(_b),"Collinear points on boundary of minimal enclosing disc"); +#endif + md = mtf_md(lnode, _b); + L.Remove(lnode); + L.AddFirst(lnode); + } + lnode = lnext; + } + return md; + } + } + /// + /// static methods for obtaining a minimum enclosing disc of a collection of points + /// + public static class MinimumEnclosingDisc + { + /// + /// linear-time computation using the move-to-front heuristic by Welzl + /// + /// points that must be enclosed + /// Smallest disc that encloses all the points + public static Disc LinearComputation(Point[] points) + { + MoveToFront m = new MoveToFront(points); + return m.disc; + } + /// + /// Computing the minimum enclosing disc the slow stupid way. Just for testing purposes. + /// + /// + /// Smallest disc that encloses all the points + public static Disc SlowComputation(Point[] points) + { + ValidateArg.IsNotNull(points, "points"); + int n = points.Length; + Disc mc = null; + int[] b = null; + for (int i = 0; i < n; ++i) + { + for (int j = 0; j < n; ++j) + { + if (i != j) + { + Disc c = new Disc(points[i], points[j]); + if (c.Contains(points, new int[] { i, j })) + { + if (mc == null || mc.Radius > c.Radius) + { + mc = c; + b = new int[] { i, j }; + } + } + } + for (int k = 0; k < n; ++k) + { + if (k != i && k != j && !Disc.Collinear(points[i],points[j],points[k])) + { + Disc c3 = new Disc(points[i], points[j], points[k]); + if (c3.Contains(points, new int[]{i,j,k})) + { + if (mc == null || mc.Radius > c3.Radius) + { + mc = c3; + b = new int[] { i, j, k }; + } + } + } + } + } + } + Debug.Assert(b != null); + return mc; + } + + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/MinEnclosingDisc/MinimumEnclosingDisc.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/MinEnclosingDisc/MinimumEnclosingDisc.cs.meta new file mode 100644 index 000000000..86210fd6d --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/MinEnclosingDisc/MinimumEnclosingDisc.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 98faddf76e7e677428f79ff1b922051a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/MultidimensionalScaling.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/MultidimensionalScaling.cs new file mode 100644 index 000000000..765696a63 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/MultidimensionalScaling.cs @@ -0,0 +1,468 @@ +using System; + +namespace Microsoft.Msagl.Core.Geometry { + /// + /// Classical Multidimensional Scaling. Given a set of proximities or + /// dissimilarities ordistances between objects, multidimensional scaling + /// recovers low-dimensional coordinates for these objects with these + /// distances. + /// + sealed public class MultidimensionalScaling { + MultidimensionalScaling() { }//suppressing the creation of the public constructor + /// + /// Double-centers a matrix in such a way that the center of gravity is zero. + /// After double-centering, each row and each column sums up to zero. + /// + /// + public static void DoubleCenter(double[][] matrix) { + ValidateArg.IsNotNull(matrix, "matrix"); + double[] rowMean=new double[matrix.Length]; + double[] colMean = new double[matrix[0].Length]; + double mean = 0; + for (int i = 0; i < matrix.Length; i++) { + for (int j = 0; j < matrix[0].Length; j++) { + rowMean[i] += matrix[i][j]; + colMean[j] += matrix[i][j]; + mean += matrix[i][j]; + } + } + for (int i = 0; i < matrix.Length; i++) rowMean[i] /= matrix.Length; + for (int j = 0; j < matrix[0].Length; j++) colMean[j] /= matrix[0].Length; + mean /= matrix.Length; + mean /= matrix[0].Length; + for (int i = 0; i < matrix.Length; i++) { + for (int j = 0; j < matrix[0].Length; j++) { + matrix[i][j] -= rowMean[i] + colMean[j] - mean; + } + } + } + + /// + /// Squares all entries of a matrix. + /// + /// A matrix. + public static void SquareEntries(double[][] matrix) { + ValidateArg.IsNotNull(matrix, "matrix"); + for (int i = 0; i < matrix.Length; i++) { + for (int j = 0; j < matrix[0].Length; j++) { + matrix[i][j] = Math.Pow(matrix[i][j], 2); + } + } + } + + /// + /// Multiplies a matrix with a scalar factor. + /// + /// A matrix. + /// A scalar factor. + public static void Multiply(double[][] matrix, double factor) { + ValidateArg.IsNotNull(matrix, "matrix"); + for (int i = 0; i < matrix.Length; i++) { + for (int j = 0; j < matrix[0].Length; j++) { + matrix[i][j] *= factor; + } + } + } + + /// + /// Multiply a square matrix and a vector. + /// Note that matrix width and vector length + /// have to be equal, otherwise null is returned. + /// + /// A matrix. + /// A vector. + /// The resulting product vector, or null if matrix and vector + /// are incompatible. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "x"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "A"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "A")] + public static double[] Multiply(double[][] A, double[] x) { + ValidateArg.IsNotNull(A, "A"); + ValidateArg.IsNotNull(x, "x"); + if(A[0].Length!=x.Length) return null; + double[] y=new double[x.Length]; + for (int i = 0; i < A.Length; i++) { + for (int j = 0; j < A[0].Length; j++) { + y[i]+=A[i][j]*x[j]; + } + } + return y; + } + + /// + /// Gives the norm of a vector, that is, its length in + /// vector.length dimensional Euclidean space. + /// + /// A vector. + /// The norm of the vector. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "x")] + public static double Norm(double[] x) { + ValidateArg.IsNotNull(x, "x"); + double norm=0; + for (int i = 0; i < x.Length; i++) { + norm += Math.Pow(x[i], 2); + } + norm = Math.Sqrt(norm); + return norm; + } + + /// + /// Normalizes a vector to unit length (1.0) in + /// vector.length dimensional Euclidean space. + /// If the vector is the 0-vector, nothing is done. + /// + /// A vector. + /// The norm of the vector. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "x")] + public static double Normalize(double[] x) { + ValidateArg.IsNotNull(x, "x"); + double lambda = Norm(x); + if (lambda <= 0) return 0; + for (int i = 0; i < x.Length; i++) { + x[i] /= lambda; + } + return lambda; + } + + /// ` + /// Gives a random unit Euclidean length vector of a given size. + /// + /// The size ofthe vector. + /// A seed for the random number generator. + /// A random vector. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "n")] + public static double[] RandomUnitLengthVector(int n, int seed) { + double[] result=new double[n]; + Random random=new Random(seed); + for (int i = 0; i < n; i++) { + result[i] = random.NextDouble(); + } + Normalize(result); + return result; + } + + /// + /// Computes the two dominant eigenvectors and eigenvalues of a symmetric + /// square matrix. + /// + /// A matrix. + /// First eigenvector. + /// First eigenvalue. + /// Second eigenvector. + /// Second eigenvalue. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "u"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "A"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "A"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "4#"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "3#"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#")] + public static void SpectralDecomposition(double[][] A, + out double[] u1, out double lambda1, + out double[] u2, out double lambda2) { + SpectralDecomposition(A, out u1, out lambda1, out u2, out lambda2, 30, 1e-6); + } + + /// + /// Computes the two dominant eigenvectors and eigenvalues of a symmetric + /// square matrix. + /// + /// A matrix. + /// First eigenvector. + /// First eigenvalue. + /// Second eigenvector. + /// Second eigenvalue. + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "u"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "A"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "A"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "4#"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "3#"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#")] + public static void SpectralDecomposition(double[][] A, + out double[] u1, out double lambda1, + out double[] u2, out double lambda2, int maxIterations, double epsilon) { + ValidateArg.IsNotNull(A, "A"); + int n = A[0].Length; + u1 = RandomUnitLengthVector(n, 0); lambda1 = 0; + u2 = RandomUnitLengthVector(n, 1); lambda2 = 0; + double r = 0; + double limit = 1.0 - epsilon; + // iterate until convergence but at most 30 steps + for (int i = 0; (i < maxIterations && r < limit); i++) { + double[] x1 = Multiply(A, u1); + double[] x2 = Multiply(A, u2); + + lambda1 = Normalize(x1); + lambda2 = Normalize(x2); + MakeOrthogonal(x2, x1); + Normalize(x2); + + // convergence is assumed if the inner product of + // two consecutive (unit length) iterates is close to 1 + r = Math.Min(DotProduct(u1, x1), DotProduct(u2, x2)); + u1 = x1; + u2 = x2; + } + } + /// + /// Gives the inner product of two vectors of the same size. + /// + /// A vector. + /// A vector. + /// The inner product of the two vectors. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "y"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "x")] + public static double DotProduct(double[] x, double[] y) { + ValidateArg.IsNotNull(x, "x"); + ValidateArg.IsNotNull(y, "y"); + if (x.Length != y.Length) return 0; + double result = 0; + for (int i = 0; i < x.Length; i++) { + result += x[i]*y[i]; + } + return result; + } + + /// + /// Orthogonalizes a vector against another vector, so that + /// their scalar product is 0. + /// + /// Vector to be orthogonalized. + /// Vector to orthogonalize against. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "y"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "x")] + public static void MakeOrthogonal(double[] x, double[] y) { + ValidateArg.IsNotNull(x, "x"); + ValidateArg.IsNotNull(y, "y"); + if (x.Length != y.Length) return; + double prod = DotProduct(x, y) / DotProduct(y, y); + for (int i = 0; i < x.Length; i++) { + x[i] -= prod*y[i]; + } + } + + /// + /// Classical multidimensional scaling. Computes two-dimensional coordinates + /// for a given distance matrix by computing the two largest eigenvectors + /// and eigenvalues of a matrix assiciated with the distance matrix (called + /// "fitting inner products"). + /// + /// The distance matrix. + /// The first eigenvector (scaled by the root of its eigenvalue) + /// The second eigenvector (scaled by the root of its eigenvalue) + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "1")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "2")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "d")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "y")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "x")] + public static void ClassicalScaling(double[][] d, out double[] x, out double[] y) { + ValidateArg.IsNotNull(d, "d"); + double[][] b = new double[d.Length][]; + for (int i = 0; i < d.Length; i++) { + b[i] = new double[d[0].Length]; + d[i].CopyTo(b[i], 0); + } + SquareEntries(b); + DoubleCenter(b); + Multiply(b, -.5); + double lambda1; + double lambda2; + SpectralDecomposition(b, out x, out lambda1, out y, out lambda2); + lambda1=Math.Sqrt(Math.Abs(lambda1)); + lambda2=Math.Sqrt(Math.Abs(lambda2)); + for (int i = 0; i < x.Length; i++) { + x[i] *= lambda1; + y[i] *= lambda2; + } + } + + /// + /// Multidimensional scaling. Computes two-dimensional coordinates + /// for a given distance matrix by fitting the coordinates to these distances + /// iteratively by majorization (called "distance fitting"). + /// Only objects that have rows in the distance/weight matrix + /// is subject to iterative relocation. + /// + /// A distance matrix. + /// Coordinate vector. + /// Coordinate vector. + /// Weight matrix. + /// Number of iteration steps. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "y"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "x"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "w"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "d")] + public static void DistanceScalingSubset(double[][] d, double[] x, double[] y, double[][] w, int numberOfIterations) { + ValidateArg.IsNotNull(d, "d"); + ValidateArg.IsNotNull(x, "x"); + ValidateArg.IsNotNull(y, "y"); + ValidateArg.IsNotNull(w, "w"); + int n = x.Length; + int k = d.Length; + int[] index = new int[k]; + for (int i = 0; i < k; i++) { + for (int j = 0; j < n; j++) { + if (d[i][j] == 0) { + index[i] = j; + } + } + } + + double[] wSum = new double[k]; + for (int i = 0; i < k; i++) { + for (int j = 0; j < n; j++) { + if(index[i]!=j) { + wSum[i] += w[i][j]; + } + } + } + for (int c = 0; c < numberOfIterations; c++) { + for (int i = 0; i < k; i++) { + double xNew = 0; + double yNew = 0; + for (int j = 0; j < n; j++) { + if (i != j) { + double inv = Math.Sqrt(Math.Pow(x[index[i]] - x[j], 2) + Math.Pow(y[index[i]] - y[j], 2)); + if (inv > 0) inv = 1 / inv; + xNew += w[i][j] * (x[j] + d[i][j] * (x[index[i]] - x[j]) * inv); + yNew += w[i][j] * (y[j] + d[i][j] * (y[index[i]] - y[j]) * inv); + } + } + x[index[i]] = xNew / wSum[i]; + y[index[i]] = yNew / wSum[i]; + } + } + } + + /// + /// Multidimensional scaling. Computes two-dimensional coordinates + /// for a given distance matrix by fitting the coordinates to these distances + /// iteratively by majorization (called "distance fitting"). + /// (McGee, Kamada-Kawai) + /// + /// A distance matrix. + /// Coordinate vector. + /// Coordinate vector. + /// Weight matrix. + /// Number of iteration steps. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Console.Write(System.String)"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "d"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "y"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "x"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "w"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "iter")] + public static void DistanceScaling(double[][] d, double[] x, double[] y, double[][] w, int iter) { + ValidateArg.IsNotNull(d, "d"); + ValidateArg.IsNotNull(x, "x"); + ValidateArg.IsNotNull(y, "y"); + ValidateArg.IsNotNull(w, "w"); + int n = x.Length; + double[] wSum = new double[n]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + if (i != j) + wSum[i] += w[i][j]; + } + } + System.Console.Write("distance scaling "); + for (int c = 0; c < iter; c++) { + for (int i = 0; i < n; i++) { + double xNew = 0; + double yNew = 0; + for (int j = 0; j < n; j++) { + if(i!=j) { + double inv = Math.Sqrt(Math.Pow(x[i] - x[j], 2) + Math.Pow(y[i] - y[j], 2)); + if (inv > 0) inv=1/inv; + xNew += w[i][j] * (x[j] + d[i][j] * (x[i] - x[j]) * inv); + yNew += w[i][j] * (y[j] + d[i][j] * (y[i] - y[j]) * inv); + } + } + x[i] = xNew/wSum[i]; + y[i] = yNew/wSum[i]; + } + } + } + + /// + /// Convenience method for generating a weight matrix from a distance matrix. + /// Each output entry is the corresponding input entry powered by a constant + /// exponent. + /// + /// A distance matrix. + /// The exponent. + /// A weight matrix. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "d")] + public static double[][] ExponentialWeightMatrix(double[][] d, double exponent) { + ValidateArg.IsNotNull(d, "d"); + double[][] w=new double[d.Length][]; + for (int i = 0; i < d.Length; i++) { + w[i] = new double[d[i].Length]; + for (int j = 0; j < d[i].Length; j++) { + if(d[i][j]>0) + w[i][j] = Math.Pow(d[i][j], exponent); + } + } + return w; + } + + + /// + /// Convenience method for all Euclidean distances within two-dimensional + /// positions. + /// + /// Coordinates. + /// Coordinates. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "y"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "x")] + public static double[][] EuclideanDistanceMatrix(double[] x, double[] y) { + ValidateArg.IsNotNull(x, "x"); + ValidateArg.IsNotNull(y, "y"); + double[][] d = new double[x.Length][]; + for (int i = 0; i < x.Length; i++) { + d[i] = new double[x.Length]; + for (int j = 0; j < x.Length; j++) { + d[i][j] = Math.Sqrt(Math.Pow(x[i] - x[j], 2) + Math.Pow(y[i] - y[j], 2)); + } + } + return d; + } + + + + + + /// + /// Approximation to classical multidimensional scaling. + /// Computes two-dimensional coordinates + /// for a given rectangular distance matrix. + /// + /// The distance matrix. + /// The first eigenvector (scaled by the root of its eigenvalue) + /// The second eigenvector (scaled by the root of its eigenvalue) + /// index of pivots + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "y"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "x"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "d"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#")] + internal static void LandmarkClassicalScaling(double[][] d, out double[] x, out double[] y, int[] pivotArray ) { + + double[][] c=new double[d.Length][]; + for (int i = 0; i < d.Length; i++) { + c[i]=new double[d.Length]; + for (int j = 0; j < d.Length; j++) { + c[i][j]=d[i][pivotArray[j]]; + } + } + SquareEntries(c); + double[] mean=new double[d.Length]; + for (int i = 0; i < d.Length; i++) { + for (int j = 0; j < d.Length; j++) { + mean[i] += c[i][j]; + } + mean[i] /= d.Length; + } + DoubleCenter(c); + Multiply(c, -.5); + double[] u1, u2; + double lambda1, lambda2; + SpectralDecomposition(c, out u1, out lambda1, out u2, out lambda2); + lambda1 = Math.Sqrt(Math.Abs(lambda1)); + lambda2 = Math.Sqrt(Math.Abs(lambda2)); + + /* + for (int i = 0; i < u1.Length; i++) { + u1[i] *= lambda1; + u2[i] *= lambda2; + } + */ + + // place non-pivots by weighted barycenter + x=new double[d[0].Length]; + y=new double[d[0].Length]; + for (int i = 0; i < x.Length; i++) { + for (int j = 0; j < c.Length; j++) { + x[i] -= u1[j] * (Math.Pow(d[j][i], 2) - mean[j]) / 2; + y[i] -= u2[j] * (Math.Pow(d[j][i], 2) - mean[j]) / 2; + } + } + } + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/MultidimensionalScaling.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/MultidimensionalScaling.cs.meta new file mode 100644 index 000000000..00b703aa8 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/MultidimensionalScaling.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b37844ac4a386a44bae99cb46db4d6ef +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval.meta new file mode 100644 index 000000000..2cda7452f --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5ff56b9b6d3f60c4f98da8fedcee1cb8 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/BorderInfo.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/BorderInfo.cs new file mode 100644 index 000000000..ea5c38e7d --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/BorderInfo.cs @@ -0,0 +1,252 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// (c) Microsoft Corporation. All rights reserved. +// +// +// MSAGL class for Cluster border information. +// +// -------------------------------------------------------------------------------------------------------------------- + +using System; +using System.Globalization; + +namespace Microsoft.Msagl.Core.Geometry +{ + using System.Diagnostics; + + + /// + /// Specifies information for one of the four borders of a rectangular cluster. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")] + public struct BorderInfo + { + /// + /// Space between the border and any nodes or clusters within it, in addition + /// to any internode padding specified for the ConstraintGenerator. This effectively + /// sets the outer border margin to the outermost node's outer border plus this + /// InnerMargin (unless fixed, in which case the FixedPosition is the outer border + /// and it is assumed to include space for InnerMargin). + /// Does not apply to nodes or clusters outside the cluster. + /// + public double InnerMargin { get; set; } + + /// + /// A fixed border position calculated by the application; nodes and clusters + /// inside or outside the border will move in relation to it but the border + /// remains stationary. This is the axis coordinate of the outer border edge + /// of the cluster on this side. This value is NoFixedPosition if not set, and + /// may be set to NoFixedPosition to clear it. In order for the position to + /// remain "fixed", set Weight to some large value, such as DefaultFixedWeight. + /// + public double FixedPosition { get; set; } + + /// + /// Border weight; set high to enforce FixedPosition. By default it is low + /// to allow the border to move freely, sizing the cluster according to the + /// movement of its contained nodes and clusters. + /// + public double Weight + { + get { return this.borderWeight; } + set + { + if (value <= 0.0) + { + throw new ArgumentOutOfRangeException("value" +#if DEBUG + , @"Weight must be greater than zero" +#endif // DEBUG + ); + } + this.borderWeight = value; + } + } + double borderWeight; + + /// + /// Returns whether FixedPosition has been set. + /// + public bool IsFixedPosition + { + get { return !Double.IsNaN(FixedPosition) && (Weight > 0.0); } + } + + /// + /// Default weight for an unfixed border's Weight property; the property may be overridden + /// by the application. + /// + public static double DefaultFreeWeight + { + get { return OverlapRemovalGlobalConfiguration.ClusterDefaultFreeWeight; } + } + /// + /// Default weight for a fixed border's Weight property; the property may be overridden + /// by the application. + /// + public static double DefaultFixedWeight + { + get { return OverlapRemovalGlobalConfiguration.ClusterDefaultFixedWeight; } + } + + /// + /// Value gotten from or set to FixedPosition indicating that it's not set. + /// + public static double NoFixedPosition + { + get { return Double.NaN; } + } + + /// + /// Sets the border to fixed (resistant to movement). + /// + /// desired position + /// coefficient of allowed movement relative to other terms; higher + /// weight is more resistant to movement. High-weight borders can still move each other + /// due to constraint satisfaction of intervening clusters/variables. + public void SetFixed(double position, double weight) + { + this.FixedPosition = position; + this.Weight = weight; + } + + /// + /// Sets the border to unfixed (freely moving). + /// + public void SetUnfixed() + { + this.FixedPosition = NoFixedPosition; + this.Weight = DefaultFreeWeight; + } + + /// + /// Constructor taking only a margin-width value. + /// + /// + public BorderInfo(double innerMargin) : this(innerMargin, NoFixedPosition, DefaultFreeWeight) { } + + /// + /// Constructor taking values for margin, fixed position, and weight. + /// + /// + /// + /// + public BorderInfo(double innerMargin, double fixedPosition, double weight) + : this() + { + this.InnerMargin = innerMargin; + this.FixedPosition = fixedPosition; + this.Weight = weight; + } + + internal void EnsureWeight() + { + // Weight must be > 0.0 (we'll divide by this later); use DefaultFreeWeight or DefaultFixedWeight, + // or call one of the parameterized ctors or SetFixed/SetUnfixed. Assert this for DEBUG builds + // but handle the default case for release. + Debug.Assert(this.Weight > 0.0, "BorderInfo.Weight must be > 0.0; use DefaultFreeWeight or DefaultFixedWeight or a parameterized ctor"); + if (0.0 == this.Weight) + { + // This is probably the default ctor that we can't override in a struct so default to unfixed. + this.Weight = DefaultFreeWeight; + if (0.0 == this.FixedPosition) + { + this.FixedPosition = NoFixedPosition; + } + } + } + + /// + /// Generate a string representation of the BorderInfo. + /// + /// A string representation of the BorderInfo. + public override string ToString() + { + return String.Format(CultureInfo.InvariantCulture + , "m {0:F5} p {1:F5} w {2:F5}" + , this.InnerMargin, this.FixedPosition, this.Weight); + } + + #region RequiredOverridesForStruct + // These probably aren't compared but it is not expensive to provide the overrides, + // especially given the Reflection issue if they are compared without these, as + // described in FxCop rule CA1815. + + // Omitting getHashCode violates rule: OverrideGetHashCodeOnOverridingEquals. + /// + /// Return a hashcode based upon data members. + /// + /// + public override int GetHashCode() + { + return InnerMargin.GetHashCode() ^ FixedPosition.GetHashCode() ^ Weight.GetHashCode(); + } + + // Omitting any of the following violates rule: OverrideEqualsAndOperatorEqualsOnValueTypes + /// + /// Compare objects based upon data members. + /// + /// Object to be compared to the current object. + /// + public override bool Equals(Object obj) + { + if (!(obj is BorderInfo)) + return false; + var bi = (BorderInfo)obj; + return (bi.FixedPosition == this.FixedPosition) + && (bi.Weight == this.Weight) + && (bi.InnerMargin == this.InnerMargin); + } + /// + /// Compare two BorderInfo objects for equality of data members. + /// + /// + public static bool operator ==(BorderInfo left, BorderInfo right) + { + return left.Equals(right); + } + + /// + /// Compare two BorderInfo objects for inequality of data members. + /// + /// + public static bool operator !=(BorderInfo left, BorderInfo right) + { + return !(left == right); + } + + /// + /// Compare two BorderInfo objects for ordering based upon data members. + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates")] + public static bool operator <(BorderInfo left, BorderInfo right) + { + if (left.FixedPosition < right.FixedPosition) + { + return true; + } + if (left.FixedPosition > right.FixedPosition) + { + return false; + } + if (left.Weight < right.Weight) + { + return true; + } + if (left.Weight > right.Weight) + { + return false; + } + return (left.InnerMargin < right.InnerMargin); + } + + /// Compare two BorderInfo objects for ordering based upon data members. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates")] + public static bool operator >(BorderInfo left, BorderInfo right) + { + return right < left; + } + #endregion // RequiredOverridesForStruct + } +} \ No newline at end of file diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/BorderInfo.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/BorderInfo.cs.meta new file mode 100644 index 000000000..6b8e4528d --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/BorderInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: da823a720f637e34daaa0eb1fa329a64 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/ConstraintGenerator.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/ConstraintGenerator.cs new file mode 100644 index 000000000..b9f796ac4 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/ConstraintGenerator.cs @@ -0,0 +1,326 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// (c) Microsoft Corporation. All rights reserved. +// +// +// MSAGL main class for Overlap removal constraint generation for Projection solutions. +// +// -------------------------------------------------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Microsoft.Msagl.Core.Geometry +{ + /// + /// ConstraintGenerator is the driving class for overlap removal. The caller + /// adds variables (so it is similar to ProjectionSolver in that way, and in + /// fact the variables added here are passed to the ProjectionSolver to solve + /// the generated constraints). + /// + public class ConstraintGenerator + { + // A ClusterHierarchy is simply a Cluster (there is no ClusterHierarchy class). + // This contains always at least one hierarchy, the DefaultClusterHierarchy, which is + // created in the ConstraintGenerator constructor. All nodes live in a ClusterHierarchy; + // AddNode and AddCluster pass the appropriate cluster (DefaultClusterHierarchy, + // a ClusterHierarchy created by AddCluster with a null parent Cluster, or a child cluster + // of one of these clusters). Thus there is no concept of a single "Root Cluster"; rather, + // each cluster in clusterHierarchies is the root of a separate hierarchy. + // + // We only generate constraints between objects in the same ClusterHierarchy. Because we + // enumerate all ClusterHierarchies and recurse into each one, each Hierarchy is completely + // unaware of the others. Hierarchies added via AddCluster can be sparse. + /// + /// Read-only enumeration of the ClusterHierarchies; new cluster hierarchies are created + /// by calling AddCluster + /// + public IEnumerable ClusterHierarchies + { + get { return this.clusterHierarchies; } + } + readonly List clusterHierarchies = new List(); + + /// + /// The initial, default ClusterHierarchy; a "flat" graph (with no user-defined clusters) + /// lives entirely in this cluster. + /// + public OverlapRemovalCluster DefaultClusterHierarchy { get { return this.clusterHierarchies[0]; } } + + // This is the padding in the relevant direction, and the perpendicular padding that is + // used if we are doing horizontal constraints (for the "amount of movement" comparisons). + // It also includes fixed-border specifications. + /// + /// Padding in the direction of the primary axis. + /// + public double Padding { get; private set; } + + /// + /// Padding in the secondary (Perpendicular) axis. + /// + public double PaddingP { get; private set; } + + /// + /// Padding outside clusters in the parallel direction. + /// + public double ClusterPadding { get; private set; } + + /// + /// Padding outside clusters in the perpendicular direction. + /// + public double ClusterPaddingP { get; private set; } + + /// + /// Default padding value that is used (in both axes) if no padding is specified when + /// calling the ConstraintGenerator constructor. + /// + public static double DefaultPadding { get { return 7.0; } } + + // An identifier to avoid duplicates in the ScanLine tree (otherwise the first + // one encountered gets all the neighbours). This sequence is shared with Clusters, + // which are derived from Node; each Cluster consumes 3 IDs, one for the cluster + // itself and one for each of its fake border nodes. + uint nextNodeId; + + /// + /// As passed to ctor; if this is true, we are doing horizontal (x) constraint generation, + /// and must therefore consider whether a smaller vertical movement would remove the overlap. + /// + public bool IsHorizontal { get; private set; } + + /// + /// This form of the constructor uses default values for the padding parameters. + /// Whether to generate horizontal or vertical constraints + /// + public ConstraintGenerator(bool isHorizontal) : this(isHorizontal, DefaultPadding, DefaultPadding) { } + + /// + /// This form of the constructor uses specifies the padding parameters. + /// Whether to generate horizontal or vertical constraints + /// Padding outside nodes in the parallel direction + /// Padding outside nodes in the perpendicular direction + /// Padding outside clusters in the parallel direction + /// Padding outside clusters in the perpendicular direction + /// + public ConstraintGenerator(bool isHorizontal, double padding, double paddingP, double clusterPadding, double clusterPaddingP) + { + ValidateArg.IsPositive(padding, "padding"); + this.IsHorizontal = isHorizontal; + this.Padding = padding; + this.PaddingP = paddingP; + this.ClusterPadding = clusterPadding; + this.ClusterPaddingP = clusterPaddingP; + + // Create the DefaultClusterHierarchy. + this.clusterHierarchies.Add(new OverlapRemovalCluster(0, null /* parentCluster */, 0 /* default userData is 0 id */, Padding, PaddingP)); + this.nextNodeId += OverlapRemovalCluster.NumInternalNodes; + } + + /// + /// Alternate form of the constructor to allow overriding the default padding. + /// + /// Whether to generate horizontal or vertical constraints + /// Minimal space between node or cluster rectangles in the primary axis. + /// Minimal space between node or cluster rectangles in the secondary (Perpendicular) axis; + /// used only when isHorizontal is true, to optimize the direction of movement. + public ConstraintGenerator(bool isHorizontal, double padding, double paddingP) : this(isHorizontal, padding, paddingP, padding, paddingP) { } + + /// + /// Add a new variable to the ConstraintGenerator. + /// + /// The cluster this node is to be a member of. It may not be null; pass + /// DefaultClusterHierarchy to create a node at the lowest level. Subsequently a node + /// may be added to additional clusters, but only to one cluster per hierarchy. + /// An object that is passed through. + /// Position of the node in the primary axis; if isHorizontal, it contains horizontal + /// position and size, else it contains vertical position and size. + /// Size of the node in the primary axis. + /// Position of the node in the secondary (Perpendicular) axis. + /// Size of the node in the secondary (Perpendicular) axis. + /// Weight of the node (indicates how freely it should move). + /// The created node. + public OverlapRemovalNode AddNode(OverlapRemovalCluster initialCluster, object userData, double position, + double positionP, double size, double sizeP, double weight) + { + ValidateArg.IsNotNull(initialCluster, "initialCluster"); + // @@PERF: Currently every node will have at least one constraint generated if there are any + // other nodes along its line, regardless of whether the perpendicular coordinates result in overlap. + // It might be worthwhile to add a check to avoid constraint generation in the case that there cannot + // be such an overlap on a line, or if the nodes are separated by some amount of distance. + Debug.Assert(null != initialCluster, "initialCluster must not be null"); + var nodNew = new OverlapRemovalNode(this.nextNodeId++, userData, position, positionP, size, sizeP, weight); + initialCluster.AddNode(nodNew); + return nodNew; + } + + /// + /// Creates a new cluster with no minimum size within the specified parent cluster. Clusters allow creating a subset of + /// nodes that must be within a distinct rectangle. + /// + /// The cluster this cluster is to be a member of; if null, this is the root of a + /// new hierarchy, otherwise must be non-NULL (perhaps DefaultClusterHierarchy). + /// An object that is passed through. + /// Information about the Left (if isHorizontal, else Top) border. + /// Information about the Right (if isHorizontal, else Bottom) border. + /// Same as OpenBorder, but in the secondary (Perpendicular) axis. + /// Same as CloseBorder, but in the secondary (Perpendicular) axis. + /// The new Cluster. + /// + public OverlapRemovalCluster AddCluster(OverlapRemovalCluster parentCluster, object userData, + BorderInfo openBorderInfo, BorderInfo closeBorderInfo, + BorderInfo openBorderInfoP, BorderInfo closeBorderInfoP) + { + return AddCluster(parentCluster, userData, 0.0 /*minSize*/, 0.0 /*minSizeP*/, + openBorderInfo, closeBorderInfo, openBorderInfoP, closeBorderInfoP); + } + + /// + /// Creates a new cluster with a minimum size within the specified parent cluster. Clusters allow creating a subset of + /// nodes that must be within a distinct rectangle. + /// + /// The cluster this cluster is to be a member of; if null, this is the root of a + /// new hierarchy, otherwise must be non-NULL (perhaps DefaultClusterHierarchy). + /// An object that is passed through. + /// Minimum cluster size along the primary axis. + /// Minimum cluster size along the perpendicular axis. + /// Information about the Left (if isHorizontal, else Top) border. + /// Information about the Right (if isHorizontal, else Bottom) border. + /// Same as OpenBorder, but in the secondary (Perpendicular) axis. + /// Same as CloseBorder, but in the secondary (Perpendicular) axis. + /// The new Cluster. + /// + public OverlapRemovalCluster AddCluster(OverlapRemovalCluster parentCluster, object userData, + double minimumSize, double minimumSizeP, + BorderInfo openBorderInfo, BorderInfo closeBorderInfo, + BorderInfo openBorderInfoP, BorderInfo closeBorderInfoP) + { + var newCluster = new OverlapRemovalCluster(this.nextNodeId, parentCluster, userData, minimumSize, minimumSizeP, + this.Padding, this.PaddingP, this.ClusterPadding, this.ClusterPaddingP, + openBorderInfo, closeBorderInfo, openBorderInfoP, closeBorderInfoP); + this.nextNodeId += OverlapRemovalCluster.NumInternalNodes; + if (null == parentCluster) + { + this.clusterHierarchies.Add(newCluster); + } + else + { + // @@DCR: Enforce that Clusters live in only one hierarchy - they can have only one parent, so add a + // Cluster.parentCluster to enforce this. + parentCluster.AddNode(newCluster); + } + return newCluster; + } + + /// + /// Add a Cluster with default border information and no minimum sizes. + /// + /// The cluster this cluster is to be a member of; if null, this is the root of a + /// new hierarchy, otherwise must be non-NULL (perhaps DefaultClusterHierarchy). + /// An object that is passed through. + /// The new Cluster. + public OverlapRemovalCluster AddCluster(OverlapRemovalCluster parentCluster, object userData) + { + return AddCluster(parentCluster, userData, + new BorderInfo(0.0), new BorderInfo(0.0), + new BorderInfo(0.0), new BorderInfo(0.0)); // 0.0 margins + } + + /// + /// Add a Cluster with default border information and specified minimum sizes. + /// + /// The cluster this cluster is to be a member of; if null, this is the root of a + /// new hierarchy, otherwise must be non-NULL (perhaps DefaultClusterHierarchy). + /// An object that is passed through. + /// Minimum cluster size along the primary axis. + /// Minimum cluster size along the perpendicular axis. + /// The new Cluster. + public OverlapRemovalCluster AddCluster(OverlapRemovalCluster parentCluster, object userData, + double minimumSize, double minimumSizeP) + { + return AddCluster(parentCluster, userData, minimumSize, minimumSizeP, + new BorderInfo(0.0), new BorderInfo(0.0), + new BorderInfo(0.0), new BorderInfo(0.0)); // 0.0 margins + } + + /// + /// Add a node to a cluster in another hierarchy (a node can be in only one cluster per hierarchy). + /// + /// + /// + // @@DCR: Keep a node->hierarchyParentsList hash and use cluster.parentCluster to traverse to the hierarchy root + // to verify the node is in one cluster per hierarchy. This will require that the function be + // non-static, hence the rule suppression. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")] + public void AddNodeToCluster(OverlapRemovalCluster cluster, OverlapRemovalNode node) + { + // Node derives from Cluster so make sure we don't have this - the only way to create + // cluster hierarchies is by AddCluster. + ValidateArg.IsNotNull(cluster, "cluster"); + if (node is OverlapRemovalCluster) + { + throw new InvalidOperationException( +#if DEBUG + "Argument 'node' must not be a Cluster" +#endif // DEBUG + ); + } + cluster.AddNode(node); + } + + /// + /// Generate the necessary constraints to ensure there is no overlap (unless we're doing + /// a horizontal pass and deferring some movement, which would be smaller, to the vertical pass). + /// + /// The solver to generate into. + /// Parameters to OverlapRemoval and ProjectionSolver.Solver.Solve(). + public void Generate(ProjectionSolver.Solver solver, OverlapRemovalParameters parameters) + { + ValidateArg.IsNotNull(solver, "solver"); + if (null == parameters) + { + parameters = new OverlapRemovalParameters(); + } + foreach (OverlapRemovalCluster cluster in this.clusterHierarchies) + { + cluster.Generate(solver, parameters, this.IsHorizontal); + } + + // For Clusters we reposition their "fake border" variables between the constraint-generation + // and solving phases, so we need to tell the solver to do this. + solver.UpdateVariables(); // @@PERF: Not needed if no clusters were created. + } + + /// + /// Generates and solves the constraints. + /// + /// The solver to generate into and solve. May be null, in which case one + /// is created by the method. + /// Parameters to OverlapRemoval and ProjectionSolver.Solver.Solve(). + /// Generate constraints before solving; if false, solver is assumed to + /// have already been populated by this.Generate(). + /// The set of OverlapRemoval.Constraints that were unsatisfiable, or NULL. + public ProjectionSolver.Solution Solve(ProjectionSolver.Solver solver, + OverlapRemovalParameters parameters, bool doGenerate) + { + if (null == solver) + { + solver = new ProjectionSolver.Solver(); + } + if (null == parameters) + { + parameters = new OverlapRemovalParameters(); + } + if (doGenerate) + { + Generate(solver, parameters); + } + ProjectionSolver.Solution solverSolution = solver.Solve(parameters.SolverParameters); + foreach (OverlapRemovalCluster cluster in this.clusterHierarchies) + { + cluster.UpdateFromVariable(); // "recursively" processes all child clusters + } + return solverSolution; + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/ConstraintGenerator.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/ConstraintGenerator.cs.meta new file mode 100644 index 000000000..f687b90cd --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/ConstraintGenerator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e95d0690e0157a74c80a7e7acf798235 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemoval Design.docx b/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemoval Design.docx new file mode 100644 index 0000000000000000000000000000000000000000..d7539654696237f8fa3b56a62669da97145826d7 GIT binary patch literal 76325 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 literal 0 HcmV?d00001 diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemoval Design.docx.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemoval Design.docx.meta new file mode 100644 index 000000000..2a8a4120f --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemoval Design.docx.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 6d043c85369bb7341ab6b6b13316eec2 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalCluster.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalCluster.cs new file mode 100644 index 000000000..ab8ae0d9c --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalCluster.cs @@ -0,0 +1,1100 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// (c) Microsoft Corporation. All rights reserved. +// +// +// MSAGL Cluster class for Overlap removal constraint generation for Projection solutions. +// +// -------------------------------------------------------------------------------------------------------------------- + +// Remove this from project build and uncomment here to selectively enable per-class. +//#define VERBOSE + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using Microsoft.Msagl.Core.ProjectionSolver; + +// +// How Clusters Work +// +// As in the doc, we extend the existing constraint-generation mechanism by processing a +// Cluster as a normal Node (which it inherits from), adding a pair of fake variables +// (corresponding to its borders along the primary axis) to the Solver for it. This +// allows us to generate Constraints between Nodes and Clusters at the same level +// (i.e. within the same Cluster). Clusters may contain Nodes which are other Clusters; +// these form a tree starting at the root of a ClusterHierarchy. Multiple Cluster +// Hierarchies may exist; each has its constraints evaluated separately from all other +// ClusterHierarchies, thereby allowing intersecting clusters; a common example of this +// is the "layer cake", where # are horizontal cluster borders and =+| are vertical: +// +===+ +===+ +===+ +// | 0 | | 1 | | 2 | +// ##|###|#|###|#|###|## +// # | 3 | | 4 | | 5 | # +// ##|###|#|###|#|###|## +// | | | | | | +// ##|###|#|###|#|###|## +// # | 6 | | 7 | | 8 | # +// ##|###|#|###|#|###|## +// | 9 | | 10| | 11| +// +===+ +===+ +===+ +// +// In terms of flow, we process in a depth-first manner, going down through all Clusters +// to the lowest level before calling the Solver. Constraints can be generated in either +// the top-down or bottom-up order; doing it bottom-up allows us to extend this to support +// a mode that uses a Solver at the level of each cluster to solve within the cluster so +// we know the location and size of the Cluster *after* its internal nodes have been moved +// to their "solved" positions (@@DCR; see "Precalculate Cluster Sizes" in the design doc). +// +// Within each Cluster, we add a "fake node" to the event list, one for each Left (Top) or +// Right (Bottom) border, and then generate constraints using the scan-line algorithm in the doc. +// Using the two "fake nodes" allows the within-border constraints to fall out automatically +// from the algorithm. +// +// After a Cluster's component Clusters have been solved in this way, then its Clusters are +// processed as simple Nodes in its parent Cluster, including the horizontal/vertical decision +// on least-movement direction. However, when the Constraints are generated, they're generated +// to the left or right border "fake nodes" rather than to the single node for the cluster. +// We need two nodes here so the cluster can grow or shrink along the axis. +// +// This proceeds until we back up to the root cluster of the ClusterHierarchy. +// +namespace Microsoft.Msagl.Core.Geometry +{ + /// + /// A cluster is a structure that acts as a Node for Nodes and Clusters at a sibling level, + /// and can also contain other Clusters and/or Nodes. + /// + public partial class OverlapRemovalCluster : OverlapRemovalNode + { + // Our internal Node list - some of which may be Clusters. + readonly List nodeList = new List(); + + // Our internal child Cluster list - duplicated from nodeList for iterative recursion perf. + readonly List clusterList = new List(); + + /// + /// Empty clusters are ignored on positioning. + /// + public bool IsEmpty { get { return 0 == this.nodeList.Count; } } + + /// + /// If the following is true then constraints will be generated the prevent children coming + /// any closer to the cluster boundaries. In effect, this means that the cluster and all + /// it's children will be translated together rather than being "compressed" if there are + /// overlaps with external nodes. + /// + // AKA: "Bump Mode" + public bool TranslateChildren { get; set; } + + // Our internal "fake nodes" as above; these are separate from the size calculations + // for the overall Cluster. + + /// + /// The internal Node containing the Variable to which left-border constraints are made. + /// + public OverlapRemovalNode LeftBorderNode { get; private set; } + + /// + /// The internal Node containing the Variable to which right-border constraints are made. + /// + public OverlapRemovalNode RightBorderNode { get; private set; } + + // Indicates if the cluster's GenerateWorker placed anything into the solver. + bool IsInSolver { get; set; } + + /// + /// Opening margin of this cluster (additional space inside the cluster border) + /// along the primary axis; on Left if horizontal, else on Top. + /// + public BorderInfo OpenBorderInfo { get; private set; } + + /// + /// Closing margin of this cluster (additional space inside the cluster border) + /// along the primary axis; on Right if horizontal, else on Bottom. + /// + public BorderInfo CloseBorderInfo { get; private set; } + + /// + /// Opening margin of this cluster (additional space inside the cluster border) + /// along the secondary (Perpendicular) axis; on Top if horizontal, else on Left. + /// + public BorderInfo OpenBorderInfoP { get; private set; } + + /// + /// Closing margin of this cluster (additional space inside the cluster border) + /// along the secondary (Perpendicular) axis; on Bottom if horizontal, else on Right. + /// + public BorderInfo CloseBorderInfoP { get; private set; } + + /// + /// Minimum size along the primary axis. + /// + public double MinimumSize { get; set; } + + /// + /// Minimum size along the perpendicular axis. + /// + public double MinimumSizeP { get; set; } + + /// + /// Padding of nodes within the cluster in the parallel direction. + /// + public double NodePadding { get; set; } + + /// + /// Padding of nodes within the cluster in the perpendicular direction. + /// + public double NodePaddingP { get; set; } + + /// + /// Padding outside the cluster in the parallel direction. + /// + public double ClusterPadding { get; set; } + + /// + /// Padding outside the cluster in the perpendicular direction. + /// + public double ClusterPaddingP { get; set; } + +#if VERBOSE + string Name { + get { return "Clus_" + (this.IsRootCluster ? "Root" : (this.LeftBorderNode.UserData + "-" + this.RightBorderNode.UserData)); } + } +#endif // VERBOSE + + // The number of node IDs used by a Cluster - for the cluster itself and its fake nodes. + internal static uint NumInternalNodes + { + get { return 3; } + } + + // The width (height) of the node along the primary axis, which should be fairly thin + // (along the secondary (perpendicular) axis, it is the full size of the cluster). + internal static double DefaultBorderWidth + { + get { return OverlapRemovalGlobalConfiguration.ClusterDefaultBorderWidth; } + } + + /// + /// The Root Cluster is a special case, functioning as the "infinite" root cluster of a hierarchy + /// with no border nodes. If a size is desired then create a single cluster in the root. Leaving + /// the root cluster "infinite" means we don't have to generate the constraints for nodes and clusters + /// in the root, which may be numerous. + /// + public bool IsRootCluster { get { return null == this.ParentCluster; } } + OverlapRemovalCluster ParentCluster { get; set; } + + // Zero cluster margins. This ctor is currently used only by the generator's DefaultClusterHierarchy, + // which by default is created with non-fixed borders and no margins. + internal OverlapRemovalCluster(uint id, OverlapRemovalCluster parentCluster, object userData, double padding, double paddingP) + : this(id, parentCluster, userData, 0, 0, padding, paddingP, 0, 0, new BorderInfo(0.0), new BorderInfo(0.0), new BorderInfo(0.0), new BorderInfo(0.0)) + { + } + + internal OverlapRemovalCluster(uint id, OverlapRemovalCluster parentCluster, + object userData, double minSize, double minSizeP, + double nodePadding, double nodePaddingP, double clusterPadding, double clusterPaddingP, + BorderInfo openBorderInfo, BorderInfo closeBorderInfo, + BorderInfo openBorderInfoP, BorderInfo closeBorderInfoP) + : base(id, userData) + { + this.MinimumSize = minSize; + this.MinimumSizeP = minSizeP; + this.NodePadding = nodePadding; + this.NodePaddingP = nodePaddingP; + this.ClusterPadding = clusterPadding; + this.ClusterPaddingP = clusterPaddingP; + this.ParentCluster = parentCluster; + this.OpenBorderInfo = openBorderInfo; + this.OpenBorderInfo.EnsureWeight(); + this.CloseBorderInfo = closeBorderInfo; + this.CloseBorderInfo.EnsureWeight(); + this.OpenBorderInfoP = openBorderInfoP; + this.OpenBorderInfoP.EnsureWeight(); + this.CloseBorderInfoP = closeBorderInfoP; + this.CloseBorderInfoP.EnsureWeight(); + CreateBorderNodes(); + } + + void CreateBorderNodes() + { + if (!this.IsRootCluster) + { + string strNodeIdL = null, strNodeIdR = null; +#if VERIFY || VERBOSE + strNodeIdL = "L" + this.UserDataString; + strNodeIdR = "R" + this.UserDataString; +#endif // VERIFY || VERBOSE + this.LeftBorderNode = new OverlapRemovalNode(this.Id + 1, strNodeIdL); + this.RightBorderNode = new OverlapRemovalNode(this.Id + 2, strNodeIdR); + } + } + + // Enumerates only cluster children of this cluster. + internal IEnumerable Clusters { get { return this.clusterList; } } + + /// + /// Generate a string representation of the Cluster. + /// + /// A string representation of the Cluster. + public override string ToString() + { + // Currently this is just the same as the base Node; all zero if we haven't + // yet called Solve(), else the values at the last time we called Solve(). + return string.Format(System.Globalization.CultureInfo.InvariantCulture, + "Cluster '{0}': id {1} p {2:F5} s {3:F5} pP {4:F5} sP {5:F5}", + this.UserDataString, this.Id, this.Position, this.Size, this.PositionP, this.SizeP); + } + + // newNode may be a cluster in which case we add it to the cluster list. We never call this to + // add the fake border nodes to nodeList; the caller never sees them. + internal void AddNode(OverlapRemovalNode newNode) + { + this.nodeList.Add(newNode); + var newCluster = newNode as OverlapRemovalCluster; + if (null != newCluster) + { + this.clusterList.Add(newCluster); + } + } + + // Adds an open/close event pair for the node. paddingP is either cluster or node padding. + void AddEvents(OverlapRemovalNode node, List events) + { + // Add/subtract only half the padding so they meet in the middle of the padding. + events.Add(new Event(true /* fForOpen */, node, node.OpenP - (NodePaddingP / 2))); + events.Add(new Event(false /* fForOpen */, node, node.CloseP + (NodePaddingP / 2))); + } + + // This is internal rather than so Test_OverlapRemoval can see it. + internal static double CalcBorderWidth(double margin) + { + // Margin applies only to the inside edge. + if (margin > 0.0) + { + return margin; + } + return DefaultBorderWidth; + } + + // For iterative recursion (though we do not expect deeply nested clusters). + class ClusterItem + { + internal readonly OverlapRemovalCluster Cluster; + internal bool ChildrenHaveBeenPushed; + internal ClusterItem(OverlapRemovalCluster cluster) + { + this.Cluster = cluster; + } + } + + internal delegate void ClusterDelegate(OverlapRemovalCluster cluster); + + static void ProcessClusterHierarchy(OverlapRemovalCluster root, ClusterDelegate worker) + { + var stack = new Stack(); + stack.Push(new ClusterItem(root)); + while (stack.Count > 0) + { + // Keep the cluster on the stack until we're done with its children. + var item = stack.Peek(); + int prevStackCount = stack.Count; + if (!item.ChildrenHaveBeenPushed) + { + item.ChildrenHaveBeenPushed = true; + foreach (var childCluster in item.Cluster.Clusters) + { + stack.Push(new ClusterItem(childCluster)); + } + if (stack.Count > prevStackCount) + { + continue; + } + } // endif !node.ChildrenHaveBeenPushed + + // No children to push so pop and process this cluster. + Debug.Assert(stack.Peek() == item, "stack.Peek() should be 'item'"); + stack.Pop(); + worker(item.Cluster); + } + } + + internal void Generate(Solver solver, OverlapRemovalParameters parameters, bool isHorizontal) + { + ProcessClusterHierarchy(this, + cluster => cluster.IsInSolver = cluster.GenerateWorker(solver, parameters, isHorizontal)); + ProcessClusterHierarchy(this, cluster => cluster.SqueezeNonFixedBorderPositions()); + } + + /// + /// If a border is not fixed swap its position with the opposite border to ensure + /// cluster is tight to its contents. + /// + void SqueezeNonFixedBorderPositions() + { + // Here's an example of why this is necessary: If we base the initial border position + // on a child cluster's border and that child cluster is initially sparse, then its + // size can shrink considerably; leaving the border of this cluster further out than + // it needs to be (since we expand space between nodes but don't necessarily shrink it). + // Squeezing causes constraints to be *minimally* enforced. Note - this may introduce + // a defer-to-vertical issue, so we special-case that section to ignore border nodes). + if (this.IsEmpty || this.IsRootCluster || !this.IsInSolver) + { + return; + } + + double leftBorderDesiredPos = this.LeftBorderNode.Position; + double rightBorderDesiredPos = this.RightBorderNode.Position; + if (!this.OpenBorderInfo.IsFixedPosition) + { + this.LeftBorderNode.Variable.DesiredPos = rightBorderDesiredPos; + } + if (!this.CloseBorderInfo.IsFixedPosition) + { + this.RightBorderNode.Variable.DesiredPos = leftBorderDesiredPos; + } + } + + // Returns false if the cluster is empty; this handles nested clusters of empty clusters. + // TODOunit: several of the test files cover this but add a specific test for it. + bool GenerateWorker(Solver solver, OverlapRemovalParameters parameters, + bool isHorizontal) + { + // @@DCR "Precalculate Cluster Sizes": if we are solving per-cluster to calculate best sizes before + // generating constraints, then solver would be passed in as null and we'd create one here. + + // Variables to calculate our boundaries. Top and Bottom refer to the perpendicular direction; + // for vertical, read Top <-> Left and Bottom <-> Right. + var boundaryRect = new Rectangle + { + //Left = + // this.OpenBorderInfo.IsFixedPosition && this.TranslateChildren + // ? this.OpenBorderInfo.FixedPosition + // : double.MaxValue, + //Right = + // this.CloseBorderInfo.IsFixedPosition && this.TranslateChildren + // ? this.CloseBorderInfo.FixedPosition + // : double.MinValue, + //Bottom = + // this.OpenBorderInfoP.IsFixedPosition && this.TranslateChildren + // ? this.OpenBorderInfoP.FixedPosition + // : double.MaxValue, + //Top = + // this.CloseBorderInfoP.IsFixedPosition && this.TranslateChildren + // ? this.CloseBorderInfoP.FixedPosition + // : double.MinValue + Left = double.MaxValue, + Right = double.MinValue, + Bottom = double.MaxValue, + Top = double.MinValue + }; + + if (IsEmpty) + { + // Nothing to generate. + return false; + } + + // The list of open/close events, which will be sorted on the perpendicular coordinate of the event + // (e.g. for horizontal constraint generation, order on vertical position). + var events = this.CreateEvents(solver, ref boundaryRect); + + // If we added no events, we're either Fixed (so continue) or empty (so return). + if (0 == events.Count && !TranslateChildren) + { + return false; + } + + // Top/Bottom are considered the secondary (Perpendicular) axis here. + double leftBorderWidth = DefaultBorderWidth; + double rightBorderWidth = DefaultBorderWidth; + if (!this.IsRootCluster) + { + CalculateBorderWidths(solver, events, boundaryRect, out leftBorderWidth, out rightBorderWidth); +#if VERBOSE + Console.WriteLine(" {0} After CalculateBorderWidths: p {1:F5} s {2:F5} pP {3:F5} sP {4:F5}" + , this.Name, this.Size, this.Position, this.Size, this.SizeP); +#endif + } + + GenerateFromEvents(solver, parameters, events, isHorizontal); + + if (!this.IsRootCluster) + { + // Non-fixed borders are moved later by SqueezeNonFixedBorderPositions(). + this.AdjustFixedBorderPositions(solver, leftBorderWidth, rightBorderWidth, isHorizontal); + } + return true; + } + + List CreateEvents(Solver solver, ref Rectangle boundaryRect) + { + var events = new List(); + int cNodes = this.nodeList.Count; // cache for perf + double leftBorderWidth = CalcBorderWidth(this.OpenBorderInfo.InnerMargin); + double rightBorderWidth = CalcBorderWidth(this.CloseBorderInfo.InnerMargin); + double openBorderWidth = CalcBorderWidth(this.OpenBorderInfoP.InnerMargin); + double closeBorderWidth = CalcBorderWidth(this.CloseBorderInfoP.InnerMargin); + for (int nodeIndex = 0; nodeIndex < cNodes; ++nodeIndex) + { + OverlapRemovalNode node = this.nodeList[nodeIndex]; + var cluster = node as OverlapRemovalCluster; + if (null != cluster) + { + // Child Clusters have already "recursively" been processed before the current cluster, + // so we just need to check to see if it had any events. If so, then it has created its + // two fake nodes (and their variables) along the primary axis, but these are only put + // into the event list at the nested level; at this level, we put Node underlying the + // entire Cluster span (in both directions) into the event list. + + // If a child cluster is empty, it will have zero size and no way to set its position. + // That includes clusters containing nothing but empty clusters. We skip those here. + if (!cluster.IsInSolver) + { + continue; + } + } + else + { + // Not a cluster; just have it add its variable to the solver. + node.CreateVariable(solver); + } + + // Now add the Node to the ScanLine event list. Use paddingP because the scan line moves + // perpendicularly to the direction we're generating the constraints in. + AddEvents(node, events); + + // Update our boundaries if this node goes past any of them. + if (!this.IsRootCluster) + { + double pad = node.Size / 2 + ClusterPadding; + double padP = node.SizeP / 2 + ClusterPaddingP; + double newLeft = node.Position - pad - leftBorderWidth; + double newRight = node.Position + pad + rightBorderWidth; + double newBottom = node.PositionP - padP - openBorderWidth; + double newTop = node.PositionP + padP + closeBorderWidth; + + boundaryRect.Left = Math.Min(boundaryRect.Left, newLeft); + boundaryRect.Right = Math.Max(boundaryRect.Right, newRight); + boundaryRect.Bottom = Math.Min(boundaryRect.Bottom, newBottom); + boundaryRect.Top = Math.Max(boundaryRect.Top, newTop); + +#if VERBOSE + Console.WriteLine(" {0} BoundaryRect after AddEvents: L/R T/B {1:F5}/{2:F5} {3:F5}/{4:F5}" + , this.Name, boundaryRect.Left, boundaryRect.Right, boundaryRect.Top, boundaryRect.Bottom); +#endif + } + } + if (!this.IsRootCluster) + { + // Force the cluster borders to the full minimum sizes if any were specified. + // Without the full cluster boundaries being available at constraint generation time, Tuvalu was + // getting unresolved overlaps when dragging an external node over the corner of a cluster boundary. + double padMinSize = this.MinimumSize - boundaryRect.Width; + if (padMinSize > 0) + { + boundaryRect.PadWidth(padMinSize / 2); + } + double padMinSizeP = this.MinimumSizeP - boundaryRect.Height; + if (padMinSizeP > 0) + { + boundaryRect.PadHeight(padMinSizeP / 2); + } + +#if VERBOSE + Console.WriteLine(" {0} BoundaryRect after CreateEvents: L/R T/B {1:F5}/{2:F5} {3:F5}/{4:F5}" + , this.Name, boundaryRect.Left, boundaryRect.Right, boundaryRect.Top, boundaryRect.Bottom); +#endif + + } + + return events; + } + + void CalculateBorderWidths(Solver solver, List events, Rectangle boundaryRect, + out double leftBorderWidth, out double rightBorderWidth) + { + // Cluster-level padding (the space around the borders) complicates this. Margin + // is added only at the inside edge of the cluster; for example, as space for a + // title of the cluster to be printed. We just use the margin as the boundary node + // sizes. Margin is separate from padding; padding is always added. + leftBorderWidth = CalcBorderWidth(this.OpenBorderInfo.InnerMargin); + rightBorderWidth = CalcBorderWidth(this.CloseBorderInfo.InnerMargin); + + // @@DCR "Precalculate Cluster Sizes": at this point we could solve them to get the "real" cluster + // size (as above, this may be requested by solver being null on input so we create our own above). + + // Now calculate our position (as midpoints) and size. This will be used in the parentCluster's + // Generate() operation. We want to get to the outside border of the border, so subtract the + // border width. We've added pre-border padding above. Note: This is done before checking + // for fixed positions, because we want the constraint generation to see them in the correct + // relative positions - border midpoints are always outside the outermost node midpoints, so that + // constraints will be generated in the correct direction (it would be bad if, for example, a Left + // border was the rhs of a constraint with a node inside the cluster; it should always be an lhs + // to any node in the cluster, and having it as an rhs will probably generate a cycle). We adjust + // to fixed positions below after GenerateFromEvents. + this.Size = boundaryRect.Width; + this.Position = boundaryRect.Center.X; + + // The final perpendicular positions may be modified below, after GenerateFromEvents; they + // will be used by a parent cluster's Generate after we return if this is a recursive call. + // We don't do it here because we are doing the variables internal to this cluster, based + // upon their current positions, so this would get confused if we moved the P coordinates here. + this.SizeP = boundaryRect.Height; + this.PositionP = boundaryRect.Center.Y; + + // Now create the two "fake nodes" for the borders and add them to the event list line and solver. + // This constraint will never be deferred, since there is no overlap in the secondary axis but is + // in the primary axis. In the perpendicular direction, we want them to be the size of the + // outer borders of the outer nodes, regardless of whether the perpendicular borders are + // fixed-position; this ensures that the scan line will correctly see their open and close. + // Left/Open... + this.LeftBorderNode.Position = boundaryRect.Left + (leftBorderWidth / 2); + this.LeftBorderNode.Size = leftBorderWidth; + this.LeftBorderNode.Weight = this.OpenBorderInfo.Weight; + this.LeftBorderNode.PositionP = this.PositionP; + this.LeftBorderNode.SizeP = this.SizeP; + this.LeftBorderNode.CreateVariable(solver); + AddEvents(this.LeftBorderNode, events); + + // Note: The Left/Right, Open/Close terminology here is inconsistent with GenerateFromEvents + // since here Open is in the primary axis and in GenerateFromEvents it's in the secondary/P axis. + + // Right/Close... + this.RightBorderNode.Position = boundaryRect.Right - (rightBorderWidth / 2); + this.RightBorderNode.Size = rightBorderWidth; + this.RightBorderNode.Weight = this.CloseBorderInfo.Weight; + this.RightBorderNode.PositionP = this.PositionP; + this.RightBorderNode.SizeP = this.SizeP; + this.RightBorderNode.CreateVariable(solver); + AddEvents(this.RightBorderNode, events); + } + + void AdjustFixedBorderPositions(Solver solver, double leftBorderWidth, double rightBorderWidth, bool isHorizontal) + { + // Note: Open == Left, Close == Right. + if (this.OpenBorderInfo.IsFixedPosition && this.CloseBorderInfo.IsFixedPosition) + { + // Both are fixed, so just move them to their specified positions. For FixedPosition + // the API is that it's the outer border edge, so add or subtract half the (left|right)BorderWidth + // to set the position to the midpoint. Since both borders are fixed, this provides a + // limit to the size of the overall node. + this.LeftBorderNode.UpdateDesiredPosition( + this.OpenBorderInfo.FixedPosition + (leftBorderWidth / 2)); + this.RightBorderNode.UpdateDesiredPosition( + this.CloseBorderInfo.FixedPosition - (rightBorderWidth / 2)); + this.Size = this.CloseBorderInfo.FixedPosition - this.OpenBorderInfo.FixedPosition; + this.Position = this.OpenBorderInfo.FixedPosition + (this.Size / 2); + } + else if (this.OpenBorderInfo.IsFixedPosition || this.CloseBorderInfo.IsFixedPosition) + { + // One border is fixed and the other isn't. We'll keep the same cluster size, + // move the fixed border to its specified position, adjust our midpoint to reflect that, + // and then move the unfixed border to be immediately adjacent to the fixed border; the + // solver will cause it to be moved to the minimal position satisfying the constraints. + if (this.OpenBorderInfo.IsFixedPosition) + { + // FixedPosition is the outer border edge so add BorderWidth/2 to set it to the Left midpoint. + this.LeftBorderNode.UpdateDesiredPosition( + this.OpenBorderInfo.FixedPosition + (leftBorderWidth / 2)); + this.Position = this.OpenBorderInfo.FixedPosition + (this.Size / 2); + } + else /* this.CloseBorderInfo.IsFixedPosition */ + { + // FixedPosition is the outer border edge so subtract BorderWidth/2 to set it to the Right midpoint. + this.RightBorderNode.UpdateDesiredPosition( + this.CloseBorderInfo.FixedPosition - (rightBorderWidth / 2)); + this.Position = this.CloseBorderInfo.FixedPosition - (this.Size / 2); + } + } + + // If we have a minimum size, generate constraints for it. Although this may change the size + // considerably, so may the movement of variables in the cluster, so we need no precalculation + // of sizes or positions; but after the Horizontal pass, the caller must pass in the resultant + // positions in the Horizontal (perpendicular) BorderInfos parameter to Vertical generation; + // otherwise, because the Horizontal cluster span may be larger than is calculated simply from + // variable positions, some variables may not have appropriate constraints generated. + if (this.MinimumSize > 0.0) + { + Constraint cst = solver.AddConstraint( + this.LeftBorderNode.Variable, this.RightBorderNode.Variable, this.MinimumSize - leftBorderWidth/2 - rightBorderWidth/2); + Debug.Assert(null != cst, "Minimum Cluster size: unexpected null cst"); +#if VERBOSE + Console.WriteLine(" {0} MinClusterSizeCst {1} -> {2} g {3:F5}", isHorizontal ? "H" : "V" + , cst.Left.Name, cst.Right.Name, cst.Gap); +#endif + // VERBOSE + } + + // Now recalculate our perpendicular PositionP/SizeP if either perpendicular border is fixed, + // since we know we're going to move things there. We don't actually create variables for the + // perpendicular axis on this pass, but we set the primary axis border nodes' perpendicular size + // and position, thus creating "virtual" perpendicular borders used by the parent cluster's + // Generate() and for its events in its GenerateFromEvents(). This must be done on both H and V + // passes, because multiple heavyweight Fixed borders can push each other around on the horizontal + // pass and leave excessive space between the fixed border and the outer nodes. In that case the + // Vertical pass can't get the true X border positions by evaluating our nodes' X positions; the + // caller must pass this updated position in (the same thing it must do for nodes' X coordinates). + if (this.OpenBorderInfoP.IsFixedPosition || this.CloseBorderInfoP.IsFixedPosition) + { + // If both are fixed, we'll set to those positions and recalculate size. + // Remember that FixedPosition API is the outer border edge so we don't need to adjust for border width. + if (this.OpenBorderInfoP.IsFixedPosition && this.CloseBorderInfoP.IsFixedPosition) + { + this.SizeP = this.CloseBorderInfoP.FixedPosition - this.OpenBorderInfoP.FixedPosition; + this.PositionP = this.OpenBorderInfoP.FixedPosition + (this.SizeP / 2); + if (this.SizeP < 0) + { + // Open border is to the right of close border; they'll move later, but we have to + // make the size non-negative. TODOunit: create a specific test for this (fixed LRTB) + this.SizeP = -this.SizeP; + } + } + else + { + // Only one is fixed, so we'll adjust in the appropriate direction as needed. + // - If we're on the horizontal pass we'll preserve the above calculation of this.SizeP + // and only shift things around to preserve the relative vertical starting positions; + // running the Solver will change these positions. + // - If we're on the vertical pass, we know the horizontal nodes are in their final positions, + // so we need to accommodate the case described above, where the Solver opened up space + // between the fixed border and the outermost nodes (it will never *reduce* this distance + // of course). This means we adjust both border position and our overall node size. + double curTopOuterBorder = this.PositionP - (this.SizeP / 2); + double curBottomOuterBorder = this.PositionP + (this.SizeP / 2); + if (this.OpenBorderInfoP.IsFixedPosition) + { + if (isHorizontal) + { + // Don't change SizeP. + this.PositionP += this.OpenBorderInfoP.FixedPosition - curTopOuterBorder; + } + else + { + this.SizeP = curBottomOuterBorder - this.OpenBorderInfoP.FixedPosition; + this.PositionP = this.OpenBorderInfoP.FixedPosition + (this.SizeP / 2); + } + } + else + { + if (isHorizontal) + { + // Don't change SizeP. + this.PositionP += this.CloseBorderInfoP.FixedPosition - curBottomOuterBorder; + } + else + { + this.SizeP = this.CloseBorderInfoP.FixedPosition - curTopOuterBorder; + this.PositionP = curTopOuterBorder + (this.SizeP / 2); + } + } + } // endifelse both borders fixed or only one border is + + // Now update our fake border nodes' PositionP/SizeP to be consistent. + this.LeftBorderNode.PositionP = this.PositionP; + this.LeftBorderNode.SizeP = this.SizeP; + this.RightBorderNode.PositionP = this.PositionP; + this.RightBorderNode.SizeP = this.SizeP; + } + } + + // end Generate() + + // Get the Node to use in generating constraints: + // - If the Node is not a Cluster, then use the Node. + // - Else if it is being operated on as the left neighbour, use its right border as the + // variable FROM which we create the constraint. + // - Else it is being operated on as the right neighbour, so use its left border as the + // variable TO which we create the constraint. + internal static OverlapRemovalNode GetLeftConstraintNode(OverlapRemovalNode node) + { + var cluster = node as OverlapRemovalCluster; + return (null != cluster) ? cluster.RightBorderNode : node; + } + internal static OverlapRemovalNode GetRightConstraintNode(OverlapRemovalNode node) + { + var cluster = node as OverlapRemovalCluster; + return (null != cluster) ? cluster.LeftBorderNode : node; + } + + void GenerateFromEvents(Solver solver, OverlapRemovalParameters parameters, + List events, bool isHorizontal) + { + // First, sort the events on the perpendicular coordinate of the event + // (e.g. for horizontal constraint generation, order on vertical position). + events.Sort(); + +#if VERBOSE + Console.WriteLine("Events:"); + foreach (Event evt in events) + { + Console.WriteLine(" {0}", evt); + } +#endif // VERBOSE + + var scanLine = new ScanLine(); + foreach (Event evt in events) + { + OverlapRemovalNode currentNode = evt.Node; + if (evt.IsForOpen) + { + // Insert the current node into the scan line. + scanLine.Insert(currentNode); +#if VERBOSE + Console.WriteLine("ScanAdd: {0}", currentNode); +#endif // VERBOSE + + // Find the nodes that are currently open to either side of it and are either overlapping + // nodes or the first non-overlapping node in that direction. + currentNode.LeftNeighbors = GetLeftNeighbours(parameters, scanLine, currentNode, isHorizontal); + currentNode.RightNeighbors = GetRightNeighbours(parameters, scanLine, currentNode, isHorizontal); + + // Use counts for indexing for performance (rather than foreach, and hoist the count-control + // variable out of the loop so .Count isn't checked on each iteration, since we know it's + // not going to be changed). + int numLeftNeighbors = currentNode.LeftNeighbors.Count; + int numRightNeighbors = currentNode.RightNeighbors.Count; + + // If there is currently a non-overlap constraint between any two nodes across the + // two neighbour lists we've just created, we can remove them because they will be + // transitively enforced by the constraints we'll create for the current node. + // I.e., we can remove the specification for the constraint + // leftNeighborNode + gap + padding <= rightNeighborNode + // because it is implied by the constraints we'll create for + // leftNeighborNode + gap + padding <= node + // node + gap + padding <= rightNeighborNode + // We must also add the current node as a neighbour in the appropriate direction. + // @@PERF: List.Remove is a sequential search so averages 1/2 the size of the + // lists. We currently don't expect the neighbour lists to be very large or Remove + // to be a frequent operation, and using HashSets would incur the GetEnumerator overhead + // on the outer and inner loops; but .Remove creates an inner-inner loop so do some + // timing runs to compare performance. + // @@PERF: handles the case where we are node c and have added node b as a lnbour + // and node d as rnbour, where those nodes are already nbours. But it does not handle + // the case where we add node b and node a as lnbours, and node b already has node a + // as an lnbour. To do this I think we'd just want to skip adding the node-a lnbour, + // but that forms a new inner loop (iterating all lnbours before adding a new one) + // unless we develop different storage for nbours. + for (int ii = 0; ii < numLeftNeighbors; ++ii) + { + OverlapRemovalNode leftNeighborNode = currentNode.LeftNeighbors[ii]; + for (int jj = 0; jj < numRightNeighbors; ++jj) + { // TODOunit: test this + OverlapRemovalNode nodeToRemove = currentNode.RightNeighbors[jj]; + if (leftNeighborNode.RightNeighbors.Remove(nodeToRemove)) + { +#if VERBOSE + Console.WriteLine(" {0} RnbourRem {1} --> {2}", isHorizontal ? "H" : "V", leftNeighborNode, nodeToRemove); +#endif // VERBOSE + } + } + leftNeighborNode.RightNeighbors.Add(currentNode); + } + for (int ii = 0; ii < numRightNeighbors; ++ii) + { // TODOunit: test this + OverlapRemovalNode rightNeighborNode = currentNode.RightNeighbors[ii]; + for (int jj = 0; jj < numLeftNeighbors; ++jj) + { + OverlapRemovalNode nodeToRemove = currentNode.LeftNeighbors[jj]; + if (rightNeighborNode.LeftNeighbors.Remove(nodeToRemove)) + { +#if VERBOSE + Console.WriteLine(" {0} LnbourRem {1} --> {2}", isHorizontal ? "H" : "V", nodeToRemove, rightNeighborNode); +#endif // VERBOSE + } + } + rightNeighborNode.LeftNeighbors.Add(currentNode); + } + } // endif evt.IsForOpen + else + { + // This is a close event, so generate the constraints and remove the closing node + // from its neighbours lists. If we're closing we should have left neighbours so + // this is null then we've likely got some sort of internal calculation error. + if (null == currentNode.LeftNeighbors) + { + Debug.Assert(null != currentNode.LeftNeighbors, "LeftNeighbors should not be null for a Close event"); + continue; + } + + // currentNode is the current node; if it's a cluster, translate it to the node that + // should be involved in the constraint (if it's the left neighbour then use its + // right border as the constraint variable, and vice-versa). + OverlapRemovalNode currentLeftNode = GetLeftConstraintNode(currentNode); + OverlapRemovalNode currentRightNode = GetRightConstraintNode(currentNode); + + // LeftNeighbors must end before the current node... + int cLeftNeighbours = currentNode.LeftNeighbors.Count; + for (int ii = 0; ii < cLeftNeighbours; ++ii) + { + // Keep track of the original Node; it may be the base of a Cluster, in which + // case it will have the active neighbours list, not leftNeighborNode (which will + // be the left border "fake Node"). + OverlapRemovalNode origLeftNeighborNode = currentNode.LeftNeighbors[ii]; + origLeftNeighborNode.RightNeighbors.Remove(currentNode); + OverlapRemovalNode leftNeighborNode = GetLeftConstraintNode(origLeftNeighborNode); + Debug.Assert(leftNeighborNode.OpenP == origLeftNeighborNode.OpenP, "leftNeighborNode.OpenP must == origLeftNeighborNode.OpenP"); + + // This assert verifies we match the Solver.ViolationTolerance check in AddNeighbor. + // We are closing the node here so use an alternative to OverlapP for additional + // consistency verification. Allow a little rounding error. + Debug.Assert(isHorizontal + || ((currentNode.CloseP + NodePaddingP - leftNeighborNode.OpenP) > (parameters.SolverParameters.GapTolerance - 1e-6)), + "LeftNeighbors: unexpected close/open overlap"); + + double p = leftNeighborNode == LeftBorderNode || currentRightNode == RightBorderNode ? ClusterPadding : NodePadding; + double separation = ((leftNeighborNode.Size + currentRightNode.Size) / 2) + p; + if (TranslateChildren) + { + separation = Math.Max(separation, currentRightNode.Position - leftNeighborNode.Position); + } + Constraint cst = solver.AddConstraint(leftNeighborNode.Variable, currentRightNode.Variable, separation); + Debug.Assert(null != cst, "LeftNeighbors: unexpected null cst"); +#if VERBOSE + Console.WriteLine(" {0} LnbourCst {1} -> {2} g {3:F5}", isHorizontal ? "H" : "V" + , cst.Left.Name, cst.Right.Name, cst.Gap); +#endif // VERBOSE + } + + // ... and RightNeighbors must start after the current node. + int cRightNeighbours = currentNode.RightNeighbors.Count; + for (int ii = 0; ii < cRightNeighbours; ++ii) + { + // Keep original node, which may be a cluster; see comments in LeftNeighbors above. + OverlapRemovalNode origRightNeighborNode = currentNode.RightNeighbors[ii]; + origRightNeighborNode.LeftNeighbors.Remove(currentNode); + OverlapRemovalNode rightNeighborNode = GetRightConstraintNode(origRightNeighborNode); + + // This assert verifies we match the Solver.ViolationTolerance check in AddNeighbor. + // Allow a little rounding error. + Debug.Assert(isHorizontal + || ((currentNode.CloseP + NodePaddingP - rightNeighborNode.OpenP) > (parameters.SolverParameters.GapTolerance - 1e-6)), + "RightNeighbors: unexpected close/open overlap"); + + double p = currentLeftNode == LeftBorderNode || rightNeighborNode == RightBorderNode ? ClusterPadding : NodePadding; + double separation = ((currentLeftNode.Size + rightNeighborNode.Size) / 2) + p; + if (TranslateChildren) + { + separation = Math.Max(separation, rightNeighborNode.Position - currentLeftNode.Position); + } + Constraint cst = solver.AddConstraint(currentLeftNode.Variable, rightNeighborNode.Variable, separation); + Debug.Assert(null != cst, "RightNeighbors: unexpected null cst"); +#if VERBOSE + Console.WriteLine(" {0} RnbourCst {1} -> {2} g {3:F5}", isHorizontal ? "H" : "V" + , cst.Left.Name, cst.Right.Name, cst.Gap); +#endif // VERBOSE + } + + // Note: although currentNode is closed, there may still be open nodes in its + // Neighbour lists; these will subsequently be processed (and removed from + // currentNode.*Neighbour) when those Neighbors are closed. + scanLine.Remove(currentNode); +#if VERBOSE + Console.WriteLine("ScanRem: {0}", currentNode); +#endif // VERBOSE + } // endelse !evt.IsForOpen + + // @@PERF: Set Node.Left/RightNeighbors null to let the GC know we're not using them + // anymore, unless we can reasonably assume a short lifetime for the ConstraintGenerator. + } // endforeach Event + } + + List GetLeftNeighbours(OverlapRemovalParameters parameters, ScanLine scanLine, + OverlapRemovalNode currentNode, bool isHorizontal) + { + var lstNeighbours = new List(); + OverlapRemovalNode nextNode = scanLine.NextLeft(currentNode); + for (; null != nextNode; nextNode = scanLine.NextLeft(nextNode)) + { + // AddNeighbor returns false if we are done adding them. + if (!AddNeighbour(parameters, currentNode, nextNode, lstNeighbours, true /* isLeftNeighbor */ + , isHorizontal)) + { + if (!nextNode.DeferredLeftNeighborToV) + { + break; + } + } + } // endfor NextLeft + return lstNeighbours; + } // end GetLeftNeighbours + + List GetRightNeighbours(OverlapRemovalParameters parameters, ScanLine scanLine, + OverlapRemovalNode currentNode, bool isHorizontal) + { + var lstNeighbours = new List(); + OverlapRemovalNode nextNode = scanLine.NextRight(currentNode); + for (; null != nextNode; nextNode = scanLine.NextRight(nextNode)) + { + // AddNeighbor returns false if we are done adding them. + if (!AddNeighbour(parameters, currentNode, nextNode, lstNeighbours, false /* isLeftNeighbor */ + , isHorizontal)) + { + if (!nextNode.DeferredRightNeighborToV) + { + break; + } + } + } // endfor NextLeft + return lstNeighbours; + } // end GetRightNeighbours + + bool AddNeighbour(OverlapRemovalParameters parameters, OverlapRemovalNode currentNode, OverlapRemovalNode nextNode, + List neighbors, bool isLeftNeighbor, bool isHorizontal) + { + // Sanity check to be sure that the borders are past all other nodes. + Debug.Assert(currentNode != (isLeftNeighbor ? this.LeftBorderNode : this.RightBorderNode), "currentNode must != BorderNode"); + + double overlap = Overlap(currentNode, nextNode, NodePadding); + if (overlap <= 0) + { + // This is the first node encountered on this neighbour-traversal that did not + // overlap within the required padding. Add it to the list and we're done with + // this traversal, unless this is a vertical pass and it is not an overlap on + // the horizontal axis; in that case, pretend we never saw it and return true + // so the next non-overlapping node will be found. (See below for more information + // on why this is necessary). + if (!isHorizontal && (OverlapP(currentNode, nextNode, NodePaddingP) <= parameters.SolverParameters.GapTolerance)) + { +#if VERBOSE + Console.WriteLine(" V {0}nbourHTolSkipNO: {1}", isLeftNeighbor ? "L" : "R", nextNode); +#endif // VERBOSE + return true; + } + neighbors.Add(nextNode); +#if VERBOSE + Console.WriteLine("{0}nbourAddNO: {1}", isLeftNeighbor ? "L" : "R", nextNode); +#endif // VERBOSE + return false; + } + + if (isHorizontal) + { + if (parameters.AllowDeferToVertical) + { + // We are doing horizontal constraints so examine the vertical overlap and see which + // is the smallest (normalized by total node size in that orientation) such that the + // least amount of movement required. this.padding is currently the same in both + // directions; if this changes, we'll have to add different padding values here for + // each direction. @@DCR: consider adding weights to the defer-to-vertical calculation; + // this would allow two nodes to pop up/down if they're being squeezed, rather than + // force apart the borders (which happens regardless of their weight). + double overlapP = OverlapP(currentNode, nextNode, NodePaddingP); + bool isOverlapping = + parameters.ConsiderProportionalOverlap + ? overlap / (currentNode.Size + nextNode.Size) > overlapP / (currentNode.SizeP + nextNode.SizeP) + : overlap > overlapP; + if (isOverlapping) + { + // Don't skip if either of these is a border node. + if ((currentNode != this.LeftBorderNode) + && (currentNode != this.RightBorderNode) + && (nextNode != this.LeftBorderNode) + && (nextNode != this.RightBorderNode)) + { + // Moving in the horizontal direction requires more movement than in the vertical + // direction to remove the overlap, so skip this node on horizontal constraint + // generation and we'll pick it up on vertical constraint generation. Return true + // to keep looking for more overlapping nodes. + // Note: it is still possible that we'll pick up a constraint in both directions, + // due to either or both of this.padding and the "create a constraint to the first + // non-overlapping node" logic. This is expected and the latter helps retain stability. +#if VERBOSE + Console.WriteLine("{0}nbourDeferToV: {1}", isLeftNeighbor ? "L" : "R", nextNode); +#endif // VERBOSE + // We need to track whether we skipped these so that we don't have a broken transition chain. + // See Test_OverlapRemoval.cs, Test_DeferToV_Causing_Missing_Cst() for more information. + if (isLeftNeighbor) + { + currentNode.DeferredLeftNeighborToV = true; + nextNode.DeferredRightNeighborToV = true; + } + else + { + currentNode.DeferredRightNeighborToV = true; + nextNode.DeferredLeftNeighborToV = true; + } + return true; + } + } // endif Overlap is greater than OverlapP + } // endif AllowDeferToVertical + } + else + { + // We're on the vertical pass so make sure we match up with the Solver's tolerance in the + // scanline direction, because it is possible that there was a horizontal constraint between + // these nodes that was within the Solver's tolerance and thus was not enforced. In that + // case, we could spuriously add a vertical constraint here that would result in undesired + // and possibly huge vertical movement. There is a corresponding Assert during constraint + // generation when the node is Closed. We have to do this here rather than at runtime because + // doing it then may skip a Neighbour that replaced other Neighbors by transitivity. + if (OverlapP(currentNode, nextNode, NodePaddingP) <= parameters.SolverParameters.GapTolerance) + { +#if VERBOSE + Console.WriteLine(" V {0}nbourHTolSkipO: {1}", isLeftNeighbor ? "L" : "R", nextNode); +#endif // VERBOSE + return true; + } + } + + // Add this overlapping neighbour and return true to keep looking for more overlapping neighbours. + neighbors.Add(nextNode); +#if VERBOSE + Console.WriteLine("{0}nbourAddO: {1}", isLeftNeighbor ? "L" : "R", nextNode); +#endif // VERBOSE + return true; + } + + // Overrides Node.UpdateFromVariable. + internal override void UpdateFromVariable() + { + ProcessClusterHierarchy(this, cluster => cluster.UpdateFromVariableWorker()); + } + + void UpdateFromVariableWorker() + { + // The root cluster has no "position" and thus no border variables. + if (!this.IsRootCluster) + { + // If empty, we had nothing to do. We're also empty if we had child clusters + // that were empty; in that case we check that we don't have our Variables created. + if (IsEmpty || (null == this.LeftBorderNode.Variable)) + { + return; + } + + // We put the fake border Nodes right up against the outer true Nodes (plus padding) initially, + // and then moved them to the midpoint (subsequently, the caller may have updated their position + // to the barycenter of the cluster, e.g. FastIncrementalLayout). Because the algorithm + // guarantees a minimal solution, the borders are in their optimal position now (including + // padding from their outer nodes). + this.LeftBorderNode.UpdateFromVariable(); + this.RightBorderNode.UpdateFromVariable(); + + // Now update our position and size from those nodes, accounting for possibly different widths + // of the border variables (due to cluster margin specifications). Because the margins apply at + // the inner edge (only), we must take our size as the difference between the outer borders of + // the border variables. This leaves our single Node as having the size necessary to accommodate + // the internal padding when external nodes/clusters have their constraints to it formed. + double clusterLeft = this.LeftBorderNode.Position - (this.LeftBorderNode.Size / 2); + double clusterRight = this.RightBorderNode.Position + (this.RightBorderNode.Size / 2); + this.Size = clusterRight - clusterLeft; + this.Position = clusterLeft + (this.Size / 2); + } + + // Update our child nodes. + int cNodes = this.nodeList.Count; // cache for perf + for (int nodeIndex = 0; nodeIndex < cNodes; ++nodeIndex) + { + OverlapRemovalNode node = this.nodeList[nodeIndex]; + + // Child Clusters have already "recursively" been processed before the current cluster. + if (!(node is OverlapRemovalCluster)) + { + node.UpdateFromVariable(); + } + } + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalCluster.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalCluster.cs.meta new file mode 100644 index 000000000..7306fe892 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalCluster.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: afee140caed64d64fb87e78225625e92 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalClusterEvent.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalClusterEvent.cs new file mode 100644 index 000000000..cdf5d4ebb --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalClusterEvent.cs @@ -0,0 +1,83 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// (c) Microsoft Corporation. All rights reserved. +// +// +// MSAGL Event class for Overlap removal constraint generation for Projection solutions. +// +// -------------------------------------------------------------------------------------------------------------------- + +using System; + +namespace Microsoft.Msagl.Core.Geometry +{ + public partial class OverlapRemovalCluster + { + // An event signifies that the scan line has encountered the opening or closing border + // of a node. Thus, the ConstraintGenerator has a list that contains two events for + // each node: the position at which the scan encounters the opening border, and the + // position at which it encounters the closing border. + // @@PERF: two of these are created per node/cluster during Generate(); consider making + // them a struct instead to reduce heap churn - they are in lists that are sorted + // so consider the CompareTo overhead of passing the struct instead of classref. + class Event : IComparable + { + internal bool IsForOpen { get; private set; } + double Position { get; set; } + internal OverlapRemovalNode Node { get; private set; } + + internal Event(bool isForOpen, OverlapRemovalNode node, double position) + { + this.IsForOpen = isForOpen; + this.Node = node; + this.Position = position; + } + + /// + /// Generate a string representation of the Event. + /// + /// A string representation of the Event. + public override string ToString() + { + return string.Format(System.Globalization.CultureInfo.InvariantCulture, + "Event: pos {0:F5} {1} {2}", + this.Position, this.IsForOpen ? "open" : "close", this.Node); + } + + #region IComparable Members + /// + /// Compare the current event's position to that of rhs in ascending left-to-right order, + /// with Close events for the same position coming before Open events (so we don't generate + /// unnecessary constraints for adjacent nodes). + /// + /// The right-hand side of the comparison + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1307:SpecifyStringComparison", MessageId = "System.String.CompareTo(System.String)")] + public int CompareTo(Event other) + { + ValidateArg.IsNotNull(other, "other"); + + int cmp = 0; + // Use a range so that rounding inaccuracy will give consistent results. + if (Math.Abs(this.Position - other.Position) > OverlapRemovalGlobalConfiguration.EventComparisonEpsilon) + { + cmp = this.Position.CompareTo(other.Position); + } + if (0 == cmp) + { + // Sub-order by IsRendered (false precedes true, which is what we want). + cmp = this.IsForOpen.CompareTo(other.IsForOpen); + + if (0 == cmp) + { + // Sub-order by node id + cmp = this.Node.Id.CompareTo(other.Node.Id); + } + } + return cmp; + } + #endregion + + } // end class Event + } +} \ No newline at end of file diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalClusterEvent.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalClusterEvent.cs.meta new file mode 100644 index 000000000..4c5766c07 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalClusterEvent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2a64cb58cfd18f0449d0386d435fa3de +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalClusterScanLine.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalClusterScanLine.cs new file mode 100644 index 000000000..1be08448b --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalClusterScanLine.cs @@ -0,0 +1,61 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// (c) Microsoft Corporation. All rights reserved. +// +// +// MSAGL ScanLine class for Overlap removal constraint generation for Projection solutions. +// +// -------------------------------------------------------------------------------------------------------------------- + +using System.Diagnostics; +using Microsoft.Msagl.Core.DataStructures; + +namespace Microsoft.Msagl.Core.Geometry +{ + public partial class OverlapRemovalCluster + { + class ScanLine + { + + // This is the data structure that allows fast insert/remove of nodes as well as + // scanning next/prev in the perpendicular direction to the scan line movement + // (i.e. if this the scan line is moving vertically (top to bottom), then we are + // on the horizontal separation constraint pass and this.nodeTree orders nodes in the + // horizontal direction). + // Note that this is ordered on the midpoint (aka Variable.DesiredPos). Once + // again, transitivity saves us; we don't have to worry about combinations of + // midpoint and sizes that mean that a node with a further midpoint has a closer + // border than a node with a nearer midpoint, because in that case, the node with + // the nearer midpoint would have a constraint generated on the node with the + // further midpoint (though in that case we probably generate a duplicative constraint + // between the current node and the node with the further midpoint). + readonly RbTree nodeTree = new RbTree(new NodeComparer()); + + internal void Insert(OverlapRemovalNode node) + { + Debug.Assert(null == this.nodeTree.Find(node), "node already exists in the rbtree"); + + // RBTree's internal operations on insert/remove etc. mean the node can't cache the + // RBNode returned by insert(); instead we must do find() on each call. + this.nodeTree.Insert(node); + } + + internal void Remove(OverlapRemovalNode node) + { + this.nodeTree.Remove(node); + } + + internal OverlapRemovalNode NextLeft(OverlapRemovalNode node) + { + var pred = this.nodeTree.Previous(this.nodeTree.Find(node)); + return (null != pred) ? pred.Item : null; + } + + internal OverlapRemovalNode NextRight(OverlapRemovalNode node) + { + var succ = this.nodeTree.Next(this.nodeTree.Find(node)); + return (null != succ) ? succ.Item : null; + } + } // end class ScanLine + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalClusterScanLine.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalClusterScanLine.cs.meta new file mode 100644 index 000000000..ee694c602 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalClusterScanLine.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b78b23c09d7ecc34a945ec4e72fd537d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalGlobalConfiguration.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalGlobalConfiguration.cs new file mode 100644 index 000000000..49eca32a8 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalGlobalConfiguration.cs @@ -0,0 +1,45 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// (c) Microsoft Corporation. All rights reserved. +// +// +// MSAGL class for Overlap removal global configuration constants. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace Microsoft.Msagl.Core.Geometry { + /// + /// Global configuration constants for the OverlapRemoval namespace. + /// + public static class OverlapRemovalGlobalConfiguration { + /// + /// Default weight for a freely movable cluster border; overridable per BorderInfo instance. + /// Should be very small compared to default node weight (1) so that it has no visible effect on layout. + /// Too large and it will cause clusters to be squashed by their bounding variables (since OverlapRemovalCluster + /// swaps the positions of Left/Right, Top/Bottom nodes to ensure that cluster bounds tightly fit their contents after a solve). + /// Too small and you will see cluster boundaries "sticking" to nodes outside the cluster (because such constraints will not be + /// split when they can be because the lagrangian multipliers will be so small as to be ignored before solver termination). + /// + public const double ClusterDefaultFreeWeight = 1e-6; + + /// + /// Default weight for an unfixed (freely movable) cluster border; overridable per BorderInfo instance. + /// + public const double ClusterDefaultFixedWeight = 1e8; + + /// + /// Default width of cluster borders; overridable per BorderInfo instance via BorderInfo.InnerMargin. + /// + public const double ClusterDefaultBorderWidth = 1e-3; + + #region InternalConstants + + /// + /// For comparing event positions, the rounding epsilon. + /// + internal const double EventComparisonEpsilon = 1e-6; + + #endregion // InternalConstants + + } // end struct GlobalConfiguration +} // end namespace ProjectionSolver diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalGlobalConfiguration.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalGlobalConfiguration.cs.meta new file mode 100644 index 000000000..133db3230 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalGlobalConfiguration.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4fcb8d1c74ed3884db6da629bece833f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalNode.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalNode.cs new file mode 100644 index 000000000..a8951c7f8 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalNode.cs @@ -0,0 +1,340 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// (c) Microsoft Corporation. All rights reserved. +// +// +// MSAGL Node class for Overlap removal constraint generation for Projection solutions. +// +// -------------------------------------------------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; + +namespace Microsoft.Msagl.Core.Geometry +{ + /// + /// A node essentially wraps the coordinates of a Variable for the Open and Close Events for + /// that Variable. It contains the list of left and right nodes which are immediate neighbours, + /// where immediate is defined as overlapping or some subset of the closest non-overlapping + /// Variables (currently this subset is the first one encountered on any event, since it is + /// transitive; if there is a second non-overlapping node, then the first non-overlapping + /// node will have a constraint on it). + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1036:OverrideMethodsOnComparableTypes")] + public class OverlapRemovalNode : IComparable, IComparable + { + /// + /// Passed through as a convenience to the caller; it is not used by OverlapRemoval directly + /// (except in VERIFY/VERBOSE where it uses ToString()). When Solve() is complete, the caller + /// should copy the Node.Position property into whatever property the class specialization for this has. + /// + public Object UserData { get; set; } + + /// + /// The string representing the user data object, or a null indicator string. + /// + protected string UserDataString + { + get + { + return (null == this.UserData) ? "-0-" : this.UserData.ToString(); + } + } + + /// + /// The Variable representing this Node (or Cluster border) in the ProjectionSolver passed to + /// Generate(). Once Solve() is called, this is cleared out. + /// + public ProjectionSolver.Variable Variable { get; set; } + + // Set and retrieved during Cluster.GenerateFromEvents. + internal List LeftNeighbors { get; set; } + internal List RightNeighbors { get; set; } + + // If these are set, it means that during the horizontal pass we deferred a node's constraint + // generation to the vertical pass, so we can't jump out of neighbour evaluation on that node. + internal bool DeferredLeftNeighborToV { get; set; } + internal bool DeferredRightNeighborToV { get; set; } + + // These track the (P)erpendicular coordinates to the Variable's coordinates. + // This is to order the Events, and for horizontal constraint generation, is + // also used to decide which direction resolves the overlap with minimal movement. + + /// + /// The coordinate of the Node along the primary axis. Updated by ConstraintGenerator.Solve(). + /// + public double Position { get; internal set; } + + /// + /// The coordinate of the Node along the secondary (Perpendicular) axis. + /// + public double PositionP { get; internal set; } // Updated only for Clusters + + /// + /// The size of the Node along the primary axis. + /// + public double Size { get; internal set; } // Updated only for Clusters + + /// + /// The size of the Node along the secondary (Perpendicular) axis. + /// + public double SizeP { get; internal set; } // Updated only for Clusters + + /// + /// The opening border of the Node along the primary axis; Left if horizontal, + /// Top if Vertical. + /// + public double Open + { + get { return Position - (Size / 2); } + } + + /// + /// The closing border of the Node along the primary axis; Right if horizontal, + /// Bottom if Vertical. + /// + public double Close + { + get { return Position + (Size / 2); } + } + + /// + /// The opening border of the Node along the secondary (Perpendicular) axis; Top if horizontal, + /// Bottom if Vertical. + /// + public double OpenP + { + get { return PositionP - (SizeP / 2); } + } + + /// + /// The closing border of the Node along the secondary (Perpendicular) axis; Bottom if horizontal, + /// Right if Vertical. + /// + public double CloseP + { + get { return PositionP + (SizeP / 2); } + } + + /// + /// The weight of the node along the primary axis. + /// + public double Weight { get; internal set; } + + // This identifies the node for consistent-sorting purposes in the Event list. + internal uint Id { get; private set; } + + // This is the normal node ctor, from ConstraintGenerator. + internal OverlapRemovalNode(uint id, object userData, double position, double positionP, + double size, double sizeP, double weight) + { + ValidateArg.IsPositive(size, "size"); + ValidateArg.IsPositive(size, "sizeP"); + ValidateArg.IsPositive(weight, "weight"); + if (weight <= 0) + { + throw new ArgumentOutOfRangeException("weight" +#if DEBUG + , "Invalid node properties" +#endif // DEBUG + ); + } + double dblCheck = (Math.Abs(position) + size) * weight; + if (double.IsInfinity(dblCheck) || double.IsNaN(dblCheck)) + { + throw new ArgumentOutOfRangeException("position" +#if DEBUG + , "Invalid node properties" +#endif // DEBUG + ); + } + dblCheck = (Math.Abs(positionP) + sizeP) * weight; + if (double.IsInfinity(dblCheck) || double.IsNaN(dblCheck)) + { + throw new ArgumentOutOfRangeException("positionP" +#if DEBUG + , "Invalid node properties" +#endif // DEBUG + ); + } + this.Id = id; + this.UserData = userData; + this.Position = position; + this.PositionP = positionP; + this.Size = size; + this.SizeP = sizeP; + this.Weight = weight; + } + + // This is the constructor for the "fake nodes" of a Cluster and its Borders. + // We default to free border weight so the cluster borders can move freely during Solve(). + // The weight is overridden for the Cluster border nodes during Cluster.Generate. + internal OverlapRemovalNode(uint id, object userData) + : this(id, userData, 0.0, 0.0, 0.0, 0.0, BorderInfo.DefaultFreeWeight) + { + } + + internal static double Overlap(OverlapRemovalNode n1, OverlapRemovalNode n2, double padding) + { + // Returns > 0 if the nodes overlap (combined sizes/2 plus required padding between + // nodes is greater than the distance between the nodes). + return ((n1.Size + n2.Size) / 2) + padding - Math.Abs(n1.Position - n2.Position); + } + + internal static double OverlapP(OverlapRemovalNode n1, OverlapRemovalNode n2, double paddingP) + { + // Returns > 0 if the nodes overlap (combined sizes/2 plus required padding between + // nodes is greater than the distance between the nodes). + return ((n1.SizeP + n2.SizeP) / 2) + paddingP - Math.Abs(n1.PositionP - n2.PositionP); + } + + /// + /// Create the backing Variable for this Node in the solver. + /// + /// + public void CreateVariable(ProjectionSolver.Solver solver) + { + ValidateArg.IsNotNull(solver, "solver"); + // Due to multiple hierarchies, we must check to see if the variable has been created yet; + // we share one Node (and its single Variable) across all clusters it's a member of. + if (null == this.Variable) + { + this.Variable = solver.AddVariable(this /* userData */, this.Position, this.Weight); + } + else + { + // Make sure the position is updated as the caller may have called this before and then we recalculated + // the position at some point (e.g. for Cluster boundary nodes). + UpdateDesiredPosition(this.Position); + } + } + + // Overridden by Cluster. Called after Solve(); sets the Node position to that of its Variable. + internal virtual void UpdateFromVariable() + { + // If the Variable is null then we were already updated from an earlier cluster in + // another hierarchy. + if (null != this.Variable) + { + this.Position = this.Variable.ActualPos; + + // Currently we don't use this anymore. + this.Variable = null; + } + } + + // Currently called only from clusters when repositioning "fake border" nodes from the + // constraint-generation position (outer edge) to the pre-Solve position (central edge). + internal void UpdateDesiredPosition(double newPosition) + { + this.Position = newPosition; + this.Variable.DesiredPos = newPosition; + } + + /// + /// Generate a string representation of the Node. + /// + /// A string representation of the Node. + public override string ToString() + { + return string.Format(System.Globalization.CultureInfo.InvariantCulture, + "Nod '{0}': id {1} p {2:F5} s {3:F5} pP {4:F5} sP {5:F5}", + this.UserDataString, this.Id, this.Position, this.Size, this.PositionP, this.SizeP); + } + + #region IComparable Members + /// + /// Compare the Nodes by ActualPos in ascending left-to-right order (this == lhs, other == rhs). + /// + /// The object being compared to. + /// -1 if 'this' is "less"; +1 if 'this' is "greater"; 0 if 'this' and rhs are equal. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1307:SpecifyStringComparison", MessageId = "System.String.CompareTo(System.String)")] + public int CompareTo(OverlapRemovalNode other) + { + ValidateArg.IsNotNull(other, "other"); + int cmp = this.Position.CompareTo(other.Position); + if (0 == cmp) + { + cmp = this.Id.CompareTo(other.Id); + } + return cmp; + } + #endregion // IComparable Members + + #region IComparable Members + /// + /// Compare the Nodes by ActualPos in ascending left-to-right order (this == lhs, other == rhs). + /// + /// The object being compared to. + /// -1 if 'this' is "less"; +1 if 'this' is "greater"; 0 if 'this' and rhs are equal. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1307:SpecifyStringComparison", MessageId = "System.String.CompareTo(System.String)")] + public int CompareTo(object obj) + { + var rhs = obj as OverlapRemovalNode; + if (null == rhs) + { + throw new InvalidOperationException( +#if DEBUG + "Argument 'obj' must be a Node" +#endif // DEBUG +); + } + return this.CompareTo(rhs); + } + #endregion // IComparable Members + +#if NOTNEEDED_FXCOP // This entails a perf hit due to ==/!= becoming a non-inlined function call in some cases. + // We only create one Node object per variable so do not need anything but reference + // ==/!=, so we suppress:1036 above. Add UnitTests for these if they're enabled. + #region RequiredOverridesForIComparable + + // Omitting getHashCode violates rule: OverrideGetHashCodeOnOverridingEquals. + public override int GetHashCode() { + return this.Position.GetHashCode() ^ this.Id.GetHashCode(); + } + // Omitting any of the following violates rule: OverrideMethodsOnComparableTypes. + public override bool Equals(Object obj) { + if (!(obj is Node)) + return false; + return (this.CompareTo(obj) == 0); + } + public static bool operator == (Node lhs, Node rhs) { + if (null == (object)lhs) { // Cast to object to avoid recursive op== + return (null == (object)rhs); + } + return lhs.Equals(rhs); + } + public static bool operator != (Node lhs, Node rhs) { + return !(lhs == rhs); + } + public static bool operator < (Node lhs, Node rhs) { + return (lhs.CompareTo(rhs) < 0); + } + public static bool operator > (Node lhs, Node rhs) { + return (lhs.CompareTo(rhs) > 0); + } + #endregion // RequiredOverridesForIComparable +#endif // NOTNEEDED_FXCOP + } // end class Node + + // NodeComparer is used by the RBTree. + internal class NodeComparer : IComparer + { + #region IComparer Members + /// + /// Compare the points by point ActualPos in ascending left-to-right order. + /// + /// Left-hand side of the comparison. + /// Right-hand side of the comparison. + /// -1 if lhs is less than rhs; +1 if lhs is greater than rhs; else 0. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1307:SpecifyStringComparison", MessageId = "System.String.CompareTo(System.String)")] + public int Compare(OverlapRemovalNode lhs, OverlapRemovalNode rhs) + { + ValidateArg.IsNotNull(lhs, "lhs"); + ValidateArg.IsNotNull(rhs, "rhs"); + + return lhs.CompareTo(rhs); + } + #endregion // IComparer Members + } +} \ No newline at end of file diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalNode.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalNode.cs.meta new file mode 100644 index 000000000..099cc0a31 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalNode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8c8c94c3ee1e1284c90c6e727f9faf62 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalParameters.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalParameters.cs new file mode 100644 index 000000000..403aff586 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalParameters.cs @@ -0,0 +1,86 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// (c) Microsoft Corporation. All rights reserved. +// +// +// MSAGL class for Overlap removal parameters. +// +// -------------------------------------------------------------------------------------------------------------------- + +using System; +using Microsoft.Msagl.Core.ProjectionSolver; + +namespace Microsoft.Msagl.Core.Geometry +{ + /// + /// Per-instance parameters for OverlapRemoval.ConstraintGenerator.Generate()/Solve(). + /// + public class OverlapRemovalParameters + : ICloneable + { + /// + /// If true and the current instance's IsHorizontal property is true, then by default + /// constraints will not be generated on the horizontal pass if a vertical constraint + /// would result in less movement. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")] + public bool AllowDeferToVertical { get; set; } + + /// + /// The calculation to choose in deciding which way to resolve overlap (horizontally or vertically) + /// between two nodes u and v. + /// If this is false the calculation is simply HOverlap > VOverlap, otherwise we use: + /// HOverlap / (u.Width + v.Width) > VOverlap / (u.Height + v.Height) + /// + public bool ConsiderProportionalOverlap { get; set; } + + /// + /// Parameters to the Solver, used in Generate as well as passed through to the Solver. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")] + public Parameters SolverParameters { get; set; } + + /// + /// Default Constructor. + /// + public OverlapRemovalParameters() + : this(new Parameters()) + { + } + + /// + /// Constructor taking solver parameters. + /// + /// + public OverlapRemovalParameters(Parameters solverParameters) + { + this.SolverParameters = solverParameters; + AllowDeferToVertical = true; + } + + /// + /// Constructor taking OverlapRemoval parameter and solver parameters. + /// + /// + /// + public OverlapRemovalParameters(bool allowDeferToVertical, Parameters solverParameters) + { + this.AllowDeferToVertical = allowDeferToVertical; + this.SolverParameters = solverParameters; + } + + #region ICloneable members + /// + /// Deep-copy the SolverParameters. + /// + /// + public object Clone() + { + OverlapRemovalParameters newParams = (OverlapRemovalParameters)this.MemberwiseClone(); + newParams.SolverParameters = (Parameters)this.SolverParameters.Clone(); + return newParams; + } + #endregion // ICloneable members + + } +} \ No newline at end of file diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalParameters.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalParameters.cs.meta new file mode 100644 index 000000000..b232bfd49 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalParameters.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 31f510a239548e84e96c1d71a4a7520e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/RectangularClusterBoundary.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/RectangularClusterBoundary.cs new file mode 100644 index 000000000..152057acd --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/RectangularClusterBoundary.cs @@ -0,0 +1,293 @@ +using System.Diagnostics; +using Microsoft.Msagl.Core.Geometry.Curves; + +namespace Microsoft.Msagl.Core.Geometry { + /// + /// Clusters can (optionally) have a rectangular border which is respected by overlap avoidance. + /// Currently, this is controlled by FastIncrementalLayoutSettings.RectangularClusters. + /// If FastIncrementalLayoutSettings.RectangularClusters is true, then the + /// FastIncrementalLayout constructor will create a RectangularBoundary in each cluster. + /// Otherwise it will be null. + /// + public class RectangularClusterBoundary { + /// + /// Set margins to zero which also initializes other members. + /// + public RectangularClusterBoundary() { + this.LeftBorderInfo = new BorderInfo(0.0); + this.RightBorderInfo = new BorderInfo(0.0); + this.TopBorderInfo = new BorderInfo(0.0); + this.BottomBorderInfo = new BorderInfo(0.0); + } + internal Rectangle rectangle; // Used only for RectangularHull + internal OverlapRemovalCluster olapCluster; // For use with RectangularHull only, and only valid during verletIntegration() + /// + /// Left margin of this cluster (additional space inside the cluster border). + /// + public double LeftMargin { + get { return this.LeftBorderInfo.InnerMargin; } + set { this.LeftBorderInfo = new BorderInfo(value, this.LeftBorderInfo.FixedPosition, this.LeftBorderInfo.Weight); } + } + /// + /// Right margin of this cluster (additional space inside the cluster border). + /// + public double RightMargin { + get { return this.RightBorderInfo.InnerMargin; } + set { this.RightBorderInfo = new BorderInfo(value, this.RightBorderInfo.FixedPosition, this.RightBorderInfo.Weight); } + } + /// + /// Top margin of this cluster (additional space inside the cluster border). + /// + public double TopMargin { + get { return this.TopBorderInfo.InnerMargin; } + set { this.TopBorderInfo = new BorderInfo(value, this.TopBorderInfo.FixedPosition, this.TopBorderInfo.Weight); } + } + /// + /// Bottom margin of this cluster (additional space inside the cluster border). + /// + public double BottomMargin { + get { return this.BottomBorderInfo.InnerMargin; } + set { this.BottomBorderInfo = new BorderInfo(value, this.BottomBorderInfo.FixedPosition, this.BottomBorderInfo.Weight); } + } + + /// + /// Information for the Left border of the cluster. + /// + public BorderInfo LeftBorderInfo { get; set; } + /// + /// Information for the Right border of the cluster. + /// + public BorderInfo RightBorderInfo { get; set; } + /// + /// Information for the Top border of the cluster. + /// + public BorderInfo TopBorderInfo { get; set; } + /// + /// Information for the Bottom border of the cluster. + /// + public BorderInfo BottomBorderInfo { get; set; } + + /// + /// When this is set, the OverlapRemovalCluster will generate equality constraints rather than inequalities + /// to keep its children within its bounds. + /// + public bool GenerateFixedConstraints + { + get; + set; + } + + bool generateFixedConstraintsDefault; + + /// + /// The default value that GenerateFixedConstraints will be reverted to when a lock is released + /// + public bool GenerateFixedConstraintsDefault + { + get + { + return generateFixedConstraintsDefault; + } + set + { + GenerateFixedConstraints = generateFixedConstraintsDefault = value; + } + } + + /// + /// The rectangular hull of all the points of all the nodes in the cluster, as set by + /// ProjectionSolver.Solve(). + /// Note: This rectangle may not originate at the barycenter. Drawing uses only the results + /// of this function; the barycenter is used only for gravity computations. + /// + /// + public ICurve RectangularHull() { + Debug.Assert(rectangle.Bottom <= rectangle.Top); + if (RadiusX > 0 || RadiusY > 0) + { + return CurveFactory.CreateRectangleWithRoundedCorners(rectangle.Width, rectangle.Height, RadiusX, RadiusY, rectangle.Center); + } + else + { + return CurveFactory.CreateRectangle(rectangle.Width, rectangle.Height, rectangle.Center); + } + } + /// + /// Will only return something useful if FastIncrementalLayoutSettings.AvoidOverlaps is true. + /// + public Rectangle Rect { + get { + return rectangle; + } + set + { + rectangle = value; + } + } + + /// + /// Returns (bounding) Rect with margins subtracted + /// + public Rectangle InnerRect + { + get + { + var outer = Rect; + var inner = new Rectangle(outer.Left + LeftMargin, outer.Bottom + BottomMargin, + outer.Right - RightMargin, outer.Top - TopMargin); + return inner; + } + } + + class Margin + { + public double Left; + public double Right; + public double Top; + public double Bottom; + } + + Margin defaultMargin; + internal bool DefaultMarginIsSet { + get { return defaultMargin!=null; } + } + + /// + /// The default margin stored by StoreDefaultMargin + /// + public double DefaultLeftMargin + { + get + { + return defaultMargin.Left; + } + } + /// + /// The default margin stored by StoreDefaultMargin + /// + public double DefaultTopMargin + { + get + { + return defaultMargin.Top; + } + } + + /// + /// The default margin stored by StoreDefaultMargin + /// + public double DefaultRightMargin + { + get + { + return defaultMargin.Right; + } + } + /// + /// The default margin stored by StoreDefaultMargin + /// + public double DefaultBottomMargin + { + get + { + return defaultMargin.Bottom; + } + } + + /// + /// store a the current margin as the default which we can revert to later with the RestoreDefaultMargin + /// + public void StoreDefaultMargin() + { + defaultMargin = new Margin { Left = LeftMargin, Right = RightMargin, Bottom = BottomMargin, Top = TopMargin }; + } + + /// + /// store a default margin which we can revert to later with the RestoreDefaultMargin + /// + public void StoreDefaultMargin(double left, double right, double bottom, double top) + { + defaultMargin = new Margin { Left = left, Right = right, Bottom = bottom, Top = top }; + } + + /// + /// revert to a previously stored default margin + /// + public void RestoreDefaultMargin() + { + if (defaultMargin != null) + { + LeftMargin = defaultMargin.Left; + RightMargin = defaultMargin.Right; + TopMargin = defaultMargin.Top; + BottomMargin = defaultMargin.Bottom; + } + } + + + /// + /// Move the bounding box by delta + /// + /// + public void TranslateRectangle(Point delta) + { + rectangle = new Rectangle(rectangle.Left + delta.X, rectangle.Bottom + delta.Y, new Point(rectangle.Width, rectangle.Height)); + } + + /// + /// Radius on the X axis + /// + public double RadiusX { get; set; } + /// + /// Radius on the Y axis + /// + public double RadiusY { get; set; } + /// + /// Creates a lock on all four borders + /// + /// + /// + /// + /// + public void Lock(double left, double right, double top, double bottom) { + double weight = 1e4; + LeftBorderInfo = new BorderInfo(LeftBorderInfo.InnerMargin, left, weight); + RightBorderInfo = new BorderInfo(RightBorderInfo.InnerMargin, right, weight); + TopBorderInfo = new BorderInfo(TopBorderInfo.InnerMargin, top, weight); + BottomBorderInfo = new BorderInfo(BottomBorderInfo.InnerMargin, bottom, weight); + } + /// + /// Releases the lock on all four borders + /// + public void Unlock() { + LeftBorderInfo = new BorderInfo(LeftBorderInfo.InnerMargin); + RightBorderInfo = new BorderInfo(RightBorderInfo.InnerMargin); + TopBorderInfo = new BorderInfo(TopBorderInfo.InnerMargin); + BottomBorderInfo = new BorderInfo(BottomBorderInfo.InnerMargin); + } + /// + /// Locks all four borders at their current positions + /// + public void Lock() { + Lock(rectangle.Left, rectangle.Right, rectangle.Top, rectangle.Bottom); + } + /// + /// boundary can shrink no more than this + /// + public double MinWidth + { + get; + set; + } + /// + /// boundary can shrink no more than this + /// + public double MinHeight + { + get; + set; + } + + + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/RectangularClusterBoundary.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/RectangularClusterBoundary.cs.meta new file mode 100644 index 000000000..9330afee1 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/OverlapRemoval/RectangularClusterBoundary.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 19fedfa169ddccd4f8876823d8c93cf0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Point.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Point.cs new file mode 100644 index 000000000..26d130c05 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Point.cs @@ -0,0 +1,831 @@ +using System; +using System.Diagnostics; +using System.Globalization; +using Microsoft.Msagl.Core.Geometry.Curves; +using Unity.Mathematics; +using UnityEngine; +using Debug = System.Diagnostics.Debug; +using Random = System.Random; + +namespace Microsoft.Msagl.Core.Geometry { + /// + /// Two dimensional point + /// +#if TEST_MSAGL + [Serializable] +#endif + [DebuggerDisplay("({X},{Y})")] + public struct Point : IComparable { + /// + /// overrides the equality + /// + /// + /// + public override bool Equals(object obj) { + if (!(obj is Point)) + return false; + return (Point)obj == this; + } + + static internal Point P(double xCoordinate, double yCoordinate) { + return new Point(xCoordinate, yCoordinate); + } + /// + /// + /// + /// + public override int GetHashCode() { + uint hc = (uint)X.GetHashCode(); + return (int)((hc << 5 | hc >> 27) + (uint)Y.GetHashCode()); + } + /// + /// the point norm + /// + public double Length { get { return (double)Math.Sqrt(X * X + Y * Y); } } + /// + /// point norm squared (faster to compute than Length) + /// + public double LengthSquared { get { return X * X + Y * Y; } } + /// + /// overrides the equality + /// + /// + /// + /// + public static bool operator ==(Point point0, Point point1) { + return point0.X == point1.X && point0.Y == point1.Y; + } + /// + /// overrides the less operator + /// + /// + /// + /// + public static bool operator <(Point point0, Point point1) { + return point0.CompareTo(point1) < 0; + } + /// + /// overrides the less or equal operator + /// + /// + /// + /// + public static bool operator <=(Point point0, Point point1) { + return point0.CompareTo(point1) <= 0; + } + + /// + /// overrides the greater or equal operator + /// + /// + /// + /// + public static bool operator >=(Point point0, Point point1) { + return point0.CompareTo(point1) >= 0; + } + /// + /// overrides the greater operator + /// + /// + /// + /// + public static bool operator >(Point point0, Point point1) { + return point0.CompareTo(point1) > 0; + } + + /// + /// the inequality operator + /// + /// + /// + /// + public static bool operator !=(Point point0, Point point1) { + return !(point0 == point1); + } + /// + /// the negation operator + /// + /// + /// + public static Point operator -(Point point0) { + return new Point(-point0.X, -point0.Y); + } + /// + /// the negation operator + /// + /// + public Point Negate() { + return -this; + } + + /// + /// the addition + /// + /// + /// + /// + public static Point operator +(Point point0, Point point1) { + return new Point(point0.X + point1.X, point0.Y + point1.Y); + } + /// + /// the addition + /// + /// + /// + /// + public static Point Add(Point point0, Point point1) { + return point0 + point1; + } + /// + /// overrides the substraction + /// + /// + /// + /// + public static Point operator -(Point point0, Point point1) { + return new Point(point0.X - point1.X, point0.Y - point1.Y); + } + + + /// + /// overrides the substraction + /// + /// + /// + /// + public static Point Subtract(Point point0, Point point1) { + return point0 - point1; + } + + /// + /// othe internal product + /// + /// + /// + /// + public static double operator *(Point point0, Point point1) { + return point0.X * point1.X + point0.Y * point1.Y; + } + + + /// + /// cross product + /// + /// + /// + /// + public static double CrossProduct(Point point0, Point point1) { + return point0.X * point1.Y - point0.Y * point1.X; + } + /// + /// the multipliction by scalar in x and y + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "x"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "y")] + public static Point Scale(double xScale, double yScale, Point point) { + return new Point(xScale * point.X, yScale * point.Y); + } + /// + /// the multipliction by scalar + /// + /// + /// + /// + public static Point operator *(double coefficient, Point point) { + return new Point(coefficient * point.X, coefficient * point.Y); + } + /// + /// multiplication on coefficient scalar + /// + /// + /// + /// + public static Point operator *(Point point, double coefficient) { + return new Point(coefficient * point.X, coefficient * point.Y); + } + /// + /// multiplication on coefficient scalar + /// + /// + /// + /// + public static Point Multiply(double coefficient, Point point) { + return new Point(coefficient * point.X, coefficient * point.Y); + } + + /// + /// multiplication on coefficient scalar + /// + /// + /// + /// + public static Point Multiply(Point point, double coefficient) { + return new Point(coefficient * point.X, coefficient * point.Y); + } + /// + /// division on coefficient scalar + /// + /// + /// + /// + public static Point operator /(Point point, double coefficient) { + return new Point(point.X / coefficient, point.Y / coefficient); + } + + /// + /// division on coefficient scalar + /// + /// + /// + /// + public static Point operator /(double coefficient, Point point) { + return new Point(point.X / coefficient, point.Y / coefficient); + } + /// + /// division on coefficient scalar + /// + /// + /// + /// + public static Point Divide(Point point, double coefficient) { + return new Point(point.X / coefficient, point.Y / coefficient); + } + /// + /// division on coefficient scalar + /// + /// + /// + /// + public static Point Divide(double coefficient, Point point) { + return new Point(point.X / coefficient, point.Y / coefficient); + } + static internal string DoubleToString(double d) { + return (Math.Abs(d) < 1e-11) ? "0" : d.ToString("#.##########", CultureInfo.InvariantCulture); + } + /// + /// Rounded representation of the points. DebuggerDisplay shows the unrounded form. + /// + /// + public override string ToString() { + return "(" + DoubleToString(X) + "," + DoubleToString(Y) + ")"; + } + + /// + /// the x coordinate + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "X")] +#if SHARPKIT //https://code.google.com/p/sharpkit/issues/detail?id=369 + // Filippo: because structs become by-ref in SharpKit, assigning to the coordinates directly can be really dangerous. If you assign Point P1 to Point P2, and then + // modify P2, in .NET you're modifying a copy, but in JS you're modifying the content of a reference. For this reason, I need to take a look every instance of + // assignment to a Point coordinate and make sure it's not accidentally using a reference in the JS version (in this case, the assignment where the Point was + // created must be converted to a call to Clone()). + private double m_X; + public double X + { + get { return m_X; } + set + { + m_X = value; + UpdateHashKey(); + } + } +#else + public double X; // This is member accessed a lot. Using a field instead of a property for performance. +#endif + + /// + /// the y coordinate + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Y")] +#if SHARPKIT //https://code.google.com/p/sharpkit/issues/detail?id=369 + private double m_Y; + public double Y + { + get { return m_Y; } + set + { + m_Y = value; + UpdateHashKey(); + } + } +#else + public double Y; // This is member accessed a lot. Using a field instead of a property for performance. +#endif + +#if SHARPKIT //https://code.google.com/p/sharpkit/issues/detail?id=289 + //SharpKit/Colin - hashing + private SharpKit.JavaScript.JsString _hashKey; + private void UpdateHashKey() + { + _hashKey = "(" + X + "," + Y + ")"; + } + + public Point Clone() + { + return new Point(m_X, m_Y); + } +#endif + + /// + /// construct the point from x and y coordinates + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "x"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "y")] + public Point(double xCoordinate, double yCoordinate) + { +#if SHARPKIT //https://code.google.com/p/sharpkit/issues/detail?id=289 + m_X = xCoordinate; + m_Y = yCoordinate; + _hashKey = null; + UpdateHashKey(); +#else + X = xCoordinate; + Y = yCoordinate; +#endif + } + + static internal bool ParallelWithinEpsilon(Point a, Point b, double eps) { + double alength = a.Length; + double blength = b.Length; + if (alength < eps || blength < eps) + return true; + + a /= alength; + b /= blength; + + return Math.Abs(-a.X * b.Y + a.Y * b.X) < eps; + } + + + /// + /// returns this rotated by the angle counterclockwise; does not change "this" value + /// + /// + /// + public Point Rotate(double angle) { + double c = Math.Cos(angle); + double s = Math.Sin(angle); + + return new Point(c * X - s * Y, s * X + c * Y); + + } + + /// + /// creates coefficient new point with the norm 1, does not change "this" point + /// + /// + public Point Normalize() { + var length = Length; + if (length < ApproximateComparer.Tolerance) + throw new InvalidOperationException(); //the vector is too short to be normalized + return this / length; + } + + /// + /// The counter-clockwise angle when rotating point1 towards point3 around point2 + /// + /// + static public double Angle(Point point1, Point center, Point point3) { + return Angle(point1 - center, point3 - center); + } + + public static double Dot(Point v1, Point v2) + { + return v1.X*v2.X + v1.Y*v2.Y; + } + + /// + /// The angle you need to turn "side0" counterclockwise to make it collinear with "side1" + /// + /// + /// + /// + static public double Angle(Point side0, Point side1) { + var ax = side0.X; + var ay = side0.Y; + var bx = side1.X; + var by = side1.Y; + + double cross = ax * by - ay * bx; + double dot = ax * bx + ay * by; + + if (Math.Abs(dot) < ApproximateComparer.Tolerance) { + if (Math.Abs(cross) < ApproximateComparer.Tolerance) + return 0; + + + if (cross < -ApproximateComparer.Tolerance) + return 3 * Math.PI / 2; + return Math.PI / 2; + } + + if (Math.Abs(cross) < ApproximateComparer.Tolerance) { + if (dot < -ApproximateComparer.Tolerance) + return Math.PI; + return 0.0; + } + + double atan2 = Math.Atan2(cross, dot); + if (cross >= -ApproximateComparer.Tolerance) + return atan2; + return Math.PI * 2.0 + atan2; + } + + /// + /// computes orientation of three vectors with a common source + /// (compare polar angles of v1 and v2 with respect to v0) + /// + /// + /// -1 if the orientation is v0 v1 v2 + /// 1 if the orientation is v0 v2 v1 + /// 0 if v1 and v2 are collinear and codirectinal + /// + static public int GetOrientationOf3Vectors(Point vector0, Point vector1, Point vector2) { + const double multiplier = 1000; //TODO, need to fix it? + + vector0 *= multiplier; + vector1 *= multiplier; + vector2 *= multiplier; + + double xp2 = Point.CrossProduct(vector0, vector2); + double dotp2 = vector0 * vector2; + double xp1 = Point.CrossProduct(vector0, vector1); + double dotp1 = vector0 * vector1; + + // v1 is collinear with v0 + if (ApproximateComparer.Close(xp1, 0.0) && ApproximateComparer.GreaterOrEqual(dotp1, 0.0)) { + if (ApproximateComparer.Close(xp2, 0.0) && ApproximateComparer.GreaterOrEqual(dotp2, 0.0)) + return 0; + return 1; + } + + // v2 is collinear with v0 + if (ApproximateComparer.Close(xp2, 0.0) && ApproximateComparer.GreaterOrEqual(dotp2, 0.0)) { + return -1; + } + + if (ApproximateComparer.Close(xp1, 0.0) || ApproximateComparer.Close(xp2, 0.0) || xp1 * xp2 > 0.0) { + // both on same side of v0, compare to each other + return ApproximateComparer.Compare(Point.CrossProduct(vector2, vector1), 0.0); + } + + // vectors "less than" zero degrees are actually large, near 2 pi + return -ApproximateComparer.Compare(Math.Sign(xp1), 0.0); + } + + /// + /// If the area is negative then C lies to the right of the line [cornerA, cornerB] or, in another words, the triangle (A , B, C) is oriented clockwize + /// If it is positive then C lies ot he left of the line [A,B] another words, the triangle A,B,C is oriented counter-clockwize. + /// Otherwise A ,B and C are collinear. + /// + /// + /// + /// + /// + static public double SignedDoubledTriangleArea(Point cornerA, Point cornerB, Point cornerC) { + return (cornerB.X - cornerA.X) * (cornerC.Y - cornerA.Y) - (cornerC.X - cornerA.X) * (cornerB.Y - cornerA.Y); + } + + /// + /// figures out the triangle on the plane orientation: positive- counterclockwise, negative - clockwise + /// + /// + /// + /// + /// + static public TriangleOrientation GetTriangleOrientation(Point cornerA, Point cornerB, Point cornerC) { + double area = SignedDoubledTriangleArea(cornerA, cornerB, cornerC); + if (area > ApproximateComparer.DistanceEpsilon) + return TriangleOrientation.Counterclockwise; + if (area < -ApproximateComparer.DistanceEpsilon) + return TriangleOrientation.Clockwise; + return TriangleOrientation.Collinear; + } + + + /// + /// figures out the triangle on the plane orientation: positive- counterclockwise, negative - clockwise + /// + /// + /// + /// + /// + static public TriangleOrientation GetTriangleOrientationWithIntersectionEpsilon(Point cornerA, Point cornerB, Point cornerC) + { + var area = Point.SignedDoubledTriangleArea(cornerA, cornerB, cornerC); + if (area > ApproximateComparer.IntersectionEpsilon) + return TriangleOrientation.Counterclockwise; + if (area < -ApproximateComparer.IntersectionEpsilon) + return TriangleOrientation.Clockwise; + return TriangleOrientation.Collinear; + } + + + /// + /// figures out the triangle on the plane orientation: positive- counterclockwise, negative - clockwise + /// + /// + /// + /// + /// + static public TriangleOrientation GetTriangleOrientationWithNoEpsilon(Point cornerA, Point cornerB, Point cornerC) + { + var area = Point.SignedDoubledTriangleArea(cornerA, cornerB, cornerC); + if (area > 0) + return TriangleOrientation.Counterclockwise; + if (area < 0) + return TriangleOrientation.Clockwise; + return TriangleOrientation.Collinear; + } + + + /// + /// returns true if an orthogonal projection of point on [segmentStart,segmentEnd] exists + /// + /// + /// + /// + /// + public static bool CanProject(Point point, Point segmentStart, Point segmentEnd) { + Point bc = segmentEnd - segmentStart; + + Point ba = point - segmentStart; + + if (ba * bc < 0) // point belongs to the halfplane before the segment + return false; + + Point ca = point - segmentEnd; + if (ca * bc > 0) //point belongs to the halfplane after the segment + return false; + + return true; + } + + //returns true if there is an intersection lying in the inner part of the segment + static internal bool IntervalIntersectsRay(Point segStart, Point segEnd, Point rayOrigin, Point rayDirection, out Point x) { + if (!LineLineIntersection(segStart, segEnd, rayOrigin, rayOrigin + rayDirection, out x)) + return false; + var ds = segStart - x; + var de = x - segEnd; + if (ds * de <= 0) + return false; + if ((x - rayOrigin) * rayDirection < 0) + return false; + return ds * ds > ApproximateComparer.SquareOfDistanceEpsilon && de * de >= ApproximateComparer.SquareOfDistanceEpsilon; + } + + //returns true if there is an intersection lying in the inner part of rays + static internal bool RayIntersectsRayInteriors(Point aOrig, Point aDirection, Point bOrig, Point bDirection, out Point x) { + return Point.LineLineIntersection(aOrig, aOrig + aDirection, + bOrig, bOrig + bDirection, out x) && + (x - aOrig) * aDirection / aDirection.L1 > ApproximateComparer.DistanceEpsilon && + (x - bOrig) * bDirection / bDirection.L1 > ApproximateComparer.DistanceEpsilon; + } + + /// + /// projects a point to an infinite line + /// + /// a point on the line + /// a point on the line + /// the point to project + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "OnLine")] + static public Point ProjectionToLine(Point pointOnLine0, Point pointOnLine1, Point point) { + var d = pointOnLine1 - pointOnLine0; + var dLen = d.Length; + if (dLen < ApproximateComparer.DistanceEpsilon) + return pointOnLine0; + d /= dLen; + var pr = (point - pointOnLine0) * d; //projection + var ret = pointOnLine0 + pr * d; + Debug.Assert(Math.Abs((point - ret) * d) < ApproximateComparer.DistanceEpsilon); + return ret; + } + + /// + /// The closest point on the segment [segmentStart,segmentEnd] to "point". + /// See the drawing DistToLineSegment.gif. + /// + /// + /// + /// + /// the parameter of the closest point + /// + static internal double DistToLineSegment(Point point, Point segmentStart, Point segmentEnd, out double parameter) { + Point bc = segmentEnd - segmentStart; + Point ba = point - segmentStart; + double c1, c2; + if ((c1 = bc * ba) <= 0.0 + ApproximateComparer.Tolerance) { + parameter = 0; + return ba.Length; + } + if ((c2 = bc * bc) <= c1 + ApproximateComparer.Tolerance) { + parameter = 1; + return (point - segmentEnd).Length; + } + parameter = c1 / c2; + return (ba - parameter * bc).Length; + } + + /// + /// The closest point on the segment [segmentStart,segmentEnd] to "point". + /// See the drawing DistToLineSegment.gif. + /// + /// + /// + /// + /// + static public Point ClosestPointAtLineSegment(Point point, Point segmentStart, Point segmentEnd) { + Point bc = segmentEnd - segmentStart; + Point ba = point - segmentStart; + double c1, c2; + if ((c1 = bc * ba) <= 0.0 + ApproximateComparer.Tolerance) + return segmentStart; + + if ((c2 = bc * bc) <= c1 + ApproximateComparer.Tolerance) + return segmentEnd; + + double parameter = c1 / c2; + return segmentStart + parameter * bc; + } + + /// + /// return parameter on the segment [segStart, segEnd] which is closest to the "point" + /// see the drawing DistToLineSegment.gif + /// + /// + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "seg"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "OnLine")] + static public double ClosestParameterOnLineSegment(Point point, Point segmentStart, Point segmentEnd) { + Point bc = segmentEnd - segmentStart; + Point ba = point - segmentStart; + double c1, c2; + if ((c1 = bc * ba) <= 0.0 + ApproximateComparer.Tolerance) + return 0; + + if ((c2 = bc * bc) <= c1 + ApproximateComparer.Tolerance) + return 1; + + return c1 / c2; + } + + + /// + /// get the intersection of two infinite lines + /// + /// + /// + /// + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "4#"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "x"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "d"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "c"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "b"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "a")] + static public bool LineLineIntersection(Point a, Point b, Point c, Point d, out Point x) { + //look for the solution in the form a+u*(b-a)=c+v*(d-c) + double u, v; + Point ba = b - a; + Point cd = c - d; + Point ca = c - a; + bool ret = LinearSystem2.Solve(ba.X, cd.X, ca.X, ba.Y, cd.Y, ca.Y, out u, out v); + if (ret) { + x = a + u * ba; + return true; + } + else { + x = new Point(); + return false; + } + } + + /// + /// get the intersection of two line segments + /// + /// + /// + /// + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "4#"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "x"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "d"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "c"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "b"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "a")] + static public bool SegmentSegmentIntersection(Point a, Point b, Point c, Point d, out Point x) { + //look for the solution in the form a+u*(b-a)=c+v*(d-c) + double u, v; + Point ba = b - a; + Point cd = c - d; + Point ca = c - a; + bool ret = LinearSystem2.Solve(ba.X, cd.X, ca.X, ba.Y, cd.Y, ca.Y, out u, out v); + double eps = ApproximateComparer.Tolerance; + if (ret && u > -eps && u < 1.0 + eps && v > -eps && v < 1.0 + eps) { + x = a + u * ba; + return true; + } + else { + x = new Point(); + return false; + } + } + + /// + ///returns true if "point" lies to the left of or on the line linePoint0, linePoint1 + /// + /// + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702")] + public static bool PointToTheLeftOfLineOrOnLine(Point point, Point linePoint0, Point linePoint1) { + return SignedDoubledTriangleArea(point, linePoint0, linePoint1) >= 0; + } + + /// + /// returns true if "point" lies to the left of the line linePoint0, linePoint1 + /// + /// + public static bool PointToTheLeftOfLine(Point point, Point linePoint0, Point linePoint1) { + return SignedDoubledTriangleArea(point, linePoint0, linePoint1) > 0; + } + /// + ///returns true if "point" lies to the right of the line linePoint0, linePoint1 + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")] + public static bool PointToTheRightOfLineOrOnLine(Point point, Point linePoint0, Point linePoint1) { + return SignedDoubledTriangleArea(linePoint0, linePoint1, point) <= 0; + } + + /// + /// + /// + public static bool PointToTheRightOfLine(Point point, Point linePoint0, Point linePoint1) { + return SignedDoubledTriangleArea(linePoint0, linePoint1, point) < 0; + } + + + #region IComparable Members + /// + /// compares two points in the lexigraphical order + /// + /// + /// + public int CompareTo(Point other) { + var r = X.CompareTo(other.X); + return r != 0 ? r : Y.CompareTo(other.Y); + } + /// + /// + /// + internal Directions CompassDirection { + get { return CompassVector.VectorDirection(this); } + } + /// + /// the L1 norm + /// + public double L1 { + get { return Math.Abs(X) + Math.Abs(Y); } + } + + #endregion + + /// + ///rotates the point 90 degrees counterclockwise + /// + public Point Rotate90Ccw() { + return new Point(-Y, X); + } + + /// + ///rotates the point 90 degrees counterclockwise + /// + public Point Rotate90Cw() { + return new Point(Y, -X); + } + + internal static bool PointIsInsideCone(Point p, Point apex, Point leftSideConePoint, Point rightSideConePoint) { + return PointToTheRightOfLineOrOnLine(p, apex, leftSideConePoint) && + PointToTheLeftOfLineOrOnLine(p, apex, rightSideConePoint); + } + + static Random rnd = new Random(1); + /// + ///creates random unit point + /// + internal static Point RandomPoint() { + double x = -1 + 2 * rnd.NextDouble(); + double y = -1 + 2 * rnd.NextDouble(); + return new Point(x, y).Normalize(); + } + + public static implicit operator Point(float2 v) { return new Point(v.x, v.y); } + public static implicit operator float2(Point v) { return new float2((float) v.X, (float) v.Y); } + + public static implicit operator Point(Vector2 v) { return new Point(v.x, v.y); } + public static implicit operator Vector2(Point v) { return new Vector2((float) v.X, (float) v.Y); } + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Point.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Point.cs.meta new file mode 100644 index 000000000..9e88d7a3c --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Point.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5079a1dee30478f42b7eee00e806a7e3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/PointNodesList.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/PointNodesList.cs new file mode 100644 index 000000000..79e0e4dc3 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/PointNodesList.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; + +namespace Microsoft.Msagl.Core.Geometry { + internal class PointNodesList : IEnumerator, IEnumerable { + Site current, head; + + Site Head { + get { return head; } + } + + internal PointNodesList(Site pointNode) { + head = pointNode; + } + + internal PointNodesList() { } + #region IEnumerator Members + + public Point Current { + get { return current.Point; } + } + + #endregion + + #region IDisposable Members + + public void Dispose() { GC.SuppressFinalize(this); } + + #endregion + + #region IEnumerator Members + + object System.Collections.IEnumerator.Current { + get { return current.Point; } + } + + public bool MoveNext() { + if (current != null) { + if (current.Next != null) { + current = current.Next; + return true; + } else + return false; + } else { + current = Head; + return true; + } + + } + + public void Reset() { + current = null; + } + + #endregion + + #region IEnumerable Members + + public IEnumerator GetEnumerator() { + return this; + } + + #endregion + + #region IEnumerable Members + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { + return this; + } + + #endregion + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/PointNodesList.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/PointNodesList.cs.meta new file mode 100644 index 000000000..0efee32e0 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/PointNodesList.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3cdbd5faf3199334082aa19829c773f1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/PointPair.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/PointPair.cs new file mode 100644 index 000000000..5a6162789 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/PointPair.cs @@ -0,0 +1,86 @@ +using System; + +namespace Microsoft.Msagl.Core.Geometry { + /// + /// A segment line wrapper used for creation Dictionary of pairs of points + /// + internal class PointPair : IComparable { + Point first; + Point second; + +#if SHARPKIT //https://code.google.com/p/sharpkit/issues/detail?id=289 + //SharpKit/Colin - hashing + SharpKit.JavaScript.JsString _hashKey; +#endif + + public PointPair(Point first, Point second) + { + if (IsLess(first, second)) { + this.first = first; + this.second = second; + } else { + this.first = second; + this.second = first; + } +#if SHARPKIT //https://code.google.com/p/sharpkit/issues/detail?id=289 + //SharpKit/Colin - hashing + _hashKey = this.first.X + "," + this.first.Y + ";" + this.second.X + "," + this.second.Y; +#endif + } + + public Point First { + get { return first; } + } + + public Point Second { + get { return second; } + } + + public double Length { + get { return (first - second).Length; } + } + + #region IComparable Members + + public int CompareTo(PointPair other) { + ValidateArg.IsNotNull(other, "other"); + int cr = first.CompareTo(other.first); + if (cr != 0) return cr; + return second.CompareTo(other.second); + } + + #endregion + + static bool IsLess(Point p1, Point p2) { + if (p1.Y < p2.Y) + return true; + if (p1.Y > p2.Y) + return false; + return p1.X < p2.X; + } + + public override int GetHashCode() { + var hc = (uint) first.GetHashCode(); + return (int) ((hc << 5 | hc >> 27) + (uint) second.GetHashCode()); + } + + public override bool Equals(object obj) { + var otherPair = obj as PointPair; + if (ReferenceEquals(otherPair, null)) + return false; + return otherPair == this; + } + + public static bool operator ==(PointPair pair0, PointPair pair1) { + return (pair0.first == pair1.first && pair0.second == pair1.second); + } + + public static bool operator !=(PointPair pair0, PointPair pair1) { + return !(pair0 == pair1); + } + + public override string ToString() { + return first + " " + second; + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/PointPair.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/PointPair.cs.meta new file mode 100644 index 000000000..e6ce6471b --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/PointPair.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4ee287d55ce862548889fb1762b8e43f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree.meta new file mode 100644 index 000000000..360015921 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ef59b89a39a942144bb10296129db8dd +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/HitTestBehavior.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/HitTestBehavior.cs new file mode 100644 index 000000000..40c85f203 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/HitTestBehavior.cs @@ -0,0 +1,15 @@ +namespace Microsoft.Msagl.Core.Geometry { + /// + ///continue or stop the hit tree traversal + /// + public enum HitTestBehavior { + /// + ///continue the hit tree traversal + /// + Continue, + /// + ///stop the hit tree traversal + /// + Stop + } +} \ No newline at end of file diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/HitTestBehavior.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/HitTestBehavior.cs.meta new file mode 100644 index 000000000..7bcbaf4f1 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/HitTestBehavior.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2c4872ad19222e449b32ba147d419bb5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/IntervalNode.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/IntervalNode.cs new file mode 100644 index 000000000..a0e9808cc --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/IntervalNode.cs @@ -0,0 +1,478 @@ +#region Using directives + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Diagnostics; +using Microsoft.Msagl.Layout.LargeGraphLayout; + +#endregion + +namespace Microsoft.Msagl.Core.Geometry { + /// + /// Represents a node containing a box and some user data. + /// Is used in curve intersections routines. + /// + public class IntervalNode { +#if TEST_MSAGL + /// + /// + /// + /// + [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.Int32.ToString")] + public override string ToString() { + return IsLeaf ? (Count + " " + UserData) : Count.ToString(); + } +#endif + /// + /// + /// + public int Count { get; set; } + IntervalNode left; + + IntervalNode right; + + /// + /// creates an empty node + /// + public IntervalNode() { + } + + /// + /// + /// + /// + /// + public IntervalNode(TData data, Interval rect) { + UserData = data; + Interval = rect; + Count = 1; + } + + IntervalNode(int count) { + Count=count; + } + + /// + /// This field provides direct internal access to the value type Interval, which RTree and other callers + /// modify directly with .Add(); the auto-property returns a temporary value-by-copy that is immediately discarded. + /// +// ReSharper disable InconsistentNaming + internal Interval interval; +// ReSharper restore InconsistentNaming + + /// + /// gets or sets the interval of the node + /// + public Interval Interval { + get { return interval; } + set { interval = value; } + } + + /// + /// false if it is an internal node and true if it is a leaf + /// + internal bool IsLeaf + { + get { return left==null /*&& right==null*/; } //if left is a null then right is also a null + } + /// + /// + /// + public IntervalNode Left { + get { return left; } + internal set { + if (left != null && left.Parent==this) + left.Parent = null; + left = value; + if (left != null) + left.Parent = this; + } + } + + /// + /// + /// + public IntervalNode Right { + get { return right; } + internal set { + if (right != null&& right.Parent==this) + right.Parent = null; + right = value; + if (right != null) + right.Parent = this; + } + } + + /// + /// The actual data if a leaf node, else null or a value-type default. + /// + public TData UserData { get; set; } + + /// + /// Parent of this node. + /// + public IntervalNode Parent { get; private set; } + + internal bool IsLeftChild { + get { + Debug.Assert(Parent!=null); + return Equals(Parent.Left); + } + } + + /// + /// brings the first leaf which interval was hit and the delegate is happy with the object + /// + /// + /// + /// + public IntervalNode FirstHitNode(double point, Func hitTestFordoubleDelegate) { + if (interval.Contains(point)) { + if (IsLeaf) { + if (hitTestFordoubleDelegate != null) { + return hitTestFordoubleDelegate(point, UserData) == HitTestBehavior.Stop ? this : null; + } + return this; + } + return Left.FirstHitNode(point, hitTestFordoubleDelegate) ?? + Right.FirstHitNode(point, hitTestFordoubleDelegate); + } + return null; + } + + + /// + /// brings the first leaf which interval was intersected + /// + /// + public IntervalNode FirstIntersectedNode(Interval r) { + if (r.Intersects(interval)) { + if (IsLeaf) + return this; + return Left.FirstIntersectedNode(r) ?? Right.FirstIntersectedNode(r); + } + return null; + } + + + + /// + /// brings the first leaf which interval was hit and the delegate is happy with the object + /// + /// + /// + public IntervalNode FirstHitNode(double point) { + if (interval.Contains(point)) { + if (IsLeaf) + return this; + return Left.FirstHitNode(point) ?? Right.FirstHitNode(point); + } + return null; + } + + + /// + /// returns all leaf nodes for which the interval was hit and the delegate is happy with the object + /// + /// + /// + /// + public IEnumerable AllHitItems(Interval intervalPar, Func hitTestAccept) { + var stack = new Stack>(); + stack.Push(this); + while (stack.Count > 0) { + IntervalNode node = stack.Pop(); + if (node.Interval.Intersects(intervalPar)) { + if (node.IsLeaf) { + if ((null == hitTestAccept) || hitTestAccept(node.UserData)) { + yield return node.UserData; + } + } + else { + stack.Push(node.left); + stack.Push(node.right); + } + } + } + } + + /// + /// returns all items for which the interval contains the point + /// + /// + public IEnumerable AllHitItems(double point) { + var stack = new Stack>(); + stack.Push(this); + while (stack.Count > 0) { + var node = stack.Pop(); + if (node.Interval.Contains(point)) { + if (node.IsLeaf) + yield return node.UserData; + else { + stack.Push(node.left); + stack.Push(node.right); + } + } + } + } + + static HitTestBehavior VisitTreeStatic(IntervalNode intervalNode, Func hitTest, Interval hitInterval) { + if (intervalNode.Interval.Intersects(hitInterval)) { + if (hitTest(intervalNode.UserData) == HitTestBehavior.Continue) { + if (intervalNode.Left != null) { + // If intervalNode.Left is not null, intervalNode.Right won't be either. + if (VisitTreeStatic(intervalNode.Left, hitTest, hitInterval) == HitTestBehavior.Continue && + VisitTreeStatic(intervalNode.Right, hitTest, hitInterval) == HitTestBehavior.Continue) { + return HitTestBehavior.Continue; + } + return HitTestBehavior.Stop; + } + return HitTestBehavior.Continue; + } + return HitTestBehavior.Stop; + } + return HitTestBehavior.Continue; + } + + /// + /// + /// + /// + public IntervalNode Clone() { + var ret = new IntervalNode(Count) {UserData = UserData, Interval = Interval}; + if (Left != null) + ret.Left = Left.Clone(); + if (Right != null) + ret.Right = Right.Clone(); + return ret; + } + + /// + /// yields all leaves which intervals intersect the given one. We suppose that leaves are all nodes having UserData not a null. + /// + /// + /// + public IEnumerable GetNodeItemsIntersectingInterval(Interval intervalPar) { + return GetLeafIntervalNodesIntersectingInterval(intervalPar).Select(node => node.UserData); + } + + /// + /// yields all leaves whose intervals intersect the given one. We suppose that leaves are all nodes having UserData not a null. + /// + /// + /// + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + public IEnumerable> GetLeafIntervalNodesIntersectingInterval(Interval intervalPar) { + var stack = new Stack>(); + stack.Push(this); + while (stack.Count > 0) { + IntervalNode node = stack.Pop(); + if (node.Interval.Intersects(intervalPar)) { + if (node.IsLeaf) { + yield return node; + } else { + stack.Push(node.left); + stack.Push(node.right); + } + } + } + } + + /// + /// Walk the tree and return the data from all leaves + /// + /// + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] + public IEnumerable GetAllLeaves() { + return GetAllLeafNodes().Select(n => n.UserData); + } + + internal IEnumerable> GetAllLeafNodes() { + return EnumIntervalNodes(true /*leafOnly*/); + } + + IEnumerable> EnumIntervalNodes(bool leafOnly) { + var stack = new Stack>(); + stack.Push(this); + while (stack.Count > 0) { + var node = stack.Pop(); + if (node.IsLeaf || !leafOnly) { + yield return node; + } + if (!node.IsLeaf) { + stack.Push(node.left); + stack.Push(node.right); + } + } + } + + const int GroupSplitThreshold = 2; + + + /// + /// calculates a tree based on the given nodes + /// + /// + /// + [SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes"), SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + public static IntervalNode CreateIntervalNodeOnEnumeration(IEnumerable> nodes) { + if(nodes==null) + return null; + var nodeList = new List>(nodes); + return CreateIntervalNodeOnListOfNodes(nodeList); + } + + /// + ///calculates a tree based on the given nodes + /// + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes")] + public static IntervalNode CreateIntervalNodeOnData(IEnumerable dataEnumeration, Func intervalDelegate) { + if (dataEnumeration == null || intervalDelegate == null) + return null; + var nodeList = new List>(dataEnumeration.Select(d=>new IntervalNode(d, intervalDelegate(d)))); + return CreateIntervalNodeOnListOfNodes(nodeList); + } + + + /// + /// + /// + /// + /// + [SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes"), SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + static public IntervalNode CreateIntervalNodeOnListOfNodes(IList> nodes) { + ValidateArg.IsNotNull(nodes, "nodes"); + if (nodes.Count == 0) return null; + + if (nodes.Count == 1)return nodes[0]; + + //Finding the seeds + var b0 = nodes[0].Interval; + + //the first seed + int seed0 = 1; + + int seed1 = ChooseSeeds(nodes, ref b0, ref seed0); + + //We have two seeds at hand. Build two groups. + var gr0 = new List>(); + var gr1 = new List>(); + + gr0.Add(nodes[seed0]); + gr1.Add(nodes[seed1]); + + var box0 = nodes[seed0].Interval; + var box1 = nodes[seed1].Interval; + //divide nodes on two groups + DivideNodes(nodes, seed0, seed1, gr0, gr1, ref box0, ref box1, GroupSplitThreshold); + + var ret = new IntervalNode(nodes.Count) { + Interval = new Interval(box0, box1), + Left = CreateIntervalNodeOnListOfNodes(gr0), + Right = CreateIntervalNodeOnListOfNodes(gr1) + }; + + return ret; + + } + + static int ChooseSeeds(IList> nodes, ref Interval b0, ref int seed0) { + double area = new Interval(b0, nodes[seed0].Interval).Length; + for (int i = 2; i < nodes.Count; i++) { + double area0 = new Interval(b0, nodes[i].Interval).Length; + if (area0 > area) { + seed0 = i; + area = area0; + } + } + + //Got the first seed seed0 + //Now looking for a seed for the second group + int seed1 = 0; //the compiler forces me to init it + + //init seed1 + for (int i = 0; i < nodes.Count; i++) { + if (i != seed0) { + seed1 = i; + break; + } + } + + area = new Interval(nodes[seed0].Interval, nodes[seed1].Interval).Length; + //Now try to improve the second seed + + for (int i = 0; i < nodes.Count; i++) { + if (i == seed0) + continue; + double area1 = new Interval(nodes[seed0].Interval, nodes[i].Interval).Length; + if (area1 > area) { + seed1 = i; + area = area1; + } + } + return seed1; + } + + static void DivideNodes(IList> nodes, int seed0, int seed1, List> gr0, List> gr1, + ref Interval box0, ref Interval box1, int groupSplitThreshold) { + for (int i = 0; i < nodes.Count; i++) { + + if (i == seed0 || i == seed1) + continue; + +// ReSharper disable InconsistentNaming + var box0_ = new Interval(box0, nodes[i].Interval); + double delta0 = box0_.Length - box0.Length; + + var box1_ = new Interval(box1, nodes[i].Interval); + double delta1 = box1_.Length - box1.Length; +// ReSharper restore InconsistentNaming + + //keep the tree roughly balanced + + if (gr0.Count * groupSplitThreshold < gr1.Count) { + gr0.Add(nodes[i]); + box0 = box0_; + } else if (gr1.Count * groupSplitThreshold < gr0.Count) { + gr1.Add(nodes[i]); + box1 = box1_; + } else if (delta0 < delta1) { + gr0.Add(nodes[i]); + box0 = box0_; + } else if (delta1 < delta0) { + gr1.Add(nodes[i]); + box1 = box1_; + } else if (box0.Length < box1.Length) { + gr0.Add(nodes[i]); + box0 = box0_; + } else { + gr1.Add(nodes[i]); + box1 = box1_; + } + } + } + + + + /// + /// Walk the tree from node down and apply visitor to all nodes + /// + /// + /// + [SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes"), SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + static public void TraverseHierarchy(IntervalNode node, Action> visitor) { + ValidateArg.IsNotNull(node, "node"); + ValidateArg.IsNotNull(visitor, "visitor"); + visitor(node); + if (node.Left != null) + TraverseHierarchy(node.Left, visitor); + if (node.Right != null) + TraverseHierarchy(node.Right, visitor); + } + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/IntervalNode.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/IntervalNode.cs.meta new file mode 100644 index 000000000..d6d739e1e --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/IntervalNode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7d80cdba0a1b4414e9aab75443b5dc18 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/IntervalNodeUtils.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/IntervalNodeUtils.cs new file mode 100644 index 000000000..19ed66d32 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/IntervalNodeUtils.cs @@ -0,0 +1,115 @@ +using System; + +namespace Microsoft.Msagl.Core.Geometry { + internal sealed class IntervalNodeUtils { + internal static void CrossIntervalNodes(IntervalNode a, IntervalNode b, Action action) { + if (!a.interval.Intersects(b.interval)) + return; + if (a.Left == null) { //a is a leat + if (b.Left == null) //b is a leaf + action(a.UserData, b.UserData); + else { + CrossIntervalNodes(a, b.Left, action); + CrossIntervalNodes(a, b.Right, action); + } + } else { //a is not a leaf + if (b.Left != null) { + CrossIntervalNodes(a.Left, b.Left, action); + CrossIntervalNodes(a.Left, b.Right, action); + CrossIntervalNodes(a.Right, b.Left, action); + CrossIntervalNodes(a.Right, b.Right, action); + } else { // b is a leaf + CrossIntervalNodes(a.Left, b, action); + CrossIntervalNodes(a.Right, b, action); + } + } + } + + internal static void CrossIntervalNodes(IntervalNode a, IntervalNode b, Action action) { + if (!a.interval.Intersects(b.interval)) + return; + if (Equals(a, b)) + HandleEquality(a, action); + else if (a.Left == null) { + if (b.Left == null) { + action(a.UserData, b.UserData); + } else { + CrossIntervalNodes(a, b.Left, action); + CrossIntervalNodes(a, b.Right, action); + } + } else { + if (b.Left != null) { + CrossIntervalNodes(a.Left, b.Left, action); + CrossIntervalNodes(a.Left, b.Right, action); + CrossIntervalNodes(a.Right, b.Left, action); + CrossIntervalNodes(a.Right, b.Right, action); + } else { + CrossIntervalNodes(a.Left, b, action); + CrossIntervalNodes(a.Right, b, action); + } + } + } + + /// + /// returns true if "property" holds for some pair + /// + /// + /// + /// + /// + /// + internal static bool FindIntersectionWithProperty(IntervalNode a, IntervalNode b, Func property) { + if (!a.interval.Intersects(b.interval)) + return false; + if (Equals(a, b)) + return HandleEqualityCheck(a, property); + + if (a.Left == null) { + if (b.Left == null) + return property(a.UserData, b.UserData); + + if (FindIntersectionWithProperty(a, b.Left, property)) + return true; + if (FindIntersectionWithProperty(a, b.Right, property)) + return true; + } else { + if (b.Left != null) { + if (FindIntersectionWithProperty(a.Left, b.Left, property)) + return true; + if (FindIntersectionWithProperty(a.Left, b.Right, property)) + return true; + if (FindIntersectionWithProperty(a.Right, b.Left, property)) + return true; + if (FindIntersectionWithProperty(a.Right, b.Right, property)) + return true; + } else { + + if (FindIntersectionWithProperty(a.Left, b, property)) + return true; + if (FindIntersectionWithProperty(a.Right, b, property)) + return true; + } + } + return false; + } + + static bool HandleEqualityCheck(IntervalNode a, Func func) { + if (a.Left == null) return false; //we don't do anything for two equal leafs + return FindIntersectionWithProperty(a.Left, a.Left, func) || + FindIntersectionWithProperty(a.Left, a.Right, func) || FindIntersectionWithProperty(a.Right, a.Right, func); + } + + /// + /// we need to avoid calling action twice for the same pair + /// + /// + /// + /// + static void HandleEquality(IntervalNode a, Action action) { + if (a.Left == null) return; //we don't do anything for two equal leafs + CrossIntervalNodes(a.Left, a.Left, action); + CrossIntervalNodes(a.Left, a.Right, action); + CrossIntervalNodes(a.Right, a.Right, action); + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/IntervalNodeUtils.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/IntervalNodeUtils.cs.meta new file mode 100644 index 000000000..54d169f3b --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/IntervalNodeUtils.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b817c742940f257439a08a329ce6f7c3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/IntervalRTree.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/IntervalRTree.cs new file mode 100644 index 000000000..a2dfa6045 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/IntervalRTree.cs @@ -0,0 +1,270 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Microsoft.Msagl.Layout.LargeGraphLayout; + +namespace Microsoft.Msagl.Core.Geometry { + /// + /// A search tree for rapid lookup of TData objects keyed by rectangles inside a given rectangular region + /// It is very similar to "R-TREES. A DYNAMIC INDEX STRUCTURE FOR SPATIAL SEARCHING" by Antonin Guttman + /// + public class IntervalRTree { + /// + /// + /// + public IntervalNode RootNode + { + get { return rootNode; } + set { rootNode=value; } + } + + IntervalNode rootNode; + + + /// + /// Create the query tree for a given enumerable of TData keyed by Intervals + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + public IntervalRTree(IEnumerable> rectsAndData) { + rootNode = IntervalNode.CreateIntervalNodeOnEnumeration(GetNodeRects(rectsAndData)); + } + + /// + /// Create a query tree for a given root node + /// + /// + public IntervalRTree(IntervalNode rootNode) { + this.rootNode = rootNode; + } + + /// + /// + public IntervalRTree() { + + } + + /// + /// The number of data elements in the tree (number of leaf nodes) + /// + public int Count { + get { return rootNode == null ? 0 : rootNode.Count; } + } + + + /// + /// Add the given key, value pair + /// + /// + /// + public void Add(Interval key, TData value) { + Add(new IntervalNode(value, key)); + } + + internal void Add(IntervalNode node) { + + if (rootNode == null) + rootNode = node; + else if (Count <= 2) + rootNode = IntervalNode.CreateIntervalNodeOnEnumeration(rootNode.GetAllLeafNodes().Concat(new[] {node})); + else + AddNodeToTreeRecursive(node, rootNode); + } + /// + /// rebuild the whole tree + /// + public void Rebuild() { + rootNode = IntervalNode.CreateIntervalNodeOnEnumeration(rootNode.GetAllLeafNodes()); + } + + static IEnumerable> GetNodeRects(IEnumerable> nodes) { + return nodes.Select(v => new IntervalNode(v.Value, v.Key)); + } + + static void AddNodeToTreeRecursive(IntervalNode newNode, IntervalNode existingNode) { + if (existingNode.IsLeaf) { + existingNode.Left = new IntervalNode(existingNode.UserData, existingNode.Interval); + existingNode.Right = newNode; + existingNode.Count = 2; + existingNode.UserData = default(TData); + } else { + existingNode.Count++; + Interval leftBox; + Interval rightBox; + if (2 * existingNode.Left.Count < existingNode.Right.Count) { + //keep the balance + AddNodeToTreeRecursive(newNode, existingNode.Left); + existingNode.Left.Interval = new Interval(existingNode.Left.Interval, newNode.Interval); + } else if (2 * existingNode.Right.Count < existingNode.Left.Count) { + //keep the balance + AddNodeToTreeRecursive(newNode, existingNode.Right); + existingNode.Right.Interval = new Interval(existingNode.Right.Interval, newNode.Interval); + } else { //decide basing on the boxes + leftBox = new Interval(existingNode.Left.Interval, newNode.Interval); + var delLeft = leftBox.Length - existingNode.Left.Interval.Length; + rightBox = new Interval(existingNode.Right.Interval, newNode.Interval); + var delRight = rightBox.Length - existingNode.Right.Interval.Length; + if (delLeft < delRight) { + AddNodeToTreeRecursive(newNode, existingNode.Left); + existingNode.Left.Interval = leftBox; + } else if(delLeft>delRight){ + AddNodeToTreeRecursive(newNode, existingNode.Right); + existingNode.Right.Interval = rightBox; + } else { //the deltas are the same; add to the smallest + if(leftBox.Length + /// return all the data elements stored at the leaves of the BSPTree in an IEnumerable + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] + public IEnumerable GetAllLeaves() { + return rootNode!=null && Count>0 ? rootNode.GetAllLeaves():new TData[0]; + } + + /// + /// Get all data items with rectangles intersecting the specified rectangular region + /// + /// + /// + public IEnumerable GetAllIntersecting(Interval queryRegion) + { + return rootNode == null || Count == 0 ? new TData[0] : rootNode.GetNodeItemsIntersectingInterval(queryRegion); + } + + /// + /// Does minimal work to determine if any objects in the tree intersect with the query region + /// + /// + /// + public bool IsIntersecting(Interval queryRegion) { + return GetAllIntersecting(queryRegion).Any(); + } + + /// + /// return true iff there is a node with the rectangle and UserData that equals to the parameter "userData" + /// + /// + /// + /// + public bool Contains(Interval rectangle, TData userData) { + if (rootNode == null) return false; + return + rootNode.GetLeafIntervalNodesIntersectingInterval(rectangle) + .Any(node => node.UserData.Equals(userData)); + } + + /// + /// + /// + /// + /// + public TData Remove(Interval rectangle, TData userData) { + if (rootNode==null) + { + return default(TData); + } + var ret = rootNode.GetLeafIntervalNodesIntersectingInterval(rectangle).FirstOrDefault(node => node.UserData.Equals(userData)); + if (ret == null) + return default(TData); + if (RootNode.Count == 1) + RootNode = null; + else + RemoveLeaf(ret); + return ret.UserData; + } + + void RemoveLeaf(IntervalNode leaf) { + Debug.Assert(leaf.IsLeaf); + + var unbalancedNode = FindTopUnbalancedNode(leaf); + if (unbalancedNode != null) { + RebuildUnderNodeWithoutLeaf(unbalancedNode, leaf); + UpdateParent(unbalancedNode); + } else { + //replace the parent with the sibling and update bounding boxes and counts + var parent = leaf.Parent; + if (parent == null) { + Debug.Assert(rootNode == leaf); + rootNode = new IntervalNode(); + } else { + TransferFromSibling(parent, leaf.IsLeftChild ? parent.Right : parent.Left); + UpdateParent(parent); + } + } + Debug.Assert(TreeIsCorrect(RootNode)); + } + + static bool TreeIsCorrect(IntervalNode node) + { + if (node == null) + return true; + bool ret= node.Left != null && node.Right != null || + node.Left == null && node.Right == null; + if (!ret) + return false; + return TreeIsCorrect(node.Left) && TreeIsCorrect(node.Right); + } + + static void UpdateParent(IntervalNode parent) { + for(var node=parent.Parent; node!=null; node=node.Parent) { + node.Count--; + node.Interval=new Interval(node.Left.Interval, node.Right.Interval); + } + } + + static void TransferFromSibling(IntervalNode parent, IntervalNode sibling) { + parent.UserData=sibling.UserData; + parent.Left = sibling.Left; + parent.Right=sibling.Right; + parent.Count--; + parent.Interval = sibling.Interval; + } + + static void RebuildUnderNodeWithoutLeaf(IntervalNode nodeForRebuild, IntervalNode leaf) + { + Debug.Assert(leaf.IsLeaf); + Debug.Assert(!nodeForRebuild.IsLeaf); + var newNode = + IntervalNode.CreateIntervalNodeOnEnumeration( + nodeForRebuild.GetAllLeafNodes().Where(n => !(n.Equals(leaf)))); + nodeForRebuild.Count = newNode.Count; + nodeForRebuild.Left = newNode.Left; + nodeForRebuild.Right = newNode.Right; + nodeForRebuild.Interval = new Interval(newNode.Left.interval, newNode.Right.interval); + } + + static IntervalNode FindTopUnbalancedNode(IntervalNode node) { + for (var parent = node.Parent; parent != null; parent = parent.Parent) + if (! Balanced(parent)) + return parent; + return null; + } + + static bool Balanced(IntervalNode rectangleNode) { + return 2*rectangleNode.Left.Count >= rectangleNode.Right.Count && + 2*rectangleNode.Right.Count >= rectangleNode.Left.Count; + } + /// + /// Removes everything from the tree + /// + public void Clean() + { + RootNode = null; + } + } + +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/IntervalRTree.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/IntervalRTree.cs.meta new file mode 100644 index 000000000..39ff4b50d --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/IntervalRTree.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ddf66e38897bb4b4bb35090ad5b62372 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/RTree.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/RTree.cs new file mode 100644 index 000000000..99973d774 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/RTree.cs @@ -0,0 +1,310 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Microsoft.Msagl.Core.Layout; + +namespace Microsoft.Msagl.Core.Geometry { + /// + /// A search tree for rapid lookup of TData objects keyed by rectangles inside a given rectangular region + /// It is very similar to "R-TREES. A DYNAMIC INDEX STRUCTURE FOR SPATIAL SEARCHING" by Antonin Guttman + /// + public class RTree { + /// + /// + /// + public RectangleNode RootNode + { + get { return _rootNode; } + set { _rootNode=value; } + } + + RectangleNode _rootNode; + + + /// + /// Create the query tree for a given enumerable of TData keyed by Rectangles + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + public RTree(IEnumerable> rectsAndData) { + _rootNode = RectangleNode.CreateRectangleNodeOnEnumeration(GetNodeRects(rectsAndData)); + } + + /// + /// Create a query tree for a given root node + /// + /// + public RTree(RectangleNode rootNode) { + this._rootNode = rootNode; + } + + /// + /// + public RTree() { + + } + + /// + /// The number of data elements in the tree (number of leaf nodes) + /// + public int Count { + get { return _rootNode == null ? 0 : _rootNode.Count; } + } + + + /// + /// Add the given key, value pair + /// + /// + /// + public void Add(Rectangle key, TData value) { + Add(new RectangleNode(value, key)); + } + + internal void Add(RectangleNode node) { + if (_rootNode == null) + _rootNode = node; + else if (Count <= 2) + _rootNode = RectangleNode.CreateRectangleNodeOnEnumeration(_rootNode.GetAllLeafNodes().Concat(new[] {node})); + else + AddNodeToTreeRecursive(node, _rootNode); + } + /// + /// rebuild the whole tree + /// + public void Rebuild() { + _rootNode = RectangleNode.CreateRectangleNodeOnEnumeration(_rootNode.GetAllLeafNodes()); + } + + static IEnumerable> GetNodeRects(IEnumerable> nodes) { + return nodes.Select(v => new RectangleNode(v.Value, v.Key)); + } + + static void AddNodeToTreeRecursive(RectangleNode newNode, RectangleNode existingNode) { + if (existingNode.IsLeaf) { + existingNode.Left = new RectangleNode(existingNode.UserData, existingNode.Rectangle); + existingNode.Right = newNode; + existingNode.Count = 2; + existingNode.UserData = default(TData); + } else { + existingNode.Count++; + Rectangle leftBox; + Rectangle rightBox; + if (2 * existingNode.Left.Count < existingNode.Right.Count) { + //keep the balance + AddNodeToTreeRecursive(newNode, existingNode.Left); + existingNode.Left.Rectangle = new Rectangle(existingNode.Left.Rectangle, newNode.Rectangle); + } else if (2 * existingNode.Right.Count < existingNode.Left.Count) { + //keep the balance + AddNodeToTreeRecursive(newNode, existingNode.Right); + existingNode.Right.Rectangle = new Rectangle(existingNode.Right.Rectangle, newNode.Rectangle); + } else { //decide basing on the boxes + leftBox = new Rectangle(existingNode.Left.Rectangle, newNode.Rectangle); + var delLeft = leftBox.Area - existingNode.Left.Rectangle.Area; + rightBox = new Rectangle(existingNode.Right.Rectangle, newNode.Rectangle); + var delRight = rightBox.Area - existingNode.Right.Rectangle.Area; + if (delLeft < delRight) { + AddNodeToTreeRecursive(newNode, existingNode.Left); + existingNode.Left.Rectangle = leftBox; + } else if(delLeft>delRight){ + AddNodeToTreeRecursive(newNode, existingNode.Right); + existingNode.Right.Rectangle = rightBox; + } else { //the deltas are the same; add to the smallest + if(leftBox.Area + /// return all the data elements stored at the leaves of the BSPTree in an IEnumerable + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] + public IEnumerable GetAllLeaves() { + return _rootNode!=null && Count>0 ? _rootNode.GetAllLeaves():new TData[0]; + } + + /// + /// Get all data items with rectangles intersecting the specified rectangular region + /// + /// + /// + public TData[] GetAllIntersecting(Rectangle queryRegion) + { + return _rootNode == null || Count == 0 ? new TData[0] : _rootNode.GetNodeItemsIntersectingRectangle(queryRegion).ToArray(); + } + + public bool OneIntersecting(Rectangle queryRegion, out TData intersectedLeaf) { + if (_rootNode == null || Count == 0) { + intersectedLeaf = default(TData); + return false; + } + RectangleNode ret = _rootNode.FirstIntersectedNode(queryRegion); + if (ret == null) { + intersectedLeaf = default(TData); + return false; + } + intersectedLeaf = ret.UserData; + return true; + } + + /// + /// Get all leaf nodes with rectangles intersecting the specified rectangular region + /// + /// + /// + internal IEnumerable> GetAllLeavesIntersectingRectangle(Rectangle queryRegion) { + return _rootNode == null || Count == 0 ? new RectangleNode[0] : _rootNode.GetLeafRectangleNodesIntersectingRectangle(queryRegion); + } + + /// + /// Does minimal work to determine if any objects in the tree intersect with the query region + /// + /// + /// + public bool IsIntersecting(Rectangle queryRegion) { + return GetAllIntersecting(queryRegion).Any(); + } + + /// + /// return true iff there is a node with the rectangle and UserData that equals to the parameter "userData" + /// + /// + /// + /// + public bool Contains(Rectangle rectangle, TData userData) { + if (_rootNode == null) return false; + return + _rootNode.GetLeafRectangleNodesIntersectingRectangle(rectangle) + .Any(node => node.UserData.Equals(userData)); + } + + /// + /// + /// + /// + /// + public TData Remove(Rectangle rectangle, TData userData) { + if (_rootNode==null) + { + return default(TData); + } + var ret = _rootNode.GetLeafRectangleNodesIntersectingRectangle(rectangle).FirstOrDefault(node => node.UserData.Equals(userData)); + if (ret == null) + return default(TData); + if (RootNode.Count == 1) + RootNode = null; + else + RemoveLeaf(ret); + return ret.UserData; + } + + void RemoveLeaf(RectangleNode leaf) { + Debug.Assert(leaf.IsLeaf); + + var unbalancedNode = FindTopUnbalancedNode(leaf); + if (unbalancedNode != null) { + RebuildUnderNodeWithoutLeaf(unbalancedNode, leaf); + UpdateParent(unbalancedNode); + } else { + //replace the parent with the sibling and update bounding boxes and counts + var parent = leaf.Parent; + if (parent == null) { + Debug.Assert(_rootNode == leaf); + _rootNode = new RectangleNode(); + } else { + TransferFromSibling(parent, leaf.IsLeftChild ? parent.Right : parent.Left); + UpdateParent(parent); + } + } + // Debug.Assert(TreeIsCorrect(RootNode)); + } + +// static bool TreeIsCorrect(RectangleNode node) +// { +// if (node == null) +// return true; +// bool ret= node.Left != null && node.Right != null || +// node.Left == null && node.Right == null; +// if (!ret) +// return false; +// return TreeIsCorrect(node.Left) && TreeIsCorrect(node.Right); +// } + + static void UpdateParent(RectangleNode parent) { + for(var node=parent.Parent; node!=null; node=node.Parent) { + node.Count--; + node.Rectangle=new Rectangle(node.Left.Rectangle, node.Right.Rectangle); + } + } + + static void TransferFromSibling(RectangleNode parent, RectangleNode sibling) { + parent.UserData=sibling.UserData; + parent.Left = sibling.Left; + parent.Right=sibling.Right; + parent.Count--; + parent.Rectangle = sibling.Rectangle; + } + + static void RebuildUnderNodeWithoutLeaf(RectangleNode nodeForRebuild, RectangleNode leaf) + { + Debug.Assert(leaf.IsLeaf); + Debug.Assert(!nodeForRebuild.IsLeaf); + var newNode = + RectangleNode.CreateRectangleNodeOnEnumeration( + nodeForRebuild.GetAllLeafNodes().Where(n => !(n.Equals(leaf)))); + nodeForRebuild.Count = newNode.Count; + nodeForRebuild.Left = newNode.Left; + nodeForRebuild.Right = newNode.Right; + nodeForRebuild.Rectangle = new Rectangle(newNode.Left.rectangle, newNode.Right.rectangle); + } + + static RectangleNode FindTopUnbalancedNode(RectangleNode node) { + for (var parent = node.Parent; parent != null; parent = parent.Parent) + if (! Balanced(parent)) + return parent; + return null; + } + + static bool Balanced(RectangleNode rectangleNode) { + return 2*rectangleNode.Left.Count >= rectangleNode.Right.Count && + 2*rectangleNode.Right.Count >= rectangleNode.Left.Count; + } + + /// + /// Removes everything from the tree + /// + public void Clear() { + RootNode = null; + } + + public bool NumberOfIntersectedIsLessThanBound(Rectangle rect, int bound, Func conditionFunc ) { + return NumberOfIntersectedIsLessThanBoundOnNode(_rootNode, rect, ref bound, conditionFunc); + } + + static bool NumberOfIntersectedIsLessThanBoundOnNode(RectangleNode node, Rectangle rect, ref int bound, Func conditionFunc) { + Debug.Assert(bound > 0); + if (!node.Rectangle.Intersects(rect)) return true; + if (node.IsLeaf) { + if (conditionFunc(node.UserData)) + return (--bound) != 0; + return true; + } + + return NumberOfIntersectedIsLessThanBoundOnNode(node.Left, rect, ref bound, conditionFunc) && + NumberOfIntersectedIsLessThanBoundOnNode(node.Right, rect, ref bound, conditionFunc); + + } + } + +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/RTree.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/RTree.cs.meta new file mode 100644 index 000000000..1b6694f70 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/RTree.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8c95ce06069f8fb4a9246c98efd13452 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/RectangleNode.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/RectangleNode.cs new file mode 100644 index 000000000..4ae453704 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/RectangleNode.cs @@ -0,0 +1,488 @@ +#region Using directives + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Diagnostics; + +#endregion + +namespace Microsoft.Msagl.Core.Geometry { + /// + /// Represents a node containing a box and some user data. + /// Is used in curve intersections routines. + /// + public class RectangleNode { +#if TEST_MSAGL + /// + /// + /// + /// + [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.Int32.ToString")] + public override string ToString() { + return IsLeaf ? (Count + " " + UserData) : Count.ToString(); + } +#endif + /// + /// + /// + public int Count { get; set; } + RectangleNode left; + + RectangleNode right; + + /// + /// creates an empty node + /// + public RectangleNode() { + } + + /// + /// + /// + /// + /// + public RectangleNode(TData data, Rectangle rect) { + UserData = data; + Rectangle = rect; + Count = 1; + } + + RectangleNode(int count) { + Count=count; + } + + /// + /// This field provides direct internal access to the value type Rectangle, which RTree and other callers + /// modify directly with .Add(); the auto-property returns a temporary value-by-copy that is immediately discarded. + /// +// ReSharper disable InconsistentNaming + internal Rectangle rectangle; +// ReSharper restore InconsistentNaming + + /// + /// gets or sets the rectangle of the node + /// + public Rectangle Rectangle { + get { return rectangle; } + set { rectangle = value; } + } + + /// + /// false if it is an internal node and true if it is a leaf + /// + internal bool IsLeaf + { + get { return left==null /*&& right==null*/; } //if left is a null then right is also a null + } + /// + /// + /// + public RectangleNode Left { + get { return left; } + internal set { + if (left != null && left.Parent==this) + left.Parent = null; + left = value; + if (left != null) + left.Parent = this; + } + } + + /// + /// + /// + public RectangleNode Right { + get { return right; } + internal set { + if (right != null&& right.Parent==this) + right.Parent = null; + right = value; + if (right != null) + right.Parent = this; + } + } + + /// + /// The actual data if a leaf node, else null or a value-type default. + /// + public TData UserData { get; set; } + + /// + /// Parent of this node. + /// + public RectangleNode Parent { get; private set; } + + internal bool IsLeftChild { + get { + Debug.Assert(Parent!=null); + return Equals(Parent.Left); + } + } + + /// + /// brings the first leaf which rectangle was hit and the delegate is happy with the object + /// + /// + /// + /// + public RectangleNode FirstHitNode(Point point, Func hitTestForPointDelegate) { + if (rectangle.Contains(point)) { + if (IsLeaf) { + if (hitTestForPointDelegate != null) { + return hitTestForPointDelegate(point, UserData) == HitTestBehavior.Stop ? this : null; + } + return this; + } + return Left.FirstHitNode(point, hitTestForPointDelegate) ?? + Right.FirstHitNode(point, hitTestForPointDelegate); + } + return null; + } + + + /// + /// brings the first leaf which rectangle was intersected + /// + /// + public RectangleNode FirstIntersectedNode(Rectangle r) { + if (r.Intersects(rectangle)) { + if (IsLeaf) + return this; + return Left.FirstIntersectedNode(r) ?? Right.FirstIntersectedNode(r); + } + return null; + } + + + + /// + /// brings the first leaf which rectangle was hit and the delegate is happy with the object + /// + /// + /// + public RectangleNode FirstHitNode(Point point) { + if (rectangle.Contains(point)) { + if (IsLeaf) + return this; + return Left.FirstHitNode(point) ?? Right.FirstHitNode(point); + } + return null; + } + + + /// + /// returns all leaf nodes for which the rectangle was hit and the delegate is happy with the object + /// + /// + /// + /// + public IEnumerable AllHitItems(Rectangle rectanglePar, Func hitTestAccept) { + var stack = new Stack>(); + stack.Push(this); + while (stack.Count > 0) { + RectangleNode node = stack.Pop(); + if (node.Rectangle.Intersects(rectanglePar)) { + if (node.IsLeaf) { + if ((null == hitTestAccept) || hitTestAccept(node.UserData)) { + yield return node.UserData; + } + } + else { + stack.Push(node.left); + stack.Push(node.right); + } + } + } + } + + /// + /// returns all items for which the rectangle contains the point + /// + /// + public IEnumerable AllHitItems(Point point) { + var stack = new Stack>(); + stack.Push(this); + while (stack.Count > 0) { + var node = stack.Pop(); + if (node.Rectangle.Contains(point)) { + if (node.IsLeaf) + yield return node.UserData; + else { + stack.Push(node.left); + stack.Push(node.right); + } + } + } + } + + + /// + /// Returns all leaves whose rectangles intersect hitRectangle (or all leaves before hitTest returns false). + /// + /// + /// + /// + internal void VisitTree(Func hitTest, Rectangle hitRectangle) { + VisitTreeStatic(this, hitTest, hitRectangle); + } + + static HitTestBehavior VisitTreeStatic(RectangleNode rectangleNode, Func hitTest, Rectangle hitRectangle) { + if (rectangleNode.Rectangle.Intersects(hitRectangle)) { + if (hitTest(rectangleNode.UserData) == HitTestBehavior.Continue) { + if (rectangleNode.Left != null) { + // If rectangleNode.Left is not null, rectangleNode.Right won't be either. + if (VisitTreeStatic(rectangleNode.Left, hitTest, hitRectangle) == HitTestBehavior.Continue && + VisitTreeStatic(rectangleNode.Right, hitTest, hitRectangle) == HitTestBehavior.Continue) { + return HitTestBehavior.Continue; + } + return HitTestBehavior.Stop; + } + return HitTestBehavior.Continue; + } + return HitTestBehavior.Stop; + } + return HitTestBehavior.Continue; + } + + /// + /// + /// + /// + public RectangleNode Clone() { + var ret = new RectangleNode(Count) {UserData = UserData, Rectangle = Rectangle}; + if (Left != null) + ret.Left = Left.Clone(); + if (Right != null) + ret.Right = Right.Clone(); + return ret; + } + + /// + /// yields all leaves which rectangles intersect the given one. We suppose that leaves are all nodes having UserData not a null. + /// + /// + /// + public IEnumerable GetNodeItemsIntersectingRectangle(Rectangle rectanglePar) { + return GetLeafRectangleNodesIntersectingRectangle(rectanglePar).Select(node => node.UserData); + } + + /// + /// yields all leaves whose rectangles intersect the given one. We suppose that leaves are all nodes having UserData not a null. + /// + /// + /// + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + public IEnumerable> GetLeafRectangleNodesIntersectingRectangle(Rectangle rectanglePar) { + var stack = new Stack>(); + stack.Push(this); + while (stack.Count > 0) { + RectangleNode node = stack.Pop(); + if (node.Rectangle.Intersects(rectanglePar)) { + if (node.IsLeaf) { + yield return node; + } else { + stack.Push(node.left); + stack.Push(node.right); + } + } + } + } + + /// + /// Walk the tree and return the data from all leaves + /// + /// + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] + public IEnumerable GetAllLeaves() { + return GetAllLeafNodes().Select(n => n.UserData); + } + + internal IEnumerable> GetAllLeafNodes() { + return EnumRectangleNodes(true /*leafOnly*/); + } + + IEnumerable> EnumRectangleNodes(bool leafOnly) { + var stack = new Stack>(); + stack.Push(this); + while (stack.Count > 0) { + var node = stack.Pop(); + if (node.IsLeaf || !leafOnly) { + yield return node; + } + if (!node.IsLeaf) { + stack.Push(node.left); + stack.Push(node.right); + } + } + } + + const int GroupSplitThreshold = 2; + + + /// + /// calculates a tree based on the given nodes + /// + /// + /// + [SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes"), SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + public static RectangleNode CreateRectangleNodeOnEnumeration(IEnumerable> nodes) { + if(nodes==null) + return null; + var nodeList = new List>(nodes); + return CreateRectangleNodeOnListOfNodes(nodeList); + } + + /// + ///calculates a tree based on the given nodes + /// + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes")] + public static RectangleNode CreateRectangleNodeOnData(IEnumerable dataEnumeration, Func rectangleDelegate) { + if (dataEnumeration == null || rectangleDelegate == null) + return null; + var nodeList = new List>(dataEnumeration.Select(d=>new RectangleNode(d, rectangleDelegate(d)))); + return CreateRectangleNodeOnListOfNodes(nodeList); + } + + + /// + /// + /// + /// + /// + [SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes"), SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + static public RectangleNode CreateRectangleNodeOnListOfNodes(IList> nodes) { + ValidateArg.IsNotNull(nodes, "nodes"); + if (nodes.Count == 0) return null; + + if (nodes.Count == 1)return nodes[0]; + + //Finding the seeds + var b0 = nodes[0].Rectangle; + + //the first seed + int seed0 = 1; + + int seed1 = ChooseSeeds(nodes, ref b0, ref seed0); + + //We have two seeds at hand. Build two groups. + var gr0 = new List>(); + var gr1 = new List>(); + + gr0.Add(nodes[seed0]); + gr1.Add(nodes[seed1]); + + var box0 = nodes[seed0].Rectangle; + var box1 = nodes[seed1].Rectangle; + //divide nodes on two groups + DivideNodes(nodes, seed0, seed1, gr0, gr1, ref box0, ref box1, GroupSplitThreshold); + + var ret = new RectangleNode(nodes.Count) { + Rectangle = new Rectangle(box0, box1), + Left = CreateRectangleNodeOnListOfNodes(gr0), + Right = CreateRectangleNodeOnListOfNodes(gr1) + }; + + return ret; + + } + + static int ChooseSeeds(IList> nodes, ref Rectangle b0, ref int seed0) { + double area = new Rectangle(b0, nodes[seed0].Rectangle).Area; + for (int i = 2; i < nodes.Count; i++) { + double area0 = new Rectangle(b0, nodes[i].Rectangle).Area; + if (area0 > area) { + seed0 = i; + area = area0; + } + } + + //Got the first seed seed0 + //Now looking for a seed for the second group + int seed1 = 0; //the compiler forces me to init it + + //init seed1 + for (int i = 0; i < nodes.Count; i++) { + if (i != seed0) { + seed1 = i; + break; + } + } + + area = new Rectangle(nodes[seed0].Rectangle, nodes[seed1].Rectangle).Area; + //Now try to improve the second seed + + for (int i = 0; i < nodes.Count; i++) { + if (i == seed0) + continue; + double area1 = new Rectangle(nodes[seed0].Rectangle, nodes[i].Rectangle).Area; + if (area1 > area) { + seed1 = i; + area = area1; + } + } + return seed1; + } + + static void DivideNodes(IList> nodes, int seed0, int seed1, List> gr0, List> gr1, + ref Rectangle box0, ref Rectangle box1, int groupSplitThreshold) { + for (int i = 0; i < nodes.Count; i++) { + + if (i == seed0 || i == seed1) + continue; + +// ReSharper disable InconsistentNaming + var box0_ = new Rectangle(box0, nodes[i].Rectangle); + double delta0 = box0_.Area - box0.Area; + + var box1_ = new Rectangle(box1, nodes[i].Rectangle); + double delta1 = box1_.Area - box1.Area; +// ReSharper restore InconsistentNaming + + //keep the tree roughly balanced + + if (gr0.Count * groupSplitThreshold < gr1.Count) { + gr0.Add(nodes[i]); + box0 = box0_; + } else if (gr1.Count * groupSplitThreshold < gr0.Count) { + gr1.Add(nodes[i]); + box1 = box1_; + } else if (delta0 < delta1) { + gr0.Add(nodes[i]); + box0 = box0_; + } else if (delta1 < delta0) { + gr1.Add(nodes[i]); + box1 = box1_; + } else if (box0.Area < box1.Area) { + gr0.Add(nodes[i]); + box0 = box0_; + } else { + gr1.Add(nodes[i]); + box1 = box1_; + } + } + } + + + + /// + /// Walk the tree from node down and apply visitor to all nodes + /// + /// + /// + [SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes"), SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + static public void TraverseHierarchy(RectangleNode node, Action> visitor) { + ValidateArg.IsNotNull(node, "node"); + ValidateArg.IsNotNull(visitor, "visitor"); + visitor(node); + if (node.Left != null) + TraverseHierarchy(node.Left, visitor); + if (node.Right != null) + TraverseHierarchy(node.Right, visitor); + } + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/RectangleNode.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/RectangleNode.cs.meta new file mode 100644 index 000000000..3a3b71d5f --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/RectangleNode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0fec0da586c80de4e90b5c2b9f7b89d0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/RectangleNodeUtils.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/RectangleNodeUtils.cs new file mode 100644 index 000000000..9e34b6143 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/RectangleNodeUtils.cs @@ -0,0 +1,115 @@ +using System; + +namespace Microsoft.Msagl.Core.Geometry { + internal sealed class RectangleNodeUtils { + internal static void CrossRectangleNodes(RectangleNode a, RectangleNode b, Action action) { + if (!a.rectangle.Intersects(b.rectangle)) + return; + if (a.Left == null) { //a is a leat + if (b.Left == null) //b is a leaf + action(a.UserData, b.UserData); + else { + CrossRectangleNodes(a, b.Left, action); + CrossRectangleNodes(a, b.Right, action); + } + } else { //a is not a leaf + if (b.Left != null) { + CrossRectangleNodes(a.Left, b.Left, action); + CrossRectangleNodes(a.Left, b.Right, action); + CrossRectangleNodes(a.Right, b.Left, action); + CrossRectangleNodes(a.Right, b.Right, action); + } else { // b is a leaf + CrossRectangleNodes(a.Left, b, action); + CrossRectangleNodes(a.Right, b, action); + } + } + } + + internal static void CrossRectangleNodes(RectangleNode a, RectangleNode b, Action action) { + if (!a.rectangle.Intersects(b.rectangle)) + return; + if (Equals(a, b)) + HandleEquality(a, action); + else if (a.Left == null) { + if (b.Left == null) { + action(a.UserData, b.UserData); + } else { + CrossRectangleNodes(a, b.Left, action); + CrossRectangleNodes(a, b.Right, action); + } + } else { + if (b.Left != null) { + CrossRectangleNodes(a.Left, b.Left, action); + CrossRectangleNodes(a.Left, b.Right, action); + CrossRectangleNodes(a.Right, b.Left, action); + CrossRectangleNodes(a.Right, b.Right, action); + } else { + CrossRectangleNodes(a.Left, b, action); + CrossRectangleNodes(a.Right, b, action); + } + } + } + + /// + /// returns true if "property" holds for some pair + /// + /// + /// + /// + /// + /// + internal static bool FindIntersectionWithProperty(RectangleNode a, RectangleNode b, Func property) { + if (!a.rectangle.Intersects(b.rectangle)) + return false; + if (Equals(a, b)) + return HandleEqualityCheck(a, property); + + if (a.Left == null) { + if (b.Left == null) + return property(a.UserData, b.UserData); + + if (FindIntersectionWithProperty(a, b.Left, property)) + return true; + if (FindIntersectionWithProperty(a, b.Right, property)) + return true; + } else { + if (b.Left != null) { + if (FindIntersectionWithProperty(a.Left, b.Left, property)) + return true; + if (FindIntersectionWithProperty(a.Left, b.Right, property)) + return true; + if (FindIntersectionWithProperty(a.Right, b.Left, property)) + return true; + if (FindIntersectionWithProperty(a.Right, b.Right, property)) + return true; + } else { + + if (FindIntersectionWithProperty(a.Left, b, property)) + return true; + if (FindIntersectionWithProperty(a.Right, b, property)) + return true; + } + } + return false; + } + + static bool HandleEqualityCheck(RectangleNode a, Func func) { + if (a.Left == null) return false; //we don't do anything for two equal leafs + return FindIntersectionWithProperty(a.Left, a.Left, func) || + FindIntersectionWithProperty(a.Left, a.Right, func) || FindIntersectionWithProperty(a.Right, a.Right, func); + } + + /// + /// we need to avoid calling action twice for the same pair + /// + /// + /// + /// + static void HandleEquality(RectangleNode a, Action action) { + if (a.Left == null) return; //we don't do anything for two equal leafs + CrossRectangleNodes(a.Left, a.Left, action); + CrossRectangleNodes(a.Left, a.Right, action); + CrossRectangleNodes(a.Right, a.Right, action); + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/RectangleNodeUtils.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/RectangleNodeUtils.cs.meta new file mode 100644 index 000000000..d73757fd8 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RTree/RectangleNodeUtils.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 55e8ec84308a40042a760804514aaf84 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Rectangle.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Rectangle.cs new file mode 100644 index 000000000..a0d686c00 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Rectangle.cs @@ -0,0 +1,596 @@ +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Collections.Generic; +using Microsoft.Msagl.Core.DataStructures; +using Microsoft.Msagl.Core.Geometry.Curves; + +namespace Microsoft.Msagl.Core.Geometry{ + /// + /// Just a rectangle + /// +#if TEST_MSAGL + [Serializable] +#endif + public struct Rectangle{ + /// + /// shows min and max coordinates of corners + /// + /// string leftbottom, righttop + public override string ToString(){ + return "(" + LeftBottom + " " + RightTop + ")"; + } + + double left; + double right; + double top; + double bottom; + + /// + /// returns true if r intersect this rectangle + /// + /// + /// + public bool Intersects(Rectangle rectangle){ + return IntersectsOnX(rectangle) && IntersectsOnY(rectangle); + } + + /// + /// intersection (possibly empty) of rectangles + /// + /// + /// + public Rectangle Intersection(Rectangle rectangle) + { + Rectangle intersection = new Rectangle(); + if (!Intersects(rectangle)) + { + intersection.SetToEmpty(); + return intersection; + } + double l = Math.Max(Left, rectangle.Left); + double r = Math.Min(Right, rectangle.Right); + double b = Math.Max(Bottom, rectangle.Bottom); + double t = Math.Min(Top, rectangle.Top); + return new Rectangle(l,b,r,t); + } + + /// + /// the center of the bounding box + /// + public Point Center{ + get { return 0.5*(LeftTop + RightBottom); } + set{ + Point shift = value - Center; + LeftTop += shift; + RightBottom += shift; + } + } + + internal bool IntersectsOnY(Rectangle r){ + if (r.Bottom > top + ApproximateComparer.DistanceEpsilon) + return false; + + if (r.Top < bottom - ApproximateComparer.DistanceEpsilon) + return false; + + return true; + //return ApproximateComparer.Compare(r.bottom, top) <= 0 && ApproximateComparer.Compare(bottom, r.top) <= 0; + } + + internal bool IntersectsOnX(Rectangle r){ + //return ApproximateComparer.Compare(r.left, right) <= 0 && ApproximateComparer.Compare(left, r.right) <= 0; + if (r.Left > right + ApproximateComparer.DistanceEpsilon) + return false; + + if (r.Right < left - ApproximateComparer.DistanceEpsilon) + return false; + + return true; + + //return ApproximateComparer.Compare(r.left, right) <= 0 && ApproximateComparer.Compare(left, r.right) <= 0; + } + + /// + /// + /// + /// + /// + public override bool Equals(object obj) { + if (obj is Rectangle) + Equals((Rectangle) obj); + return false; + } + + /// + /// + /// + /// + /// + public bool Equals(Rectangle rec) { + return rec.Left == Left && rec.Right == Right && rec.Top == Top && rec.Bottom == Bottom; + + } + + /// + /// + /// + /// + public override int GetHashCode(){ + return base.GetHashCode(); + } + + /// + /// creates an empty rectangle + /// + /// + public static Rectangle CreateAnEmptyBox(){ + return new Rectangle(0, 0, new Point(-1, -1)); + } + + /// + /// the left of the rectangle + /// + public double Left{ + get { return left; } + set { left = value; } + } + + /// + /// the right of the rectangle + /// + public double Right{ + get { return right; } + set { right = value; } + } + + /// + /// the top of the rectangle + /// + public double Top{ + get { return top; } + set { top = value; } + } + + /// + /// the bottom of the rectangle + /// + public double Bottom{ + get { return bottom; } + set { bottom = value; } + } + + /// + /// the left bottom corner + /// + public Point LeftBottom{ + get { return new Point(Left, Bottom); } + } + + /// + /// the right top corner + /// + public Point RightTop{ + get { return new Point(Right, Top); } + } + + /// + /// the left top corner + /// + public Point LeftTop{ + get { return new Point(left, top); } + set{ + left = value.X; + top = value.Y; + } + } + + /// + /// the right bottom corner + /// + public Point RightBottom{ + get { return new Point(right, bottom); } + set{ + right = value.X; + Bottom = value.Y; + } + } + + /// + /// create a box of two points + /// + /// + /// + public Rectangle(Point point0, Point point1){ + left = right = point0.X; + top = bottom = point0.Y; + Add(point1); + } + + /// + /// create rectangle from a point + /// + /// + public Rectangle(Point point){ + left = right = point.X; + top = bottom = point.Y; + } + + + /// + /// + /// + /// left + /// bottom + /// size + public Rectangle(double left, double bottom, Point sizeF){ + this.left = left; + this.bottom = bottom; + right = left + sizeF.X; + top = bottom + sizeF.Y; + } + + /// + /// create a box on points (x0,y0), (x1,y1) + /// + /// + /// + /// + /// + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "y"), + SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "x")] + public Rectangle(double x0, double y0, double x1, double y1){ + left = right = x0; + top = bottom = y0; + Add(new Point(x1, y1)); + } + + /// + /// Create rectangle that is the bounding box of the given points + /// + public Rectangle(IEnumerable points) + : this(0, 0, new Point(-1, -1)) + { + ValidateArg.IsNotNull(points, "points"); + foreach (var p in points) + { + Add(p); + } + } + + /// + /// Create rectangle that is the bounding box of the given Rectangles + /// + public Rectangle(IEnumerable rectangles) + : this(0, 0, new Point(-1, -1)) + { + ValidateArg.IsNotNull(rectangles, "rectangles"); + foreach (var r in rectangles) + { + Add(r); + } + } + + /// + /// the width of the rectangle + /// + public double Width{ + get { return right - left; } + set{ + double hw = value/2.0f; + double cx = (left + right)/2.0f; + left = cx - hw; + right = cx + hw; + } + } + + /// + /// returns true if the rectangle has negative width + /// + public bool IsEmpty{ + get { return Width < 0; } + } + + /// + /// makes the rectangle empty + /// + public void SetToEmpty(){ + Left = 0; + Right = -1; + } + + /// + /// Height of the rectangle + /// + public double Height{ + get { return top - bottom; } + set{ + double hw = value/2.0f; + double cx = (top + bottom)/2.0f; + top = cx + hw; + bottom = cx - hw; + } + } + + /// + /// rectangle containing both a and side1 + /// + /// + /// + public Rectangle(Rectangle rectangle0, Rectangle rectangle1){ + left = rectangle0.left; + right = rectangle0.right; + top = rectangle0.top; + bottom = rectangle0.bottom; + + Add(rectangle1); + } + + /// + /// contains with padding + /// + /// + /// + /// + public bool Contains(Point point, double padding){ + return left - padding - ApproximateComparer.DistanceEpsilon <= point.X && point.X <= right + padding + ApproximateComparer.DistanceEpsilon && + bottom - padding - ApproximateComparer.DistanceEpsilon <= point.Y && point.Y<=top + padding + ApproximateComparer.DistanceEpsilon; + } + + /// + /// Rectangle area + /// + public double Area{ + get { return (right - left)*(top - bottom); } + } + + //internal Rectangle(){} + + /// + /// adding a point to the rectangle + /// + /// + public void Add(Point point){ + if (!IsEmpty){ + if(left > point.X) + left = point.X; + + if(top < point.Y) + top = point.Y; + + if(right < point.X) + right = point.X; + + if(bottom > point.Y) + bottom = point.Y; + }else{ + left = right = point.X; + top = bottom = point.Y; + } + } + + /// + /// extend the box to keep the point. + /// Assume here that the box is initialized correctly + /// + /// + /// true if the box has been extended + public bool AddWithCheck(Point point){ + bool wider; + if(wider = (point.X < left)) + left = point.X; + else if(wider = (right < point.X)) + right = point.X; + + bool higher; + + if(higher = (point.Y > top)) + top = point.Y; + else if(higher = (bottom > point.Y)) + bottom = point.Y; + + return wider || higher; + } + + /// + /// adding rectangle + /// + /// + public void Add(Rectangle rectangle){ + Add(rectangle.LeftTop); + Add(rectangle.RightBottom); + } + + /// + /// override == + /// + /// + /// + /// + public static bool operator ==(Rectangle rectangle0, Rectangle rectangle1) { + return rectangle0.Equals(rectangle1); + } + + /// + /// overrides != + /// + /// + /// + /// + public static bool operator !=(Rectangle rectangle0, Rectangle rectangle1) { + return !rectangle0.Equals(rectangle1); + } + + /// + /// Return copy of specified rectangle translated by the specified delta + /// + /// source to copy and translate + /// translation vector + /// copy of specified rectangle translated by the specified delta + public static Rectangle Translate(Rectangle rectangle, Point delta) + { + rectangle.Center += delta; +#if SHARPKIT //https://code.google.com/p/sharpkit/issues/detail?id=369 there are no structs in js + return rectangle.Clone(); +#else + return rectangle; +#endif + } + + /// + /// returns true if the rectangle contains the point + /// + /// + /// + public bool Contains(Point point){ + return (ApproximateComparer.Compare(left, point.X) <= 0) && (ApproximateComparer.Compare(right, point.X) >= 0) + && (ApproximateComparer.Compare(top, point.Y) >= 0) && (ApproximateComparer.Compare(bottom, point.Y) <= 0); + } + + /// + /// returns true if this rectangle completely contains the specified rectangle + /// + /// + /// + public bool Contains(Rectangle rect){ + return Contains(rect.LeftTop) && Contains(rect.RightBottom); + } + + + /// + /// return the length of the diagonal + /// + /// + public double Diagonal{ + get { return Math.Sqrt(Width*Width + Height*Height); } + } + + /// + /// pad the rectangle horizontally by the given padding + /// + /// + public void PadWidth(double padding){ + Left -= padding; + Right += padding; + } + + /// + /// pad the rectangle vertically by the given padding + /// + /// + public void PadHeight(double padding){ + Top += padding; + Bottom -= padding; + } + + /// + /// pad the rectangle by the given padding + /// + /// + public void Pad(double padding) { + if (padding < -Width/2) + padding = -Width/2; + if (padding < -Height/2) + padding = -Height/2; + + + + PadWidth(padding); + PadHeight(padding); + } + + /// + /// Pad the rectangle by the given amount on each side + /// + /// + /// + /// + /// + public void Pad(double left, double bottom, double right, double top) + { + Left -= left; + Right += right; + Bottom -= bottom; + Top += top; + } + + /// + /// Returns the intersection of two rectangles. + /// + /// + /// + /// + public static Rectangle Intersect(Rectangle rect1,Rectangle rect2) { + if (rect1.Intersects(rect2)) + return new Rectangle(new Point(Math.Max(rect1.Left, rect2.Left), Math.Max(rect1.Bottom, rect2.Bottom)), + new Point(Math.Min(rect1.Right, rect2.Right), Math.Min(rect1.Top, rect2.Top))); + return Rectangle.CreateAnEmptyBox(); + } + + /// + /// + /// + public Polyline Perimeter() { + var poly = new Polyline(); + poly.AddPoint(LeftTop); + poly.AddPoint(RightTop); + poly.AddPoint(RightBottom); + poly.AddPoint(LeftBottom); + poly.Closed = true; + return poly; + } + + /// + /// + /// + public void ScaleAroundCenter(double scale) { + Width=Width*scale; + Height=Height*scale; + } + + internal Rectangle Clone() { + return new Rectangle(LeftTop, RightBottom); + } + + + + /// + /// gets or sets the Size + /// + public Size Size { + get { return new Size(Width, Height); } + set { + Width = value.Width; + Height = value.Height; + } + } +/// +/// constructor with Size and center +/// +/// +/// + public Rectangle(Size size, Point center) { + var w = size.Width/2; + left = center.X - w; + right = center.X + w; + var h = size.Height / 2; + bottom = center.Y - h; + top = center.Y + h; + } + + /// + /// adding a point with a Size + /// + /// + /// + public void Add(Size size, Point point) { + var w = size.Width / 2; + var h = size.Height / 2; + + Add(new Point(point.X - w, point.Y - h)); + Add(new Point(point.X + w, point.Y - h)); + Add(new Point(point.X - w, point.Y + h)); + Add(new Point(point.X + w, point.Y + h)); + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Rectangle.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Rectangle.cs.meta new file mode 100644 index 000000000..2c3a6a900 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Rectangle.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6adc41edb181b8b47965d22afc1e475f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking.meta new file mode 100644 index 000000000..8d05ef13e --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8f66851e9ef877841b2227464e263437 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/ColumnPacking.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/ColumnPacking.cs new file mode 100644 index 000000000..1d9e02220 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/ColumnPacking.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; + +namespace Microsoft.Msagl.Core.Geometry +{ + /// + /// Flow fill of columns to some maximum height + /// + public class ColumnPacking : Packing + { + IEnumerable> orderedRectangles; + double maxHeight; + + /// + /// Constructor for packing, call Run to do the actual pack. + /// Each RectangleToPack.Rectangle is updated in place. + /// Pack rectangles tallest to shortest, left to right until wrapWidth is reached, + /// then wrap to right-most rectangle still with vertical space to fit the next rectangle + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + public ColumnPacking(IEnumerable> rectangles, double maxHeight) + { + this.orderedRectangles = rectangles; + this.maxHeight = maxHeight; + } + + /// + /// Pack columns by iterating over rectangle enumerator until column height exceeds wrapHeight. + /// When that happens, create a new column at position PackedWidth. + /// + protected override void RunInternal() + { + PackedHeight = PackedWidth = 0; + double columnPosition = 0; + double columnHeight = 0; + foreach (var current in orderedRectangles) + { + Rectangle r = current.Rectangle; + if (columnHeight + r.Height > maxHeight) + { + columnPosition = PackedWidth; + columnHeight = 0; + } + r = current.Rectangle = new Rectangle(columnPosition, columnHeight, + new Point(r.Width, r.Height)); + PackedWidth = Math.Max(PackedWidth, columnPosition + r.Width); + columnHeight += r.Height; + PackedHeight = Math.Max(PackedHeight, columnHeight); + } + } + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/ColumnPacking.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/ColumnPacking.cs.meta new file mode 100644 index 000000000..91413b0de --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/ColumnPacking.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a1b236fca3387a74e90f663e0ad97748 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/OptimalColumnPacking.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/OptimalColumnPacking.cs new file mode 100644 index 000000000..d3ff312f9 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/OptimalColumnPacking.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; + +namespace Microsoft.Msagl.Core.Geometry +{ + /// + /// Pack rectangles (without rotation) into a given aspect ratio + /// + public class OptimalColumnPacking : OptimalPacking + { + /// + /// Constructor for packing, call Run to do the actual pack. + /// Each RectangleToPack.Rectangle is updated in place. + /// Performs a Golden Section Search on packing width for the + /// closest aspect ratio to the specified desired aspect ratio + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + public OptimalColumnPacking(IEnumerable> rectangles, double aspectRatio) + : base(rectangles.ToList(), aspectRatio) + { + ValidateArg.IsNotNull(rectangles, "rectangles"); + Debug.Assert(rectangles.Any(), "Expected more than one rectangle in rectangles"); + Debug.Assert(aspectRatio > 0, "aspect ratio should be greater than 0"); + + this.createPacking = (rs, height) => new ColumnPacking(rs, height); + } + + /// + /// Performs a Golden Section Search on packing height for the + /// closest aspect ratio to the specified desired aspect ratio + /// + protected override void RunInternal() + { + double minRectHeight = double.MaxValue; + double maxRectHeight = 0; + double totalHeight = 0; + + // initial widthLowerBound is the width of a perfect packing for the desired aspect ratio + foreach (var rtp in rectangles) + { + Rectangle r = rtp.Rectangle; + Debug.Assert(r.Width > 0, "Width must be greater than 0"); + Debug.Assert(r.Height > 0, "Height must be greater than 0"); + totalHeight += r.Height; + minRectHeight = Math.Min(minRectHeight, r.Height); + maxRectHeight = Math.Max(maxRectHeight, r.Height); + } + Pack(maxRectHeight, totalHeight, minRectHeight); + } + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/OptimalColumnPacking.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/OptimalColumnPacking.cs.meta new file mode 100644 index 000000000..4f2287777 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/OptimalColumnPacking.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 49f9778a062ecd34e85f35ec31a4c483 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/OptimalPacking.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/OptimalPacking.cs new file mode 100644 index 000000000..f63a2bcbd --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/OptimalPacking.cs @@ -0,0 +1,177 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; + +#pragma warning disable 1591 +namespace Microsoft.Msagl.Core.Geometry +{ + /// + /// Pack rectangles (without rotation) into a given aspect ratio + /// + public abstract class OptimalPacking : AlgorithmBase + { + double desiredAspectRatio = 1.2; + + double bestPackingCost; + + Packing bestPacking = null; + + protected IList> rectangles; + + Dictionary cachedCosts = new Dictionary(); + + protected OptimalPacking(IList> rectangles, double aspectRatio) + { + this.rectangles = rectangles; + this.desiredAspectRatio = aspectRatio; + } + + /// + /// The width of the widest row in the packed solution + /// + public double PackedWidth { + get + { + if (bestPacking != null) + { + return bestPacking.PackedWidth; + } + return 0; + } + } + + /// + /// The height of the bounding box of the packed solution + /// + public double PackedHeight { + get + { + if (bestPacking != null) + { + return bestPacking.PackedHeight; + } + return 0; + } + } + + // controls the maximum number of steps we are allowed to take in our golden section search + // (actually worst case is O (n log n) for n=MaxSteps) + protected const double MaxSteps = 1000; + + protected delegate Packing PackingFactory(IList> rectangles, double limit); + + protected PackingFactory createPacking; + + protected void Pack(double lowerBound, double upperBound, double minGranularity) + { + double c0 = GetGoldenSectionStep(lowerBound, upperBound); + + // the worst case time complexity is O(n log(n)) where we have to do a full traversal of the + // golden section search tree because it each stage the two candidate split points we chose had + // the same cost. + // the following calculation for precision limits the worst case time by making max(n) = MaxSteps. + double precision = Math.Max(minGranularity / 10, (upperBound - lowerBound) / MaxSteps); + + // need to overshoot upperbound in case upperbound is actually optimal + upperBound += precision; + + bestPackingCost = double.MaxValue; + + if (rectangles.Count == 1) + { + // the trivial solution for just one rectangle is widthLowerBound + Pack(lowerBound); + } + else if (rectangles.Count == 2) + { + // if we have 2 rectangles just try the two possibilities + Pack(lowerBound); + Pack(upperBound); + } + else if (rectangles.Count > 2) + { + GoldenSectionSearch(Pack, lowerBound, c0, upperBound, precision); + } + + // packing works on the rectangles in place, so we need to rerun to get back the best packing. + bestPacking.Run(); + } + + double Pack(double limit) + { + double cost; + if (!cachedCosts.TryGetValue(limit, out cost)) + { + var packing = this.createPacking(rectangles, limit); + packing.Run(); + cachedCosts[limit] = cost = Math.Abs(packing.PackedAspectRatio - desiredAspectRatio); + if (cost < bestPackingCost) + { + bestPackingCost = cost; + bestPacking = packing; + } + } + return cost; + } + + /// + /// recursively searches a weakly unimodal function f(x) between x1 and x3 for the minimum. It is assumed x2 \le x1 and x2 \le x3 + /// and x2-x1=a \lt b=x3-x2. The recursion generates a fourth point x4-x1=b \gt a=x3-x4 where x4-x2=c and b=a+c and: + /// if f(x4) \lt f(x2) we search in the range [x2, x3] + /// else if f(x2) \lt f(x4) we search in the range [x1, x4] + /// else + /// f(x2)==f(x4) and we know that f is only weakly unimodal (not strongly unimodal) and we must search both branches. + /// + internal static double GoldenSectionSearch(Func f, double x1, double x2, double x3, double precision) + { + Debug.Assert((Math.Abs(x3 - x1) - precision) / precision <= MaxSteps + 0.1, "precision would violate the limit imposed by MaxSteps"); + + // check termination + if (Math.Abs(x1 - x3) < precision) + { + return f(x1) < f(x3) ? x1 : x3; + } + + // x2 must be between x1 and x3 + Debug.Assert(x1 < x2 && x2 < x3 || x3 < x2 && x2 < x1, "x2 not bounded by x1 and x3"); + + // x4 will be our new midpoint candidate + double x4 = GetGoldenSectionStep(x2, x3); + + // now we have two candidates (x2,x4) both between x1 and x3: choose the bracket that most reduces f + double fx2 = f(x2); + double fx4 = f(x4); + + Func leftSearch = () => GoldenSectionSearch(f, x4, x2, x1, precision); + Func rightSearch = () => GoldenSectionSearch(f, x2, x4, x3, precision); + + if (fx4 < fx2) + { + Debug.Assert(Math.Abs(x2 - x3) < Math.Abs(x1 - x3), "Search region not narrowing!"); + return rightSearch(); + } + if (fx4 > fx2) + { + Debug.Assert(Math.Abs(x4 - x1) < Math.Abs(x1 - x3), "Search region not narrowing!"); + return leftSearch(); + } + + // Doh! f(x2) == f(x4)! Have to search both branches. + double right = rightSearch(); + double left = leftSearch(); + return f(left) < f(right) ? left : right; + } + + static double GetGoldenSectionStep(double x1, double x2) + { + if (x1 < x2) + { + return x1 + PackingConstants.GoldenRatioRemainder * (x2 - x1); + } + return x1 - PackingConstants.GoldenRatioRemainder * (x1 - x2); + } + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/OptimalPacking.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/OptimalPacking.cs.meta new file mode 100644 index 000000000..6d82ca2f2 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/OptimalPacking.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6fdf39aaee794724e8b85b630c29b39f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/OptimalRectanglePacking.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/OptimalRectanglePacking.cs new file mode 100644 index 000000000..b70df62ba --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/OptimalRectanglePacking.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; + +namespace Microsoft.Msagl.Core.Geometry +{ + /// + /// Pack rectangles (without rotation) into a given aspect ratio + /// + public class OptimalRectanglePacking : OptimalPacking + { + /// + /// Constructor for packing, call Run to do the actual pack. + /// Each RectangleToPack.Rectangle is updated in place. + /// Performs a Golden Section Search on packing width for the + /// closest aspect ratio to the specified desired aspect ratio + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + public OptimalRectanglePacking(IEnumerable> rectangles, double aspectRatio) + : base(RectanglePacking.SortRectangles(rectangles).ToList(), aspectRatio) + { + ValidateArg.IsNotNull(rectangles, "rectangles"); + Debug.Assert(rectangles.Any(), "Expected more than one rectangle in rectangles"); + Debug.Assert(aspectRatio > 0, "aspect ratio should be greater than 0"); + + this.createPacking = (rs, width) => new RectanglePacking(rs, width, rectanglesPresorted: true); + } + + /// + /// Performs a Golden Section Search on packing width for the + /// closest aspect ratio to the specified desired aspect ratio + /// + protected override void RunInternal() + { + double minRectWidth = double.MaxValue; + double maxRectWidth = 0; + double totalWidth = 0; + + // initial widthLowerBound is the width of a perfect packing for the desired aspect ratio + foreach (var r in rectangles) + { + Debug.Assert(r.Rectangle.Width > 0, "Width must be greater than 0"); + Debug.Assert(r.Rectangle.Height > 0, "Height must be greater than 0"); + + double width = r.Rectangle.Width; + totalWidth += width; + minRectWidth = Math.Min(minRectWidth, width); + maxRectWidth = Math.Max(maxRectWidth, width); + } + + Pack(maxRectWidth, totalWidth, minRectWidth); + } + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/OptimalRectanglePacking.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/OptimalRectanglePacking.cs.meta new file mode 100644 index 000000000..1528be2ef --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/OptimalRectanglePacking.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 76611f007ec5b4f4baa8741347cbfc13 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/Packing.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/Packing.cs new file mode 100644 index 000000000..3588816e0 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/Packing.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; + +namespace Microsoft.Msagl.Core.Geometry +{ + /// + /// Algorithm to pack rectangles + /// + public abstract class Packing : AlgorithmBase + { + /// + /// The width of the widest row in the packed solution + /// + public double PackedWidth { get; protected set; } + + /// + /// The height of the bounding box of the packed solution + /// + public double PackedHeight { get; protected set; } + + /// + /// Aspect ratio of the bounding box of the packed solution + /// + public double PackedAspectRatio + { + get + { + return PackedWidth / PackedHeight; + } + } + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/Packing.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/Packing.cs.meta new file mode 100644 index 000000000..39a0ffdde --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/Packing.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0af88044ea3ca5e48b78d959cd13024e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/PackingConstants.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/PackingConstants.cs new file mode 100644 index 000000000..2d384ce3e --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/PackingConstants.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Microsoft.Msagl.Core.Geometry +{ + /// + /// Constants used by OptimalRectanglePacking + /// + public static class PackingConstants + { + /// + /// The greeks thought the GoldenRatio was a good aspect ratio: Phi = (1 + Math.Sqrt(5)) / 2 + /// + /// we also use this internally in our golden section search + public static readonly double GoldenRatio = (1 + Math.Sqrt(5)) / 2; + + /// + /// equiv to 1 - (1/Phi) where Phi is the Golden Ratio: i.e. the smaller of the two sections + /// if you divide a unit length by the golden ratio + /// + internal static readonly double GoldenRatioRemainder = 2 - GoldenRatio; + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/PackingConstants.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/PackingConstants.cs.meta new file mode 100644 index 000000000..16d827beb --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/PackingConstants.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 08f3647164e658440a103a599c3cf7f1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/RectanglePacking.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/RectanglePacking.cs new file mode 100644 index 000000000..3032e964a --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/RectanglePacking.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; + +namespace Microsoft.Msagl.Core.Geometry +{ + /// + /// Greedily pack rectangles (without rotation) into a given aspect ratio + /// + public class RectanglePacking : Packing + { + IEnumerable> rectanglesByDescendingHeight; + + double wrapWidth; + + /// + /// Constructor for packing, call Run to do the actual pack. + /// Each RectangleToPack.Rectangle is updated in place. + /// Pack rectangles tallest to shortest, left to right until wrapWidth is reached, + /// then wrap to right-most rectangle still with vertical space to fit the next rectangle + /// + /// + /// + /// If the rectangles are already sorted into the order to pack, then specify true + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + public RectanglePacking(IEnumerable> rectangles, double wrapWidth, bool rectanglesPresorted = false) + { + this.rectanglesByDescendingHeight = rectanglesPresorted ? rectangles : SortRectangles(rectangles); + this.wrapWidth = wrapWidth; + } + /// + /// Sort rectangles by height + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + public static IEnumerable> SortRectangles(IEnumerable> rectangles) + { + return from r in rectangles orderby r.Rectangle.Height descending, r.Rectangle.Width select r; + } + + /// + /// Pack rectangles tallest to shortest, left to right until wrapWidth is reached, + /// then wrap to right-most rectangle still with vertical space to fit the next rectangle + /// + protected override void RunInternal() + { + this.Pack(rectanglesByDescendingHeight.GetEnumerator()); + } + + /// + /// Traverses the rectangleEnumerator and places rectangles at the next available slot beneath the current parent, + /// until the parent is filled or until maxRowWidth is reached. Each successfully placed rectangle is pushed onto + /// a stack, when there is no room for the rectangle we pop the stack for a new parent and try again. + /// + /// rectangles to pack + void Pack(IEnumerator> rectangleEnumerator) + { + PackedHeight = PackedWidth = 0; + + // get next rectangle + var stack = new Stack>(); + bool wrap = false; + double verticalPosition = 0; + double packedWidth = 0; + double packedHeight = 0; + while (wrap || rectangleEnumerator.MoveNext()) + { + var current = rectangleEnumerator.Current; + var parent = stack.Count > 0 ? stack.Peek() : null; + Rectangle r = current.Rectangle; + if (parent == null || + parent.Rectangle.Right + r.Width <= wrapWidth && + verticalPosition + r.Height <= parent.Rectangle.Top) + { + r = current.Rectangle = new Rectangle( + parent == null ? 0 : parent.Rectangle.Right, + verticalPosition, + new Point(r.Width, r.Height)); + packedWidth = Math.Max(packedWidth, r.Right); + packedHeight = Math.Max(packedHeight, r.Top); + stack.Push(current); + wrap = false; + } + else + { + verticalPosition = parent.Rectangle.Top; + stack.Pop(); + wrap = true; + } + } + this.PackedWidth = packedWidth; + this.PackedHeight = packedHeight; + } + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/RectanglePacking.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/RectanglePacking.cs.meta new file mode 100644 index 000000000..39765ab62 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/RectanglePacking.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 655a8489f2085e44991a04bc21f4f65d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/RectangleToPack.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/RectangleToPack.cs new file mode 100644 index 000000000..c702bf2a4 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/RectangleToPack.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Microsoft.Msagl.Core.Geometry +{ + /// + /// A rectangle and associated data that need to be packed + /// + /// + public class RectangleToPack + { + /// + /// Rectangle to be packed - packing will translate the rectangle + /// + public Rectangle Rectangle { get; internal set; } + + /// + /// data associated with rectangle + /// + public TData Data { get; private set; } + + /// + /// Associate a rectangle with a data item to be packed + /// + /// + /// + public RectangleToPack(Rectangle rectangle, TData data) + { + this.Rectangle = rectangle; + this.Data = data; + } + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/RectangleToPack.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/RectangleToPack.cs.meta new file mode 100644 index 000000000..811ceacd9 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/RectanglePacking/RectangleToPack.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7b1251ee0d7b3c443a1084f2e5faf95a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Site.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Site.cs new file mode 100644 index 000000000..fcb78ee1f --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Site.cs @@ -0,0 +1,147 @@ +using System; + +namespace Microsoft.Msagl.Core.Geometry { + /// + /// A class for keeping polyline points in a double linked list + /// +#if TEST_MSAGL + [Serializable] +#endif + public class Site{ + /// + /// the coeffiecient used to calculate the first and the second control points of the + /// Bezier segment for the fillet at the site + /// + double previouisBezierCoefficient = 0.5; + /// + /// used to calculate the first control points: the formula is kPrev * a + (1 - kPrev) * b + /// + public double PreviousBezierSegmentFitCoefficient { + get { return previouisBezierCoefficient; } + set { previouisBezierCoefficient = value; } + } + /// + /// the coeffiecient used to calculate the third and the fourth control points of the + /// Bezier segment for the fillet at the site + /// + double nextBezierCoefficient = 0.5; + /// + /// the coefficient tells how tight the segment fits to the segment after the site; the formula is kNext * c + (1 - kNext) * b + /// + public double NextBezierSegmentFitCoefficient { + get { return nextBezierCoefficient; } + set { nextBezierCoefficient = value; } + } + + double previousTangentCoefficient=1.0/3; + + /// + ///used to calculate the second control point + /// + public double PreviousTangentCoefficient { + get { return previousTangentCoefficient; } + set { previousTangentCoefficient = value; } + } + double nextTangentCoefficient = 1.0 / 3; + + /// + ///used to calculate the third control point + /// + public double NextTangentCoefficient + { + get { return nextTangentCoefficient; } + set { nextTangentCoefficient = value; } + } + + // internal double par; + Point point; + + /// + /// gets the site point + /// + public Point Point { + get { return point; } + set { point = value; } + } + + Site prev; +/// +/// gets the previous site +/// + public Site Previous + { + get { return prev; } + set { prev = value; } + } + + Site next; +/// +/// gets the next site +/// + public Site Next + { + get { return next; } + set { next = value; } + } + internal Site() { } + /// + /// the constructor + /// + /// + public Site(Point sitePoint) { + point = sitePoint; + } + /// + /// a constructor + /// + /// + /// + public Site(Site previousSite, Point sitePoint ) + { + ValidateArg.IsNotNull(previousSite, "pr"); + point = sitePoint; + prev = previousSite; + previousSite.next = this; + } + /// + /// a constructor + /// + /// + /// + /// + public Site(Site previousSite, Point sitePoint, Site nextSite ) + { + ValidateArg.IsNotNull(previousSite, "pr"); + ValidateArg.IsNotNull(nextSite, "ne"); + prev = previousSite; + point = sitePoint; + next = nextSite; + + previousSite.next = this; + next.prev = this; + } + + internal double Turn { + get { + if (this.Next == null || this.Previous == null) + return 0; + return Point.SignedDoubledTriangleArea(Previous.Point, Point, Next.Point); + } + } + + internal Site Clone() { + Site s = new Site(); + s.PreviousBezierSegmentFitCoefficient = PreviousBezierSegmentFitCoefficient; + s.Point = Point; + return s; + } +/// +/// +/// +/// + public override string ToString() { + return Point.ToString(); + } + + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/Site.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Site.cs.meta new file mode 100644 index 000000000..81ee34443 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/Site.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b0ccb6de6e49b0e4d9d86e13525aa236 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/SmoothedPolyline.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/SmoothedPolyline.cs new file mode 100644 index 000000000..5535cc6e0 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/SmoothedPolyline.cs @@ -0,0 +1,173 @@ +using System; +using System.Collections.Generic; +using Microsoft.Msagl.Core.Geometry.Curves; + +namespace Microsoft.Msagl.Core.Geometry { + /// + /// represents the polyline of an edge + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix"), + System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", + MessageId = "Polyline") +#if TEST_MSAGL + ,Serializable +#endif + ] + public class SmoothedPolyline: IEnumerable { + + + /// + /// creates the polyline from corner points + /// + ///points of the polyline + public static SmoothedPolyline FromPoints(IEnumerable points){ + ValidateArg.IsNotNull(points, "points"); + SmoothedPolyline ret=null; + Site site=null; + foreach(Point p in points){ + if(site==null){ + site=new Site(p); + ret=new SmoothedPolyline(site); + } else { + Site s=new Site(p); + s.Previous=site; + site.Next=s; + site=s; + } + } + return ret; + } + + + readonly Site headSite; + /// + /// the first site of the polyline + /// + public Site HeadSite { + get { return headSite; } + } +/// +/// clones the polyline +/// +/// + public SmoothedPolyline Clone() { + Site h; //the site of teh clone + Site s = headSite; //the old site + Site prev = null; + Site headOfTheClone=null; + while (s != null) { + h=s.Clone(); + h.Previous=prev; + if (prev != null) + prev.Next = h; + else + headOfTheClone = h; + s = s.Next; + prev = h; + } + return new SmoothedPolyline(headOfTheClone); + } +/// +/// a constructor +/// +/// + public SmoothedPolyline(Site head) { + this.headSite = head; + } + + /// + /// the last site of the polyline + /// + public Site LastSite { + get { + Site ret = headSite; + while (ret.Next != null) + ret = ret.Next; + return ret; + } + } + +#if TEST_MSAGL + /// + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] + public IEnumerable GetSegments() { + Site s0 = headSite; + Site s1 = headSite.Next; + while (s1 != null) { + yield return new LineSegment(s0.Point,s1.Point); + s0=s1; + s1=s1.Next; + } + } +#endif + + /// + /// Creates a curve by using the underlying polyline + /// + /// + public Curve CreateCurve() { + Curve curve = new Curve(); + Site a = HeadSite;//the corner start + Site b; //the corner origin + Site c;//the corner other end + + while (Curve.FindCorner(a, out b, out c)) { + CubicBezierSegment bezierSeg = CreateBezierSegOnSite(b); + if (curve.Segments.Count == 0) { + if (!ApproximateComparer.Close(a.Point, bezierSeg.Start)) + Curve.AddLineSegment(curve, a.Point, bezierSeg.Start); + } else if (!ApproximateComparer.Close(curve.End, bezierSeg.Start)) + Curve.ContinueWithLineSegment(curve, bezierSeg.Start); + curve.AddSegment(bezierSeg); + a = b; + } + + System.Diagnostics.Debug.Assert(a.Next.Next == null); + + if (curve.Segments.Count == 0) { + if (!ApproximateComparer.Close(a.Point, a.Next.Point)) { + Curve.AddLineSegment(curve, a.Point, a.Next.Point); + } else { + double w = 5; + curve.Segments.Add(new CubicBezierSegment(a.Point, a.Point + new Point(w, w), a.Point + new Point(-w, w), b.Point)); + } + } else if (!ApproximateComparer.Close(curve.End, a.Next.Point)) + Curve.ContinueWithLineSegment(curve, a.Next.Point); + return curve; + } + + static internal CubicBezierSegment CreateBezierSegOnSite(Site b) { + var kPrev = b.PreviousBezierSegmentFitCoefficient; + var kNext = b.NextBezierSegmentFitCoefficient; + var a = b.Previous; + var c = b.Next; + var s = kPrev*a.Point + (1 - kPrev)*b.Point; + var e = kNext*c.Point + (1 - kNext)*b.Point; + var u = s*b.PreviousTangentCoefficient + (1 - b.PreviousTangentCoefficient)*b.Point; + var v = e*b.NextTangentCoefficient + (1 - b.NextTangentCoefficient)*b.Point; + return new CubicBezierSegment(s, u, v, e); + } + + #region IEnumerable Members +/// +/// the enumerator of the polyline corners +/// +/// + public IEnumerator GetEnumerator() { + return new PointNodesList(this.headSite); + } + + #endregion + + #region IEnumerable Members + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { + return new PointNodesList(this.headSite); + } + + #endregion + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/SmoothedPolyline.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/SmoothedPolyline.cs.meta new file mode 100644 index 000000000..5a4162cce --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/SmoothedPolyline.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 487dd40216bfce244b7f64f0884776f1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/TriangleOrientation.cs b/Assets/Scripts/NIH/MSAGL/Core/Geometry/TriangleOrientation.cs new file mode 100644 index 000000000..8a9ef8e25 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/TriangleOrientation.cs @@ -0,0 +1,19 @@ +namespace Microsoft.Msagl.Core.Geometry{ + /// + /// triangle orientations + /// + public enum TriangleOrientation { + /// + /// clockwise orientation + /// + Clockwise, + /// + /// counter clockwise orientation + /// + Counterclockwise, + /// + /// the points are collinear + /// + Collinear + } +} \ No newline at end of file diff --git a/Assets/Scripts/NIH/MSAGL/Core/Geometry/TriangleOrientation.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Geometry/TriangleOrientation.cs.meta new file mode 100644 index 000000000..eb89f4974 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Geometry/TriangleOrientation.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 619d0ab6b19334e458026502c0463413 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms.meta b/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms.meta new file mode 100644 index 000000000..dd8f6e9e1 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c1f209fb24a9a8148adb6060f83493d0 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/BasicGraph.cs b/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/BasicGraph.cs new file mode 100644 index 000000000..2c3689ab5 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/BasicGraph.cs @@ -0,0 +1,275 @@ +using System.Collections; +using System.Collections.Generic; +using Microsoft.Msagl.Core.DataStructures; + +namespace Microsoft.Msagl.Core.GraphAlgorithms { + internal class BasicGraph : BasicGraph where TEdge : IEdge + { + /// + /// a default constructor + /// + internal BasicGraph() + { + } + + internal BasicGraph(IEnumerable edges) : base(edges, VertexCount(edges)) { } + + /// + /// constructor + /// + /// + /// + internal BasicGraph(IEnumerable edges, int numberOfVerts) : base(edges, numberOfVerts) + { + } + + IList nodes; + + /// + /// array of nodes + /// + public IList Nodes { + get { return nodes; } + set { nodes = value; } + } + } + + /// + /// The base class for graphs: layering and ordering work on an instance of this class. + /// + internal class BasicGraph where TEdge : IEdge { + protected List edges; + TEdge[][] inEdges; + int numberOfVertices; + TEdge[][] outEdges; + TEdge[][] selfEdges; + + /// + /// a default constructor + /// + internal BasicGraph() { + SetEdges(new List(), 0); + } + + internal BasicGraph(IEnumerable edges) : this(edges, VertexCount(edges)) {} + + /// + /// constructor + /// + /// + /// + internal BasicGraph(IEnumerable edges, int numberOfVerts) { + SetEdges(edges, numberOfVerts); + } + + + /// + /// returning number of vertices of the graph + /// + internal int NodeCount { + get { return numberOfVertices; } + } + + /// + /// returning all edges of the graph + /// + public ICollection Edges { + get { return edges; } + } + + /// + /// the method is not efficient, takes linear time + /// + /// + internal void RemoveEdge(TEdge edge) { + edges.Remove(edge); + if (edge.Source != edge.Target) { + TEdge[] edgesToChange = outEdges[edge.Source]; + var newEdges = new TEdge[edgesToChange.Length - 1]; + FillArraySkippingTheEdge(edge, edgesToChange, newEdges); + outEdges[edge.Source] = newEdges; + edgesToChange = inEdges[edge.Target]; + newEdges = new TEdge[edgesToChange.Length - 1]; + FillArraySkippingTheEdge(edge, edgesToChange, newEdges); + inEdges[edge.Target] = newEdges; + } + } + + static void FillArraySkippingTheEdge(TEdge edge, TEdge[] edgesToChange, TEdge[] newEdges) { + for (int i = 0, j = 0; i < edgesToChange.Length; i++) + if ((object) edgesToChange[i] != (object) edge) + newEdges[i - j] = edgesToChange[i]; + else + j = 1; + } + + /// + /// actually finds maximum of sources and targets+1 + /// + /// + /// + internal static int VertexCount(IEnumerable edges) { + int nov = 0; + foreach (TEdge ie in edges) { + if (ie.Source >= nov) + nov = ie.Source + 1; + if (ie.Target >= nov) + nov = ie.Target + 1; + } + return nov; + } + + /// + /// Edges exiting a vertex + /// + /// + /// + public IList OutEdges(int vertex) { + return outEdges[vertex]; + } + + internal IList SelfEdges(int vertex) { + return selfEdges[vertex]; + } + + /// + /// Edges entering a vertex + /// + /// + /// + public IList InEdges(int vertex) { + return inEdges[vertex]; + } + + /// + /// sets edges of the graph + /// + /// + /// number of vertices + internal void SetEdges(IEnumerable valEdges, int nov) { + edges = valEdges as List ?? new List(valEdges); + + numberOfVertices = nov; + var outEdgesCounts = new int[numberOfVertices]; + var inEdgesCounts = new int[numberOfVertices]; + var selfEdgesCounts = new int[numberOfVertices]; + + outEdges = new TEdge[numberOfVertices][]; + inEdges = new TEdge[numberOfVertices][]; + selfEdges = new TEdge[numberOfVertices][]; + + foreach (TEdge e in edges) { + //if (e.Source >= nov) + //Console.WriteLine(""); + if (e.Source != e.Target) { + outEdgesCounts[e.Source]++; + inEdgesCounts[e.Target]++; + } else { + selfEdgesCounts[e.Source]++; + } + } + + //allocate now + for (int i = 0; i < numberOfVertices; i++) { + outEdges[i] = new TEdge[outEdgesCounts[i]]; + outEdgesCounts[i] = 0; //used later for edge insertion + inEdges[i] = new TEdge[inEdgesCounts[i]]; + inEdgesCounts[i] = 0; //used later for edge insertion + + selfEdges[i] = new TEdge[selfEdgesCounts[i]]; + selfEdgesCounts[i] = 0; //used later for edge insertion + } + + //set up backward and forward edges now + foreach (TEdge e in edges) { + int u = e.Source; + int v = e.Target; + if (u != v) { + outEdges[u][outEdgesCounts[u]++] = e; + inEdges[v][inEdgesCounts[v]++] = e; + } else + selfEdges[u][selfEdgesCounts[u]++] = e; + } + } + + public int InEdgesCount(int node) { + return InEdges(node).Count; + } + + public int OutEdgesCount(int node) { + return OutEdges(node).Count; + } + + /// + /// this function is extremely non-efficient and is called only when adding virtual edges + /// + /// + internal void AddEdge(TEdge e) { + Edges.Add(e); + AddEdgeToInEdges(e, e.Target); + AddEdgeToOutEdges(e, e.Source); + } + + /// + /// this function is extremely non-efficient and is called only when adding virtual edges + /// + /// + /// + void AddEdgeToOutEdges(TEdge e, int source) { + TEdge[] ies = outEdges[source]; + var nies = new TEdge[ies.Length + 1]; + ies.CopyTo(nies, 1); + nies[0] = e; + outEdges[source] = nies; + } + + /// + /// this function is extremely non-efficient and is called only when adding virtual edges + /// + /// + /// + void AddEdgeToInEdges(TEdge e, int target) { + TEdge[] ies = inEdges[target]; + var nies = new TEdge[ies.Length + 1]; + ies.CopyTo(nies, 1); + nies[0] = e; + inEdges[target] = nies; + } + + /// + /// We assume that the graph is connected here + /// + /// + internal IEnumerable NodesOfConnectedGraph() { + if (edges.Count > 0) { + var enqueed = new Set(); + var q = new Queue(); + int i = edges[0].Source; + Enqueue(enqueed, q, i); + yield return i; + while (q.Count > 0) { + i = q.Dequeue(); + foreach (TEdge e in outEdges[i]) { + int s = e.Target; + if (!enqueed.Contains(s)) { + Enqueue(enqueed, q, s); + yield return s; + } + } + foreach (TEdge e in inEdges[i]) { + int s = e.Source; + if (!enqueed.Contains(s)) { + Enqueue(enqueed, q, s); + yield return s; + } + } + } + } + } + + static void Enqueue(Set enqueed, Queue q, int i) { + q.Enqueue(i); + enqueed.Insert(i); + } + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/BasicGraph.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/BasicGraph.cs.meta new file mode 100644 index 000000000..69465b543 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/BasicGraph.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3f8e0ff2b46e7a941a7f2e0743bd65fc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/CycleRemoval.meta b/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/CycleRemoval.meta new file mode 100644 index 000000000..8b05495c1 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/CycleRemoval.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 59f313f021d868343aeba4700fc14246 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/CycleRemoval/ConnectedComponentCalculator.cs b/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/CycleRemoval/ConnectedComponentCalculator.cs new file mode 100644 index 000000000..bb67e42fc --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/CycleRemoval/ConnectedComponentCalculator.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; + +namespace Microsoft.Msagl.Core.GraphAlgorithms { + + + static internal class ConnectedComponentCalculator where TEdge:IEdge { + static internal IEnumerable> GetComponents(BasicGraph graph) { + bool[] enqueueed = new bool[graph.NodeCount]; +#if SHARPKIT //http://code.google.com/p/sharpkit/issues/detail?id=367 bool arrays are not default initialised + for (var i = 0; i < graph.NodeCount; i++) + enqueueed[i] = false; +#endif + Queue queue = new Queue(); + for (int i = 0; i < graph.NodeCount; i++) { + if (!enqueueed[i]) { + List nodes = new List(); + + Enqueue(i, queue, enqueueed); + + while (queue.Count > 0) { + int s = queue.Dequeue(); + nodes.Add(s); + foreach (int neighbor in Neighbors(graph, s)) + Enqueue(neighbor, queue, enqueueed); + } + + yield return nodes; + } + } + } + + static IEnumerable Neighbors(BasicGraph graph, int s) { + foreach (TEdge e in graph.OutEdges(s)) + yield return e.Target; + foreach (TEdge e in graph.InEdges(s)) + yield return e.Source; + } + + static void Enqueue(int i, Queue q, bool[] enqueueed) { + if (enqueueed[i] == false) { + q.Enqueue(i); + enqueueed[i] = true; + } + } + + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/CycleRemoval/ConnectedComponentCalculator.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/CycleRemoval/ConnectedComponentCalculator.cs.meta new file mode 100644 index 000000000..b61366519 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/CycleRemoval/ConnectedComponentCalculator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4b89d1a270ff5804ea2b8811dba00a0b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/CycleRemoval/CycleRemoval.cs b/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/CycleRemoval/CycleRemoval.cs new file mode 100644 index 000000000..bd00d9cbb --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/CycleRemoval/CycleRemoval.cs @@ -0,0 +1,110 @@ +using System.Collections.Generic; +using Microsoft.Msagl.Core.DataStructures; + +namespace Microsoft.Msagl.Core.GraphAlgorithms { + /// + /// Calculates a set of edges to reverse, so called "feedback set", for obtaining a DAG + /// + static internal class CycleRemoval where TEdge : IEdge { + + /// + /// Returning a list of edges reversing which makes the graph into a DAG + /// + /// + /// + /// + static internal IEnumerable GetFeedbackSetWithConstraints(BasicGraph graph, Set constraints) { + if (constraints == null || constraints.Count == 0) { + return GetFeedbackSet(graph); + /* + var reg = GetFeedbackSetFromGraph(graph); + var con= GetFeedbackSetWithConstraints(graph, new HashSet>()); + int i = 0; + foreach (var z in reg) + i++; + foreach (var z in con) + i--; + Console.WriteLine("Eades {0} by {1}", i>0?"wins":"loses", i); + return con; + */ + + } else + return GetFeedbackSetWithConstraintsLocal(graph, constraints); + } + + static IEnumerable GetFeedbackSetWithConstraintsLocal(BasicGraph graph, Set constraints) { + var v = new CycleRemovalWithConstraints(graph, constraints); + return v.GetFeedbackSet(); + } + + + static void Push(Stack> enumStack, Stack vertexStack, VertStatus[] status, int vertex, IEnumerator outEnum) { + status[vertex] = VertStatus.InStack; + enumStack.Push(outEnum); + vertexStack.Push(vertex); + } + + static void Pop(Stack> enumStack, Stack vertexStack, VertStatus[] status, out int vertex, out IEnumerator outEnum) { + outEnum = enumStack.Pop(); + vertex = vertexStack.Pop(); + status[vertex] = VertStatus.Visited; + } + + enum VertStatus { + NotVisited, + InStack, + Visited, + } + + /// + /// We build a spanning tree by following the DFS, the tree induces an order on vertices + /// measured by the distance from the tree root. The feedback set will consist of edges + /// directed against this order. + /// + /// + /// + static internal IEnumerable GetFeedbackSet(BasicGraph graph) { + if (graph != null && graph.NodeCount > 0 && graph.Edges.Count > 0) { + Set feedbackSet = new Set(); + VertStatus[] status = new VertStatus[graph.NodeCount]; //will be Unvisited at the beginning +#if SHARPKIT //http://code.google.com/p/sharpkit/issues/detail?id=367 arrays are not default initialised + for (int i = 0; i < status.Length; i++) + status[i] = VertStatus.NotVisited; +#endif + for (int vertex = 0; vertex < graph.NodeCount; vertex++) { + if (status[vertex] == VertStatus.Visited) + continue; + + System.Diagnostics.Debug.Assert(status[vertex] != VertStatus.InStack); + + Stack> enumStack = new Stack>(); //avoiding the recursion + Stack vertexStack = new Stack(); //avoiding the recursion + IEnumerator outEnum = graph.OutEdges(vertex).GetEnumerator(); + Push(enumStack, vertexStack, status, vertex, outEnum); + while (enumStack.Count > 0) { + Pop(enumStack, vertexStack, status, out vertex, out outEnum); + + while (outEnum.MoveNext()) { + TEdge e = outEnum.Current; + + if (e.Source == e.Target) + continue; + + VertStatus targetStatus = status[e.Target]; + if (targetStatus == VertStatus.InStack) { + feedbackSet.Insert(e); + } else if (targetStatus == VertStatus.NotVisited) { //have to go deeper + Push(enumStack, vertexStack, status, vertex, outEnum); + vertex = e.Target; + status[e.Target] = VertStatus.Visited; + outEnum = graph.OutEdges(vertex).GetEnumerator(); + } + } + } + } + return feedbackSet as IEnumerable; + } else + return new Set(); + } + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/CycleRemoval/CycleRemoval.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/CycleRemoval/CycleRemoval.cs.meta new file mode 100644 index 000000000..0ffaf9c14 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/CycleRemoval/CycleRemoval.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 82af1727e3de7a24cae1e5161ddab0fa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/CycleRemoval/CycleRemovalWithConstraints.cs b/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/CycleRemoval/CycleRemovalWithConstraints.cs new file mode 100644 index 000000000..c55d19fd3 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/CycleRemoval/CycleRemovalWithConstraints.cs @@ -0,0 +1,82 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.Msagl.Core.DataStructures; + +namespace Microsoft.Msagl.Core.GraphAlgorithms { + internal class CycleRemovalWithConstraints where TEdge : IEdge { + BasicGraph graph; + BasicGraph graphOfConstraints; + IEnumerable constrainedEdges; + internal CycleRemovalWithConstraints(BasicGraph graph, Set constraints) { + this.graph = graph; + constrainedEdges = constraints; + graphOfConstraints=new BasicGraph(constrainedEdges, graph.NodeCount); + } + + internal IEnumerable GetFeedbackSet() { + foreach (GraphForCycleRemoval graphForCycleRemoval in CreateGraphsForCycleRemoval()) { + foreach (IEdge edge in GetFeedbackEdgeSet(graphForCycleRemoval)) + yield return edge; + } + } + /// + /// following H.A.D Nascimento and P. Eades "User Hints for Directed Graph Drawing" + /// + /// graphForCycleRemoval is connected + /// + IEnumerable GetFeedbackEdgeSet(GraphForCycleRemoval graphForCycleRemoval) { + graphForCycleRemoval.Initialize(); + //empty at the end of the method + List sl = new List(); //sl - the sequence left part + List sr = new List(); //sr - the sequence right part. In our case it is a reversed right part + while (!graphForCycleRemoval.IsEmpty()) { + int u; + while (graphForCycleRemoval.TryGetSink(out u)) { + graphForCycleRemoval.RemoveNode(u); + sr.Add(u); + } + while (graphForCycleRemoval.TryGetSource(out u)) { + graphForCycleRemoval.RemoveNode(u); + sl.Add(u); + } + if (graphForCycleRemoval.TryFindVertexWithNoIncomingConstrainedEdgeAndMaximumOutDegreeMinusInDedree(out u)) { + graphForCycleRemoval.RemoveNode(u); + sl.Add(u); + } + } + + Dictionary S = new Dictionary(sl.Count + sr.Count); + int j=0; + foreach (int u in sl) + S[u] = j++; + for (int i = sr.Count - 1; i >= 0; i--) + S[sr[i]] = j++; + + + foreach (IntPair pair in graphForCycleRemoval.GetOriginalIntPairs()) + if (S[pair.First] > S[pair.Second]) + yield return pair; + + } + + IEnumerable CreateGraphsForCycleRemoval() { + foreach (IEnumerable componentNodes in ConnectedComponentCalculator.GetComponents(GetCommonGraph())) + yield return CreateGraphForCycleRemoval(componentNodes); + } + + BasicGraph GetCommonGraph() { + return new BasicGraph((from edge in graph.Edges select new IntPair(edge.Source, edge.Target)).Concat(constrainedEdges), graph.NodeCount); + } + + GraphForCycleRemoval CreateGraphForCycleRemoval(IEnumerable componentNodes) { + GraphForCycleRemoval graphForCycleRemoval = new GraphForCycleRemoval(); + foreach (int i in componentNodes) { + foreach (TEdge edge in this.graph.OutEdges(i)) + graphForCycleRemoval.AddEdge(new IntPair(edge.Source,edge.Target)); + foreach (IntPair intPair in this.graphOfConstraints.OutEdges(i)) + graphForCycleRemoval.AddConstraintEdge(intPair); + } + return graphForCycleRemoval; + } + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/CycleRemoval/CycleRemovalWithConstraints.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/CycleRemoval/CycleRemovalWithConstraints.cs.meta new file mode 100644 index 000000000..216df9588 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/CycleRemoval/CycleRemovalWithConstraints.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1c3317ecc2ddab04695299976fc872ae +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/CycleRemoval/GraphForCycleRemoval.cs b/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/CycleRemoval/GraphForCycleRemoval.cs new file mode 100644 index 000000000..de5dbacba --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/CycleRemoval/GraphForCycleRemoval.cs @@ -0,0 +1,154 @@ +using System.Collections.Generic; +using Microsoft.Msagl.Core.DataStructures; + +namespace Microsoft.Msagl.Core.GraphAlgorithms { + + internal class GraphForCycleRemoval { + /// + /// this dictionary contains only buckets with nodes which are sources in the graph of constrained edges + /// + SortedDictionary> deltaDegreeBucketsForSourcesInConstrainedSubgraph = new SortedDictionary>(); + Dictionary nodeInfoDictionary = new Dictionary(); + Set sources=new Set(); + Set sinks=new Set(); + Set edgesToKeep = new Set(); + + internal void AddEdge(IntPair edge) { + edgesToKeep.Insert(edge); + int source = edge.First; int target = edge.Second; + GetOrCreateNodeInfo(source).AddOutEdge(target); + GetOrCreateNodeInfo(target).AddInEdge(source); + } + + + NodeInfo GetOrCreateNodeInfo(int node) { + NodeInfo nodeInfo; + if (!nodeInfoDictionary.TryGetValue(node, out nodeInfo)) { + nodeInfo = new NodeInfo(); + nodeInfoDictionary[node] = nodeInfo; + } + return nodeInfo; + } + + internal void AddConstraintEdge(IntPair intPair) { + int source = intPair.First; int target = intPair.Second; + GetOrCreateNodeInfo(source).AddOutConstrainedEdge(target); + GetOrCreateNodeInfo(target).AddInConstrainedEdge(source); + } + + internal bool IsEmpty() { + return nodeInfoDictionary.Count == 0; + } + + internal void RemoveNode(int u) { + sources.Remove(u); + sinks.Remove(u); + RemoveNodeFromItsBucket(u); + NodeInfo uNodeInfo=this.nodeInfoDictionary[u]; + Set allNbs = new Set(uNodeInfo.AllNeighbors); + foreach(int v in allNbs) + RemoveNodeFromItsBucket(v); + + DisconnectNodeFromGraph(u, uNodeInfo); + + foreach (int v in allNbs) + AddNodeToBucketsSourcesAndSinks(v, nodeInfoDictionary[v]); + + + } + + void DisconnectNodeFromGraph(int u, NodeInfo uNodeInfo) { + foreach (int v in uNodeInfo.OutEdges) + nodeInfoDictionary[v].RemoveInEdge(u); + + foreach (int v in uNodeInfo.OutConstrainedEdges) + nodeInfoDictionary[v].RemoveInConstrainedEdge(u); + + foreach (int v in uNodeInfo.InEdges) + nodeInfoDictionary[v].RemoveOutEdge(u); + + foreach (int v in uNodeInfo.InConstrainedEdges) + nodeInfoDictionary[v].RemoveOutConstrainedEdge(u); + + nodeInfoDictionary.Remove(u); + } + + + + void RemoveNodeFromItsBucket(int v) { + int delta = DeltaDegree(v); + Set bucket; + if (this.deltaDegreeBucketsForSourcesInConstrainedSubgraph.TryGetValue(delta, out bucket)) { + bucket.Remove(v); + if (bucket.Count == 0) + deltaDegreeBucketsForSourcesInConstrainedSubgraph.Remove(delta); + } + } + + int DeltaDegree(int v) { + int delta = nodeInfoDictionary[v].DeltaDegree; + return delta; + } + + + + internal bool TryFindVertexWithNoIncomingConstrainedEdgeAndMaximumOutDegreeMinusInDedree(out int u) { + var enumerator = this.deltaDegreeBucketsForSourcesInConstrainedSubgraph.GetEnumerator(); + if (enumerator.MoveNext()) { + var bucketSet = enumerator.Current.Value; + System.Diagnostics.Debug.Assert(bucketSet.Count > 0); + var nodeEnumerator = bucketSet.GetEnumerator(); + nodeEnumerator.MoveNext(); + u = nodeEnumerator.Current; + return true; + } + u = -1; + return false; + } + + internal bool TryGetSource(out int u) { + var enumerator = this.sources.GetEnumerator(); + if (enumerator.MoveNext()) { + u = enumerator.Current; + return true; + } + u = -1; + return false; + } + + internal bool TryGetSink(out int u) { + var enumerator = this.sinks.GetEnumerator(); + if (enumerator.MoveNext()) { + u = enumerator.Current; + return true; + } + u = -1; + return false; + } + + internal IEnumerable GetOriginalIntPairs() { + return this.edgesToKeep; + } + + internal void Initialize() { + foreach (var p in nodeInfoDictionary) + AddNodeToBucketsSourcesAndSinks(p.Key, p.Value); + } + + void AddNodeToBucketsSourcesAndSinks(int v, NodeInfo nodeInfo) { + if (nodeInfo.InDegree == 0) + sources.Insert(v); + else if (nodeInfo.OutDegree == 0) + sinks.Insert(v); + else if (nodeInfo.InDegreeOfConstrainedEdges == 0) + GetOrCreateBucket(nodeInfo.DeltaDegree).Insert(v); + } + + Set GetOrCreateBucket(int delta) { + Set ret; + if (this.deltaDegreeBucketsForSourcesInConstrainedSubgraph.TryGetValue(delta, out ret)) + return ret; + return deltaDegreeBucketsForSourcesInConstrainedSubgraph[delta] = new Set(); + } + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/CycleRemoval/GraphForCycleRemoval.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/CycleRemoval/GraphForCycleRemoval.cs.meta new file mode 100644 index 000000000..835f9ac32 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/CycleRemoval/GraphForCycleRemoval.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1667e215913f43a46ad2acafab75c517 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/CycleRemoval/NodeInfo.cs b/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/CycleRemoval/NodeInfo.cs new file mode 100644 index 000000000..34201f113 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/CycleRemoval/NodeInfo.cs @@ -0,0 +1,91 @@ +using System.Collections.Generic; +using Microsoft.Msagl.Core.DataStructures; + +namespace Microsoft.Msagl.Core.GraphAlgorithms { + internal class NodeInfo { + Set outEdges = new Set(); + + internal Set OutEdges { + get { return outEdges; } + } + Set inEdges = new Set(); + + internal Set InEdges { + get { return inEdges; } + } + Set outConstrainedEdges = new Set(); + + internal Set OutConstrainedEdges { + get { return outConstrainedEdges; } + } + + Set inConstrainedEdges = new Set(); + + public Set InConstrainedEdges { + get { return inConstrainedEdges; } + } + /// + /// it is the out degree without the in degree + /// + internal int DeltaDegree { + get { return InDegree-OutDegree; } + } + internal void AddOutEdge(int v) { + outEdges.Insert(v); + } + internal void RemoveOutEdge(int v) { + outEdges.Remove(v); + } + + internal void AddInEdge(int v) { + inEdges.Insert(v); + } + internal void RemoveInEdge(int v) { + inEdges.Remove(v); + } + internal void AddOutConstrainedEdge(int v) { + outConstrainedEdges.Insert(v); + } + internal void RemoveOutConstrainedEdge(int v) { + outConstrainedEdges.Remove(v); + } + + internal void AddInConstrainedEdge(int v) { + inConstrainedEdges.Insert(v); + } + internal void RemoveInConstrainedEdge(int v) { + inConstrainedEdges.Remove(v); + } + + internal int OutDegree { + get { + return outEdges.Count+outConstrainedEdges.Count; + } + } + internal int InDegreeOfConstrainedEdges { + get { + return inConstrainedEdges.Count; + } + } + internal int InDegree { + get { return inEdges.Count+inConstrainedEdges.Count; } + } + + + /// + /// including constrained neighbors + /// + internal IEnumerable AllNeighbors { + get { + foreach (int v in this.OutConstrainedEdges) + yield return v; + foreach (int v in this.InConstrainedEdges) + yield return v; + foreach (int v in this.OutEdges) + yield return v; + foreach (int v in this.InEdges) + yield return v; + } + } + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/CycleRemoval/NodeInfo.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/CycleRemoval/NodeInfo.cs.meta new file mode 100644 index 000000000..6cf5c1be2 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/CycleRemoval/NodeInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c998b06b460335b4394e9d1db18b498c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/IEdge.cs b/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/IEdge.cs new file mode 100644 index 000000000..3bfd71b12 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/IEdge.cs @@ -0,0 +1,15 @@ +namespace Microsoft.Msagl.Core.GraphAlgorithms { + /// + /// an edge interface + /// + public interface IEdge { + /// + /// source + /// + int Source { get; set; } + /// + /// target + /// + int Target { get; set; } + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/IEdge.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/IEdge.cs.meta new file mode 100644 index 000000000..4880386d4 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/IEdge.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 412607acef4383a4a9e71cde1b01bd1c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/IntPair.cs b/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/IntPair.cs new file mode 100644 index 000000000..fc5ec6f86 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/IntPair.cs @@ -0,0 +1,167 @@ +using System; + +namespace Microsoft.Msagl.Core.GraphAlgorithms { + /// + /// Represents a couple of integers. + /// +#if TEST_MSAGL + [Serializable] +#endif +#if TEST_MSAGL + public +#else + internal +#endif + sealed class IntPair : IEdge { + + internal int x; + internal int y; + + /// + /// the first element of the pair + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811")] + public int First { + get { return x; } + set + { + x = value; +#if SHARPKIT //https://code.google.com/p/sharpkit/issues/detail?id=289 Support Dictionary directly based on object's GetHashCode + UpdateHashKey(); +#endif + } + } + + /// + /// the second element of the pair + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811")] + public int Second { + get { return y; } + set + { + y = value; +#if SHARPKIT //https://code.google.com/p/sharpkit/issues/detail?id=289 Support Dictionary directly based on object's GetHashCode + UpdateHashKey(); +#endif + } + } + /// + /// the less operator + /// + /// + /// + /// + public static bool operator <(IntPair pair0, IntPair pair1) { + if (pair0 != null && pair1 != null) + return pair0.x < pair1.x || pair0.x == pair1.x && pair0.y < pair1.y; + + throw new InvalidOperationException(); + } + /// + /// the greater operator + /// + /// + /// + /// + public static bool operator >(IntPair pair0, IntPair pair1) { + return pair1 < pair0; + } + /// + /// Compares two pairs + /// + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811")] + public static int Compare(IntPair pair0, IntPair pair1) { + if (pair0 < pair1) + return 1; + if (pair0 == pair1) + return 0; + + return -1; + } + + /// + /// override the equality + /// + /// + /// + public override bool Equals(object obj) { + IntPair other = obj as IntPair; + if (other == null) + return false; + + return x == other.x && y == other.y; + } + +#if SHARPKIT //https://code.google.com/p/sharpkit/issues/detail?id=289 Support Dictionary directly based on object's GetHashCode + private SharpKit.JavaScript.JsString _hashKey; + private void UpdateHashKey() + { + _hashKey = ""+x+","+y; + } +#endif + + /// + /// + /// + /// + public override int GetHashCode() { + uint hc = (uint)x.GetHashCode(); + return (int)((hc << 5 | hc >> 27) + (uint)y); + } + + /// + /// the constructor + /// + /// + /// + public IntPair(int first, int second) { + this.x = first; + this.y = second; +#if SHARPKIT //https://code.google.com/p/sharpkit/issues/detail?id=289 Support Dictionary directly based on object's GetHashCode + UpdateHashKey(); +#endif + } + + /// + /// + /// + /// + public override string ToString() { + return "(" + x + "," + y + ")"; + } + +#if SHARPKIT //http://code.google.com/p/sharpkit/issues/detail?id=203 Explicitly implemented interfaces are not generate without any warning + public int Source { +#else + int IEdge.Source { +#endif + get { return x; } + set + { + x = value; +#if SHARPKIT //https://code.google.com/p/sharpkit/issues/detail?id=289 Support Dictionary directly based on object's GetHashCode + UpdateHashKey(); +#endif + } + } + +#if SHARPKIT //http://code.google.com/p/sharpkit/issues/detail?id=203 Explicitly implemented interfaces are not generate without any warning + public int Target { +#else + int IEdge.Target { +#endif + get { return y; } + set + { + y = value; +#if SHARPKIT //https://code.google.com/p/sharpkit/issues/detail?id=289 Support Dictionary directly based on object's GetHashCode + UpdateHashKey(); +#endif + } + } + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/IntPair.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/IntPair.cs.meta new file mode 100644 index 000000000..6bc517134 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/IntPair.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ca9070170dbb48e4ba59f08b92a62357 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/MinimumSpanningTreeByPrim.cs b/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/MinimumSpanningTreeByPrim.cs new file mode 100644 index 000000000..9a92cc1aa --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/MinimumSpanningTreeByPrim.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections.Generic; +using Microsoft.Msagl.Core.DataStructures; +using Microsoft.Msagl.Core.Geometry; +using Microsoft.Msagl.Core.Geometry.Curves; +using Microsoft.Msagl.Core.Layout; +using Microsoft.Msagl.DebugHelpers; + +namespace Microsoft.Msagl.Core.GraphAlgorithms { + /// + /// + /// + public class MinimumSpanningTreeByPrim { + readonly BasicGraph graph; + readonly Func weight; + readonly int root; + readonly BinaryHeapPriorityQueue q; + Set treeNodes = new Set(); + //map of neighbors of the tree to the edges connected them to the tree + Dictionary hedgehog = new Dictionary(); + + /// + /// + /// + public static void Test() { + + } + + /// + /// constructor + /// + /// + /// + /// the node we start building the tree + internal MinimumSpanningTreeByPrim(BasicGraph graph, Func weight, int root) { + this.graph = graph; + this.weight = weight; + this.root = root; + q=new BinaryHeapPriorityQueue(graph.NodeCount); + } + + bool NodeIsInTree(int i) { + return treeNodes.Contains(i); + } + + /// + /// + /// + /// + public IList GetTreeEdges() + { + var ret = new List(graph.NodeCount - 1); + Init(); + while (ret.Count < graph.NodeCount - 1 && q.Count > 0) //some nodes might have no edges + AddEdgeToTree(ret); + return ret; + } + + void AddEdgeToTree(List ret) { + var v = q.Dequeue(); + var e = hedgehog[v]; + treeNodes.Insert(v); + ret.Add(e); + UpdateOutEdgesOfV(v); + UpdateInEdgesOfV(v); + } + + void UpdateOutEdgesOfV(int v) { + foreach (var outEdge in graph.OutEdges(v)) { + var u = outEdge.Target; + if (NodeIsInTree(u)) continue; + IEdge oldEdge; + if (hedgehog.TryGetValue(u, out oldEdge)) { + var oldWeight = weight(oldEdge); + var newWeight = weight(outEdge); + if (newWeight < oldWeight) { + q.DecreasePriority(u, newWeight); + hedgehog[u] = outEdge; + } + } else { + q.Enqueue(u, weight(outEdge)); + hedgehog[u] = outEdge; + } + } + } + + void UpdateInEdgesOfV(int v) + { + foreach (var inEdge in graph.InEdges(v)) + { + var u = inEdge.Source; + if (NodeIsInTree(u)) continue; + IEdge oldEdge; + if (hedgehog.TryGetValue(u, out oldEdge)) + { + var oldWeight = weight(oldEdge); + var newWeight = weight(inEdge); + if (newWeight < oldWeight) + { + q.DecreasePriority(u, newWeight); + hedgehog[u] = inEdge; + } + } + else + { + q.Enqueue(u, weight(inEdge)); + hedgehog[u] = inEdge; + } + } + } + + void Init() { + treeNodes.Insert(root); + + foreach (var outEdge in graph.OutEdges(root)) { + var w = weight(outEdge); + q.Enqueue(outEdge.Target, w); + hedgehog[outEdge.Target] = outEdge; + } + + foreach (var inEdge in graph.InEdges(root)) { + var w = weight(inEdge); + q.Enqueue(inEdge.Source, w); + hedgehog[inEdge.Source] = inEdge; + } + } + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/MinimumSpanningTreeByPrim.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/MinimumSpanningTreeByPrim.cs.meta new file mode 100644 index 000000000..7ca2d9e94 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/MinimumSpanningTreeByPrim.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 69dd407910fba6148a5d7f72bbf5e631 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/TopologicalSort.cs b/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/TopologicalSort.cs new file mode 100644 index 000000000..a6da9f9e5 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/TopologicalSort.cs @@ -0,0 +1,140 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.Msagl.Core.GraphAlgorithms{ + /// + /// Implements the topological sort + /// + public static class TopologicalSort{ + /// + /// Do a topological sort of a list of int edge tuples + /// + /// number of vertices + /// edge pairs + /// ordered node indexes + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + public static int[] GetOrder(int numberOfVertices, IEnumerable> edges) + { + var dag = new BasicGraph(from e in edges select new IntPair(e.Item1, e.Item2), numberOfVertices); + return GetOrder(dag); + } + + /// + /// The function returns an array arr such that + /// arr is a permutation of the graph vertices, + /// and for any edge e in graph if e.Source=arr[i] + /// e.Target=arr[j], then i is less than j + /// + /// + /// + internal static int[] GetOrder(BasicGraph graph) where TEdge : IEdge{ + var visited = new bool[graph.NodeCount]; + + //no recursion! So we have to organize a stack + var sv = new Stack(); + var se = new Stack>(); + + var order = new List(); + + IEnumerator en; + for (int u = 0; u < graph.NodeCount; u++){ + if (visited[u]) + continue; + + int cu = u; + visited[cu] = true; + en = graph.OutEdges(u).Select(e => e.Target).GetEnumerator(); + + do{ + while (en.MoveNext()){ + int v = en.Current; + if (!visited[v]){ + visited[v] = true; + sv.Push(cu); + se.Push(en); + cu = v; + en = graph.OutEdges(cu).Select(e => e.Target).GetEnumerator(); + } + } + order.Add(cu); + + + if (sv.Count > 0){ + en = se.Pop(); + cu = sv.Pop(); + } + else + break; + } while (true); + } + order.Reverse(); + return order.ToArray(); + } + + /// + /// The function returns an array arr such that + /// arr is a permutation of edge vertices, + /// and for any edge e from the list, if e.Source=arr[i] + /// e.Target=arr[j], then i is less than j + /// + /// + internal static int[] GetOrderOnEdges(IEnumerable edges) where TEdge : IEdge { + var visited = new Dictionary(); + var graph = new Dictionary>(); + foreach (var e in edges) { + visited[e.Source]=visited[e.Target]=false; + var x=e.Source; + List list; + if(! graph.TryGetValue(x, out list)) { + graph[x]=list=new List(); + } + list.Add(e.Target); + } + //organize a couple of stacks to avoid the recursion + var sv = new Stack(); + var se = new Stack>(); + + var order = new List(); + + IEnumerator en; + foreach (var u in visited.Keys.ToArray()) { + if (visited[u]) + continue; + + int cu = u; + + visited[cu] = true; + List glist; + if (!graph.TryGetValue(u, out glist)) continue; + if (glist == null) continue; + en = glist.GetEnumerator(); + do { + while (en.MoveNext()) { + int v = en.Current; + if (!visited[v]) { + visited[v] = true; + List list; + if (!graph.TryGetValue(v, out list)) { + order.Add(v);//it is a leaf + continue; + } + sv.Push(cu); + se.Push(en); + cu = v; + en = list.GetEnumerator(); + } + } + order.Add(cu); + + if (sv.Count > 0) { + en = se.Pop(); + cu = sv.Pop(); + } else + break; + } while (true); + } + order.Reverse(); + return order.ToArray(); + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/TopologicalSort.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/TopologicalSort.cs.meta new file mode 100644 index 000000000..996498298 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/GraphAlgorithms/TopologicalSort.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d2eef5061878c1346b763980d91ca0ae +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Layout.meta b/Assets/Scripts/NIH/MSAGL/Core/Layout.meta new file mode 100644 index 000000000..34cef589d --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Layout.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: af0ba736088ac7640b90f54d5bf81f70 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Layout/Arrowhead.cs b/Assets/Scripts/NIH/MSAGL/Core/Layout/Arrowhead.cs new file mode 100644 index 000000000..c9dbc84fc --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Layout/Arrowhead.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using Microsoft.Msagl.Core.Geometry; + +namespace Microsoft.Msagl.Core.Layout +{ + /// + /// keeps the arrowhead info + /// +#if TEST_MSAGL + [Serializable] +#endif + public class Arrowhead + { + + /// + /// + public const double DefaultArrowheadLength = 10; + double length = DefaultArrowheadLength; + + /// + /// The overall length of the arrow head + /// + public double Length { + get { return length; } + set { length = value; } + } + + /// + /// The width of the arrow head at the base + /// + public double Width { get; set; } + + /// + /// Where the tip of the arrow head is + /// + public Point TipPosition { get; set; } + + /// + /// A relative offset that moves the tip position + /// + public double Offset { get; set; } + + /// + /// Clone the arrowhead information + /// + /// + public Arrowhead Clone() + { + return new Arrowhead() + { + Length = this.length, + Width = this.Width, + TipPosition = this.TipPosition, + Offset = this.Offset + }; + } + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Layout/Arrowhead.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Layout/Arrowhead.cs.meta new file mode 100644 index 000000000..ae0b14fb7 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Layout/Arrowhead.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 047f0ef3254e59b46b806115770164d8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Layout/Arrowheads.cs b/Assets/Scripts/NIH/MSAGL/Core/Layout/Arrowheads.cs new file mode 100644 index 000000000..b01ea6045 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Layout/Arrowheads.cs @@ -0,0 +1,292 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +using Microsoft.Msagl.Core.Geometry; +using Microsoft.Msagl.Core.Geometry.Curves; + +namespace Microsoft.Msagl.Core.Layout +{ + /// + /// Arrowhead calculations + /// + public static class Arrowheads { + /// + /// calculates new curve ends that are the arrowhead starts + /// + /// The edgeGeometry.Curve is trimmed already by the node boundaries + /// + internal static bool CalculateArrowheads(EdgeGeometry edgeGeometry) { + ValidateArg.IsNotNull(edgeGeometry, "edgeGeometry"); + if (edgeGeometry.SourceArrowhead == null && edgeGeometry.TargetArrowhead == null) + return true; + double parStart, parEnd; + if (!FindTrimStartForArrowheadAtSource(edgeGeometry, out parStart)) + return false; + if (!FindTrimEndForArrowheadAtTarget(edgeGeometry, out parEnd)) + return false; + if (parStart > parEnd - ApproximateComparer.IntersectionEpsilon || ApproximateComparer.CloseIntersections(edgeGeometry.Curve[parStart], edgeGeometry.Curve[parEnd])) + return false; //after the trim nothing would be left of the curve + var c = edgeGeometry.Curve.Trim(parStart, parEnd); + if (c == null) + return false; + if (edgeGeometry.SourceArrowhead != null) + edgeGeometry.SourceArrowhead.TipPosition = PlaceTip(c.Start, edgeGeometry.Curve.Start, edgeGeometry.SourceArrowhead.Offset); + if (edgeGeometry.TargetArrowhead != null) + edgeGeometry.TargetArrowhead.TipPosition = PlaceTip(c.End, edgeGeometry.Curve.End, edgeGeometry.TargetArrowhead.Offset); + edgeGeometry.Curve = c; + return true; + } + + + static IList GetIntersectionsWithArrowheadCircle(ICurve curve, double arrowheadLength, Point circleCenter) { + Debug.Assert(arrowheadLength > 0); + var e = new Ellipse(arrowheadLength, arrowheadLength, circleCenter); + return Curve.GetAllIntersections(e, curve, true); + } + /// + /// we need to pass arrowhead length here since the original length mibh + /// + /// + /// + /// + static bool FindTrimEndForArrowheadAtTarget(EdgeGeometry edgeGeometry, out double p) { + var eps = ApproximateComparer.DistanceEpsilon*ApproximateComparer.DistanceEpsilon; + //Debug.Assert((edgeGeometry.Curve.End - edgeGeometry.Curve.Start).LengthSquared > eps); + p = edgeGeometry.Curve.ParEnd; + if (edgeGeometry.TargetArrowhead == null || + edgeGeometry.TargetArrowhead.Length <= ApproximateComparer.DistanceEpsilon) + return true; + var curve = edgeGeometry.Curve; + var arrowheadLength = edgeGeometry.TargetArrowhead.Length; + Point newCurveEnd; + IList intersections; + int reps = 10; + do { + reps--; + if (reps == 0) + return false; + intersections = GetIntersectionsWithArrowheadCircle(curve, arrowheadLength, curve.End); + p = intersections.Count != 0 ? intersections.Max(x => x.Par1) : curve.ParEnd; + newCurveEnd = edgeGeometry.Curve[p]; + arrowheadLength /= 2; + } while (((newCurveEnd - curve.Start).LengthSquared < eps || intersections.Count == 0)); + //we would like to have at least something left from the curve + return true; + } + + static bool FindTrimStartForArrowheadAtSource(EdgeGeometry edgeGeometry, out double p) { + p = 0; //does not matter + if (edgeGeometry.SourceArrowhead == null || edgeGeometry.SourceArrowhead.Length <= ApproximateComparer.DistanceEpsilon) + return true; + var eps = ApproximateComparer.DistanceEpsilon * ApproximateComparer.DistanceEpsilon; + Debug.Assert((edgeGeometry.Curve.End - edgeGeometry.Curve.Start).LengthSquared > eps); + var arrowheadLength = edgeGeometry.SourceArrowhead.Length; + Point newStart; + var curve = edgeGeometry.Curve; + IList intersections; + int reps = 10; + do + { + reps--; + if (reps == 0) + return false; + intersections = GetIntersectionsWithArrowheadCircle(curve, arrowheadLength, curve.Start); + p = intersections.Count != 0 ? intersections.Min(x => x.Par1) : curve.ParStart; + newStart = curve[p]; + arrowheadLength /= 2; + } while ((newStart - curve.End).LengthSquared < eps || intersections.Count==0); + //we are checkng that something will be left from the curve + return true; + } + + + internal static Point PlaceTip(Point arrowBase, Point arrowTip, double offset) + { + if (Math.Abs(offset) < ApproximateComparer.Tolerance) + return arrowTip; + + var d=arrowBase - arrowTip; + var dLen=d.Length; + if(dLen + /// trim the edge curve with the node boundaries + /// + /// + /// + /// + /// to keep the original spline + /// + public static bool TrimSplineAndCalculateArrowheads(Edge edge, ICurve spline, bool narrowestInterval, bool keepOriginalSpline) + { + ValidateArg.IsNotNull(edge, "edge"); + return TrimSplineAndCalculateArrowheads(edge.EdgeGeometry, + edge.Source.BoundaryCurve, + edge.Target.BoundaryCurve, + spline, + narrowestInterval, keepOriginalSpline); + } + + /// + /// trim the edge curve with the node boundaries + /// + /// + /// + /// + /// + /// + /// + /// + public static bool TrimSplineAndCalculateArrowheads(EdgeGeometry edgeGeometry, + ICurve sourceBoundary, + ICurve targetBoundary, + ICurve spline, + bool narrowestInterval, + bool keepOriginalSpline) { + ValidateArg.IsNotNull(spline, "spline"); + ValidateArg.IsNotNull(edgeGeometry, "edgeGeometry"); + + + edgeGeometry.Curve = Curve.TrimEdgeSplineWithNodeBoundaries(sourceBoundary, targetBoundary, spline, + narrowestInterval); + if (edgeGeometry.Curve == null) + return false; + + if ((edgeGeometry.SourceArrowhead == null || + edgeGeometry.SourceArrowhead.Length < ApproximateComparer.DistanceEpsilon) && + (edgeGeometry.TargetArrowhead == null || + edgeGeometry.TargetArrowhead.Length < ApproximateComparer.DistanceEpsilon)) + return true; //there are no arrowheads + bool success = false; + double sourceArrowheadSavedLength = edgeGeometry.SourceArrowhead != null + ? edgeGeometry.SourceArrowhead.Length + : 0; + double targetArrowheadSavedLength = edgeGeometry.TargetArrowhead != null + ? edgeGeometry.TargetArrowhead.Length + : 0; + var len = (edgeGeometry.Curve.End - edgeGeometry.Curve.Start).Length; + if (edgeGeometry.SourceArrowhead!=null) + edgeGeometry.SourceArrowhead.Length = Math.Min(len, sourceArrowheadSavedLength); + if (edgeGeometry.TargetArrowhead != null) + edgeGeometry.TargetArrowhead.Length = Math.Min(len, targetArrowheadSavedLength); + int count = 10; + while ( + ( + edgeGeometry.SourceArrowhead != null && + edgeGeometry.SourceArrowhead.Length > ApproximateComparer.IntersectionEpsilon + || + edgeGeometry.TargetArrowhead != null && + edgeGeometry.TargetArrowhead.Length > ApproximateComparer.IntersectionEpsilon) && !success) { + success = Arrowheads.CalculateArrowheads(edgeGeometry); + if (!success) { + if (edgeGeometry.SourceArrowhead != null) edgeGeometry.SourceArrowhead.Length *= 0.5; + if (edgeGeometry.TargetArrowhead != null) edgeGeometry.TargetArrowhead.Length *= 0.5; + } + count--; + if (count == 0) + break; + } + + if (!success) { + //to avoid drawing the arrowhead to (0,0) + if (edgeGeometry.SourceArrowhead != null) edgeGeometry.SourceArrowhead.TipPosition = spline.Start; + if (edgeGeometry.TargetArrowhead != null) edgeGeometry.TargetArrowhead.TipPosition = spline.End; + } + + if (edgeGeometry.SourceArrowhead != null) edgeGeometry.SourceArrowhead.Length = sourceArrowheadSavedLength; + if (edgeGeometry.TargetArrowhead != null) edgeGeometry.TargetArrowhead.Length = targetArrowheadSavedLength; + + return success; + } + + /// + /// Creates a spline between two nodes big enough to draw arrowheads + /// + /// + public static void CreateBigEnoughSpline(Edge edge) + { + ValidateArg.IsNotNull(edge, "edge"); + Point a = edge.Source.Center; + Point b = edge.Target.Center; + Point bMinA = b - a; + + double l = bMinA.Length; + Point perp; + if (l < 0.001) + { + perp = new Point(1, 0); + b = a + perp.Rotate(Math.PI / 2); + } + else + { + perp = bMinA.Rotate(Math.PI / 2); + } + + double maxArrowLength = 1; + if (edge.EdgeGeometry.SourceArrowhead != null) + { + maxArrowLength += edge.EdgeGeometry.SourceArrowhead.Length; + } + if (edge.EdgeGeometry.TargetArrowhead != null) + { + maxArrowLength += edge.EdgeGeometry.TargetArrowhead.Length; + } + perp = perp.Normalize() * 1.5 * maxArrowLength; + + int i = 1; + do + { + CubicBezierSegment seg = Curve.CreateBezierSeg(a, b, perp, i); + if (TrimSplineAndCalculateArrowheads(edge.EdgeGeometry, edge.Source.BoundaryCurve, + edge.Target.BoundaryCurve, + seg, false, false)) + { + break; + } + + i *= 2; + const int stop = 10000; + if (i >= stop) + { + CreateEdgeCurveWithNoTrimming(edge, a, b); + return; + } + } while (true); + } + + /// + /// this code should never work! + /// + /// + /// + /// + static void CreateEdgeCurveWithNoTrimming(Edge edge, Point a, Point b) + { + Point ab = b - a; + + ab = ab.Normalize(); + + Point lineStart = a; + Point lineEnd = b; + + Arrowhead targetArrow = edge.EdgeGeometry.TargetArrowhead; + if (targetArrow != null) + { + targetArrow.TipPosition = b; + lineEnd = b - ab * targetArrow.Length; + } + Arrowhead sourceArrow = edge.EdgeGeometry.SourceArrowhead; + if (sourceArrow != null) + { + sourceArrow.TipPosition = a; + lineStart = a + ab * sourceArrow.Length; + } + edge.Curve = new LineSegment(lineStart, lineEnd); + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/NIH/MSAGL/Core/Layout/Arrowheads.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Layout/Arrowheads.cs.meta new file mode 100644 index 000000000..6f91dcc7d --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Layout/Arrowheads.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d4c85a8900196644fb478a650de69eed +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Layout/Cluster.cs b/Assets/Scripts/NIH/MSAGL/Core/Layout/Cluster.cs new file mode 100644 index 000000000..2bb72a83a --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Layout/Cluster.cs @@ -0,0 +1,497 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Microsoft.Msagl.Core.Geometry; +using Microsoft.Msagl.Core.Geometry.Curves; + +namespace Microsoft.Msagl.Core.Layout { + /// + /// A cluster has a list of nodes and a list of nested clusters + /// +#if TEST_MSAGL + [Serializable] +#endif + public class Cluster : Node { + bool isCollapsed; + + /// + /// this flag should be respected by layout algorithms + /// + public bool IsCollapsed { + get { return isCollapsed; } + set { isCollapsed = value; } + } + + /// + /// + /// + public override ICurve BoundaryCurve { + get { return IsCollapsed ? CollapsedBoundary : base.BoundaryCurve; } + set { base.BoundaryCurve = value; } + } + + event EventHandler layoutDoneEvent; + + /// + /// event signalling that the layout is done + /// + public event EventHandler LayoutDoneEvent { + add { layoutDoneEvent += value; } + remove { layoutDoneEvent -= value; } + } + + internal Point Barycenter; // Filled in by SetBarycenter + ICurve collapsedBoundary; + + /// + /// the boundary curve when the cluster is collapsed + /// + public ICurve CollapsedBoundary { + get { return collapsedBoundary; } + set { collapsedBoundary = value; } + } + + internal List clusters = new List(); + internal List nodes = new List(); + + /// + /// + public Cluster() : this(new Point()) {} + + /// + /// Bottom-most ctor. + /// + /// + public Cluster(Point origin) { + Weight = 0; + Barycenter = origin; + } + + /// + /// Construct a cluster with the specified nodes as members + /// + /// + [SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists")] + public Cluster(IEnumerable nodes) + : this() { + ValidateArg.IsNotNull(nodes, "nodes"); + foreach (Node v in nodes) { + AddNode(v); + } + } + + /// + /// Construct a cluster with the specified nodes and clusters as child members + /// + /// + /// + public Cluster(IEnumerable nodes, IEnumerable clusters) + : this(nodes) { + ValidateArg.IsNotNull(clusters, "clusters"); + foreach (Cluster c in clusters) { + AddCluster(c); + } + } + + /// + /// Clusters can (optionally) have a rectangular border which is respected by overlap avoidance. + /// Currently, this is controlled by FastIncrementalLayoutSettings.RectangularClusters. + /// If FastIncrementalLayoutSettings.RectangularClusters is true, then the + /// FastIncrementalLayout constructor will create a RectangularBoundary in each cluster. + /// Otherwise it will be null. + /// + public RectangularClusterBoundary RectangularBoundary { get; set; } + + /// + /// List of member nodes + /// + public IEnumerable Nodes { + get { return nodes; } + } + + /// + /// List of child clusters + /// + public IEnumerable Clusters { + get { return clusters; } + } + + /// + /// number of nodes in cluster + /// + public double Weight { get; private set; } + + /// + /// BoundingBox_get uses the RectangularBoundary.rectangle if available, otherwise uses the cluster's content bounds + /// BoundingBox_set scales the old bounds to fit the desired bounds + /// + public override Rectangle BoundingBox { + get { + if (!IsCollapsed ||CollapsedBoundary==null) { + if (RectangularBoundary != null) { + return RectangularBoundary.Rect; + } + + // Default to the cluster's content bounds + return new Rectangle(nodes.Concat(clusters).Select(n => n.BoundingBox)); + } + + return CollapsedBoundary.BoundingBox; + + } + set { FitBoundaryCurveToTarget(value); } + } + + /// + /// Add a child node or cluster. It is added to the correct list (nodes or clusters) based on type + /// + /// + public void AddChild(Node child) { + ValidateArg.IsNotNull(child, "child"); + Debug.Assert(child != this); + var childCluster = child as Cluster; + if (childCluster != null) { + clusters.Add(childCluster); + } + else { + nodes.Add(child); + } + child.AddClusterParent(this); + } + + /// + /// Cleares the child clusters. + /// + public void ClearClusters() { + clusters.Clear(); + } + + /// + /// Compute the total weight of all nodes and clusters in this cluster. + /// + /// + public double ComputeWeight() { + Weight = nodes.Count; + foreach (Cluster c in clusters) { + Weight += c.ComputeWeight(); + } + return Weight; + } + + /// + /// + /// + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Barycenter"), + SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "SetBarycenter")] + public Point SetBarycenter() { + Barycenter = new Point(); + + // If these are empty then Weight is 0 and barycenter becomes NaN. + // If there are no child clusters with nodes, then Weight stays 0. + if ((0 != nodes.Count) || (0 != clusters.Count)) { + if (0 == Weight) { + ComputeWeight(); + } + if (0 != Weight) { + foreach (Node v in nodes) { + //double wv = ((FastIncrementalLayout.Node)v.AlgorithmData).stayWeight; + //p+=wv*v.Center; + //w += wv; + Barycenter += v.Center; + } + foreach (Cluster c in clusters) { + // SetBarycenter ensures Weight is calculated so call it first. + Barycenter += c.SetBarycenter()*c.Weight; + } + Barycenter /= Weight; + } + } + Debug.Assert(!Double.IsNaN(Barycenter.X) && !Double.IsNaN(Barycenter.Y)); + return Barycenter; + } + + + /// + /// TODO: Check all the places we use this and make sure we don't have O(n^2) complexity + /// + /// This cluster and all clusters beneath this one, in depth first order + public IEnumerable AllClustersDepthFirst() { + foreach (Cluster c in clusters) { + foreach (Cluster d in c.AllClustersDepthFirst()) { + yield return d; + } + } + yield return this; + } + + + /// + /// + /// This cluster and all clusters beneath this one, in width first order + public IEnumerable AllSuccessorsWidthFirst() { + foreach (Node n in Nodes) + yield return n; + + foreach (Cluster c in clusters) { + yield return c; + foreach (Node n in c.AllSuccessorsWidthFirst()) + yield return n; + } + } + + + + /// + /// + /// This cluster and all clusters beneath this one, in depth first order + public IEnumerable AllClustersDepthFirstExcludingSelf() { + foreach (Cluster c in clusters) { + foreach (Cluster d in c.AllClustersDepthFirst()) { + yield return d; + } + } + } + + /// + /// TODO: Check all the places we use this and make sure we don't have O(n^2) complexity + /// + /// + internal void ForEachNode(Action f) { + foreach (Node v in nodes) { + f(v); + } + foreach (Cluster c in clusters) { + c.ForEachNode(f); + } + } + + /// + /// Remove the specified cluster from the list of children of this cluster + /// + /// + public void RemoveCluster(Cluster cluster) { + clusters.Remove(cluster); + } + + /// + /// Translates the cluster's contents by the delta. + /// + public void DeepContentsTranslation(Point delta, bool translateEdges) { + foreach (Cluster c in AllClustersDepthFirst()) { + foreach (Node v in c.Nodes.Concat(c.Clusters.Cast())) { + var cluster = v as Cluster; + if (cluster != null) { + cluster.RectangularBoundary.TranslateRectangle(delta); + } + v.Center += delta; + if (translateEdges) { + foreach (Edge e in EdgesIncomingToNodeWithDescendantSource(v)) { + e.Translate(delta); + } + } + } + } + } + + /// + /// Get edges both of whose end-points are immediate children of this cluster + /// + /// + public IEnumerable ChildEdges() { + return Nodes.Concat(Clusters).SelectMany(EdgesIncomingToNodeWithChildSource); + } + + /// + /// get the edges incoming to the specified node where the source of the edge is a descendant of this cluster. + /// + /// + /// + IEnumerable EdgesIncomingToNodeWithDescendantSource(Node node) { + return node.InEdges.Concat(node.SelfEdges).Where(e => e.Source.IsDescendantOf(this)); + } + + /// + /// get the edges incoming to the specified node where the source of the edge is an immediate child of this cluster. + /// + /// + /// + IEnumerable EdgesIncomingToNodeWithChildSource(Node node) { + return node.InEdges.Concat(node.SelfEdges).Where(e => e.Source.ClusterParents.Contains(this)); + } + + /// + /// Translates the cluster and all of it's contents by the delta. + /// + internal void DeepTranslation(Point delta, bool translateEdges) { + DeepContentsTranslation(delta, translateEdges); + RectangularBoundary.TranslateRectangle(delta); + Center += delta; + } + + /// + /// + /// + public void CalculateBoundsFromChildren(double padding) { + var r = new Rectangle((from v in Nodes select v.BoundingBox).Concat( + from d in Clusters select d.BoundingBox)); + r.Pad(padding); + UpdateBoundary(r); + } + + internal void UpdateBoundary(Rectangle bounds) { + Rectangle r = bounds; + if (RectangularBoundary != null) { + r = new Rectangle( + r.Left - RectangularBoundary.LeftMargin, + r.Bottom - RectangularBoundary.BottomMargin, + r.Right + RectangularBoundary.RightMargin, + r.Top + RectangularBoundary.TopMargin); + double widthPad = (RectangularBoundary.MinWidth - r.Width)/2; + if (widthPad > 0) { + r.PadWidth(widthPad); + } + double heightPad = (RectangularBoundary.MinHeight - r.Height)/2; + if (heightPad > 0) { + r.PadHeight(heightPad); + } + RectangularBoundary.Rect = r; + } + BoundingBox = r; + } + + bool isInInitialLayoutState; + + + + /// + /// Has the cluster contents been moved or changed since an initial layout was applied? + /// + public bool IsInInitialLayoutState { + get { return isInInitialLayoutState; } + } + + + /// + /// Calculate cluster's RectangularBoundary to preserve the offsets calculated in initial layout, for example, + /// to allow for extra space required for non-shortest path edge routes or for labels. + /// + /// amount of padding between child node bounding box and expected inner bounds + public void SetInitialLayoutState(double padding) { + isInInitialLayoutState = true; + if (RectangularBoundary != null) { + RectangularBoundary.StoreDefaultMargin(); + var childBounds = + new Rectangle(from v in Nodes.Concat(Clusters) select v.BoundingBox); + childBounds.Pad(padding); + RectangularBoundary.LeftMargin = childBounds.Left - RectangularBoundary.Rect.Left; + RectangularBoundary.RightMargin = RectangularBoundary.Rect.Right - childBounds.Right; + RectangularBoundary.BottomMargin = childBounds.Bottom - RectangularBoundary.Rect.Bottom; + RectangularBoundary.TopMargin = RectangularBoundary.Rect.Top - childBounds.Top; + } + } + + /// + /// Set the initial layout state such that our current margin is stored and the new margin is taken from the given rb + /// + public void SetInitialLayoutState(RectangularClusterBoundary bounds) { + isInInitialLayoutState = true; + if (RectangularBoundary != null && bounds != null) { + RectangularBoundary.StoreDefaultMargin(); + RectangularBoundary.LeftMargin = bounds.LeftMargin; + RectangularBoundary.RightMargin = bounds.RightMargin; + RectangularBoundary.BottomMargin = bounds.BottomMargin; + RectangularBoundary.TopMargin = bounds.TopMargin; + } + } + + /// + /// sets IsInitialLayoutState to false and restores the default margins if we have a RectangularBoundary + /// + public void UnsetInitialLayoutState() { + isInInitialLayoutState = false; + RectangularClusterBoundary rb = RectangularBoundary; + if (rb != null) { + rb.RestoreDefaultMargin(); + } + } + + /// + /// Unset the initial layout state of this cluster and also all of its ancestors + /// + public void UnsetInitialLayoutStateIncludingAncestors() { + UnsetInitialLayoutState(); + foreach (Cluster c in AllClusterAncestors) { + c.UnsetInitialLayoutState(); + } + } + + /// + /// + /// + public IEnumerable AllClustersWideFirstExcludingSelf() { + var q = new Queue(); + foreach (Cluster cluster in Clusters) + q.Enqueue(cluster); + + while (q.Count > 0) { + Cluster c = q.Dequeue(); + yield return c; + foreach (Cluster cluster in c.Clusters) + q.Enqueue(cluster); + } + } + + /// + /// + /// + public IEnumerable AllClustersWidthFirstExcludingSelfAvoidingChildrenOfCollapsed() { + var q = new Queue(); + foreach (Cluster cluster in Clusters) + q.Enqueue(cluster); + + while (q.Count > 0) { + Cluster c = q.Dequeue(); + yield return c; + if (c.IsCollapsed) continue; + foreach (Cluster cluster in c.Clusters) + q.Enqueue(cluster); + } + } + + /// + /// adding a node without checking that it is a cluster + /// + /// + internal void AddNode(Node node) { + node.AddClusterParent(this); + nodes.Add(node); + } + + internal void AddCluster(Cluster cluster) { + cluster.AddClusterParent(this); + clusters.Add(cluster); + } + + internal void AddRangeOfCluster(IEnumerable clustersToAdd) { + foreach (Cluster cluster in clustersToAdd) { + cluster.AddClusterParent(this); + clusters.Add(cluster); + } + } + + /// + /// to string! + /// + /// + public override string ToString() { + return UserData != null ? UserData.ToString() : base.ToString(); + } + + internal void RaiseLayoutDoneEvent() { + if (layoutDoneEvent != null) + layoutDoneEvent(this, null); + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/NIH/MSAGL/Core/Layout/Cluster.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Layout/Cluster.cs.meta new file mode 100644 index 000000000..a025e8a31 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Layout/Cluster.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 95ef37e8d0b940e4cba062c255984b59 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Layout/CurvePort.cs b/Assets/Scripts/NIH/MSAGL/Core/Layout/CurvePort.cs new file mode 100644 index 000000000..c56a62f37 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Layout/CurvePort.cs @@ -0,0 +1,52 @@ +using Microsoft.Msagl.Core.Geometry; +using Microsoft.Msagl.Core.Geometry.Curves; + +namespace Microsoft.Msagl.Core.Layout { + /// + /// + /// + public class CurvePort:Port { + double parameter; + /// + /// constructor + /// + /// + /// + public CurvePort(ICurve curve, double parameter) { + this.curve = curve; + this.parameter = parameter; + } + + + /// + /// empty constructor + /// + public CurvePort() { } + /// + /// + /// + public double Parameter { + get { return parameter; } + set { parameter = value; } + } + ICurve curve; + /// + /// + /// + override public ICurve Curve { + get { return curve; } + set { curve = value; } + } + + /// + /// + /// + public override Point Location { +#if SHARPKIT + get { return Curve[parameter].Clone(); } +#else + get { return Curve[parameter]; } +#endif + } + } +} diff --git a/Assets/Scripts/NIH/MSAGL/Core/Layout/CurvePort.cs.meta b/Assets/Scripts/NIH/MSAGL/Core/Layout/CurvePort.cs.meta new file mode 100644 index 000000000..81de46a44 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Layout/CurvePort.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fac671dcbd35e6441999723aa2c011df +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NIH/MSAGL/Core/Layout/Edge.cs b/Assets/Scripts/NIH/MSAGL/Core/Layout/Edge.cs new file mode 100644 index 000000000..6dc319b76 --- /dev/null +++ b/Assets/Scripts/NIH/MSAGL/Core/Layout/Edge.cs @@ -0,0 +1,387 @@ +using System; +using Microsoft.Msagl.Core.Geometry; +using Microsoft.Msagl.Core.Geometry.Curves; +using System.Collections.Generic; + +namespace Microsoft.Msagl.Core.Layout { + /// + /// Edge of the graph + /// +#if TEST_MSAGL + [Serializable] +#endif +#pragma warning disable 1591 + public class Edge : GeometryObject, ILabeledObject { +#pragma warning restore 1591 + /// + /// Defines the way the edge connects to the source. + /// The member is used at the moment only when adding an edge to the graph. + /// + public Port SourcePort { + get { return EdgeGeometry.SourcePort; } + set { EdgeGeometry.SourcePort = value; } + } + + /// + /// defines the way the edge connects to the target + /// The member is used at the moment only when adding an edge to the graph. + /// + public Port TargetPort { + get { return EdgeGeometry.TargetPort; } + set { EdgeGeometry.TargetPort = value; } + } + + readonly List