@@ -15,14 +15,23 @@ public struct PathFinding3n<Scalar: Vector3n.ScalarType & FloatingPoint & Compar
1515 @usableFromInline
1616 internal var graph : OrderedSet < Node >
1717
18+ /// A distance value checked against each individual axis (x,y,z) of each node. Nodes within the distance are linked together in the graph.
19+ /// - note: This value is only used when inserting a new position into the graph
1820 public var linkDistance : Scalar
1921
22+ /// A closure to fine tune graph node linking. Return `true` if the two nodes should be linked.
23+ public typealias LinkValidator = ( Position3n < Scalar > , Position3n < Scalar > ) -> Bool
24+ /// A closure to fine tune graph node linking. Return `true` if the two nodes should be linked.
25+ /// - note: This closure is only used when inserting a new position into the graph, and is only consulted if the new position passes the linkDistance check.
26+ public var linkValidator : LinkValidator ? = nil
27+
2028 /**
2129 Build an empty PathFinding3 graph, to be populated later.
2230 - parameter manhatanDistance: A distance value checked against each axis (x,y,z) of each node. Nodes within the distance are linked together in the graph
2331 */
24- public init ( linkDistance manhatanDistance: Scalar ) {
32+ public init ( linkDistance manhatanDistance: Scalar , validatingBy validator : LinkValidator ? = nil ) {
2533 self . linkDistance = manhatanDistance
34+ self . linkValidator = validator
2635 self . graph = [ ]
2736 }
2837
@@ -31,47 +40,77 @@ public struct PathFinding3n<Scalar: Vector3n.ScalarType & FloatingPoint & Compar
3140 - parameter positions: The unique unordered positions of the nodes
3241 - parameter manhatanDistance: A distance value checked against each axis (x,y,z) of each node. Nodes within the distance are linked together in the graph
3342 */
34- public init ( positions: [ Position3n < Scalar > ] , linkedWithin manhatanDistance: Scalar ) {
35- self . init ( linkDistance: manhatanDistance)
36-
43+ public init ( positions: [ Position3n < Scalar > ] , linkedWithin manhatanDistance: Scalar , validatingBy validator : LinkValidator ? = nil ) {
44+ self . init ( linkDistance: manhatanDistance, validatingBy : validator )
45+ self . graph . reserveCapacity ( positions . count )
3746 for position in positions {
3847 self . insert ( position)
3948 }
4049 }
4150
51+ /**
52+ Adds a position to the node graph returning the node index.
53+
54+ This function filters duplicate nodes, so inserting the same position multiple times will not break the graph.
55+ - parameter position: The position of the node to be created.
56+ - returns: The graph index of the inserted node.
57+ */
4258 @discardableResult
4359 public mutating func insert( _ position: Position3n < Scalar > ) -> Index {
44- var node1 = Node ( position: position)
45- let result = graph. append ( node1)
46- let node1Index = result. index
47- if result. inserted {
48- for node2Index in self . indices {
60+ var node1 = Node ( position: position, children: [ ] )
61+ let insertionResult = graph. append ( node1)
62+ let node1Index = insertionResult. index
63+ // If the node is new, check for linking
64+ if insertionResult. inserted {
65+ var node1Children = node1. children
66+ for node2Index in self . graph. indices {
4967 guard node2Index != node1Index else { continue }
5068
51- var node2 = self [ node2Index]
52- guard Swift . abs ( node1. position. x - node2. position. x) <= linkDistance else { continue }
53- guard Swift . abs ( node1. position. y - node2. position. y) <= linkDistance else { continue }
54- guard Swift . abs ( node1. position. z - node2. position. z) <= linkDistance else { continue }
69+ let node2 = self . graph [ node2Index]
70+
71+ if Swift . abs ( node1. position. x - node2. position. x) > linkDistance { continue }
72+ if Swift . abs ( node1. position. y - node2. position. y) > linkDistance { continue }
73+ if Swift . abs ( node1. position. z - node2. position. z) > linkDistance { continue }
74+
75+ let shouldLink : Bool = self . linkValidator ? ( node1. position, node2. position) ?? true
5576
56- let distance = node1. position. distance ( from: node2. position)
57- node1. children. insert ( . init( index: node2Index, distance: distance) )
58- node2. children. insert ( . init( index: node1Index, distance: distance) )
59- self . graph. update ( node2, at: node2Index)
77+ if shouldLink {
78+ let distance = node1. position. distance ( from: node2. position)
79+
80+ node1Children. insert ( . init( index: node2Index, distance: distance) )
81+
82+ var node2Children = node2. children
83+ node2Children. insert ( . init( index: node1Index, distance: distance) )
84+ let updatedNode2 : Node = . init( position: node2. position, children: node2Children)
85+ self . graph. update ( updatedNode2, at: node2Index)
86+ }
6087 }
88+ node1 = . init( position: node1. position, children: node1Children)
6189 self . graph. update ( node1, at: node1Index)
6290 }
6391 return node1Index
6492 }
93+
94+ /// Returns the children of the graph node at `index`
95+ public func children( for index: Index ) -> Set < Index > {
96+ return Set ( self . graph [ index] . children. map ( \. index) )
97+ }
6598}
6699
67100extension PathFinding3n {
68- public func nearestNodes( to position: Position3n < Scalar > , withinRadius: Scalar ? = nil ) -> [ Index ] {
101+ /**
102+ Finds nodes within the provided raidus.
103+ - parameter position: The position to compare to each no in the node graph.
104+ - parameter radius: An optional minimum distance between `position` and any node position that is required for a match. This distance is direct, not manhatan.
105+ - returns: An array of indicies for nodes near `position` sorted by nearest to farthest.
106+ */
107+ public func nearestNodes( to position: Position3n < Scalar > , withinRadius radius: Scalar ? = nil ) -> [ Index ] {
69108 let sortNodes : [ ( index: Index , distance: Scalar ) ] = self . indices. map ( { index in
70- let node = self [ index]
109+ let node = self . graph [ index]
71110 return ( index: index, distance: node. position. distance ( from: position) )
72111 } ) . sorted ( by: { $0. distance < $1. distance} )
73112 return sortNodes. compactMap ( {
74- if let radius = withinRadius {
113+ if let radius {
75114 if $0. distance > radius {
76115 return nil
77116 }
@@ -80,12 +119,18 @@ extension PathFinding3n {
80119 } )
81120 }
82121
83- public func path( from startIndex: Index , to goalIndex: Index ) -> [ Index ] ? {
122+ /**
123+ Attempts to trace a path through the node graph.
124+ - parameter startIndex: The index of the graph node to begin the path trace. Use `nearestNodes(to: _, withinRadius:_)` to find an index for this variable.
125+ - parameter goalIndex: The index of the graph node to end the path trace. Use `nearestNodes(to: _, withinRadius:_)` to find an index for this variable.
126+ - returns: An array of node graph indicies for nodes that create a path from and including `startIndex` to and including `goalIndex`, or `nil` if no path could be found.
127+ */
128+ public func tracePath( from startIndex: Index , to goalIndex: Index ) -> [ Index ] ? {
84129 precondition ( self . indices. contains ( startIndex) , " Index out of range. " )
85130 precondition ( self . indices. contains ( goalIndex) , " Index out of range. " )
86131
87132 // The node we want to reach
88- let goalNode = self [ goalIndex]
133+ let goalNode = self . graph [ goalIndex]
89134
90135 // A collection of pairs where the key is the desired node,
91136 // and the value is the previous node traveled (its parent in the path)
@@ -121,13 +166,13 @@ extension PathFinding3n {
121166 // mark current node as visited
122167 visited. insert ( currentPathNode. index)
123168
124- let currentGraphNode = self [ currentPathNode. index]
169+ let currentGraphNode = self . graph [ currentPathNode. index]
125170
126171 for child in currentGraphNode. children {
127172 // Don't check already visited nodes
128173 guard visited. contains ( child. index) == false else { continue }
129174
130- let currentGraphNodeChild = self [ child. index]
175+ let currentGraphNodeChild = self . graph [ child. index]
131176
132177 let gCost = gScores [ currentPathNode. index, default: . infinity] + child. distance
133178 let gCostChild = gScores [ child. index, default: . infinity]
@@ -165,32 +210,39 @@ extension PathFinding3n {
165210}
166211
167212extension PathFinding3n {
168- public struct Node : Equatable , Hashable {
169- public let position : Position3n < Scalar >
170- public var children : Set < Child >
171- public struct Child : Equatable , Hashable {
172- public let index : Index
173- public let distance : Scalar
174-
175- public init ( index: Index , distance: Scalar ) {
213+ @usableFromInline
214+ struct Node : Equatable , Hashable {
215+ @usableFromInline
216+ let position : Position3n < Scalar >
217+ @usableFromInline
218+ let children : Set < Child >
219+ @usableFromInline
220+ struct Child : Equatable , Hashable {
221+ @usableFromInline
222+ let index : Index
223+ @usableFromInline
224+ let distance : Scalar
225+ @usableFromInline
226+ init ( index: Index , distance: Scalar ) {
176227 self . index = index
177228 self . distance = distance
178229 }
179-
180- public static func == ( lhs: Self , rhs: Self ) -> Bool {
230+ @ usableFromInline
231+ static func == ( lhs: Self , rhs: Self ) -> Bool {
181232 return lhs. index == rhs. index
182233 }
183- public func hash( into hasher: inout Hasher ) {
234+ @usableFromInline
235+ func hash( into hasher: inout Hasher ) {
184236 hasher. combine ( index)
185237 }
186238 }
187-
188- public init ( position: Position3n < Scalar > , children: Set < Child > = [ ] ) {
239+ @ usableFromInline
240+ init ( position: Position3n < Scalar > , children: Set < Child > ) {
189241 self . position = position
190242 self . children = children
191243 }
192-
193- public static func == ( lhs: Self , rhs: Self ) -> Bool {
244+ @ usableFromInline
245+ static func == ( lhs: Self , rhs: Self ) -> Bool {
194246 return lhs. position == rhs. position
195247 }
196248 }
@@ -208,25 +260,29 @@ extension PathFinding3n {
208260
209261extension PathFinding3n : RandomAccessCollection {
210262 public typealias Index = Int
211- public typealias Element = Node
263+ public typealias Element = Position3n < Scalar >
212264
213265 @inlinable
214266 public var startIndex : Index {
215- return 0
267+ nonmutating get {
268+ return 0
269+ }
216270 }
217271
218272 @inlinable
219273 public var endIndex : Index {
220- if graph. isEmpty {
221- return startIndex
274+ nonmutating get {
275+ if graph. isEmpty {
276+ return startIndex
277+ }
278+ return graph. count
222279 }
223- return graph. count
224280 }
225281
226282 @inlinable
227- public subscript( index: Index ) -> Node {
283+ public subscript( index: Index ) -> Position3n < Scalar > {
228284 nonmutating get {
229- return graph [ index]
285+ return graph [ index] . position
230286 }
231287 }
232288}
0 commit comments