using System.Linq; using System.Numerics; using System.Runtime.CompilerServices; using Content.Client._White.Trail.SplineRenderer; using Content.Shared._White.Spline; using Content.Shared._White.Spline.Linear; using Content.Shared._White.Trail; using Robust.Client.Graphics; using Robust.Shared.Map; using Robust.Shared.Random; using Vector4 = Robust.Shared.Maths.Vector4; namespace Content.Client._White.Trail.Line; public sealed class TrailSpline : ITrailLine { private static readonly IRobustRandom Random = IoCManager.Resolve(); [ViewVariables] private readonly LinkedList _segments = new(); [ViewVariables] private Vector2 _lastCreationPos; [ViewVariables] private float _curLifetime; [ViewVariables] private Vector2? _virtualSegmentPos; [ViewVariables] public MapId MapId { get; set; } [ViewVariables] public bool Attached { get; set; } [ViewVariables] public ITrailSettings Settings { get; set; } = TrailSettings.Default; [ViewVariables] public ISpline SplineIterator { get; set; } = new SplineLinear2D(); [ViewVariables] public ISpline GradientIterator { get; set; } = new SplineLinear4D(); [ViewVariables] public ITrailSplineRenderer Renderer { get; set; } = new TrailSplineRendererDebug(); [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool HasSegments() { return _segments.Count > 0; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void AddLifetime(float time) { _curLifetime += time; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ResetLifetime() { _curLifetime = 0f; } public void TryCreateSegment((Vector2 WorldPosition, Angle WorldRotation) worldPosRot, MapId mapId) { if (!Attached) return; if (mapId != MapId) return; if (worldPosRot.WorldPosition == Vector2.Zero) return; var pos = worldPosRot.WorldPosition + worldPosRot.WorldRotation.RotateVec(Settings.CreationOffset); _lastCreationPos = pos; if (_virtualSegmentPos.HasValue) { var vPos = _virtualSegmentPos.Value; if ((vPos - pos).LengthSquared() > Settings.СreationDistanceThresholdSquared) { _segments.AddLast(new TrailSplineSegment() { Position = vPos, ExistTil = _curLifetime + Settings.Lifetime }); _virtualSegmentPos = null; } return; } var lastPos = _segments.Last?.Value.Position; if (!lastPos.HasValue || (lastPos.Value - pos).LengthSquared() > Settings.СreationDistanceThresholdSquared) _virtualSegmentPos = pos; } public void UpdateSegments(float dt) { var gravity = Settings.Gravity; var maxRandomWalk = Settings.MaxRandomWalk; var lifetime = Settings.Lifetime; if (_segments.Last != null) { var i = 0; var positions = new Vector2[_segments.Count + 1]; positions[_segments.Count] = _lastCreationPos; var curNode = _segments.First; while (curNode != null) { var offset = gravity; var curValue = curNode.Value; if (maxRandomWalk != Vector2.Zero) { positions[i] = curValue.Position; if (curNode.Next != null) { positions[i + 1] = curNode.Next.Value.Position; if (curNode.Next.Next != null) positions[i + 2] = curNode.Next.Next.Value.Position; } var effectiveRandomWalk = maxRandomWalk * (curValue.ExistTil - _curLifetime) / lifetime; var gradientNorm = -SplineIterator.SampleVelocity(positions, i).Normalized(); offset += gradientNorm * effectiveRandomWalk.Y * Random.NextFloat(-1.0f, 1.0f); var rotated90Degrees = new Vector2(-gradientNorm.Y, gradientNorm.X); offset += rotated90Degrees * effectiveRandomWalk.X * Random.NextFloat(-1.0f, 1.0f); } curValue.Position += offset; i++; curNode = curNode.Next; } } if (_virtualSegmentPos.HasValue) _virtualSegmentPos = _virtualSegmentPos.Value + gravity; if (!Attached) _lastCreationPos += gravity; } public void RemoveExpiredSegments() { while (_segments.First?.Value.ExistTil < _curLifetime) { _segments.RemoveFirst(); } } public void Render(DrawingHandleWorld handle, Texture? texture) { if (_segments.Last == null) return; var arrSize = _segments.Count + 1; var paPositions = new Vector2[arrSize]; var paLifetimes = new float[arrSize]; paPositions[0] = _lastCreationPos; paLifetimes[0] = 1f; var reversedIndexedSegments = _segments.Reverse().Select((x, i) => (x, i + 1)); foreach (var (x, i) in reversedIndexedSegments) { paPositions[i] = x.Position; paLifetimes[i] = (x.ExistTil - _curLifetime) / Settings.Lifetime; } Renderer.Render(handle, texture, SplineIterator, GradientIterator, Settings, paPositions, paLifetimes); } private sealed class TrailSplineSegment { public Vector2 Position { get; set; } public float ExistTil { get; init; } } }