Compare commits
8 Commits
9dd304811b
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
90fd0f592f | ||
|
|
c1b9543c47 | ||
|
|
36bfe5ad45 | ||
|
|
d83234c8be | ||
|
|
4446bd778f | ||
|
|
cb06980cfb | ||
|
|
d483cd0660 | ||
|
|
5da42c119e |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -64,3 +64,4 @@ Thumbs.db
|
|||||||
|
|
||||||
## Avalonia
|
## Avalonia
|
||||||
*.DotSettings.user
|
*.DotSettings.user
|
||||||
|
publish/
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -133,53 +133,42 @@ public sealed class SnakeRenderer : Control
|
|||||||
|
|
||||||
var t = InterpolationT;
|
var t = InterpolationT;
|
||||||
var segmentsList = Segments;
|
var segmentsList = Segments;
|
||||||
var prevList = PreviousSegments;
|
var count = segmentsList.Count;
|
||||||
|
|
||||||
// Build path for body (all segments except head)
|
// === Body path: from interp tail → grid tail (virtual corner anchor) → body → head ===
|
||||||
|
// The virtual grid-snapped tail prevents diagonal shortcuts at corners:
|
||||||
|
// the short leg from interp_tail to grid_tail is always straight (same direction),
|
||||||
|
// and from grid_tail onward the path strictly follows the grid.
|
||||||
|
if (count >= 2)
|
||||||
|
{
|
||||||
var bodyGeometry = new StreamGeometry();
|
var bodyGeometry = new StreamGeometry();
|
||||||
using (var ctx = bodyGeometry.Open())
|
using (var ctx = bodyGeometry.Open())
|
||||||
{
|
{
|
||||||
for (var i = segmentsList.Count - 1; i >= 1; i--)
|
// Start at interpolated tail
|
||||||
{
|
var (tailX, tailY) = GetVisualPosition(segmentsList[count - 1], count - 1, t);
|
||||||
var current = segmentsList[i];
|
ctx.BeginFigure(new Point(tailX, tailY), false);
|
||||||
var (vx, vy) = GetVisualPosition(current, i, t);
|
|
||||||
|
|
||||||
if (i == segmentsList.Count - 1 && segmentsList.Count > 2)
|
// Virtual corner anchor: grid-snapped tail position
|
||||||
{
|
var lastPos = segmentsList[count - 1];
|
||||||
ctx.BeginFigure(new Point(vx, vy), false);
|
var (gridTailX, gridTailY) = (lastPos.X * CellSize + CellSize * 0.5, lastPos.Y * CellSize + CellSize * 0.5);
|
||||||
}
|
ctx.LineTo(new Point(gridTailX, gridTailY));
|
||||||
else if (i == segmentsList.Count - 1)
|
|
||||||
{
|
|
||||||
ctx.BeginFigure(new Point(vx, vy), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw line segment
|
// Draw through middle segments down to head
|
||||||
if (i > 1)
|
for (var i = count - 2; i >= 0; i--)
|
||||||
{
|
{
|
||||||
var (px, py) = GetVisualPosition(segmentsList[i - 1], i - 1, t);
|
var (px, py) = GetVisualPosition(segmentsList[i], i, t);
|
||||||
ctx.LineTo(new Point(px, py));
|
ctx.LineTo(new Point(px, py));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (segmentsList.Count >= 2)
|
// Body thickness
|
||||||
{
|
|
||||||
var (hx, hy) = GetVisualPosition(segmentsList[0], 0, t);
|
|
||||||
ctx.LineTo(new Point(hx, hy));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate body thickness based on segment position (tail thinner)
|
|
||||||
var maxThickness = CellSize * 0.7;
|
var maxThickness = CellSize * 0.7;
|
||||||
var minThickness = CellSize * 0.35;
|
var bodyThickness = Math.Max(CellSize * 0.35, maxThickness * 0.85);
|
||||||
|
var bodyPen = new Pen(SnakeBodyBrush, bodyThickness,
|
||||||
// Draw body as a thick stroke
|
lineCap: PenLineCap.Round, lineJoin: PenLineJoin.Round);
|
||||||
var bodyThickness = Math.Max(minThickness, maxThickness * 0.85);
|
|
||||||
var bodyPen = new Pen(SnakeBodyBrush, bodyThickness, lineCap: PenLineCap.Round, lineJoin: PenLineJoin.Round);
|
|
||||||
context.DrawGeometry(null, bodyPen, bodyGeometry);
|
context.DrawGeometry(null, bodyPen, bodyGeometry);
|
||||||
|
|
||||||
// Draw a slightly thinner inner stroke for gradient effect
|
// Inner highlight stroke
|
||||||
if (segmentsList.Count >= 2)
|
|
||||||
{
|
|
||||||
var innerPen = new Pen(
|
var innerPen = new Pen(
|
||||||
new SolidColorBrush(SnakeHeadBrush.Color, 0.4),
|
new SolidColorBrush(SnakeHeadBrush.Color, 0.4),
|
||||||
bodyThickness * 0.5,
|
bodyThickness * 0.5,
|
||||||
@@ -376,11 +365,19 @@ public sealed class SnakeRenderer : Control
|
|||||||
if (Segments == null)
|
if (Segments == null)
|
||||||
return (pos.X * CellSize + CellSize * 0.5, pos.Y * CellSize + CellSize * 0.5);
|
return (pos.X * CellSize + CellSize * 0.5, pos.Y * CellSize + CellSize * 0.5);
|
||||||
|
|
||||||
|
var isHead = segmentIndex == 0;
|
||||||
|
var isTail = Segments.Count > 1 && segmentIndex == Segments.Count - 1;
|
||||||
|
|
||||||
|
// Head and tail interpolate smoothly; middle segments stay grid-snapped
|
||||||
|
if (!isHead && !isTail)
|
||||||
|
return (pos.X * CellSize + CellSize * 0.5, pos.Y * CellSize + CellSize * 0.5);
|
||||||
|
|
||||||
var from = PreviousSegments != null && PreviousSegments.Count > 0
|
var from = PreviousSegments != null && PreviousSegments.Count > 0
|
||||||
? AnimationHelper.GetPreviousPosition(segmentIndex, Segments, PreviousSegments, SnakeDirection)
|
? AnimationHelper.GetPreviousPosition(segmentIndex, Segments, PreviousSegments, SnakeDirection)
|
||||||
: pos;
|
: pos;
|
||||||
|
|
||||||
var useEasing = segmentIndex == 0; // Head gets easing for snappy feel
|
// Head: ease-out forward. Tail: ease-in backward (mirrored).
|
||||||
return AnimationHelper.InterpolatePosition(from, pos, t, CellSize, useEasing);
|
var easingMode = isHead ? "easeOut" : "easeIn";
|
||||||
|
return AnimationHelper.InterpolatePosition(from, pos, t, CellSize, easing: easingMode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user