feat: tail interpolates like head (mirrored with ease-in)
Tail now uses the same approach as head: interpolated in the body path from its previous position, with ease-in (mirror of head's ease-out). Ghost circle removed — no longer needed since tail is naturally smooth. Added EaseInCubic to AnimationHelper. InterpolatePosition now uses string easing mode instead of bool.
This commit is contained in:
@@ -26,7 +26,16 @@ public static class AnimationHelper
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Interpolates between two positions with given t value.
|
/// Ease-in cubic — mirror of EaseOutCubic, for tail interpolation.
|
||||||
|
/// </summary>
|
||||||
|
public static double EaseInCubic(double t)
|
||||||
|
{
|
||||||
|
t = Math.Clamp(t, 0.0, 1.0);
|
||||||
|
return t * t * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Interpolates between two positions with given t value and easing mode.
|
||||||
/// Returns the visual position for rendering.
|
/// Returns the visual position for rendering.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static (double X, double Y) InterpolatePosition(
|
public static (double X, double Y) InterpolatePosition(
|
||||||
@@ -34,9 +43,14 @@ public static class AnimationHelper
|
|||||||
Position to,
|
Position to,
|
||||||
double t,
|
double t,
|
||||||
double cellSize,
|
double cellSize,
|
||||||
bool useEasing = false)
|
string easing = "linear")
|
||||||
{
|
{
|
||||||
var eased = useEasing ? EaseOutCubic(t) : t;
|
var eased = easing switch
|
||||||
|
{
|
||||||
|
"easeOut" => EaseOutCubic(t),
|
||||||
|
"easeIn" => EaseInCubic(t),
|
||||||
|
_ => t
|
||||||
|
};
|
||||||
var x = from.X + (to.X - from.X) * eased;
|
var x = from.X + (to.X - from.X) * eased;
|
||||||
var y = from.Y + (to.Y - from.Y) * eased;
|
var y = from.Y + (to.Y - from.Y) * eased;
|
||||||
return (x * cellSize + cellSize * 0.5, y * cellSize + cellSize * 0.5);
|
return (x * cellSize + cellSize * 0.5, y * cellSize + cellSize * 0.5);
|
||||||
|
|||||||
@@ -172,28 +172,6 @@ public sealed class SnakeRenderer : Control
|
|||||||
context.DrawGeometry(null, innerPen, bodyGeometry);
|
context.DrawGeometry(null, innerPen, bodyGeometry);
|
||||||
}
|
}
|
||||||
|
|
||||||
// === Fading ghost circle at old tail position ===
|
|
||||||
// Creates the illusion of smooth tail movement without affecting
|
|
||||||
// the body path geometry (which stays grid-snapped with sharp corners).
|
|
||||||
if (count >= 2 && PreviousSegments != null && PreviousSegments.Count > 0)
|
|
||||||
{
|
|
||||||
var opacity = 0.4 * (1.0 - t);
|
|
||||||
if (opacity > 0)
|
|
||||||
{
|
|
||||||
var oldTailPos = PreviousSegments[PreviousSegments.Count - 1];
|
|
||||||
var currentTailPos = segmentsList[count - 1];
|
|
||||||
|
|
||||||
var (ghostX, ghostY) = AnimationHelper.InterpolatePosition(
|
|
||||||
oldTailPos, currentTailPos, t, CellSize, useEasing: false);
|
|
||||||
|
|
||||||
context.DrawEllipse(
|
|
||||||
new SolidColorBrush(SnakeBodyBrush.Color, opacity),
|
|
||||||
null,
|
|
||||||
new Point(ghostX, ghostY),
|
|
||||||
CellSize * 0.35, CellSize * 0.35);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw head
|
// Draw head
|
||||||
DrawHead(context, t);
|
DrawHead(context, t);
|
||||||
|
|
||||||
@@ -379,17 +357,22 @@ public sealed class SnakeRenderer : Control
|
|||||||
|
|
||||||
private (double X, double Y) GetVisualPosition(Position pos, int segmentIndex, double t)
|
private (double X, double Y) GetVisualPosition(Position pos, int segmentIndex, double t)
|
||||||
{
|
{
|
||||||
// Head (index 0): interpolated with easing for smooth movement
|
if (Segments == null)
|
||||||
if (segmentIndex == 0)
|
return (pos.X * CellSize + CellSize * 0.5, pos.Y * CellSize + CellSize * 0.5);
|
||||||
{
|
|
||||||
var from = PreviousSegments != null && PreviousSegments.Count > 0
|
|
||||||
? AnimationHelper.GetPreviousPosition(0, Segments!, PreviousSegments, SnakeDirection)
|
|
||||||
: pos;
|
|
||||||
|
|
||||||
return AnimationHelper.InterpolatePosition(from, pos, t, CellSize, useEasing: true);
|
var isHead = segmentIndex == 0;
|
||||||
}
|
var isTail = Segments.Count > 1 && segmentIndex == Segments.Count - 1;
|
||||||
|
|
||||||
// All other segments (body and tail): grid-snapped for sharp corners
|
// Head and tail interpolate smoothly; middle segments stay grid-snapped
|
||||||
return (pos.X * CellSize + CellSize * 0.5, pos.Y * CellSize + CellSize * 0.5);
|
if (!isHead && !isTail)
|
||||||
|
return (pos.X * CellSize + CellSize * 0.5, pos.Y * CellSize + CellSize * 0.5);
|
||||||
|
|
||||||
|
var from = PreviousSegments != null && PreviousSegments.Count > 0
|
||||||
|
? AnimationHelper.GetPreviousPosition(segmentIndex, Segments, PreviousSegments, SnakeDirection)
|
||||||
|
: pos;
|
||||||
|
|
||||||
|
// Head: ease-out forward. Tail: ease-in backward (mirrored).
|
||||||
|
var easingMode = isHead ? "easeOut" : "easeIn";
|
||||||
|
return AnimationHelper.InterpolatePosition(from, pos, t, CellSize, easing: easingMode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user