using System.Collections.Generic; using Godot; namespace WorldSim; /// /// A coarse spatial hash over NPC positions so proximity queries stop being /// O(n²). Rebuilt once per tick from the live NPC list; queries return only /// the buckets within range. At ~80 NPCs this turns every soul-pressure, /// neighbor, and teacher scan from "look at everyone" into "look at the few /// nearby" — the difference between a live view that stutters and one that /// doesn't. /// public class SpatialGrid { private readonly float _cell; private readonly Dictionary<(int, int), List> _buckets = new(); public SpatialGrid(float cellSize) => _cell = cellSize; private (int, int) Key(Vector2 p) => (Mathf.FloorToInt(p.X / _cell), Mathf.FloorToInt(p.Y / _cell)); public void Rebuild(IReadOnlyList npcs) { foreach (var list in _buckets.Values) list.Clear(); foreach (var npc in npcs) { var key = Key(npc.Position); if (!_buckets.TryGetValue(key, out var list)) _buckets[key] = list = new List(); list.Add(npc); } } /// /// Invoke for every NPC within of (excluding none — caller /// filters). Scans only the buckets the radius touches. /// public void ForEachNear(Vector2 center, float radius, System.Action action) { int reach = Mathf.CeilToInt(radius / _cell); var (cx, cy) = Key(center); for (int dx = -reach; dx <= reach; dx++) for (int dy = -reach; dy <= reach; dy++) { if (_buckets.TryGetValue((cx + dx, cy + dy), out var list)) foreach (var npc in list) action(npc); } } }