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 }