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