Skip to content

Commit 1314628

Browse files
authored
Merge pull request #36 from selimanac/main
latest
2 parents d97afee + eea5f5a commit 1314628

10 files changed

Lines changed: 122 additions & 64 deletions

File tree

NAVMESH_API.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ local cell_id, center_x, center_y = pathfinder.navmesh_cell_at_position(x, y)
303303
- `y` (number): Y coordinate of position to query (typically Z in 3D)
304304

305305
**Returns:**
306-
- `cell_id` (number): ID of cell containing position, or special value if not found
306+
- `cell_id` (number): ID of cell containing position, or `pathfinder.INVALID_ID` (UINT32_MAX) if not found
307307
- `center_x` (number): X coordinate of cell center
308308
- `center_y` (number): Y coordinate of cell center
309309

@@ -319,7 +319,7 @@ Uses the spatial grid index for O(1) average lookup, then performs point-in-poly
319319
function validate_spawn_point(self, x, z)
320320
local cell_id, center_x, center_y = pathfinder.navmesh_cell_at_position(x, z)
321321

322-
if cell_id ~= pathfinder.INVALID_CELL_ID then
322+
if cell_id ~= pathfinder.INVALID_ID then
323323
print("Position is walkable, in cell", cell_id)
324324
print("Cell center:", center_x, center_y)
325325
return true

README.md

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,25 @@
55
A high-performance A* pathfinding library written in C++11, designed for real-time games and simulations with hundreds to thousands of moving objects. Built with a focus on performance, using flat array data structures and advanced caching mechanisms.
66

77

8+
89
## Discussions & Release Notes
910

1011
https://github.com/selimanac/defold-graph-pathfinder/discussions
1112

1213
## Documentation
1314

