diff --git a/Assets/Scripts/Gameplay/ActionGameManager.cs b/Assets/Scripts/Gameplay/ActionGameManager.cs index 033060d7e..e24a6e395 100644 --- a/Assets/Scripts/Gameplay/ActionGameManager.cs +++ b/Assets/Scripts/Gameplay/ActionGameManager.cs @@ -1174,15 +1174,6 @@ public class ActionGameManager : MonoBehaviour } } - // A chunk's pose is planar (Zone.ChunkPose), read at the drawn time so the reticle sits on the drawn rock; the renderer - // draws it at the asteroid layer's height. - private float3 ChunkWorldPosition(ChunkId chunk) - { - var zone = CurrentEntity.Zone; - var pose = zone.ChunkPose(chunk.Field, chunk.Index, DrawAhead.Time(zone, Clock.Lead)); - return float3(pose.x, Settings.PlanetSettings.AsteroidVerticalOffset, pose.y); - } - // The one way a run ends, won or lost: the run is cleared and the main menu returns. private void EndRun() { @@ -1465,7 +1456,7 @@ public class ActionGameManager : MonoBehaviour ViewDot.Target = ZoneRenderer.EntityInstances[CurrentEntity].LookAtPoint.position; var target = CurrentEntity.Target.Value; if (target.Chunk is ChunkId chunk) - TargetIndicator.Target = ChunkWorldPosition(chunk).ToUnity(); + TargetIndicator.Target = DrawAhead.Chunk(CurrentEntity.Zone, Settings.GameplaySettings, chunk, lead).ToUnity(); else if (target.Entity != null) TargetIndicator.Target = DrawAhead.Position(target.Entity, lead).ToUnity(); var distance = length(ViewDot.Target.ToCultMath() - ownDrawn); diff --git a/Assets/Scripts/ServerShared/DrawAhead.cs b/Assets/Scripts/ServerShared/DrawAhead.cs index c3cde0ef3..3f2c870bc 100644 --- a/Assets/Scripts/ServerShared/DrawAhead.cs +++ b/Assets/Scripts/ServerShared/DrawAhead.cs @@ -29,6 +29,16 @@ public static class DrawAhead // The drawn height of a floater (loot, mines): the field at the drawn time plus the floater layer. public static float FloaterHeight(Zone zone, GameplaySettings settings, float2 planar, float lead) => Height(zone, planar, lead) + settings.FloaterLayerHeight; + // A belt rock's drawn position (ruling asteroids-on-floater-layer): its planar pose at the drawn time, on the floater layer over the field. + // Open question asteroid-clearance: the rock centre sits at the layer. ChunkHeight is the one line that changes if its top should (pose.w is the radius). + public static float ChunkHeight(Zone zone, GameplaySettings settings, float4 pose, float lead) => FloaterHeight(zone, settings, pose.xy, lead); + + public static float3 Chunk(Zone zone, GameplaySettings settings, ChunkId chunk, float lead) + { + var pose = zone.ChunkPose(chunk.Field, chunk.Index, Time(zone, lead)); + return float3(pose.x, ChunkHeight(zone, settings, pose, lead), pose.y); + } + // A ship in a wormhole animation has its pose scripted by the step, not integrated from Velocity and TurnRate, // so it is drawn where the step put it: extrapolating a scripted pose draws a streak that resets every step. // A child is drawn where its parent is drawn, as the step copies the parent's pose onto it. diff --git a/Assets/Scripts/Zone Display/ZoneRenderer.cs b/Assets/Scripts/Zone Display/ZoneRenderer.cs index 3c584c956..ae2f106b7 100644 --- a/Assets/Scripts/Zone Display/ZoneRenderer.cs +++ b/Assets/Scripts/Zone Display/ZoneRenderer.cs @@ -533,7 +533,6 @@ public class ZoneRenderer : MonoBehaviour // SlimeRenderer.SpawnPositions[i] = _suns[i].Body.transform.position.Flatland(); // } - Shader.SetGlobalFloat("_AsteroidVerticalOffset", ActionGameManager.Instance.Settings.PlanetSettings.AsteroidVerticalOffset); Plane[] planes = GeometryUtility.CalculateFrustumPlanes(MainCamera); bool isVisible(Bounds bounds) => GeometryUtility.TestPlanesAABB(planes, bounds); @@ -559,7 +558,7 @@ public class ZoneRenderer : MonoBehaviour { var tx = t + i * count; var transform = transforms[tx]; - matrices[i][t] = Matrix4x4.TRS(new Vector3(transform.x,0,transform.y), + matrices[i][t] = Matrix4x4.TRS(new Vector3(transform.x,DrawAhead.ChunkHeight(Zone,ItemManager.GameplaySettings,transform,lead),transform.y), Quaternion.Euler( cos(transform.z + (float)i / meshes.Length) * 100, sin(transform.z + (float)i / meshes.Length) * 100, diff --git a/tests/Aetheria.Shared.Tests/DrawnPoseTests.cs b/tests/Aetheria.Shared.Tests/DrawnPoseTests.cs index acc7f8f72..8ed638905 100644 --- a/tests/Aetheria.Shared.Tests/DrawnPoseTests.cs +++ b/tests/Aetheria.Shared.Tests/DrawnPoseTests.cs @@ -29,6 +29,7 @@ public sealed class DrawnPoseTests : IDisposable private const float StationDistance = 3f, StationPhase = .4f; private const float ShipLayerHeight = 2.5f; private const float OrbitalLayerHeight = 7.5f; + private const float FloaterLayerHeight = -1.25f; // The sun's rail is parentless and off-centre, the shape ZoneGenerator gives a cluster root (FixedPosition = cluster centre). private static readonly float2 SunCentre = new float2(700f, -300f); @@ -88,7 +89,7 @@ public sealed class DrawnPoseTests : IDisposable DefaultEntitySettings = new EntitySettings(), Tiers = new[] { new RarityTier { Name = "Common", Quality = .5f, Rarity = 0, Color = new float3(1, 1, 1) } }, QualityPriceModifier = new ExponentialLerp(), - ShipLayerHeight = ShipLayerHeight, OrbitalLayerHeight = OrbitalLayerHeight + ShipLayerHeight = ShipLayerHeight, OrbitalLayerHeight = OrbitalLayerHeight, FloaterLayerHeight = FloaterLayerHeight }, _ => { }); CultRecordKey Orbit(CultRecordKey parent, float distance, float phase, float2 fixedPosition = default) @@ -347,6 +348,32 @@ public sealed class DrawnPoseTests : IDisposable } } + // The reticle (DrawAhead.Chunk) and the renderer's per-rock instance (DrawAhead.ChunkHeight on EvaluateBelt's drawn pose) + // are one height: the field at the rock's planar position at the drawn time, plus the floater layer. Ruling asteroids-on-floater-layer. + [Fact] + public void AChunkIsDrawnOnTheFloaterLayerOverTheField() + { + var w = Build(); + Step(w, 1f / 60f, 301); + const float lead = .01f; + var time = w.Zone.ExactTime + lead; + var drawn = new float4[w.Asteroids.Length]; + w.Zone.EvaluateBelt(w.Belt, drawn, time); + var settings = w.Items.GameplaySettings; + + var deep = 0; + for (var i = 0; i < w.Asteroids.Length; i++) + { + var reticle = DrawAhead.Chunk(w.Zone, settings, new ChunkId(w.Belt, i), lead); + var field = FieldAt(w, drawn[i].xy, time); + AssertNear(drawn[i].xy, reticle.xz, 1e-3f, $"chunk {i} reticle xz"); + Assert.Equal(field + FloaterLayerHeight, reticle.y, 3); + Assert.Equal(reticle.y, DrawAhead.ChunkHeight(w.Zone, settings, drawn[i], lead), 4); + if (abs(field) > 1f) deep++; + } + Assert.True(deep > 0, "some rock sits where the field is more than a unit from zero, so a dropped field fails"); + } + // The term the shader takes sits on its node's rail at the drawn time: the lowering copies DrawAhead.Orbit of the term's // node and decides nothing, so a drawn term and the drawn planet share one pose at a non-zero lead. [Fact]