nx_treegen.nx source
↩ module page · 531 lines · 26774 B
1// nx_treegen.nx -- ★SOVEREIGN INFINIGEN: procedural VEGETATION (trees). A seed grows a UNIQUE tree by recursive
2// branching (an L-system-style growth stack): trunk -> branches -> sub-branches -> leaf clusters, with
3// phototropism (upward bias) + seeded angle/length/count variation. Branches = tapering CAPSULES, leaves = green
4// spheres. Self-contained SDF + integer ray-march with a bounding-sphere early-reject (so hundreds of parts stay
5// fast). No ML, no assets. Different seed -> different tree. license_tier: ORIGINAL
6import "nx_syscalls.nx"
7import "nx_itrig.nx"
8import "nx_vecmath.nx"
9const TG_MAGIC_6364136223846793005: i64 = 6364136223846793005
10const TG_MAGIC_1442695040888963407: i64 = 1442695040888963407
11const TG_MAGIC_2147483647: i64 = 2147483647
12const TG_MAGIC_1024: i64 = 1024
13const TG_MAGIC_1048576: i64 = 1048576
14const TG_MAGIC_65536: i64 = 65536
15const TG_MAGIC_2654435761: i64 = 2654435761
16const TG_MAGIC_1013904223: i64 = 1013904223
17const TG_MAGIC_7919: i64 = 7919
18const TG_MAGIC_1700: i64 = 1700
19const TG_MAGIC_2900: i64 = 2900
20const TG_MAGIC_1400: i64 = 1400
21const TG_MAGIC_2400: i64 = 2400
22const TG_MAGIC_2100: i64 = 2100
23const TG_MAGIC_3400: i64 = 3400
24const TG_MAGIC_4096: i64 = 4096
25const TG_MAGIC_25736: i64 = 25736
26const TG_MAGIC_2600: i64 = 2600
27const TG_MAGIC_1500: i64 = 1500
28const TG_MAGIC_25735: i64 = 25735
29const TG_MAGIC_4090: i64 = 4090
30const TG_MAGIC_1000000: i64 = 1000000
31const TG_MAGIC_1100: i64 = 1100
32const TG_MAGIC_1103515245: i64 = 1103515245
33const TG_MAGIC_12345: i64 = 12345
34
35const TG_CAP: i64 = 700 // max parts
36const TG_STR: i64 = 14 // per-part: type,kind, cx,cy,cz, bound, p1(3), r, p2(3)
37const TG_CAPS: i64 = 0
38const TG_SPH: i64 = 1
39const TG_WOOD: i64 = 0
40const TG_LEAF: i64 = 1
41const TG_W: i64 = 448
42const TG_H: i64 = 512
43const TG_FOCAL: i64 = 620
44
45func tg_isqrt(v: i64) -> i64 { return vm_isqrt(v) }
46func tg_abs(v: i64) -> i64 { if v<0 {return 0-v} return v }
47func tg_min(a: i64, b: i64) -> i64 { if a<b {return a} return b }
48func tg_max(a: i64, b: i64) -> i64 { if a>b {return a} return b }
49func tg_clamp(v: i64, lo: i64, hi: i64) -> i64 { if v<lo {return lo} if v>hi {return hi} return v }
50func tg_next(st: *i64) -> i64 { var h: i64=st[0]; h=h*TG_MAGIC_6364136223846793005+TG_MAGIC_1442695040888963407; st[0]=h; return (h>>33)&TG_MAGIC_2147483647 }
51func tg_range(st: *i64, lo: i64, hi: i64) -> i64 { return lo + tg_next(st) % (hi-lo+1) }
52
53func tg_addcaps(P: *i64, n: i64, ax: i64, ay: i64, az: i64, bx: i64, by: i64, bz: i64, r: i64, kind: i64) -> i64 {
54 let i: i64 = n*TG_STR
55 P[i]=TG_CAPS; P[i+1]=kind; P[i+2]=(ax+bx)/2; P[i+3]=(ay+by)/2; P[i+4]=(az+bz)/2
56 let hl: i64 = tg_isqrt((bx-ax)*(bx-ax)+(by-ay)*(by-ay)+(bz-az)*(bz-az))/2
57 P[i+5]=hl+r; P[i+6]=ax; P[i+7]=ay; P[i+8]=az; P[i+9]=r; P[i+10]=bx; P[i+11]=by; P[i+12]=bz; P[i+13]=0
58 return 0
59}
60func tg_addsph(P: *i64, n: i64, cx: i64, cy: i64, cz: i64, r: i64, kind: i64) -> i64 {
61 let i: i64 = n*TG_STR
62 P[i]=TG_SPH; P[i+1]=kind; P[i+2]=cx; P[i+3]=cy; P[i+4]=cz; P[i+5]=r; P[i+6]=cx; P[i+7]=cy; P[i+8]=cz; P[i+9]=r; P[i+13]=0
63 P[i+10]=0; P[i+11]=0; P[i+12]=0 // PG21: a sphere record is FULLY written -- stale words from a reused table read as nondeterminism
64 return 0
65}
66
67// tilt a unit(fx1024) direction by polar `ang` around azimuth `phi` (both it4096), + upward bias, renormalize
68func tg_tilt(dx: i64, dy: i64, dz: i64, ang: i64, phi: i64, out: *i64) -> i64 {
69 // u = cross(dir, up) with up=(0,1024,0) = (-dz*1024, 0, dx*1024)
70 var ux: i64 = 0 - dz*TG_MAGIC_1024; var uy: i64 = 0; var uz: i64 = dx*TG_MAGIC_1024
71 var ul: i64 = tg_isqrt(ux*ux+uy*uy+uz*uz)
72 if ul < 64 { ux=TG_MAGIC_1048576; uy=0; uz=0; ul=TG_MAGIC_1048576 } // dir ~ vertical -> use x axis (TG_MAGIC_1024*TG_MAGIC_1024 scale)
73 ux=ux*TG_MAGIC_1024/ul; uy=uy*TG_MAGIC_1024/ul; uz=uz*TG_MAGIC_1024/ul
74 // v = cross(dir,u)
75 var vx: i64 = (dy*uz - dz*uy)/TG_MAGIC_1024; var vy: i64 = (dz*ux - dx*uz)/TG_MAGIC_1024; var vz: i64 = (dx*uy - dy*ux)/TG_MAGIC_1024
76 let ca: i64 = it_cos4096(ang)/4; let sa: i64 = it_sin4096(ang)/4 // fx1024
77 let cp: i64 = it_cos4096(phi)/4; let sp: i64 = it_sin4096(phi)/4
78 let perpx: i64 = ux*cp/TG_MAGIC_1024 + vx*sp/TG_MAGIC_1024
79 let perpy: i64 = uy*cp/TG_MAGIC_1024 + vy*sp/TG_MAGIC_1024
80 let perpz: i64 = uz*cp/TG_MAGIC_1024 + vz*sp/TG_MAGIC_1024
81 var cxx: i64 = dx*ca/TG_MAGIC_1024 + perpx*sa/TG_MAGIC_1024
82 var cyy: i64 = dy*ca/TG_MAGIC_1024 + perpy*sa/TG_MAGIC_1024 + 210 // phototropism (upward bias)
83 var czz: i64 = dz*ca/TG_MAGIC_1024 + perpz*sa/TG_MAGIC_1024
84 let l: i64 = tg_isqrt(cxx*cxx+cyy*cyy+czz*czz)
85 if l>0 { cxx=cxx*TG_MAGIC_1024/l; cyy=cyy*TG_MAGIC_1024/l; czz=czz*TG_MAGIC_1024/l }
86 out[0]=cxx; out[1]=cyy; out[2]=czz
87 return 0
88}
89
90// ★R1 DIVERSITY: SPECIES GRAMMAR (the census critic's bar: "ONE tree grammar vs their species systems"). A
91// species = a parameter row over the SAME growth grammar (angles, decay, depth, leaf size/count, palettes):
92// 0 BROADLEAF the original umbrella-canopy tree
93// 1 CONIFER narrow: small branch angles, strong decay, deep, small dark clusters -> spire silhouette
94// 2 BIRCH pale bark, small bright leaves, slender, more branches
95// 3 PALM bare tall trunk (depth 0 sides), one crown of LONG frond blobs
96// 4 DEAD no leaves at all, grey wood, gnarled wide angles
97static TG_WOODRGB: i64 // per-species palettes read by treegen_render (0 -> defaults)
98static TG_LEAFRGB: i64
99func tg_species_palette(species: i64) -> i64 {
100 TG_WOODRGB = 96 + 66*256 + 40*TG_MAGIC_65536 // broadleaf default
101 TG_LEAFRGB = 46 + 88*256 + 36*TG_MAGIC_65536
102 if species == 1 { TG_WOODRGB = 78 + 54*256 + 36*TG_MAGIC_65536; TG_LEAFRGB = 26 + 62*256 + 30*TG_MAGIC_65536 } // conifer: dark
103 if species == 2 { TG_WOODRGB = 196 + 190*256 + 178*TG_MAGIC_65536; TG_LEAFRGB = 88 + 132*256 + 44*TG_MAGIC_65536 } // birch: pale bark
104 if species == 3 { TG_WOODRGB = 122 + 96*256 + 62*TG_MAGIC_65536; TG_LEAFRGB = 60 + 118*256 + 44*TG_MAGIC_65536 } // palm
105 if species == 4 { TG_WOODRGB = 92 + 88*256 + 84*TG_MAGIC_65536; TG_LEAFRGB = 0 } // dead: grey, leafless
106 return 0
107}
108
109// grow a tree of a SPECIES from a seed. base at (0,-380,0), grows +y. Returns part count.
110func treegen_build_sp(P: *i64, seed: i64, species: i64) -> i64 {
111 tg_species_palette(species)
112 let st: *i64 = sys_mmap(16) as *i64
113 st[0] = seed*TG_MAGIC_2654435761 + TG_MAGIC_1013904223 + species*TG_MAGIC_7919; tg_next(st); tg_next(st)
114 // species parameter row: trunk len/rad, depth, branch angle lo/hi, count lo/hi, len-decay, leaf size lo/hi, leafn
115 var tlen: i64 = tg_range(st,235,285)
116 var trad: i64 = tg_range(st,24,30)
117 var dep0: i64 = 5
118 var anglo: i64 = TG_MAGIC_1700
119 var anghi: i64 = TG_MAGIC_2900
120 var chlo: i64 = 2
121 var chhi: i64 = 3
122 var decay: i64 = 73
123 var lflo: i64 = 22
124 var lfhi: i64 = 38
125 var leafn: i64 = 6
126 var ljx: i64 = 62 // leaf jitter x/z
127 var ljyu: i64 = 74 // leaf jitter y up
128 if species == 1 { dep0=0; tlen=tg_range(st,380,460); trad=tg_range(st,20,26) } // conifer: dedicated whorl build below
129 if species == 2 { dep0=5; anglo=TG_MAGIC_1400; anghi=TG_MAGIC_2400; chlo=2; chhi=4; decay=70; lflo=14; lfhi=22; leafn=7; ljx=52; ljyu=60; trad=tg_range(st,16,20) }
130 if species == 3 { dep0=0; tlen=tg_range(st,340,420); trad=tg_range(st,20,26) } // palm: trunk only
131 if species == 4 { dep0=5; anglo=TG_MAGIC_2100; anghi=TG_MAGIC_3400; chlo=2; chhi=3; decay=76; leafn=0 } // dead: no leaves
132 let S: *i64 = sys_mmap(TG_MAGIC_4096*9*8) as *i64 // growth stack: x,y,z,dx,dy,dz,len,rad,depth
133 var sp: i64 = 0
134 S[0]=0; S[1]=0-380; S[2]=0; S[3]=0; S[4]=TG_MAGIC_1024; S[5]=0; S[6]=tlen; S[7]=trad; S[8]=dep0
135 sp = 1
136 let od: *i64 = sys_mmap(32) as *i64
137 var np: i64 = 0
138 while sp > 0 {
139 sp = sp - 1
140 let bx: i64 = S[sp*9]; let by: i64 = S[sp*9+1]; let bz: i64 = S[sp*9+2]
141 let dx: i64 = S[sp*9+3]; let dy: i64 = S[sp*9+4]; let dz: i64 = S[sp*9+5]
142 let ln: i64 = S[sp*9+6]; let rad: i64 = S[sp*9+7]; let dep: i64 = S[sp*9+8]
143 let ex: i64 = bx + dx*ln/TG_MAGIC_1024; let ey: i64 = by + dy*ln/TG_MAGIC_1024; let ez: i64 = bz + dz*ln/TG_MAGIC_1024
144 if np < TG_CAP { tg_addcaps(P, np, bx, by, bz, ex, ey, ez, rad, TG_WOOD); np = np + 1 }
145 if dep == 0 {
146 if species == 1 {
147 // CONIFER: branch WHORLS down the trunk, longer at the bottom -> the spruce cone silhouette
148 var lev: i64 = 0
149 while lev < 7 {
150 let wy: i64 = by + ln*(22 + lev*11)/100 // whorl height up the trunk
151 let wlen: i64 = ln*(46 - lev*6)/100 // branch length shrinks upward
152 var wb: i64 = 0
153 while wb < 5 {
154 let wphi: i64 = wb*TG_MAGIC_25736/5 + lev*TG_MAGIC_2600 + tg_range(st,0,TG_MAGIC_1400)
155 let wdx: i64 = it_cos4096(wphi)/4
156 let wdz: i64 = it_sin4096(wphi)/4
157 let bex: i64 = bx + wdx*wlen/TG_MAGIC_1024
158 let bey: i64 = wy - wlen/5 // branches slope DOWN
159 let bez: i64 = bz + wdz*wlen/TG_MAGIC_1024
160 if np < TG_CAP { tg_addcaps(P, np, bx, wy, bz, bex, bey, bez, 7, TG_WOOD); np = np + 1 }
161 // needle blobs along the outer 2/3 of the branch
162 var ns: i64 = 0
163 while ns < 3 {
164 let t2: i64 = 400 + ns*250
165 if np < TG_CAP { tg_addsph(P, np, bx + wdx*wlen*t2/TG_MAGIC_1048576, wy - wlen*t2/5/TG_MAGIC_1024, bz + wdz*wlen*t2/TG_MAGIC_1048576, 20 - lev, TG_LEAF); np = np + 1 }
166 ns = ns + 1
167 }
168 wb = wb + 1
169 }
170 lev = lev + 1
171 }
172 // crown tip
173 if np < TG_CAP { tg_addsph(P, np, bx, by + ln + 16, bz, 20, TG_LEAF); np = np + 1 }
174 }
175 if species == 3 {
176 // palm crown: a ring of LONG radial frond blobs from the trunk top
177 var fr: i64 = 0
178 while fr < 8 {
179 let fphi: i64 = fr*TG_MAGIC_25736/8 + tg_range(st,0,TG_MAGIC_1500)
180 let fdx: i64 = it_cos4096(fphi)/4
181 let fdz: i64 = it_sin4096(fphi)/4
182 var seg: i64 = 0
183 while seg < 6 {
184 let fd: i64 = 44 + seg*52
185 let droop: i64 = seg*seg*11
186 if np < TG_CAP { tg_addsph(P, np, ex + fdx*fd/TG_MAGIC_1024, ey + 30 - droop, ez + fdz*fd/TG_MAGIC_1024, 30 - seg*3, TG_LEAF); np = np + 1 }
187 seg = seg + 1
188 }
189 fr = fr + 1
190 }
191 }
192 if species != 1 { if species != 3 {
193 var lf: i64 = 0
194 while lf < leafn {
195 let lr: i64 = tg_range(st, lflo, lfhi)
196 if np < TG_CAP { tg_addsph(P, np, ex + tg_range(st,0-ljx,ljx), ey + tg_range(st,0-24,ljyu), ez + tg_range(st,0-ljx,ljx), lr, TG_LEAF); np = np + 1 }
197 lf = lf + 1
198 }
199 } }
200 } else {
201 let nch: i64 = tg_range(st, chlo, chhi)
202 var c: i64 = 0
203 while c < nch {
204 let phi: i64 = tg_range(st, 0, TG_MAGIC_25735)
205 let ang: i64 = tg_range(st, anglo, anghi)
206 tg_tilt(dx, dy, dz, ang, phi, od)
207 if sp < TG_MAGIC_4090 {
208 S[sp*9]=ex; S[sp*9+1]=ey; S[sp*9+2]=ez; S[sp*9+3]=od[0]; S[sp*9+4]=od[1]; S[sp*9+5]=od[2]
209 S[sp*9+6]=ln*tg_range(st,decay-5,decay+5)/100; S[sp*9+7]=rad*66/100; S[sp*9+8]=dep-1
210 sp = sp + 1
211 }
212 c = c + 1
213 }
214 }
215 }
216 return np
217}
218
219// back-compat: the original broadleaf
220func treegen_build(P: *i64, seed: i64) -> i64 { return treegen_build_sp(P, seed, 0) }
221
222func tg_part_sdf(P: *i64, k: i64, x: i64, y: i64, z: i64) -> i64 {
223 let i: i64 = k*TG_STR
224 if P[i]==TG_SPH { let dx: i64=x-P[i+2]; let dy: i64=y-P[i+3]; let dz: i64=z-P[i+4]; return tg_isqrt(dx*dx+dy*dy+dz*dz)-P[i+9] }
225 let ax: i64=P[i+6]; let ay: i64=P[i+7]; let az: i64=P[i+8]; let r: i64=P[i+9]
226 let bx: i64=P[i+10]; let by: i64=P[i+11]; let bz: i64=P[i+12]
227 let pax: i64=x-ax; let pay: i64=y-ay; let paz: i64=z-az
228 let bax: i64=bx-ax; let bay: i64=by-ay; let baz: i64=bz-az
229 let bb: i64=bax*bax+bay*bay+baz*baz
230 var h: i64=0
231 if bb>0 { h=tg_clamp((pax*bax+pay*bay+paz*baz)*TG_MAGIC_1024/bb,0,TG_MAGIC_1024) }
232 let cx: i64=pax-bax*h/TG_MAGIC_1024; let cy: i64=pay-bay*h/TG_MAGIC_1024; let cz: i64=paz-baz*h/TG_MAGIC_1024
233 return tg_isqrt(cx*cx+cy*cy+cz*cz)-r
234}
235// min-union with bounding-sphere reject; sets kindout[0] to the kind and kindout[1] to the INDEX of the nearest
236// part (the index seeds per-leaf colour jitter -- canopy detail)
237func tg_sdf(P: *i64, np: i64, x: i64, y: i64, z: i64, kindout: *i64) -> i64 {
238 var d: i64 = TG_MAGIC_1000000; var kind: i64 = 0
239 var kidx: i64 = 0
240 var k: i64 = 0
241 while k < np {
242 let i: i64 = k*TG_STR
243 let dcx: i64=x-P[i+2]; let dcy: i64=y-P[i+3]; let dcz: i64=z-P[i+4]
244 let cd2: i64 = dcx*dcx+dcy*dcy+dcz*dcz
245 let thr: i64 = d + P[i+5]
246 var skip: i64 = 0
247 if thr > 0 { if cd2 > thr*thr { skip = 1 } }
248 if skip == 0 {
249 let pd: i64 = tg_part_sdf(P, k, x, y, z)
250 if pd < d { d = pd; kind = P[i+1]; kidx = k }
251 }
252 k = k + 1
253 }
254 kindout[0] = kind
255 kindout[1] = kidx
256 return d
257}
258
259func treegen_render(P: *i64, np: i64, yaw: i64, camz: i64, bgr: i64, bgg: i64, bgb: i64, fb: *i64) -> i64 {
260 let sy4: i64 = it_sin4096(yaw); let cy4: i64 = it_cos4096(yaw)
261 let R: i64 = camz*TG_MAGIC_1024
262 let ox: i64 = 0 - sy4*R/TG_MAGIC_4096; let oz: i64 = 0 - cy4*R/TG_MAGIC_4096
263 let lx: i64 = 320; let ly: i64 = 640; let lz: i64 = 0-680 // upper-RIGHT-front = matches the worldgen sun
264 let ll: i64 = tg_isqrt(lx*lx+ly*ly+lz*lz)
265 let ko: *i64 = sys_mmap(16) as *i64
266 var py: i64 = 0
267 while py < TG_H {
268 var px: i64 = 0
269 while px < TG_W {
270 let ndcx: i64 = px - TG_W/2
271 let ndcy: i64 = TG_H/2 - py
272 var dx: i64 = sy4*TG_FOCAL/TG_MAGIC_4096 + cy4*ndcx/TG_MAGIC_4096
273 var dy: i64 = ndcy
274 var dz: i64 = cy4*TG_FOCAL/TG_MAGIC_4096 - sy4*ndcx/TG_MAGIC_4096
275 let dl: i64 = tg_isqrt(dx*dx+dy*dy+dz*dz)
276 if dl>0 { dx=dx*TG_MAGIC_1024/dl; dy=dy*TG_MAGIC_1024/dl; dz=dz*TG_MAGIC_1024/dl }
277 var t: i64 = R - TG_MAGIC_1100
278 if t<0 {t=0}
279 var hit: i64 = 0; var hx: i64=0; var hy: i64=0; var hz: i64=0; var hk: i64=0; var hki: i64=0
280 var step: i64 = 0
281 while step < 110 {
282 let sx: i64 = ox + dx*t/TG_MAGIC_1024; let sy: i64 = dy*t/TG_MAGIC_1024; let sz: i64 = oz + dz*t/TG_MAGIC_1024
283 let d: i64 = tg_sdf(P, np, sx, sy, sz, ko)
284 if d < 2 { hit=1; hx=sx; hy=sy; hz=sz; hk=ko[0]; hki=ko[1]; step=999 }
285 else { t = t + d*8/10; if t > R+TG_MAGIC_1100 { step=999 } }
286 if step != 999 { step = step + 1 }
287 }
288 var col: i64 = bgr + bgg*256 + bgb*TG_MAGIC_65536
289 if hit == 1 {
290 let e: i64 = 3
291 let gx: i64 = tg_sdf(P,np,hx+e,hy,hz,ko)-tg_sdf(P,np,hx-e,hy,hz,ko)
292 let gy: i64 = tg_sdf(P,np,hx,hy+e,hz,ko)-tg_sdf(P,np,hx,hy-e,hz,ko)
293 let gz: i64 = tg_sdf(P,np,hx,hy,hz+e,ko)-tg_sdf(P,np,hx,hy,hz-e,ko)
294 let gl: i64 = tg_isqrt(gx*gx+gy*gy+gz*gz)
295 var nd: i64 = 0
296 if gl>0 { nd=(gx*lx+gy*ly+gz*lz)*TG_MAGIC_1024/(gl*ll) }
297 if nd<0 {nd=0}
298 let lit: i64 = 46 + nd*74/TG_MAGIC_1024
299 // species palettes (statics set by tg_species_palette; 0 -> broadleaf defaults)
300 var wrgb: i64 = TG_WOODRGB
301 if wrgb == 0 { wrgb = 96 + 66*256 + 40*TG_MAGIC_65536 }
302 var lrgb: i64 = TG_LEAFRGB
303 if lrgb == 0 { lrgb = 46 + 88*256 + 36*TG_MAGIC_65536 }
304 var cr: i64 = wrgb & 255
305 var cg: i64 = (wrgb >> 8) & 255
306 var cb: i64 = (wrgb >> 16) & 255
307 if hk == TG_LEAF {
308 // per-leaf colour jitter (seeded by the part index) -> a varied, detailed canopy
309 var j2: i64 = hki*TG_MAGIC_1103515245 + TG_MAGIC_12345
310 if j2 < 0 { j2 = 0 - j2 }
311 cr = (lrgb & 255) + (j2 % 23)
312 cg = ((lrgb >> 8) & 255) + ((j2/23) % 37)
313 cb = ((lrgb >> 16) & 255) + ((j2/851) % 17)
314 }
315 var r: i64 = cr*lit/100; var g: i64 = cg*lit/100; var b: i64 = cb*lit/100
316 col = tg_clamp(r,0,255) + tg_clamp(g,0,255)*256 + tg_clamp(b,0,255)*TG_MAGIC_65536
317 }
318 fb[py*TG_W+px] = col
319 px = px + 1
320 }
321 py = py + 1
322 }
323 return 0
324}
325func tg_w() -> i64 { return TG_W }
326func tg_h() -> i64 { return TG_H }
327
328// ---- PG21 (2026-09-06): SPACE COLONIZATION (Runions 2007) -- competition for space determines branching ----------
329// tg_colonize(P, seed, height, crown_r, trunk_permil, trunk_r, leaf_r) -> part count, in treegen's own part table
330// (base at (0,-380,0), +y up, so treegen_render draws it unchanged). The crown ENVELOPE is an ellipsoid (crown_r
331// horizontal, half the crown height vertical) seeded with TG_COL_POINTS attraction points; a straight trunk climbs to
332// the crown base; then every iteration each live point pulls its nearest node within TG_COL_INFL steps, every pulled
333// node grows ONE step D toward its mean pull, and any point within TG_COL_KILL steps of a node is consumed. D is
334// DERIVED from the envelope (crown_r / TG_COL_STEP_DIV, floored at TG_COL_DMIN), so the only controls are the
335// landscaping ones: height, crown radius, trunk fraction, trunk radius, leaf radius. Branch radius follows the pipe
336// model (r = trunk_r * sqrt(leaves_below / leaves)); terminal nodes carry one leaf sphere. Integer, seeded, bit-exact:
337// distinct seeds sample distinct point clouds and therefore distinct branching. Every bound is named.
338const TG_COL_POINTS: i64 = 420 // attraction points in the envelope
339const TG_COL_MAXN: i64 = 440 // node cap; with TG_CAP 700 parts it leaves room for the leaf spheres
340const TG_COL_MAXIT: i64 = 64 // growth iterations
341const TG_COL_STEP_DIV: i64 = 8 // D = crown_r / 8
342const TG_COL_DMIN: i64 = 8
343const TG_COL_KILL: i64 = 2 // kill distance in steps
344const TG_COL_INFL: i64 = 5 // influence radius in steps
345const TG_COL_RMIN: i64 = 3 // thinnest branch radius
346const TG_COL_NS: i64 = 12 // node stride: x,y,z,parent,children,leaves,spare,pullx,pully,pullz,pulln,spare
347const TG_COL_PS: i64 = 4 // point stride: x,y,z,alive
348const TG_COL_BASE_Y: i64 = 380 // the tree base sits at -TG_COL_BASE_Y, treegen's own convention
349func tg_col_inside(px: i64, py: i64, pz: i64, cy: i64, rx: i64, ry: i64) -> i64 {
350 let a: i64 = px*TG_MAGIC_1024/rx
351 let b: i64 = (py-cy)*TG_MAGIC_1024/ry
352 let c: i64 = pz*TG_MAGIC_1024/rx
353 if a*a + b*b + c*c <= TG_MAGIC_1048576 { return 1 }
354 return 0
355}
356func tg_col_addnode(N: *i64, nn: i64, x: i64, y: i64, z: i64, parent: i64) -> i64 {
357 if nn >= TG_COL_MAXN { return nn }
358 let i: i64 = nn*TG_COL_NS
359 N[i]=x; N[i+1]=y; N[i+2]=z; N[i+3]=parent; N[i+4]=0; N[i+5]=0; N[i+6]=0; N[i+7]=0; N[i+8]=0; N[i+9]=0; N[i+10]=0; N[i+11]=0
360 if parent >= 0 { N[parent*TG_COL_NS+4] = N[parent*TG_COL_NS+4] + 1 }
361 return nn + 1
362}
363func tg_colonize(P: *i64, seed: i64, height: i64, crown_r: i64, trunk_permil: i64, trunk_r: i64, leaf_r: i64) -> i64 {
364 let st: *i64 = sys_mmap(16) as *i64
365 st[0] = seed*TG_MAGIC_2654435761 + TG_MAGIC_1013904223 + height*TG_MAGIC_7919 + crown_r
366 tg_next(st); tg_next(st)
367 var D: i64 = crown_r / TG_COL_STEP_DIV
368 if D < TG_COL_DMIN { D = TG_COL_DMIN }
369 let base_y: i64 = 0 - TG_COL_BASE_Y
370 let trunk_h: i64 = height*trunk_permil/1000
371 let crown_h: i64 = height - trunk_h
372 let cy: i64 = base_y + trunk_h + crown_h/2
373 // the attraction cloud is the REQUESTED envelope shrunk by the overshoot a grown crown adds to it -- one step (a
374 // node grows a full D toward a rim point) plus the leaf radius -- so the MEASURED canopy lands on the request
375 let shrink: i64 = D + leaf_r
376 var ry: i64 = crown_h/2 - shrink
377 if ry < D { ry = D }
378 var rx: i64 = crown_r - shrink
379 if rx < D { rx = D }
380 // attraction points by rejection inside the ellipsoid (a bounded try budget, never an open loop)
381 let A: *i64 = sys_mmap(TG_COL_POINTS*TG_COL_PS*8) as *i64
382 var na: i64 = 0
383 var tries: i64 = 0
384 while tries < TG_COL_POINTS*8 {
385 if na < TG_COL_POINTS {
386 let px: i64 = tg_range(st, 0-rx, rx)
387 let py: i64 = tg_range(st, cy-ry, cy+ry)
388 let pz: i64 = tg_range(st, 0-rx, rx)
389 if tg_col_inside(px, py, pz, cy, rx, ry) == 1 {
390 A[na*TG_COL_PS]=px; A[na*TG_COL_PS+1]=py; A[na*TG_COL_PS+2]=pz; A[na*TG_COL_PS+3]=1
391 na = na + 1
392 }
393 }
394 tries = tries + 1
395 }
396 // nodes: the trunk first, one node per step up to the crown base
397 let N: *i64 = sys_mmap(TG_COL_MAXN*TG_COL_NS*8) as *i64
398 var nn: i64 = tg_col_addnode(N, 0, 0, base_y, 0, 0-1)
399 var ty: i64 = base_y
400 while ty + D <= base_y + trunk_h {
401 ty = ty + D
402 nn = tg_col_addnode(N, nn, 0, ty, 0, nn-1)
403 }
404 // growth
405 let infl2: i64 = D*D*TG_COL_INFL*TG_COL_INFL
406 let kill2: i64 = D*D*TG_COL_KILL*TG_COL_KILL
407 var it: i64 = 0
408 while it < TG_COL_MAXIT {
409 var k: i64 = 0
410 while k < nn {
411 let i: i64 = k*TG_COL_NS
412 N[i+7]=0; N[i+8]=0; N[i+9]=0; N[i+10]=0
413 k = k + 1
414 }
415 var pulled: i64 = 0
416 var p: i64 = 0
417 while p < na {
418 let a: i64 = p*TG_COL_PS
419 if A[a+3] == 1 {
420 var best: i64 = 0 - 1
421 var bd2: i64 = infl2
422 var q: i64 = 0
423 while q < nn {
424 let i: i64 = q*TG_COL_NS
425 let dx: i64 = A[a]-N[i]
426 let dy: i64 = A[a+1]-N[i+1]
427 let dz: i64 = A[a+2]-N[i+2]
428 let d2: i64 = dx*dx+dy*dy+dz*dz
429 if d2 < bd2 { bd2 = d2; best = q }
430 q = q + 1
431 }
432 if best >= 0 {
433 if bd2 <= kill2 { A[a+3] = 0 }
434 else {
435 let i: i64 = best*TG_COL_NS
436 let dx: i64 = A[a]-N[i]
437 let dy: i64 = A[a+1]-N[i+1]
438 let dz: i64 = A[a+2]-N[i+2]
439 let l: i64 = tg_isqrt(bd2)
440 if l > 0 {
441 N[i+7] = N[i+7] + dx*TG_MAGIC_1024/l
442 N[i+8] = N[i+8] + dy*TG_MAGIC_1024/l
443 N[i+9] = N[i+9] + dz*TG_MAGIC_1024/l
444 N[i+10] = N[i+10] + 1
445 pulled = pulled + 1
446 }
447 }
448 }
449 }
450 p = p + 1
451 }
452 if pulled == 0 { it = TG_COL_MAXIT }
453 else {
454 let n0: i64 = nn
455 var k2: i64 = 0
456 while k2 < n0 {
457 let i: i64 = k2*TG_COL_NS
458 if N[i+10] > 0 {
459 let gx: i64 = N[i+7]
460 let gy: i64 = N[i+8]
461 let gz: i64 = N[i+9]
462 let gl: i64 = tg_isqrt(gx*gx+gy*gy+gz*gz)
463 if gl > 0 { nn = tg_col_addnode(N, nn, N[i] + gx*D/gl, N[i+1] + gy*D/gl, N[i+2] + gz*D/gl, k2) }
464 }
465 k2 = k2 + 1
466 }
467 it = it + 1
468 }
469 }
470 // leaves below each node (children carry larger indices than their parent, so a descending pass sums them)
471 var k3: i64 = nn - 1
472 while k3 >= 0 {
473 let i: i64 = k3*TG_COL_NS
474 if N[i+4] == 0 { N[i+5] = 1 }
475 let pa: i64 = N[i+3]
476 if pa >= 0 { N[pa*TG_COL_NS+5] = N[pa*TG_COL_NS+5] + N[i+5] }
477 k3 = k3 - 1
478 }
479 var total: i64 = N[5]
480 if total < 1 { total = 1 }
481 // emit: one tapering capsule per node from its parent (pipe model), one leaf sphere per terminal node
482 var np: i64 = 0
483 var k4: i64 = 1
484 while k4 < nn {
485 let i: i64 = k4*TG_COL_NS
486 let j: i64 = N[i+3]*TG_COL_NS
487 var r: i64 = trunk_r * tg_isqrt(N[i+5]*TG_MAGIC_1048576/total) / TG_MAGIC_1024
488 if r < TG_COL_RMIN { r = TG_COL_RMIN }
489 if np < TG_CAP { tg_addcaps(P, np, N[j], N[j+1], N[j+2], N[i], N[i+1], N[i+2], r, TG_WOOD); np = np + 1 }
490 k4 = k4 + 1
491 }
492 if leaf_r > 0 {
493 var k5: i64 = 1
494 while k5 < nn {
495 let i: i64 = k5*TG_COL_NS
496 if N[i+4] == 0 { if np < TG_CAP { tg_addsph(P, np, N[i], N[i+1], N[i+2], leaf_r, TG_LEAF); np = np + 1 } }
497 k5 = k5 + 1
498 }
499 }
500 return np
501}
502// the grown tree's measured envelope, for the gate and for placement: out = top_y, crown radius (leaf reach), wood
503// parts, leaf parts, max wood radius, min wood radius
504func tg_col_envelope(P: *i64, np: i64, out: *i64) -> i64 {
505 var top: i64 = 0 - TG_MAGIC_1000000
506 var rad: i64 = 0
507 var wood: i64 = 0
508 var leaf: i64 = 0
509 var rmax: i64 = 0
510 var rmin: i64 = TG_MAGIC_1000000
511 var k: i64 = 0
512 while k < np {
513 let i: i64 = k*TG_STR
514 if P[i] == TG_SPH {
515 leaf = leaf + 1
516 let rr: i64 = tg_isqrt(P[i+2]*P[i+2] + P[i+4]*P[i+4]) + P[i+9]
517 if rr > rad { rad = rr }
518 let t: i64 = P[i+3] + P[i+9]
519 if t > top { top = t }
520 } else {
521 wood = wood + 1
522 if P[i+9] > rmax { rmax = P[i+9] }
523 if P[i+9] < rmin { rmin = P[i+9] }
524 let t2: i64 = P[i+11]
525 if t2 > top { top = t2 }
526 }
527 k = k + 1
528 }
529 out[0]=top; out[1]=rad; out[2]=wood; out[3]=leaf; out[4]=rmax; out[5]=rmin
530 return 0
531}