zones_set / zones_clear / zones_merge, zones_contains / zones_nearest and the clusters' bounding circles, over fixed arrays of ZONES_MAX (16). WildlifeWorld.active freezes an animal outside (no state change, no dice), NpcWorld.active a walker (and no birth outside), VehicleWorld.active a moored boat, and ThingsWorld.restocks keeps a morning's restock inside; every default is "everywhere", so an unbound game is unchanged. Tests: the merge (near players one cluster, far two, a player leaving splits a chain), contains, nearest, the radius; wildlife, npc, vehicles and things pass as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
70 lines
2.3 KiB
Text
70 lines
2.3 KiB
Text
# merge.ludic - discs that overlap (or touch) are one cluster, transitively; a cluster's bounding
|
|
# circle is centred on its discs' box and reaches the farthest edge
|
|
export function zones_merge(zones_st: mut ZonesState) -> void {
|
|
for i in 0 .. ZONES_MAX { zones_st.zn_up[i] = i }
|
|
for i in 0 .. ZONES_MAX {
|
|
if zones_st.zn_on[i] == 0 { continue }
|
|
for j in i + 1 .. ZONES_MAX { if zones_st.zn_on[j] != 0 and zn_touch(zones_st, i, j) { zn_join(zones_st, i, j) } }
|
|
}
|
|
zones_st.zn_ncl = 0
|
|
for i in 0 .. ZONES_MAX { zones_st.zn_cl[i] = -1 }
|
|
for i in 0 .. ZONES_MAX {
|
|
if zones_st.zn_on[i] == 0 { continue }
|
|
let root = zn_root(zones_st, i)
|
|
if zones_st.zn_cl[root] < 0 {
|
|
zones_st.zn_cl[root] = zones_st.zn_ncl
|
|
zones_st.zn_ncl += 1
|
|
}
|
|
zones_st.zn_cl[i] = zones_st.zn_cl[root]
|
|
}
|
|
for c in 0 .. zones_st.zn_ncl { zn_bound(zones_st, c) }
|
|
}
|
|
|
|
function zn_touch(zones_st: ZonesState, i: int, j: int) -> bool {
|
|
let dx = zones_st.zn_x[i] - zones_st.zn_x[j]
|
|
let dz = zones_st.zn_z[i] - zones_st.zn_z[j]
|
|
let rr = zones_st.zn_r[i] + zones_st.zn_r[j]
|
|
return dx * dx + dz * dz <= rr * rr
|
|
}
|
|
|
|
function zn_root(zones_st: ZonesState, i: int) -> int {
|
|
var k = i
|
|
while zones_st.zn_up[k] != k { k = zones_st.zn_up[k] }
|
|
return k
|
|
}
|
|
|
|
function zn_join(zones_st: mut ZonesState, i: int, j: int) -> void {
|
|
let a = zn_root(zones_st, i)
|
|
let b = zn_root(zones_st, j)
|
|
if a < b { zones_st.zn_up[b] = a }
|
|
if b < a { zones_st.zn_up[a] = b }
|
|
}
|
|
|
|
function zn_bound(zones_st: mut ZonesState, c: int) -> void {
|
|
var x0 = 1000000000.0
|
|
var x1 = -1000000000.0
|
|
var z0 = 1000000000.0
|
|
var z1 = -1000000000.0
|
|
var n = 0
|
|
for i in 0 .. ZONES_MAX {
|
|
if zones_st.zn_on[i] == 0 or zones_st.zn_cl[i] != c { continue }
|
|
x0 = Math.min(x0, zones_st.zn_x[i] - zones_st.zn_r[i])
|
|
x1 = Math.max(x1, zones_st.zn_x[i] + zones_st.zn_r[i])
|
|
z0 = Math.min(z0, zones_st.zn_z[i] - zones_st.zn_r[i])
|
|
z1 = Math.max(z1, zones_st.zn_z[i] + zones_st.zn_r[i])
|
|
n += 1
|
|
}
|
|
let cx = (x0 + x1) * 0.5
|
|
let cz = (z0 + z1) * 0.5
|
|
var r = 0.0
|
|
for i in 0 .. ZONES_MAX {
|
|
if zones_st.zn_on[i] == 0 or zones_st.zn_cl[i] != c { continue }
|
|
let dx = zones_st.zn_x[i] - cx
|
|
let dz = zones_st.zn_z[i] - cz
|
|
r = Math.max(r, Math.sqrt(dx * dx + dz * dz) + zones_st.zn_r[i])
|
|
}
|
|
zones_st.zn_cx[c] = cx
|
|
zones_st.zn_cz[c] = cz
|
|
zones_st.zn_cr[c] = r
|
|
zones_st.zn_cn[c] = n
|
|
}
|