using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Drawing2D; using System.Numerics; using System.Windows.Forms; using System.Diagnostics; namespace ParsonsLoveheartAnimation { public partial class ParticleCanvas : Form { private List _particles = new List(); private System.Windows.Forms.Timer _renderTimer; private Random _random = new Random(); private DateTime _startTime; public ParticleCanvas() { this.Text = "WinForms Particle Tweening"; this.Size = new Size(800, 600); this.BackColor = Color.FromArgb(20, 20, 20); // Step 1: Enable Double Buffering to prevent tearing/flickering this.DoubleBuffered = true; InitializeParticles(); // Step 4: The Render Loop _startTime = DateTime.Now; _renderTimer = new System.Windows.Forms.Timer(); _renderTimer.Interval = 16; // ~60 FPS _renderTimer.Tick += (s, e) => this.Invalidate(); // Triggers the Paint event _renderTimer.Start(); } private void InitializeParticles() { // Step 2: Mocking the SVG Path using GraphicsPath GraphicsPath path = new GraphicsPath(); // Adding a simple curve to represent the SVG path path.AddBezier(100, 300, 300, 100, 500, 500, 700, 300); path.Flatten(null, 0.25f); // Flattens curve into points PointF[] pathPoints = path.PathPoints; // Center points based on the JS code (600/2, 552/2) Vector3 startPos = new Vector3(300f, 276f, 0f); // Step 3: Iterate through points (Simulating the JS for-loop) for (int i = 0; i < pathPoints.Length; i++) { PointF p = pathPoints[i]; // Construct final vector with random noise Vector3 targetPos = new Vector3(p.X, p.Y, 0); targetPos.X += ((float)_random.NextDouble() - 0.5f) * 30f; targetPos.Y += ((float)_random.NextDouble() - 0.5f) * 30f; targetPos.Z += ((float)_random.NextDouble() - 0.5f) * 70f; float delay = i * 0.05f; // Scaled up slightly from 0.002 for visibility float duration = (float)(_random.NextDouble() * 3.0 + 2.0); // Random 2 to 5 seconds _particles.Add(new Particle { StartPosition = startPos, TargetPosition = targetPos, CurrentPosition = startPos, // Starts at the origin Delay = delay, Duration = duration }); } } protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); Graphics g = e.Graphics; g.SmoothingMode = SmoothingMode.AntiAlias; float elapsedTotalTime = (float)(DateTime.Now - _startTime).TotalSeconds; // Simple Painter's Algorithm: Sort by Z-axis so items in front draw last _particles.Sort((a, b) => a.CurrentPosition.Z.CompareTo(b.CurrentPosition.Z)); foreach (var p in _particles) { // Calculate how far along the animation timeline we are (0.0 to 1.0) float timePassed = elapsedTotalTime - p.Delay; float progress = 0f; if (timePassed > 0) { progress = Math.Min(timePassed / p.Duration, 1f); } // Apply power2.inOut Easing float easeProgress = EaseInOutQuad(progress); // Lerp Current Position p.CurrentPosition = Vector3.Lerp(p.StartPosition, p.TargetPosition, easeProgress); // Simulate 3D Depth via projection scaling // Z goes roughly from -35 to +35 based on the noise logic float scale = 1.0f + (p.CurrentPosition.Z / 100f); float size = Math.Max(1f, 4f * scale); // Base size of 4 pixels // Draw the point using (SolidBrush brush = new SolidBrush(Color.DeepSkyBlue)) { g.FillEllipse(brush, p.CurrentPosition.X - (size / 2), p.CurrentPosition.Y - (size / 2), size, size); } } } // Mathematical equivalent of GSAP's "power2.inOut" private float EaseInOutQuad(float x) { return x < 0.5f ? 2f * x * x : 1f - (float)Math.Pow(-2f * x + 2f, 2f) / 2f; } } public class Particle { public Vector3 StartPosition; public Vector3 TargetPosition; public Vector3 CurrentPosition; public float Delay; public float Duration; } // Standard WinForms entry point //static class Program //{ // [STAThread] // static void Main() // { // Application.EnableVisualStyles(); // Application.SetCompatibleTextRenderingDefault(false); // Application.Run(new ParticleCanvas()); // } //} }