lnvis

nanovg lightning network visualizer
git clone git://jb55.com/lnvis
Log | Files | Refs | README | LICENSE

commit 7bd3c8e0800954a0cec321760031df35651aec23
parent 523f7641666ede528901bf0f7fc475762a724375
Author: William Casarin <jb55@jb55.com>
Date:   Mon,  6 Aug 2018 23:16:34 -0700

add grid

will be useful for moving close things away from each other

Diffstat:
MMakefile | 2++
Agrid.c | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Agrid.h | 11+++++++++++
Aln.c | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mln.h | 17+++++++++++++++++
Mmain.c | 109++++++++++---------------------------------------------------------------------
Mrender.c | 2+-
Mupdate.c | 70+++++++++++++++++++++++++++++++++++++++++++++++-----------------------
8 files changed, 247 insertions(+), 120 deletions(-)

diff --git a/Makefile b/Makefile @@ -10,6 +10,8 @@ LDFLAGS = -lglfw -lGL SRCS = main.c SRCS += update.c +SRCS += ln.c +SRCS += grid.c SRCS += render.c SRCS += perf.c SRCS += demo.c diff --git a/grid.c b/grid.c @@ -0,0 +1,70 @@ + +#include "grid.h" +#include <string.h> +#include <assert.h> +#include <stdio.h> + + +static void remove_node_from_cell(struct cell *cell, struct node *node) { + struct node **n = NULL; + + for (int i = 0; i < cell->node_count; i++) { + n = &cell->nodes[i]; + if (node != *n) + continue; + + // shortcut: reduce node count if it's the last node + if (i == cell->node_count-1) { + cell->node_count--; + return; + } + else { + assert(i + 1 < CELL_MAX_ELEMS); + memmove(n, n + 1, + sizeof(*cell->nodes) * (cell->node_count - 1)); + cell->node_count--; + return; + } + } + + /* assert(!"expect to remove a node"); */ +} + +void update_grid_move_nodes(struct ln *ln) { + struct node *n = NULL; + struct cell *new_cell, *old_cell; + + for (u32 i = 0; i < ln->node_count; i++) { + n = &ln->nodes[i]; + + int gx = ((double)(n->x / (double)ln->window_width)) * ln->grid_div; + int gy = ((double)(n->y / (double)ln->window_height)) * ln->grid_div; + + // TODO: handle outside of grid? + if (gx > ln->grid_div || gy > ln->grid_div) + continue; + + // we've moved to a new cell + if (gx != n->grid_x || gy != n->grid_y) { + new_cell = &ln->grid[gy * ln->grid_div + gx]; + + // can't move to new cell :[ + if (new_cell->node_count == CELL_MAX_ELEMS) + continue; + + old_cell = &ln->grid[n->grid_y * ln->grid_div + n->grid_x]; + + // remove from old cell + remove_node_from_cell(old_cell, n); + + // add to new cell + n->grid_x = gx; + n->grid_y = gy; + + new_cell->nodes[new_cell->node_count++] = n; + } + } +} + + + diff --git a/grid.h b/grid.h @@ -0,0 +1,11 @@ + +#ifndef LNVIS_GRID_H +#define LNVIS_GRID_H + + +#include "ln.h" + +void update_grid_move_nodes(struct ln *ln); + + +#endif /* LNVIS_GRID_H */ diff --git a/ln.c b/ln.c @@ -0,0 +1,86 @@ + +#include "ln.h" +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +void init_ln(struct ln *ln, int grid_div) { + ln->clicked = 0; + ln->grid_div = grid_div; + ln->grid = calloc(ln->grid_div * ln->grid_div, sizeof(*ln->grid)); +} + +void free_ln(struct ln *ln) { + free(ln->grid); +} + +static double rand_0to1() { + return (double) rand() / RAND_MAX; +} + +void random_network(int ww, int wh, int max_per_node, int num_nodes, struct ln *ln) { + int i, j; + int from, to; + int tries = 0; + struct node *n; + u8 connections[num_nodes][num_nodes]; + memset(connections, 0, sizeof(connections)); + + ln->nodes = calloc(sizeof(*ln->nodes), num_nodes); + ln->channel_count = 0; + ln->node_count = num_nodes; + ln->channels = calloc(sizeof(*ln->channels), num_nodes * max_per_node); + + printf("max channels %d\n", num_nodes * max_per_node); + + for (i = 0; i < num_nodes; ++i) { + n = &ln->nodes[i]; + + n->alias = "test"; + n->color.r = rand_0to1(); + n->color.g = rand_0to1(); + n->color.b = rand_0to1(); + n->color.a = 1.0; + n->x = ww * rand_0to1(); + n->y = wh * rand_0to1(); + n->ax = 0.0; + n->ay = 0.0; + n->vx = 0.0; + n->vy = 0.0; + n->size = 10; + } + + // connect nodes randomly + for (i = 0; i < num_nodes; ++i) { + + from = i; + skip: + // for each node, it can have 0 to max_per_node connections + for (j = 0; j < rand() % max_per_node; ++j) { + do { + tries++; + // if connections are way higher than the + // possible number of nodes for some reason, + // we'll need an escape hatch + if (tries > 5) { + tries = 0; + goto skip; + } + to = rand() % num_nodes; + } + while(connections[from][to]); + tries = 0; + + connections[from][to] = 1; + + struct channel *c = + &ln->channels[ln->channel_count++]; + + c->nodes[0] = &ln->nodes[from]; + c->nodes[1] = &ln->nodes[to]; + + } + + // keep trying until we find on that isn't already connected + } +} diff --git a/ln.h b/ln.h @@ -5,6 +5,8 @@ #include "nanovg/nanovg.h" #include "defs.h" +#define CELL_MAX_ELEMS 32 + struct node { const char *alias; const char *id; // pubkey @@ -16,6 +18,7 @@ struct node { }; } color; + int grid_x, grid_y; double x, y; double vx, vy; double ax, ay; @@ -71,12 +74,22 @@ struct channel { u32 last_update; }; +struct cell { + struct node *nodes[CELL_MAX_ELEMS]; + int node_count; +}; + struct ln { NVGcontext *vg; int clicked; int mdown; double mx, my; + int window_width; + int window_height; + + int grid_div; + struct cell *grid; struct node *drag_target; @@ -87,4 +100,8 @@ struct ln { u32 channel_count; }; +void init_ln(struct ln *ln, int grid_div); +void free_ln(struct ln *ln); +void random_network(int ww, int wh, int max_per_node, int num_nodes, struct ln *ln); + #endif /* LNVIS_LN_H */ diff --git a/main.c b/main.c @@ -36,11 +36,6 @@ static void key(GLFWwindow *window, int key, int scancode, int action, int mods) premult = !premult; } - -static double rand_0to1() { - return (double) rand() / RAND_MAX; -} - static double mx, my; static int mdown = 0; static int mclicked = 0; @@ -66,69 +61,6 @@ void mouse_click(GLFWwindow *win, int button, int action, int mods) } -void random_network(int ww, int wh, int max_per_node, int num_nodes, struct ln *ln) { - int i, j; - int from, to; - int tries = 0; - struct node *n; - u8 connections[num_nodes][num_nodes]; - memset(connections, 0, sizeof(connections)); - - ln->nodes = malloc(sizeof(*ln->nodes) * num_nodes); - ln->channel_count = 0; - ln->node_count = num_nodes; - ln->channels = malloc(sizeof(*ln->channels) * num_nodes * max_per_node); - - printf("max channels %d\n", num_nodes * max_per_node); - - for (i = 0; i < num_nodes; ++i) { - n = &ln->nodes[i]; - - n->alias = "test"; - n->color.r = rand_0to1(); - n->color.g = rand_0to1(); - n->color.b = rand_0to1(); - n->color.a = 1.0; - n->x = ww * rand_0to1(); - n->y = wh * rand_0to1(); - n->ax = 0.0; - n->ay = 0.0; - n->vx = 0.0; - n->vy = 0.0; - n->size = 10; - } - - // connect nodes randomly - for (i = 0; i < num_nodes; ++i) { - - from = i; - skip: - // for each node, it can have 0 to max_per_node connections - for (j = 0; j < rand() % max_per_node; ++j) { - do { - tries++; - if (tries > 5) { - tries = 0; - goto skip; - } - to = rand() % num_nodes; - } - while(connections[from][to]); - tries = 0; - - connections[from][to] = 1; - - struct channel *c = - &ln->channels[ln->channel_count++]; - - c->nodes[0] = &ln->nodes[from]; - c->nodes[1] = &ln->nodes[to]; - - } - - // keep trying until we find on that isn't already connected - } -} int main() { @@ -138,31 +70,12 @@ int main() NVGcontext *vg = NULL; PerfGraph fps; double prevt = 0; - struct ln ln; - /* struct node test_nodes[] = { */ - /* { */ - /* .alias = "@jb55", */ - /* .color = { { 1.0, 0, 0, 1.0 } }, */ - /* }, */ - /* { */ - /* .alias = "SuperHub", */ - /* .color = { { 0.0, 1.0, 0.0, 1.0 } }, */ - /* }, */ - /* { */ - /* .alias = "satoshis.place", */ - /* .color = { { 0.0, 0.5, 1.0, 1.0 } }, */ - /* }, */ - /* }; */ - - /* struct channel test_channels[] = { */ - /* { */ - /* .nodes = { &test_nodes[0], &test_nodes[1] }, */ - /* }, */ - /* { */ - /* .nodes = { &test_nodes[1], &test_nodes[2] }, */ - /* } */ - /* }; */ + // ln collision grid subdivision + // cells = grid_div * grid_div + int grid_div = 20; + + struct ln ln; if (!glfwInit()) { printf("Failed to init GLFW."); @@ -179,7 +92,7 @@ int main() glfwWindowHint(GLFW_SAMPLES, 4); #endif - window = glfwCreateWindow(1000, 600, "NanoVG", NULL, NULL); + window = glfwCreateWindow(1000, 600, "Lightning Network Vis", NULL, NULL); // window = glfwCreateWindow(1000, 600, "NanoVG", glfwGetPrimaryMonitor(), NULL); if (!window) { glfwTerminate(); @@ -207,7 +120,7 @@ int main() } ln.vg = vg; - ln.clicked = 0; + init_ln(&ln, grid_div); if (loadDemoData(vg, &data) == -1) return -1; @@ -243,6 +156,8 @@ int main() glfwGetCursorPos(window, &mx, &my); glfwGetWindowSize(window, &winWidth, &winHeight); + ln.window_height = winHeight; + ln.window_width = winWidth; if (first) { random_network(winWidth, winHeight, 3, 50, &ln); @@ -260,8 +175,9 @@ int main() if (premult) glClearColor(0, 0, 0, 0); else // base16-onedark bg color ;) - glClearColor(0x28 / 255.0, 0x2c / 255.0, 0x34 / 255.0, - 1.0f); + glClearColor(0x28 / 255.0, 0x2c / 255.0, 0x34 / 255.0, 1.0f); + /* glClearColor(1.0f, 1.0f, 1.0f, 1.0f); */ + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); @@ -278,6 +194,7 @@ int main() glfwPollEvents(); } + free_ln(&ln); freeDemoData(vg, &data); nvgDeleteGL2(vg); diff --git a/render.c b/render.c @@ -59,7 +59,7 @@ void draw_node(NVGcontext *vg, struct node *node) node->color.a); NVGcolor blend = - nvgRGBAf(0, 0, 0, 0.5); + nvgRGBAf(0, 0, 0, 1.0); nvgSave(vg); nvgTranslate(vg, node->x, node->y); diff --git a/update.c b/update.c @@ -1,5 +1,6 @@ #include "ln.h" +#include "grid.h" #include <math.h> #include <assert.h> #include <stdio.h> @@ -23,30 +24,9 @@ struct node *hit_node(struct ln *ln) { return NULL; } -// force graph update logic -void update(struct ln *ln, double dt) -{ - u32 i; - static const double friction_coeff = 0.03; - static const double friction = 1.0 - friction_coeff; - - // click detection - if (ln->clicked) { - struct node *hit = hit_node(ln); - ln->drag_target = hit; - } - - // stop dragging - if (!ln->mdown && ln->drag_target) - ln->drag_target = NULL; - // drag - if (ln->mdown && ln->drag_target) { - ln->drag_target->x = ln->mx; - ln->drag_target->y = ln->my; - ln->drag_target->vx = 0; - ln->drag_target->vy = 0; - } +void force_graph(struct ln *ln, double dt) { + u32 i; // channel constraints for (i = 0; i < ln->channel_count; ++i) { @@ -62,21 +42,29 @@ void update(struct ln *ln, double dt) static const double mindist = 100.0; if (d < mindist) { + + // normalized vector between two nodes double nx = dx / d; double ny = dy / d; + // get the distance past the minimum distance along + // the vector between the two nodes double nnx = (d - mindist) * nx; double nny = (d - mindist) * ny; if (isnan(nnx) || isnan(nny)) continue; + // normalize by frame time double mx = nnx * dt; double my = nny * dt; + // correct for the distance by accelerating the node away + // from the other node n1->vx += mx; n1->vy += my; + // do the same in the opposite direction for the other node n2->vx -= mx; n2->vy -= my; } @@ -90,7 +78,14 @@ void update(struct ln *ln, double dt) n2->vy += -dy * scale * dt; } } +} + + +void physics(struct ln *ln, double dt) { + static const double friction_coeff = 0.03; + static const double friction = 1.0 - friction_coeff; + // physics for (u32 i = 0; i < ln->node_count; i++) { struct node *node = &ln->nodes[i]; @@ -109,5 +104,34 @@ void update(struct ln *ln, double dt) node->x += node->vx * dt; node->y += node->vy * dt; } +} + + + +// force graph update logic +void update(struct ln *ln, double dt) +{ + // click detection + if (ln->clicked) { + struct node *hit = hit_node(ln); + ln->drag_target = hit; + } + + // stop dragging + if (!ln->mdown && ln->drag_target) + ln->drag_target = NULL; + + // drag + if (ln->mdown && ln->drag_target) { + ln->drag_target->x = ln->mx; + ln->drag_target->y = ln->my; + ln->drag_target->vx = 0; + ln->drag_target->vy = 0; + } + + force_graph(ln, dt); + + physics(ln, dt); + update_grid_move_nodes(ln); }