Repository navigation
Expand file tree
/
Copy pathGraph.lua
More file actions
302 lines (260 loc) · 9.33 KB
/
Copy pathGraph.lua
File metadata and controls
302 lines (260 loc) · 9.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
local current = (...):gsub('%.[^%.]+$', '');
-- ------------------------------------------------
-- Required Modules
-- ------------------------------------------------
local Node = require(current .. '.Node');
local Edge = require(current .. '.Edge');
-- ------------------------------------------------
-- Module
-- ------------------------------------------------
local Graph = {};
function Graph.new()
local self = {};
local nodes = {}; -- Contains all nodes in the graph.
local edges = {}; -- Contains all edges in the graph.
local edgeIDs = 0; -- Used to create a unique ID for new edges.
local minX, maxX, minY, maxY; -- The boundaries of the graph.
-- ------------------------------------------------
-- Local Functions
-- ------------------------------------------------
---
-- (Re-)Sets the graph's boundaries to nil.
--
local function resetBoundaries()
minX, maxX, minY, maxY = nil, nil, nil, nil;
end
---
-- Updates the boundaries of the graph.
-- This represents the rectangular area in which all nodes are contained.
-- @param nx - The new x position to check.
-- @param ny - The new y position to check.
--
local function updateBoundaries( nx, ny )
return math.min( minX or nx, nx ), math.max( maxX or nx, nx ), math.min( minY or ny, ny ), math.max( maxY or ny, ny );
end
---
-- Adds a new edge between two nodes.
-- @param origin - The node from which the edge originates.
-- @param target - The node to which the edge is pointing to.
--
local function addEdge( origin, target )
for _, edge in pairs( edges ) do
if edge.origin == origin and edge.target == target then
error "Trying to connect nodes which are already connected.";
end
end
assert( origin ~= target, "Tried to connect a node with itself." );
edges[edgeIDs] = Edge.new( edgeIDs, origin, target );
edgeIDs = edgeIDs + 1;
end
-- ------------------------------------------------
-- Public Functions
-- ------------------------------------------------
---
-- Adds a node to the graph.
-- @param id - The ID will be used to reference the Node inside of the graph.
-- @param x - The x coordinate the Node should be spawned at (optional).
-- @param y - The y coordinate the Node should be spawned at (optional).
-- @param anchor - Wether the node should be locked in place or not (optional).
-- @param ... - Additional parameters (useful when a custom Node class is used).
--
function self:addNode( id, name, x, y, anchor, ... )
assert( not nodes[id], "Node IDs must be unique." );
nodes[id] = Node.new( id, name, x, y, anchor, ... );
return nodes[id];
end
---
-- Removes a node from the graph.
-- This will also remove all edges pointing to, or originating from this
-- node.
-- @param node - The node to remove from the graph.
--
function self:removeNode( node )
nodes[node:getID()] = nil;
self:removeEdges( node );
end
---
-- Creates a graph from a .tgf formatted file.
-- @param path (string) The path to the .tgf file to load.
-- @param x (number) The x coordinate the nodes should be spawned at (optional).
-- @param y (number) The y coordinate the nodes should be spawned at (optional).
--
function self:loadTGF( path, x, y )
local dx = x or 0;
local dy = y or 0;
local n = {};
local e = {};
local target = n;
for line in io.lines( path ) do
-- '#' marks the definitions for edges in the .tgf file.
if line == '#' then
target = e;
else
target[#target + 1] = line;
end
end
for _, line in ipairs( n ) do
local tmp = {}
for part in line:gmatch( '[^%s]+' ) do
tmp[#tmp + 1] = part;
end
-- Add a slight random variation to the spawn coordinates to kick start
-- the physics simulation.
local rx = love.math.random( 2, 5 );
local ry = love.math.random( 2, 5 );
self:addNode( tmp[1], tmp[2], dx + rx, dy + ry, tmp[1] == '1' );
end
for _, line in ipairs( e ) do
local tmp = {}
for part in line:gmatch( '[^%s]+' ) do
tmp[#tmp + 1] = part;
end
self:connectIDs( tmp[1], tmp[2] );
end
end
---
-- Adds a new edge between two nodes.
-- @param origin - The node from which the edge originates.
-- @param target - The node to which the edge is pointing to.
--
function self:connectNodes( origin, target )
addEdge( origin, target );
end
---
-- Adds a new edge between two nodes referenced by their IDs.
-- @param origin - The node id from which the edge originates.
-- @param target - The node id to which the edge is pointing to.
--
function self:connectIDs( originID, targetID )
assert( nodes[originID], string.format( "Tried to add an Edge to the nonexistent Node \"%s\".", originID ));
assert( nodes[targetID], string.format( "Tried to add an Edge to the nonexistent Node \"%s\".", targetID ));
addEdge( nodes[originID], nodes[targetID] );
end
---
-- Removes all edges leading to, or originating from a node.
-- @param node - The node to remove all edges from.
--
function self:removeEdges( node )
for id, edge in pairs( edges ) do
if edge.origin == node or edge.target == node then
edges[id] = nil;
end
end
end
---
-- Updates the graph.
-- @param dt - The delta time between frames.
-- @param nodeCallback - A callback called on every node (optional).
-- @param edgeCallback - A callback called on every edge (optional).
--
function self:update( dt, nodeCallback, edgeCallback )
for _, edge in pairs( edges ) do
edge.origin:attractTo( edge.target );
edge.target:attractTo( edge.origin );
if edgeCallback then
edgeCallback( edge );
end
end
resetBoundaries();
for _, nodeA in pairs( nodes ) do
if not nodeA:isAnchor() then
for _, nodeB in pairs( nodes ) do
if nodeA ~= nodeB then
nodeA:repelFrom( nodeB );
end
end
nodeA:move( dt );
end
if nodeCallback then
nodeCallback( nodeA );
end
minX, maxX, minY, maxY = updateBoundaries( nodeA:getPosition() );
end
end
---
-- Draws the graph.
-- Takes two callback functions as a parameter. These will be called
-- on each edge and node in the graph and will be used to wite a custom
-- drawing function.
-- @param nodeCallback - A callback called on every node.
-- @param edgeCallback - A callback called on every edge.
--
function self:draw( nodeCallback, edgeCallback )
for _, edge in pairs( edges ) do
if not edgeCallback then break end
edgeCallback( edge );
end
for _, node in pairs( nodes ) do
if not nodeCallback then break end
nodeCallback( node );
end
end
---
-- Checks if a certain Node ID already exists.
-- @param id - The id to check for.
--
function self:hasNode( id )
return nodes[id] ~= nil;
end
---
-- Returns the node the id is pointing to.
-- @param id - The id to check for.
--
function self:getNode( id )
return nodes[id];
end
---
-- Gets a node at a certain point in the graph.
-- @param x - The x coordinate to check.
-- @param y - The y coordinate to check.
-- @param range - The range in which to check around the given coordinates.
--
function self:getNodeAt(x, y, range)
for _, node in pairs( nodes ) do
local nx, ny = node:getPosition();
if x < nx + range and x > nx - range and y < ny + range and y > ny - range then
return node;
end
end
end
---
-- Returns the graph's minimum and maxmimum x and y values.
--
function self:getBoundaries()
return minX, maxX, minY, maxY;
end
---
-- Returns the x and y coordinates of the graph's center.
--
function self:getCenter()
return ( maxX - minX ) * 0.5 + minX, ( maxY - minY ) * 0.5 + minY;
end
---
-- Turn a node into an anchor.
-- Anchored nodes have fixed positions and can't be moved by the physical
-- forces.
-- @param id - The node's id.
-- @param x - The x coordinate to anchor the node to.
-- @param y - The y coordinate to anchor the node to.
--
function self:setAnchor( id, x, y )
nodes[id]:setPosition( x, y );
nodes[id]:setAnchor( true );
end
return self;
end
---
-- Replaces the default Edge class with a custom one.
-- @param class - The custom Edge class to use.
--
function Graph.setEdgeClass( class )
Edge = class;
end
---
-- Replaces the default Node class with a custom one.
-- @param class - The custom Node class to use.
--
function Graph.setNodeClass( class )
Node = class;
end
return Graph;