diff --git a/Edge.lua b/Edge.lua new file mode 100644 index 0000000..0024c73 --- /dev/null +++ b/Edge.lua @@ -0,0 +1,13 @@ +local Edge = {}; + +function Edge.new( id, origin, target ) + local self = {}; + + self.id = id; + self.origin = origin; + self.target = target; + + return self; +end + +return Edge; diff --git a/Graph.lua b/Graph.lua new file mode 100644 index 0000000..19e67fd --- /dev/null +++ b/Graph.lua @@ -0,0 +1,78 @@ +local Node = require('Node'); +local Edge = require('Edge'); + +local Graph = {}; + +-- TODO remove / replace LÖVE functions +function Graph.new() + local self = {}; + + -- Node Objects are stored in a table using their ID as an index. + local nodes = {}; + local edges = {}; + local edgeIDs = 0; + + local center = Node.new( 'center ', love.graphics.getWidth() * 0.5, love.graphics.getHeight() * 0.5); + + function self:init( table ) + math.randomseed(120123091239581834213143141); + + for _, id in pairs( table.nodes ) do + self:addNode( Node.new( id, math.random(100, 800), math.random(100, 400) ) ); + end + + for _, edge in pairs( table.edges ) do + self:addEdge( nodes[edge[1]], nodes[edge[2]] ); + end + end + + function self:addNode( node ) + nodes[node:getID()] = node; + end + + function self:addEdge( origin, target ) + edges[edgeIDs] = Edge.new( edgeIDs, origin, target ); + edgeIDs = edgeIDs + 1; + end + + function self:update( dt, ... ) + for _, edge in pairs( edges ) do + edge.origin:attractTo( edge.target ); + edge.target:attractTo( edge.origin ); + end + + for _, nodeA in pairs( nodes ) do + nodeA:attractTo( center ); + for _, nodeB in pairs( nodes ) do + if nodeA ~= nodeB then + nodeA:repelFrom( nodeB ); + end + end + nodeA:move( dt ); + end + end + + function self:draw() + for _, edge in pairs( edges ) do + love.graphics.line( edge.origin:getX(), edge.origin:getY(), edge.target:getX(), edge.target:getY() ); + end + + for _, nodeA in pairs( nodes ) do + love.graphics.points( nodeA:getX(), nodeA:getY() ); + love.graphics.print( nodeA:getID(), nodeA:getX(), nodeA:getY() ); + end + end + + 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 + + return self; +end + +return Graph; diff --git a/Node.lua b/Node.lua new file mode 100644 index 0000000..42c08c9 --- /dev/null +++ b/Node.lua @@ -0,0 +1,100 @@ +local Node = {}; + +local FORCE_SPRING = -0.01; +local FORCE_CHARGE = 100000; + +local FORCE_MAX = 4; +local NODE_SPEED = 8; +local DAMPING_FACTOR = 0.95; + +--- +-- @param id - A unique id which will be used to reference this node. +-- +function Node.new( id, x, y ) + local self = {}; + + local px, py = x or 0, y or 0; + local ax, ay = 0, 0; + local vx, vy = 0, 0; + + --- + -- Clamps a value to a certain range. + -- @param min + -- @param val + -- @param max + -- + local function clamp( min, val, max ) + return math.max( min, math.min( val, max ) ); + end + + --- + -- Calculates the new xy-acceleration for this node. + -- The values are clamped to keep the graph from "exploding". + -- @param fx - The force to apply in x-direction. + -- @param fy - The force to apply in y-direction. + -- + local function applyForce( fx, fy ) + ax = clamp( -FORCE_MAX, ax + fx, FORCE_MAX ); + ay = clamp( -FORCE_MAX, ay + fy, FORCE_MAX ); + end + + function self:attractTo( node ) + local dx, dy = px - node:getX(), py - node:getY(); + local distance = math.sqrt(dx * dx + dy * dy); + dx = dx / distance; + dy = dy / distance; + + local strength = FORCE_SPRING * distance; + applyForce( dx * strength, dy * strength ); + end + + function self:repelFrom( node ) + local dx, dy = px - node:getX(), py - node:getY(); + local distance = math.sqrt(dx * dx + dy * dy); + dx = dx / distance; + dy = dy / distance; + + local mass = 0.015 * (0 + math.log(math.max(24, 0))); + local strength = FORCE_CHARGE * ((mass) / (distance * distance)); + applyForce(dx * strength, dy * strength); + end + + --- + -- Update the node's position based on the calculated velocity and + -- acceleration. + -- + function self:move( dt ) + vx = (vx + ax * dt * NODE_SPEED) * DAMPING_FACTOR; + vy = (vy + ay * dt * NODE_SPEED) * DAMPING_FACTOR; + px = px + vx; + py = py + vy; + ax, ay = 0, 0; + end + + --- + -- Returns the node's unique identifier. + -- + function self:getID() + return id; + end + + function self:getX() + return px; + end + + function self:getY() + return py; + end + + function self:getPosition() + return px, py; + end + + function self:setPosition( nx, ny ) + px, py = nx, ny; + end + + return self; +end + +return Node;