2024-01-28 17:32:55 +07:00
|
|
|
|
using System.Linq;
|
|
|
|
|
|
using System.Numerics;
|
|
|
|
|
|
using System.Runtime.CompilerServices;
|
|
|
|
|
|
using Content.Client._White.Trail.SplineRenderer;
|
2024-01-28 18:37:24 +07:00
|
|
|
|
using Content.Shared._White.Spline;
|
|
|
|
|
|
using Content.Shared._White.Spline.Linear;
|
|
|
|
|
|
using Content.Shared._White.Trail;
|
2024-01-24 12:58:57 +07:00
|
|
|
|
using Robust.Client.Graphics;
|
|
|
|
|
|
using Robust.Shared.Map;
|
|
|
|
|
|
using Robust.Shared.Random;
|
|
|
|
|
|
using Vector4 = Robust.Shared.Maths.Vector4;
|
|
|
|
|
|
|
2024-01-28 17:32:55 +07:00
|
|
|
|
namespace Content.Client._White.Trail.Line;
|
2024-01-24 12:58:57 +07:00
|
|
|
|
|
|
|
|
|
|
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; }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|