code wiki / _hdl_build / nx_viz_force.nx
nx_viz_force.nx source
↩ module page · 91 lines · 3904 B
1// nx_viz_force.nx -- the FORCE layer of the sovereign Nishi viz library (the d3-force equivalent, bits-up).
2// Fruchterman-Reingold force-directed graph layout, pure integer/fixed-point: repulsion between ALL node pairs
3// (k^2/d), attraction along edges (d^2/k), temperature cooling. Mutates xs/ys in place; DETERMINISTIC (no RNG).
4// Turns node+edge data (genealogy / WMS streams / memory link-graph) into a laid-out graph. k = ideal edge
5// length = isqrt(W*H/n). Closes layout-force. license_tier: ORIGINAL
6import "nx_viz_trig.nx"
7import "nx_syscalls.nx"
8const K_MAGIC_32768: i64 = 32768
9const K_MAGIC_100000000: i64 = 100000000
10
11// grav = centering pull (per-mille of offset-from-centre, added each tick) -- bounds the layout + draws isolated
12// nodes inward. grav=0 -> pure Fruchterman-Reingold (the gate tests that). Positions are NOT bound-clamped (that
13// pins nodes to walls); the caller fits the result to a frame afterwards.
14func vf_layout(n: i64, xs: *i64, ys: *i64, m: i64, ea: *i64, eb: *i64, W: i64, H: i64, iters: i64, grav: i64) -> i64 {
15 if n <= 0 { return 0 }
16 let k: i64 = vs_isqrt((W * H) / n)
17 let k2: i64 = k * k
18 let gcx: i64 = W / 2
19 let gcy: i64 = H / 2
20 let dispx: *i64 = sys_mmap(K_MAGIC_32768) as *i64
21 let dispy: *i64 = sys_mmap(K_MAGIC_32768) as *i64
22 var it: i64 = 0
23 while it < iters {
24 var temp: i64 = (W / 10) * (iters - it) / iters
25 if temp < 1 { temp = 1 }
26 var z: i64 = 0
27 while z < n { dispx[z] = 0; dispy[z] = 0; z = z + 1 }
28 // repulsion: all ordered pairs (i != j)
29 var i: i64 = 0
30 while i < n {
31 var j: i64 = 0
32 while j < n {
33 if i != j {
34 var dx: i64 = xs[i] - xs[j]
35 var dy: i64 = ys[i] - ys[j]
36 var d2: i64 = dx * dx + dy * dy
37 if d2 < 1 { d2 = 1; dx = 1 }
38 dispx[i] = dispx[i] + (dx * k2) / d2
39 dispy[i] = dispy[i] + (dy * k2) / d2
40 }
41 j = j + 1
42 }
43 i = i + 1
44 }
45 // attraction: along edges
46 var e: i64 = 0
47 while e < m {
48 let a: i64 = ea[e]
49 let b: i64 = eb[e]
50 var dx: i64 = xs[a] - xs[b]
51 var dy: i64 = ys[a] - ys[b]
52 var d2: i64 = dx * dx + dy * dy
53 if d2 < 1 { d2 = 1 }
54 let d: i64 = vs_isqrt(d2)
55 let fx: i64 = (dx * d) / k
56 let fy: i64 = (dy * d) / k
57 dispx[a] = dispx[a] - fx; dispy[a] = dispy[a] - fy
58 dispx[b] = dispx[b] + fx; dispy[b] = dispy[b] + fy
59 e = e + 1
60 }
61 // gravity: pull each node toward centre (bounds the layout, centres isolated nodes)
62 var gi: i64 = 0
63 while gi < n {
64 dispx[gi] = dispx[gi] + ((gcx - xs[gi]) * grav) / 1000
65 dispy[gi] = dispy[gi] + ((gcy - ys[gi]) * grav) / 1000
66 gi = gi + 1
67 }
68 // move each node, capped by temperature (preserve direction); no bound clamp (caller fits to frame)
69 var p: i64 = 0
70 while p < n {
71 var mx: i64 = dispx[p]
72 var my: i64 = dispy[p]
73 // overflow guard: halve both proportionally until magnitudes are square-safe
74 var amx: i64 = mx; if amx < 0 { amx = 0 - amx }
75 var amy: i64 = my; if amy < 0 { amy = 0 - amy }
76 var big: i64 = amx; if amy > big { big = amy }
77 while big > K_MAGIC_100000000 { mx = mx / 2; my = my / 2; big = big / 2 }
78 let mlen: i64 = vs_isqrt(mx * mx + my * my)
79 if mlen > temp {
80 mx = (mx * temp) / mlen
81 my = (my * temp) / mlen
82 }
83 xs[p] = xs[p] + mx
84 ys[p] = ys[p] + my
85 p = p + 1
86 }
87 it = it + 1
88 }
89 return k
90}
91func main() -> i64 { return 0 }