1 /** 2 An implementation of A* to find the shortest paths among connected points in space. 3 4 Copyright: 5 Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. 6 Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) 7 Copyright (c) 2017-2018 Godot-D contributors 8 9 License: $(LINK2 https://opensource.org/licenses/MIT, MIT License) 10 11 12 */ 13 module godot.astar; 14 import std.meta : AliasSeq, staticIndexOf; 15 import std.traits : Unqual; 16 import godot.d.traits; 17 import godot.core; 18 import godot.c; 19 import godot.d.bind; 20 import godot.d.reference; 21 import godot.globalenums; 22 import godot.object; 23 import godot.classdb; 24 import godot.reference; 25 /** 26 An implementation of A* to find the shortest paths among connected points in space. 27 28 A* (A star) is a computer algorithm that is widely used in pathfinding and graph traversal, the process of plotting short paths among vertices (points), passing through a given set of edges (segments). It enjoys widespread use due to its performance and accuracy. Godot's A* implementation uses points in three-dimensional space and Euclidean distances by default. 29 You must add points manually with $(D addPoint) and create segments manually with $(D connectPoints). Then you can test if there is a path between two points with the $(D arePointsConnected) function, get a path containing indices by $(D getIdPath), or one containing actual coordinates with $(D getPointPath). 30 It is also possible to use non-Euclidean distances. To do so, create a class that extends `AStar` and override methods $(D _computeCost) and $(D _estimateCost). Both take two indices and return a length, as is shown in the following example. 31 32 33 class MyAStar: 34 extends AStar 35 36 func _compute_cost(u, v): 37 return abs(u - v) 38 39 func _estimate_cost(u, v): 40 return min(0, abs(u - v) - 1) 41 42 43 $(D _estimateCost) should return a lower bound of the distance, i.e. `_estimate_cost(u, v) <= _compute_cost(u, v)`. This serves as a hint to the algorithm because the custom `_compute_cost` might be computation-heavy. If this is not the case, make $(D _estimateCost) return the same value as $(D _computeCost) to provide the algorithm with the most accurate information. 44 If the default $(D _estimateCost) and $(D _computeCost) methods are used, or if the supplied $(D _estimateCost) method returns a lower bound of the cost, then the paths returned by A* will be the lowest-cost paths. Here, the cost of a path equals the sum of the $(D _computeCost) results of all segments in the path multiplied by the `weight_scale`s of the endpoints of the respective segments. If the default methods are used and the `weight_scale`s of all points are set to `1.0`, then this equals the sum of Euclidean distances of all segments in the path. 45 */ 46 @GodotBaseClass struct AStar 47 { 48 package(godot) enum string _GODOT_internal_name = "AStar"; 49 public: 50 @nogc nothrow: 51 union { /** */ godot_object _godot_object; /** */ Reference _GODOT_base; } 52 alias _GODOT_base this; 53 alias BaseClasses = AliasSeq!(typeof(_GODOT_base), typeof(_GODOT_base).BaseClasses); 54 package(godot) __gshared bool _classBindingInitialized = false; 55 package(godot) static struct GDNativeClassBinding 56 { 57 __gshared: 58 @GodotName("_compute_cost") GodotMethod!(double, long, long) _computeCost; 59 @GodotName("_estimate_cost") GodotMethod!(double, long, long) _estimateCost; 60 @GodotName("add_point") GodotMethod!(void, long, Vector3, double) addPoint; 61 @GodotName("are_points_connected") GodotMethod!(bool, long, long, bool) arePointsConnected; 62 @GodotName("clear") GodotMethod!(void) clear; 63 @GodotName("connect_points") GodotMethod!(void, long, long, bool) connectPoints; 64 @GodotName("disconnect_points") GodotMethod!(void, long, long, bool) disconnectPoints; 65 @GodotName("get_available_point_id") GodotMethod!(long) getAvailablePointId; 66 @GodotName("get_closest_point") GodotMethod!(long, Vector3, bool) getClosestPoint; 67 @GodotName("get_closest_position_in_segment") GodotMethod!(Vector3, Vector3) getClosestPositionInSegment; 68 @GodotName("get_id_path") GodotMethod!(PoolIntArray, long, long) getIdPath; 69 @GodotName("get_point_capacity") GodotMethod!(long) getPointCapacity; 70 @GodotName("get_point_connections") GodotMethod!(PoolIntArray, long) getPointConnections; 71 @GodotName("get_point_count") GodotMethod!(long) getPointCount; 72 @GodotName("get_point_path") GodotMethod!(PoolVector3Array, long, long) getPointPath; 73 @GodotName("get_point_position") GodotMethod!(Vector3, long) getPointPosition; 74 @GodotName("get_point_weight_scale") GodotMethod!(double, long) getPointWeightScale; 75 @GodotName("get_points") GodotMethod!(Array) getPoints; 76 @GodotName("has_point") GodotMethod!(bool, long) hasPoint; 77 @GodotName("is_point_disabled") GodotMethod!(bool, long) isPointDisabled; 78 @GodotName("remove_point") GodotMethod!(void, long) removePoint; 79 @GodotName("reserve_space") GodotMethod!(void, long) reserveSpace; 80 @GodotName("set_point_disabled") GodotMethod!(void, long, bool) setPointDisabled; 81 @GodotName("set_point_position") GodotMethod!(void, long, Vector3) setPointPosition; 82 @GodotName("set_point_weight_scale") GodotMethod!(void, long, double) setPointWeightScale; 83 } 84 /// 85 pragma(inline, true) bool opEquals(in AStar other) const 86 { return _godot_object.ptr is other._godot_object.ptr; } 87 /// 88 pragma(inline, true) typeof(null) opAssign(typeof(null) n) 89 { _godot_object.ptr = n; return null; } 90 /// 91 pragma(inline, true) bool opEquals(typeof(null) n) const 92 { return _godot_object.ptr is n; } 93 /// 94 size_t toHash() const @trusted { return cast(size_t)_godot_object.ptr; } 95 mixin baseCasts; 96 /// Construct a new instance of AStar. 97 /// Note: use `memnew!AStar` instead. 98 static AStar _new() 99 { 100 static godot_class_constructor constructor; 101 if(constructor is null) constructor = _godot_api.godot_get_class_constructor("AStar"); 102 if(constructor is null) return typeof(this).init; 103 return cast(AStar)(constructor()); 104 } 105 @disable new(size_t s); 106 /** 107 Called when computing the cost between two connected points. 108 Note that this function is hidden in the default `AStar` class. 109 */ 110 double _computeCost(in long from_id, in long to_id) 111 { 112 Array _GODOT_args = Array.make(); 113 _GODOT_args.append(from_id); 114 _GODOT_args.append(to_id); 115 String _GODOT_method_name = String("_compute_cost"); 116 return this.callv(_GODOT_method_name, _GODOT_args).as!(RefOrT!double); 117 } 118 /** 119 Called when estimating the cost between a point and the path's ending point. 120 Note that this function is hidden in the default `AStar` class. 121 */ 122 double _estimateCost(in long from_id, in long to_id) 123 { 124 Array _GODOT_args = Array.make(); 125 _GODOT_args.append(from_id); 126 _GODOT_args.append(to_id); 127 String _GODOT_method_name = String("_estimate_cost"); 128 return this.callv(_GODOT_method_name, _GODOT_args).as!(RefOrT!double); 129 } 130 /** 131 Adds a new point at the given position with the given identifier. The `id` must be 0 or larger, and the `weight_scale` must be 1 or larger. 132 The `weight_scale` is multiplied by the result of $(D _computeCost) when determining the overall cost of traveling across a segment from a neighboring point to this point. Thus, all else being equal, the algorithm prefers points with lower `weight_scale`s to form a path. 133 134 135 var astar = AStar.new() 136 astar.add_point(1, Vector3(1, 0, 0), 4) # Adds the point (1, 0, 0) with weight_scale 4 and id 1 137 138 139 If there already exists a point for the given `id`, its position and weight scale are updated to the given values. 140 */ 141 void addPoint(in long id, in Vector3 position, in double weight_scale = 1) 142 { 143 checkClassBinding!(typeof(this))(); 144 ptrcall!(void)(GDNativeClassBinding.addPoint, _godot_object, id, position, weight_scale); 145 } 146 /** 147 Returns whether the two given points are directly connected by a segment. If `bidirectional` is `false`, returns whether movement from `id` to `to_id` is possible through this segment. 148 */ 149 bool arePointsConnected(in long id, in long to_id, in bool bidirectional = true) const 150 { 151 checkClassBinding!(typeof(this))(); 152 return ptrcall!(bool)(GDNativeClassBinding.arePointsConnected, _godot_object, id, to_id, bidirectional); 153 } 154 /** 155 Clears all the points and segments. 156 */ 157 void clear() 158 { 159 checkClassBinding!(typeof(this))(); 160 ptrcall!(void)(GDNativeClassBinding.clear, _godot_object); 161 } 162 /** 163 Creates a segment between the given points. If `bidirectional` is `false`, only movement from `id` to `to_id` is allowed, not the reverse direction. 164 165 166 var astar = AStar.new() 167 astar.add_point(1, Vector3(1, 1, 0)) 168 astar.add_point(2, Vector3(0, 5, 0)) 169 astar.connect_points(1, 2, false) 170 171 172 */ 173 void connectPoints(in long id, in long to_id, in bool bidirectional = true) 174 { 175 checkClassBinding!(typeof(this))(); 176 ptrcall!(void)(GDNativeClassBinding.connectPoints, _godot_object, id, to_id, bidirectional); 177 } 178 /** 179 Deletes the segment between the given points. If `bidirectional` is `false`, only movement from `id` to `to_id` is prevented, and a unidirectional segment possibly remains. 180 */ 181 void disconnectPoints(in long id, in long to_id, in bool bidirectional = true) 182 { 183 checkClassBinding!(typeof(this))(); 184 ptrcall!(void)(GDNativeClassBinding.disconnectPoints, _godot_object, id, to_id, bidirectional); 185 } 186 /** 187 Returns the next available point ID with no point associated to it. 188 */ 189 long getAvailablePointId() const 190 { 191 checkClassBinding!(typeof(this))(); 192 return ptrcall!(long)(GDNativeClassBinding.getAvailablePointId, _godot_object); 193 } 194 /** 195 Returns the ID of the closest point to `to_position`, optionally taking disabled points into account. Returns `-1` if there are no points in the points pool. 196 $(B Note:) If several points are the closest to `to_position`, the one with the smallest ID will be returned, ensuring a deterministic result. 197 */ 198 long getClosestPoint(in Vector3 to_position, in bool include_disabled = false) const 199 { 200 checkClassBinding!(typeof(this))(); 201 return ptrcall!(long)(GDNativeClassBinding.getClosestPoint, _godot_object, to_position, include_disabled); 202 } 203 /** 204 Returns the closest position to `to_position` that resides inside a segment between two connected points. 205 206 207 var astar = AStar.new() 208 astar.add_point(1, Vector3(0, 0, 0)) 209 astar.add_point(2, Vector3(0, 5, 0)) 210 astar.connect_points(1, 2) 211 var res = astar.get_closest_position_in_segment(Vector3(3, 3, 0)) # Returns (0, 3, 0) 212 213 214 The result is in the segment that goes from `y = 0` to `y = 5`. It's the closest position in the segment to the given point. 215 */ 216 Vector3 getClosestPositionInSegment(in Vector3 to_position) const 217 { 218 checkClassBinding!(typeof(this))(); 219 return ptrcall!(Vector3)(GDNativeClassBinding.getClosestPositionInSegment, _godot_object, to_position); 220 } 221 /** 222 Returns an array with the IDs of the points that form the path found by AStar between the given points. The array is ordered from the starting point to the ending point of the path. 223 224 225 var astar = AStar.new() 226 astar.add_point(1, Vector3(0, 0, 0)) 227 astar.add_point(2, Vector3(0, 1, 0), 1) # Default weight is 1 228 astar.add_point(3, Vector3(1, 1, 0)) 229 astar.add_point(4, Vector3(2, 0, 0)) 230 231 astar.connect_points(1, 2, false) 232 astar.connect_points(2, 3, false) 233 astar.connect_points(4, 3, false) 234 astar.connect_points(1, 4, false) 235 236 var res = astar.get_id_path(1, 3) # Returns $(D 1, 2, 3) 237 238 239 If you change the 2nd point's weight to 3, then the result will be `$(D 1, 4, 3)` instead, because now even though the distance is longer, it's "easier" to get through point 4 than through point 2. 240 */ 241 PoolIntArray getIdPath(in long from_id, in long to_id) 242 { 243 checkClassBinding!(typeof(this))(); 244 return ptrcall!(PoolIntArray)(GDNativeClassBinding.getIdPath, _godot_object, from_id, to_id); 245 } 246 /** 247 Returns the capacity of the structure backing the points, useful in conjunction with `reserve_space`. 248 */ 249 long getPointCapacity() const 250 { 251 checkClassBinding!(typeof(this))(); 252 return ptrcall!(long)(GDNativeClassBinding.getPointCapacity, _godot_object); 253 } 254 /** 255 Returns an array with the IDs of the points that form the connection with the given point. 256 257 258 var astar = AStar.new() 259 astar.add_point(1, Vector3(0, 0, 0)) 260 astar.add_point(2, Vector3(0, 1, 0)) 261 astar.add_point(3, Vector3(1, 1, 0)) 262 astar.add_point(4, Vector3(2, 0, 0)) 263 264 astar.connect_points(1, 2, true) 265 astar.connect_points(1, 3, true) 266 267 var neighbors = astar.get_point_connections(1) # Returns $(D 2, 3) 268 269 270 */ 271 PoolIntArray getPointConnections(in long id) 272 { 273 checkClassBinding!(typeof(this))(); 274 return ptrcall!(PoolIntArray)(GDNativeClassBinding.getPointConnections, _godot_object, id); 275 } 276 /** 277 Returns the number of points currently in the points pool. 278 */ 279 long getPointCount() const 280 { 281 checkClassBinding!(typeof(this))(); 282 return ptrcall!(long)(GDNativeClassBinding.getPointCount, _godot_object); 283 } 284 /** 285 Returns an array with the points that are in the path found by AStar between the given points. The array is ordered from the starting point to the ending point of the path. 286 $(B Note:) This method is not thread-safe. If called from a $(D Thread), it will return an empty $(D PoolVector3Array) and will print an error message. 287 */ 288 PoolVector3Array getPointPath(in long from_id, in long to_id) 289 { 290 checkClassBinding!(typeof(this))(); 291 return ptrcall!(PoolVector3Array)(GDNativeClassBinding.getPointPath, _godot_object, from_id, to_id); 292 } 293 /** 294 Returns the position of the point associated with the given `id`. 295 */ 296 Vector3 getPointPosition(in long id) const 297 { 298 checkClassBinding!(typeof(this))(); 299 return ptrcall!(Vector3)(GDNativeClassBinding.getPointPosition, _godot_object, id); 300 } 301 /** 302 Returns the weight scale of the point associated with the given `id`. 303 */ 304 double getPointWeightScale(in long id) const 305 { 306 checkClassBinding!(typeof(this))(); 307 return ptrcall!(double)(GDNativeClassBinding.getPointWeightScale, _godot_object, id); 308 } 309 /** 310 Returns an array of all points. 311 */ 312 Array getPoints() 313 { 314 checkClassBinding!(typeof(this))(); 315 return ptrcall!(Array)(GDNativeClassBinding.getPoints, _godot_object); 316 } 317 /** 318 Returns whether a point associated with the given `id` exists. 319 */ 320 bool hasPoint(in long id) const 321 { 322 checkClassBinding!(typeof(this))(); 323 return ptrcall!(bool)(GDNativeClassBinding.hasPoint, _godot_object, id); 324 } 325 /** 326 Returns whether a point is disabled or not for pathfinding. By default, all points are enabled. 327 */ 328 bool isPointDisabled(in long id) const 329 { 330 checkClassBinding!(typeof(this))(); 331 return ptrcall!(bool)(GDNativeClassBinding.isPointDisabled, _godot_object, id); 332 } 333 /** 334 Removes the point associated with the given `id` from the points pool. 335 */ 336 void removePoint(in long id) 337 { 338 checkClassBinding!(typeof(this))(); 339 ptrcall!(void)(GDNativeClassBinding.removePoint, _godot_object, id); 340 } 341 /** 342 Reserves space internally for `num_nodes` points, useful if you're adding a known large number of points at once, for a grid for instance. New capacity must be greater or equals to old capacity. 343 */ 344 void reserveSpace(in long num_nodes) 345 { 346 checkClassBinding!(typeof(this))(); 347 ptrcall!(void)(GDNativeClassBinding.reserveSpace, _godot_object, num_nodes); 348 } 349 /** 350 Disables or enables the specified point for pathfinding. Useful for making a temporary obstacle. 351 */ 352 void setPointDisabled(in long id, in bool disabled = true) 353 { 354 checkClassBinding!(typeof(this))(); 355 ptrcall!(void)(GDNativeClassBinding.setPointDisabled, _godot_object, id, disabled); 356 } 357 /** 358 Sets the `position` for the point with the given `id`. 359 */ 360 void setPointPosition(in long id, in Vector3 position) 361 { 362 checkClassBinding!(typeof(this))(); 363 ptrcall!(void)(GDNativeClassBinding.setPointPosition, _godot_object, id, position); 364 } 365 /** 366 Sets the `weight_scale` for the point with the given `id`. The `weight_scale` is multiplied by the result of $(D _computeCost) when determining the overall cost of traveling across a segment from a neighboring point to this point. 367 */ 368 void setPointWeightScale(in long id, in double weight_scale) 369 { 370 checkClassBinding!(typeof(this))(); 371 ptrcall!(void)(GDNativeClassBinding.setPointWeightScale, _godot_object, id, weight_scale); 372 } 373 }