Skip to content

Commit 470c0fa

Browse files
committed
Continue PathFinding3n implementation
1 parent 44e2bd5 commit 470c0fa

File tree

1 file changed

+102
-46
lines changed

1 file changed

+102
-46
lines changed

Sources/GameMath/3D Types (New)/Pathfinding3n.swift

Lines changed: 102 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -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

67100
extension 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

167212
extension 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

209261
extension 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

Comments
 (0)