Files

184 lines
5.6 KiB
C#
Raw Permalink Normal View History

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<IRobustRandom>();
[ViewVariables]
private readonly LinkedList<TrailSplineSegment> _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<Vector2> SplineIterator { get; set; } = new SplineLinear2D();
[ViewVariables]
public ISpline<Vector4> 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; }
}
}