diff --git a/Aetheria.State.Verify/Program.cs b/Aetheria.State.Verify/Program.cs index dff129607..940f34790 100644 --- a/Aetheria.State.Verify/Program.cs +++ b/Aetheria.State.Verify/Program.cs @@ -1816,7 +1816,11 @@ static void RequireDaemonRenderQueryAuthority(string root) "zone.GravityTerrainWaveFrequency", "public static AetheriaRuntimeDaemonBodyPose[] QueryBodyPoses(", "public static int QueryBodyPoses(", - "new AetheriaRuntimeDaemonBodyPose(" + "new AetheriaRuntimeDaemonBodyPose(", + "AetheriaRuntimeDaemonAsteroidBeltPose", + "public static AetheriaRuntimeDaemonAsteroidBeltPose[] QueryAsteroidBeltPoses(", + "public static int QueryAsteroidBeltPoses(", + "ResolveAsteroidBeltRadius(body)" }; var missingQuerySymbols = requiredQuerySymbols @@ -1894,16 +1898,27 @@ static void RequireDaemonRenderQueryAuthority(string root) "ZoneRenderer frame updates must read body render state from daemon body poses instead of indexed mirrored Unity planets."); } + if (zoneRenderer.Contains("belt.OrbitPosition", StringComparison.Ordinal) || + zoneRenderer.Contains("belt.Radius", StringComparison.Ordinal)) + { + throw new InvalidOperationException( + "ZoneRenderer frame updates must read asteroid belt pose and radius from daemon belt poses instead of mirrored Unity asteroid belts."); + } + var requiredZoneRendererSymbols = new[] { "private AetheriaRuntimeZoneSnapshotCommit _daemonZoneSnapshot;", "private readonly List _daemonBodyPoses", "private readonly Dictionary _daemonBodyPosesByBodyKey", + "private readonly List _daemonAsteroidBeltPoses", + "private readonly Dictionary _daemonAsteroidBeltPosesByBodyKey", "public void LoadZone(Zone zone, AetheriaRuntimeZoneSnapshotCommit daemonZone = null)", "_daemonZoneSnapshot = daemonZone;", "AetheriaRuntimeDaemonRenderQueries.EvaluateGravityTerrainHeight(", "AetheriaRuntimeDaemonRenderQueries.QueryBodyPoses(_daemonZoneSnapshot, _daemonBodyPoses);", + "AetheriaRuntimeDaemonRenderQueries.QueryAsteroidBeltPoses(_daemonZoneSnapshot, _daemonAsteroidBeltPoses);", "_daemonBodyPosesByBodyKey.TryGetValue(planet.Key, out var pose)", + "_daemonAsteroidBeltPosesByBodyKey.TryGetValue(key, out var beltPose)", "pose.GravityWaveSpeed" }; var missingZoneRendererSymbols = requiredZoneRendererSymbols diff --git a/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs b/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs index 77b7402f5..05983dfea 100644 --- a/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs +++ b/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs @@ -786,6 +786,54 @@ public class DaemonRuntimeDocumentTests Assert.AreEqual(3.5, poses[1].GravityWaveSpeed, 0.0001); } + [Test] + public void DaemonRenderQueriesPublishAsteroidBeltPosesFromZoneSnapshot() + { + var zone = new AetheriaRuntimeZoneSnapshotCommit + { + Orbits = new[] + { + new AetheriaRuntimeOrbitSnapshotCommit + { + OrbitKey = "orbit:belt", + FixedPositionX = 11, + FixedPositionY = -4 + } + }, + Bodies = new[] + { + new AetheriaRuntimeBodySnapshotCommit + { + BodyKey = "body:belt", + OrbitKey = "orbit:belt", + Kind = "asteroid_belt", + Asteroids = new[] + { + new AetheriaRuntimeAsteroidCommit { Distance = 3 }, + new AetheriaRuntimeAsteroidCommit { Distance = 7 }, + new AetheriaRuntimeAsteroidCommit { Distance = 5 } + } + }, + new AetheriaRuntimeBodySnapshotCommit + { + BodyKey = "body:planet", + OrbitKey = "orbit:belt", + Kind = "planet" + } + } + }; + + var poses = AetheriaRuntimeDaemonRenderQueries.QueryAsteroidBeltPoses(zone); + + Assert.AreEqual(1, poses.Length); + Assert.AreEqual("body:belt", poses[0].BodyKey); + Assert.AreEqual("orbit:belt", poses[0].OrbitKey); + Assert.AreEqual(11, poses[0].CenterX, 0.0001); + Assert.AreEqual(-4, poses[0].CenterZ, 0.0001); + Assert.AreEqual(7, poses[0].Radius, 0.0001); + Assert.AreEqual(3, poses[0].AsteroidCount); + } + [Test] public void SoaViewIndexRejectsInvalidRenderGroups() { diff --git a/Assets/Scripts/Zone Display/ZoneRenderer.cs b/Assets/Scripts/Zone Display/ZoneRenderer.cs index 255fa9f51..2e82330f2 100644 --- a/Assets/Scripts/Zone Display/ZoneRenderer.cs +++ b/Assets/Scripts/Zone Display/ZoneRenderer.cs @@ -89,6 +89,10 @@ public class ZoneRenderer : MonoBehaviour private readonly List _daemonBodyPoses = new List(); private readonly Dictionary _daemonBodyPosesByBodyKey = new Dictionary(StringComparer.Ordinal); + private readonly List _daemonAsteroidBeltPoses = + new List(); + private readonly Dictionary _daemonAsteroidBeltPosesByBodyKey = + new Dictionary(StringComparer.Ordinal); public Dictionary WormholeInstances = new Dictionary(); private List _loot = new List(); @@ -470,16 +474,21 @@ public class ZoneRenderer : MonoBehaviour Shader.SetGlobalFloat("_AsteroidVerticalOffset", ActionGameManager.Instance.Settings.PlanetSettings.AsteroidVerticalOffset); RefreshDaemonBodyPoses(); + RefreshDaemonAsteroidBeltPoses(); Plane[] planes = GeometryUtility.CalculateFrustumPlanes(MainCamera); bool isVisible(Bounds bounds) => GeometryUtility.TestPlanesAABB(planes, bounds); foreach (var (key, belt) in Zone.AsteroidBelts) { - var height = GetTerrainHeight(belt.OrbitPosition); + if (!_daemonAsteroidBeltPosesByBodyKey.TryGetValue(key, out var beltPose)) + continue; + + var center = new float2((float)beltPose.CenterX, (float)beltPose.CenterZ); + var height = GetTerrainHeight(center); if(isVisible(new Bounds( - new Vector3(belt.OrbitPosition.x,height,belt.OrbitPosition.y), - new Vector3(belt.Radius * 2,100,belt.Radius * 2)))) + new Vector3(center.x,height,center.y), + new Vector3((float)beltPose.Radius * 2,100,(float)beltPose.Radius * 2)))) { var meshes = _beltMeshes[key]; var matrices = _beltMatrices[key]; @@ -503,7 +512,7 @@ public class ZoneRenderer : MonoBehaviour } if(_showAsteroidUI) - _beltObjects[key].Update(belt.Transforms, height); + _beltObjects[key].Update(belt.Transforms, center, height, (float)beltPose.Radius); } foreach (var planet in Planets) @@ -583,6 +592,17 @@ public class ZoneRenderer : MonoBehaviour } } + private void RefreshDaemonAsteroidBeltPoses() + { + AetheriaRuntimeDaemonRenderQueries.QueryAsteroidBeltPoses(_daemonZoneSnapshot, _daemonAsteroidBeltPoses); + _daemonAsteroidBeltPosesByBodyKey.Clear(); + foreach (var pose in _daemonAsteroidBeltPoses) + { + if (!string.IsNullOrWhiteSpace(pose.BodyKey)) + _daemonAsteroidBeltPosesByBodyKey[pose.BodyKey] = pose; + } + } + private float GetTerrainHeight(float2 position) { return (float)AetheriaRuntimeDaemonRenderQueries.EvaluateGravityTerrainHeight( @@ -716,9 +736,8 @@ public class AsteroidBeltUI //_collider.sharedMesh = _mesh; } - public void Update(float4[] transforms, float height) + public void Update(float4[] transforms, float2 center, float height, float radius) { - var parentPosition = _belt.OrbitPosition; for (var i = 0; i < transforms.Length; i++) { var transform = transforms[i]; @@ -730,7 +749,8 @@ public class AsteroidBeltUI _vertices[i * 4 + 3] = rotation * new Vector3(transform.w * _scale, -transform.w * _scale, 0) + position; } - _mesh.bounds = new Bounds(new Vector3(parentPosition.x, 0, parentPosition.y), Vector3.one * (_size * 2)); + var boundsRadius = Math.Max(_size, radius); + _mesh.bounds = new Bounds(new Vector3(center.x, 0, center.y), Vector3.one * (boundsRadius * 2)); _mesh.vertices = _vertices; //_collider.sharedMesh = _mesh; } diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonRenderQueries.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonRenderQueries.cs index 437bb19a8..7e70fd535 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonRenderQueries.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonRenderQueries.cs @@ -104,6 +104,32 @@ namespace GameCult.Aetheria.State.Verse public double GravityWaveSpeed { get; } } + public readonly struct AetheriaRuntimeDaemonAsteroidBeltPose + { + public AetheriaRuntimeDaemonAsteroidBeltPose( + string bodyKey, + string orbitKey, + double centerX, + double centerZ, + double radius, + int asteroidCount) + { + BodyKey = bodyKey ?? ""; + OrbitKey = orbitKey ?? ""; + CenterX = centerX; + CenterZ = centerZ; + Radius = Math.Max(0, radius); + AsteroidCount = Math.Max(0, asteroidCount); + } + + public string BodyKey { get; } + public string OrbitKey { get; } + public double CenterX { get; } + public double CenterZ { get; } + public double Radius { get; } + public int AsteroidCount { get; } + } + public static class AetheriaRuntimeDaemonRenderQueries { public static AetheriaRuntimeGravityInfluenceBrush[] QueryGravityInfluences( @@ -198,6 +224,43 @@ namespace GameCult.Aetheria.State.Verse return poses.Count; } + public static AetheriaRuntimeDaemonAsteroidBeltPose[] QueryAsteroidBeltPoses( + AetheriaRuntimeZoneSnapshotCommit? zone) + { + var poses = new List(); + QueryAsteroidBeltPoses(zone, poses); + return poses.Count == 0 ? Array.Empty() : poses.ToArray(); + } + + public static int QueryAsteroidBeltPoses( + AetheriaRuntimeZoneSnapshotCommit? zone, + List poses) + { + if (poses == null) throw new ArgumentNullException(nameof(poses)); + poses.Clear(); + if (zone == null) + return 0; + + var orbitPositions = BuildOrbitPositions(zone); + foreach (var body in zone.Bodies ?? Array.Empty()) + { + if (body == null || + !string.Equals(body.Kind, "asteroid_belt", StringComparison.OrdinalIgnoreCase) || + !TryResolveBodyCenter(body, orbitPositions, out var center)) + continue; + + poses.Add(new AetheriaRuntimeDaemonAsteroidBeltPose( + body.BodyKey, + body.OrbitKey, + center.x, + center.z, + ResolveAsteroidBeltRadius(body), + body.Asteroids?.Count ?? 0)); + } + + return poses.Count; + } + public static AetheriaRuntimeDaemonRenderGroupDocument[] QueryRenderGroups( AetheriaRuntimeDaemonSoaViewIndex? index, double minX, @@ -419,6 +482,18 @@ namespace GameCult.Aetheria.State.Verse return body.GravityWaveSpeed; } + private static double ResolveAsteroidBeltRadius(AetheriaRuntimeBodySnapshotCommit body) + { + var radius = 0.0; + foreach (var asteroid in body.Asteroids ?? Array.Empty()) + { + if (asteroid != null && asteroid.Distance > radius) + radius = asteroid.Distance; + } + + return radius; + } + private static double Clamp(double value, double min, double max) { return value < min ? min : value > max ? max : value;