14-
For detailed function descriptions and usage examples, see the [API Reference](./API.md).
15+
For detailed function descriptions and usage examples:
16+
17+
- [Path API Reference](./API.md)
18+
- [Navmesh API Reference](./NAVMESH_API.md)
19+
20+
## Graph Pathfinder Editor
21+
22+
A visual editor for creating and editing navigation graphs for the Defold Graph Pathfinder extension.
23+
24+
https://github.com/selimanac/defold-graph-pathfinder-editor
25+
26+
![Defold Graph Pathfinder](https://raw.githubusercontent.com/selimanac/defold-graph-pathfinder-editor/refs/heads/main/.github/editor.jpg)
1527

1628
## Installation
1729

@@ -31,10 +43,11 @@ This library consists of three parts:
3143
The core library. Responsible for managing nodes, edges, and performing pathfinding.
3244

3345
✅ Projected Pathfinding
46+
✅ Static Navmesh Pathfinding
3447
✅ Min-Heap Priority Queue
3548
✅ Path Caching
3649
✅ Distance Caching
37-
❌ Smoothed Path Caching — not planned, maybe in the future
50+
3851

3952
### Path Smoothing
4053

example/navmesh.collection

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@ embedded_instances {
9090
"}\n"
9191
""
9292
position {
93-
y: 3.643508
93+
x: -2.335069
94+
z: -6.027575
9495
}
9596
}
9697
embedded_instances {
@@ -112,7 +113,7 @@ embedded_instances {
112113
"}\n"
113114
""
114115
position {
115-
y: 1.898955
116+
z: 8.216292
116117
}
117118
}
118119
embedded_instances {

example/scripts/basic.script

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,22 @@
77
-- - Projected pathfinding
88

99
-- Variables for pathfinding results
10-
local path_length = 0
11-
local status = 0
12-
local status_text = ""
13-
local entry_point = vmath.vector3()
14-
local exit_point = vmath.vector3()
15-
local path = {}
10+
local path_length = 0
11+
local status = 0
12+
local status_text = ""
13+
local entry_point = vmath.vector3()
14+
local exit_point = vmath.vector3()
15+
local path = {}
1616

1717
-- Node identifiers for pathfinding
18-
local start_id = 0
19-
local goal_id = 0
18+
local start_id = 0
19+
local goal_id = 0
20+
21+
local start_position = vmath.vector3(55, 45, 0)
22+
local goal_position = vmath.vector3(355, 345, 0)
2023

2124
-- Path smoothing configuration using Bezier quadratic curves
22-
local smooth_config = {
25+
local smooth_config = {
2326
style = pathfinder.PathSmoothStyle.BEZIER_QUADRATIC,
2427
bezier_sample_segment = 8, -- Number of segments per curve
2528
bezier_curve_radius = 0.8 -- For BEZIER_QUADRATIC style
@@ -60,6 +63,7 @@ function init(self)
6063
-- Find a path from the first node to the last node
6164
start_id = nodes[1]
6265
goal_id = nodes[4]
66+
6367
path_length, status, status_text, path = pathfinder.find_node_to_node(start_id, goal_id, 8)
6468

6569
if status == pathfinder.PathStatus.SUCCESS then
@@ -99,7 +103,7 @@ function init(self)
99103
-- Example 4: Projected pathfinding without smoothing
100104
-- Find a path from an arbitrary point (55, 45) to the goal node
101105
-- The pathfinder will create a temporary entry point on the graph
102-
path_length, status, status_text, entry_point, path = pathfinder.find_projected_to_node(55, 45, goal_id, 8)
106+
path_length, status, status_text, entry_point, path = pathfinder.find_projected_to_node(start_position.x, start_position.y, goal_id, 8)
103107

104108
if status == pathfinder.PathStatus.SUCCESS then
105109
print("Entry point:", entry_point)
@@ -127,7 +131,7 @@ function init(self)
127131

128132
-- Example 6: Node to projected position pathfinding with smoothing
129133
-- Find a path from a node to an arbitrary position (not on graph)
130-
path_length, status, status_text, exit_point, path = pathfinder.find_node_to_projected(start_id, 55, 45, 32, smooth_id)
134+
path_length, status, status_text, exit_point, path = pathfinder.find_node_to_projected(start_id, start_position.x, start_position.y, 32, smooth_id)
131135

132136
if status == pathfinder.PathStatus.SUCCESS then
133137
print("Exit point:", exit_point)
@@ -142,7 +146,7 @@ function init(self)
142146

143147
-- Example 7: Projected to projected pathfinding with smoothing
144148
-- Find a path between two arbitrary positions (both not on graph)
145-
path_length, status, status_text, entry_point, exit_point, path = pathfinder.find_projected_to_projected(55, 45, 355, 345, 32, smooth_id)
149+
path_length, status, status_text, entry_point, exit_point, path = pathfinder.find_projected_to_projected(start_position.x, start_position.y, goal_position.x, goal_position.y, 32, smooth_id)
146150

147151
if status == pathfinder.PathStatus.SUCCESS then
148152
print("Entry point:", entry_point)

example/scripts/example.script

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,9 @@ end
163163

164164
function on_input(self, action_id, action)
165165
-- Track mouse position for projected pathfinding
166-
mouse_position.x = action.x
167-
mouse_position.y = action.y
168-
go.set_position(mouse_position, "/mouse")
166+
if action.x then
167+
mouse_position.x = action.x
168+
mouse_position.y = action.y
169+
go.set_position(mouse_position, "/mouse")
170+
end
169171
end

example/scripts/navmesh.script

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,37 @@ local shift_pressed = false
1919
--==========================================================
2020
-- FUNCTIONS
2121
--==========================================================
22+
local function validate_spawn_point(x, y)
23+
local cell_id, center_x, center_y = pathfinder.navmesh_cell_at_position(x, y)
24+
25+
if cell_id ~= pathfinder.INVALID_ID then
26+
print("Position is walkable, in cell", cell_id)
27+
print("Cell center:", center_x, center_y)
28+
return true
29+
else
30+
print("Position is not walkable (outside navmesh): ", x, y)
31+
return false
32+
end
33+
end
34+
2235
function init(self)
2336
msg.post(".", "acquire_input_focus")
2437

2538
local navmesh_buffer = resource.get_buffer(self.navmesh_buffer)
2639
cam = msg.url("/camera#camera")
2740
data.path_smoothing_id = pathfinder.add_path_smoothing(const.SMOOTHING_CONFIG)
2841

29-
pathfinder.navmesh_init(600, 6, 32, 16, 256, 5, 10, 500, false)
42+
pathfinder.navmesh_init(300, 6, 32, 32, 64, 5, 10, 500, true)
3043
pathfinder.navmesh_set_buffer(navmesh_buffer)
3144
spatial_index_grid = pathfinder.navmesh_get_spatial_index()
45+
46+
start_position = go.get_position("/path_start")
47+
goal_position = go.get_position("/path_end")
48+
49+
50+
-- Example 8: Validate Cell at position
51+
validate_spawn_point(start_position.x, start_position.z)
52+
validate_spawn_point(100, 100)
3253
end
3354

3455
function update(self, dt)
@@ -45,6 +66,7 @@ function on_input(self, action_id, action)
4566
mouse_position.y = action.y
4667
end
4768

69+
-- Hold LEFT SHIFT for camera and rotate with RIGHT MOUSE BUTTON
4870
if action_id == const.TRIGGERS.LEFT_SHIFT then
4971
if action.pressed then
5072
shift_pressed = true
@@ -56,14 +78,17 @@ function on_input(self, action_id, action)
5678
if not shift_pressed then
5779
if not action.pressed then return end
5880

59-
if action_id == const.TRIGGERS.MOUSE_BUTTON_LEFT then
81+
if action_id == const.TRIGGERS.MOUSE_BUTTON_LEFT then -- LEFT CLICK for start position
6082
start_position = utils.screen_to_plane(cam, action.screen_x, action.screen_y)
6183
go.set_position(start_position, "/path_start")
62-
elseif action_id == const.TRIGGERS.MOUSE_BUTTON_RIGHT then
84+
validate_spawn_point(start_position.x, start_position.z)
85+
elseif action_id == const.TRIGGERS.MOUSE_BUTTON_RIGHT then -- RIGHT CLICK for goal position
6386
goal_position = utils.screen_to_plane(cam, action.screen_x, action.screen_y)
6487
go.set_position(goal_position, "/path_end")
88+
validate_spawn_point(goal_position.x, goal_position.z)
6589
end
6690

91+
-- Press SPACE to add agent
6792
if action_id == const.TRIGGERS.SPACE and action.pressed then
6893
agents.add(start_position, goal_position)
6994
end

graph_pathfinder/annotations.lua

Lines changed: 36 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,42 @@
1-
---@diagnostic disable: missing-return, unused-local
2-
3-
---@meta pathfinder
41
---Defold Graph Pathfinder Extension
52
---High-performance A* pathfinding library for real-time games and simulations.
63
---@class pathfinder
4+
pathfinder = {
5+
INVALID_ID = 0xFFFFFFFF, -- Invalid ID constant for cells and nodes (UINT32_MAX)
6+
7+
---PathStatus enum - Status codes for pathfinding operations
8+
---@enum PathStatus
9+
PathStatus = {
10+
SUCCESS = 0, -- Operation completed successfully
11+
SUCCESS_START_FALLBACK = 1, -- Success, but start position used fallback to nearest cell (navmesh only)
12+
SUCCESS_GOAL_FALLBACK = 2, -- Success, but goal position used fallback to nearest cell (navmesh only)
13+
ERROR_NO_PATH = -1, -- No valid path found between start and goal nodes
14+
ERROR_START_GOAL_NODE_SAME = -12, -- Start node ID and goal node ID are the same
15+
ERROR_START_NODE_INVALID = -2, -- Invalid or inactive start node ID
16+
ERROR_GOAL_NODE_INVALID = -3, -- Invalid or inactive goal node ID
17+
ERROR_START_NOT_IN_CELL = -13, -- Start position not in any cell, fallback disabled (navmesh only)
18+
ERROR_GOAL_NOT_IN_CELL = -14, -- Goal position not in any cell, fallback disabled (navmesh only)
19+
ERROR_NODE_FULL = -4, -- Node capacity reached, cannot add more nodes
20+
ERROR_EDGE_FULL = -5, -- Edge capacity reached, cannot add more edges
21+
ERROR_HEAP_FULL = -6, -- Heap pool exhausted during pathfinding
22+
ERROR_PATH_TOO_LONG = -7, -- Path exceeds maximum allowed length
23+
ERROR_GRAPH_CHANGED = -8, -- Graph modified during pathfinding (retrying)
24+
ERROR_GRAPH_CHANGED_TOO_OFTEN = -11, -- Graph changed too often during pathfinding
25+
ERROR_NO_PROJECTION = -9, -- Cannot project point onto graph (no edges exist)
26+
ERROR_VIRTUAL_NODE_FAILED = -10 -- Failed to create or connect virtual node
27+
},
28+
29+
---PathSmoothStyle enum - Path smoothing algorithms
30+
---@enum PathSmoothStyle
31+
PathSmoothStyle = {
32+
NONE = 0, -- No smoothing (angular paths, fastest)
33+
CATMULL_ROM = 1, -- Passes through all waypoints with smooth curves
34+
BEZIER_CUBIC = 2, -- Very smooth curves with two control points
35+
BEZIER_QUADRATIC = 3, -- Corner-only smoothing (recommended)
36+
BEZIER_ADAPTIVE = 4, -- Adaptive corner smoothing (highly customizable)
37+
CIRCULAR_ARC = 5 -- Perfect circular arcs (best for tile-based games)
38+
}
39+
}
740

841
---@class PathNode
942
---@field x number X coordinate of the node position
@@ -25,39 +58,6 @@
2558
---@field bezier_adaptive_max_corner_distance number Maximum corner distance for BEZIER_ADAPTIVE (default: 50.0)
2659
---@field bezier_arc_radius number Arc radius for CIRCULAR_ARC style (default: 60.0)
2760

28-
---PathStatus enum - Status codes for pathfinding operations
29-
---@enum PathStatus
30-
pathfinder.PathStatus = {
31-
SUCCESS = 0, -- Operation completed successfully
32-
SUCCESS_START_FALLBACK = 1, -- Success, but start position used fallback to nearest cell (navmesh only)
33-
SUCCESS_GOAL_FALLBACK = 2, -- Success, but goal position used fallback to nearest cell (navmesh only)
34-
ERROR_NO_PATH = -1, -- No valid path found between start and goal nodes
35-
ERROR_START_GOAL_NODE_SAME = -12, -- Start node ID and goal node ID are the same
36-
ERROR_START_NODE_INVALID = -2, -- Invalid or inactive start node ID
37-
ERROR_GOAL_NODE_INVALID = -3, -- Invalid or inactive goal node ID
38-
ERROR_START_NOT_IN_CELL = -13, -- Start position not in any cell, fallback disabled (navmesh only)
39-
ERROR_GOAL_NOT_IN_CELL = -14, -- Goal position not in any cell, fallback disabled (navmesh only)
40-
ERROR_NODE_FULL = -4, -- Node capacity reached, cannot add more nodes
41-
ERROR_EDGE_FULL = -5, -- Edge capacity reached, cannot add more edges
42-
ERROR_HEAP_FULL = -6, -- Heap pool exhausted during pathfinding
43-
ERROR_PATH_TOO_LONG = -7, -- Path exceeds maximum allowed length
44-
ERROR_GRAPH_CHANGED = -8, -- Graph modified during pathfinding (retrying)
45-
ERROR_GRAPH_CHANGED_TOO_OFTEN = -11, -- Graph changed too often during pathfinding
46-
ERROR_NO_PROJECTION = -9, -- Cannot project point onto graph (no edges exist)
47-
ERROR_VIRTUAL_NODE_FAILED = -10 -- Failed to create or connect virtual node
48-
}
49-
50-
---PathSmoothStyle enum - Path smoothing algorithms
51-
---@enum PathSmoothStyle
52-
pathfinder.PathSmoothStyle = {
53-
NONE = 0, -- No smoothing (angular paths, fastest)
54-
CATMULL_ROM = 1, -- Passes through all waypoints with smooth curves
55-
BEZIER_CUBIC = 2, -- Very smooth curves with two control points
56-
BEZIER_QUADRATIC = 3, -- Corner-only smoothing (recommended)
57-
BEZIER_ADAPTIVE = 4, -- Adaptive corner smoothing (highly customizable)
58-
CIRCULAR_ARC = 5 -- Perfect circular arcs (best for tile-based games)
59-
}
60-
6161
---Initialize the pathfinding system. Must be called before any other pathfinding operations.
6262
---@param max_nodes number Maximum number of nodes in the graph
6363
---@param max_gameobject_nodes number|nil Maximum number of game object nodes (optional, default: 0)
@@ -305,5 +305,3 @@ function pathfinder.navmesh_get_spatial_index() end
305305
---Get comprehensive statistics about navmesh pathfinding caches.
306306
---@return table stats Table containing cache statistics with fields: path_cache, distance_cache
307307
function pathfinder.navmesh_get_stats() end
308-
309-
return pathfinder

graph_pathfinder/include/pathfinder_extension.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ namespace pathfinder
3636
// Update
3737
void set_update_state(bool state);
3838
void set_update_frequency(uint8_t update_frequency);
39+
void set_max_time_step(float max_time_step);
3940
void update();
4041

4142
// Smooth

graph_pathfinder/src/pathfinder.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1491,6 +1491,12 @@ static void LuaInit(lua_State* L)
14911491
// Register module functions
14921492
luaL_register(L, MODULE_NAME, Module_methods);
14931493

1494+
// ----------------------------
1495+
// INVALID_ID constant
1496+
// ----------------------------
1497+
lua_pushnumber(L, (lua_Number)pathfinder::INVALID_ID);
1498+
lua_setfield(L, -2, "INVALID_ID");
1499+
14941500
// ----------------------------
14951501
// PathStatus enum
14961502
// ----------------------------
@@ -1548,9 +1554,11 @@ static dmExtension::Result AppInitializeGraphPathfinder(dmExtension::AppParams*
15481554
{
15491555
dmLogInfo("AppInitializeGraphPathfinder");
15501556
uint8_t update_frequency = dmConfigFile::GetInt(params->m_ConfigFile, "display.update_frequency", 0);
1557+
float max_time_step = dmConfigFile::GetFloat(params->m_ConfigFile, "engine.max_time_step", 1.0f / 30);
15511558

15521559
pathfinder::extension::init();
15531560
pathfinder::extension::set_update_frequency(update_frequency);
1561+
pathfinder::extension::set_max_time_step(max_time_step);
15541562

15551563
return dmExtension::RESULT_OK;
15561564
}

graph_pathfinder/src/pathfinder_extension.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ namespace pathfinder
5555
// Update
5656
//==========================================================
5757
static uint8_t m_UpdateFrequency;
58+
static float m_MaxTimeStep;
5859
static uint64_t m_PreviousFrameTime;
5960
static float m_AccumFrameTime;
6061
static bool m_UpdateLoopState = true;
@@ -70,9 +71,9 @@ namespace pathfinder
7071
float frame_dt = (float)(frame_time / 1000000.0);
7172

7273
// Never allow for large hitches
73-
if (frame_dt > 0.5f)
74+
if (frame_dt > m_MaxTimeStep)
7475
{
75-
frame_dt = 0.5f;
76+
frame_dt = m_MaxTimeStep;
7677
}
7778

7879
// Variable frame rate
@@ -222,6 +223,11 @@ namespace pathfinder
222223
m_UpdateLoopState = state;
223224
}
224225

226+
void set_max_time_step(float max_time_step)
227+
{
228+
m_MaxTimeStep = max_time_step;
229+
};
230+
225231
void set_update_frequency(uint8_t update_frequency)
226232
{
227233
m_UpdateFrequency = update_frequency;

0 commit comments

Comments
 (0)