using System.Collections.Generic; using Luneraria.Core; using UnityEngine; namespace Luneraria.View { /// /// S4 fog of war in the terrain: a grid over the map (2 m cells) that follows the ground and /// darkens every point no vision source of the viewer's team reaches (LuneFog.Vision: own /// units 9 m, own buildings 10 m, wards 12 m). The alpha has a soft 2 m edge and is /// recomputed ten times a second. Presentation only; the rules never read it. /// public sealed class LuneFogOverlay : MonoBehaviour { public const float Cell = 2f; public const float Darkness = .55f; public const float Edge = 2f; public const float Interval = .1f; Mesh _mesh; Vec2[] _points; Color32[] _colors; float _timer; /// Darkness of every grid point (0 = seen), for tests. public float[] Alpha { get; private set; } public int Refreshes { get; private set; } public void Build(LuneGround ground, Material material) { int side = Mathf.CeilToInt((float)(LuneMap.HalfExtent * 2.0) / Cell) + 1; var vertices = new Vector3[side * side]; _points = new Vec2[side * side]; for (int row = 0; row < side; row++) { for (int column = 0; column < side; column++) { var map = new Vec2(-LuneMap.HalfExtent + column * Cell, -LuneMap.HalfExtent + row * Cell); float height = ground != null ? ground.StandHeight(map) : 0f; _points[row * side + column] = map; vertices[row * side + column] = LuneSpace.World(map, height + .45f); } } var triangles = new List(); for (int row = 0; row + 1 < side; row++) { for (int column = 0; column + 1 < side; column++) { int a = row * side + column; triangles.AddRange(new[] { a, a + side, a + 1, a + 1, a + side, a + side + 1 }); } } _colors = new Color32[vertices.Length]; Alpha = new float[vertices.Length]; _mesh = new Mesh { name = "FogOverlay", indexFormat = UnityEngine.Rendering.IndexFormat.UInt32 }; _mesh.SetVertices(vertices); _mesh.SetTriangles(triangles, 0); _mesh.SetColors(_colors); _mesh.RecalculateBounds(); gameObject.AddComponent().sharedMesh = _mesh; var renderer = gameObject.AddComponent(); renderer.sharedMaterial = material; renderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; renderer.receiveShadows = false; } /// Recomputes the darkness at most every Interval seconds (force: now). public void Tick(float dt, List> sources, bool force = false) { _timer -= dt; if (!force && _timer > 0f) return; _timer = Interval; Refreshes++; for (int i = 0; i < _points.Length; i++) { double best = double.PositiveInfinity; foreach (KeyValuePair source in sources) { double gap = _points[i].DistanceTo(source.Key) - source.Value; if (gap < best) best = gap; } float t = Mathf.Clamp01((float)(best + Edge * .5) / Edge); Alpha[i] = Darkness * t; _colors[i] = new Color32(0, 0, 0, (byte)Mathf.RoundToInt(Alpha[i] * 255f)); } _mesh.SetColors(_colors); } } }