Scripts/fourier-epicycles.ps1
# Unique Concept: Fourier epicycles drawing a path - multiple circles rotating to trace a complex curve. # Demonstrates how combining circular motions at different frequencies creates intricate patterns. $ErrorActionPreference = 'Stop' $esc = [char]27 $reset = "$esc[0m" function Convert-HsvToRgb { param( [double]$Hue, [double]$Saturation, [double]$Value ) $h = ($Hue % 1) * 6 $sector = [math]::Floor($h) $fraction = $h - $sector $p = $Value * (1 - $Saturation) $q = $Value * (1 - $fraction * $Saturation) $t = $Value * (1 - (1 - $fraction) * $Saturation) switch ($sector) { 0 { $r = $Value; $g = $t; $b = $p } 1 { $r = $q; $g = $Value; $b = $p } 2 { $r = $p; $g = $Value; $b = $t } 3 { $r = $p; $g = $q; $b = $Value } 4 { $r = $t; $g = $p; $b = $Value } default { $r = $Value; $g = $p; $b = $q } } return @([int][math]::Round($r * 255), [int][math]::Round($g * 255), [int][math]::Round($b * 255)) } function Clamp { param([double]$Value, [double]$Min, [double]$Max) if ($Value -lt $Min) { return $Min } if ($Value -gt $Max) { return $Max } return $Value } $width = 120 $height = 40 # Define epicycle parameters: radius, frequency, phase (enhanced) $epicycles = @( @{ Radius = 0.45; Freq = 1; Phase = 0 }, @{ Radius = 0.28; Freq = -3; Phase = 1.2 }, @{ Radius = 0.18; Freq = 5; Phase = 0.5 }, @{ Radius = 0.14; Freq = -7; Phase = 2.1 }, @{ Radius = 0.10; Freq = 11; Phase = 0.8 }, @{ Radius = 0.07; Freq = -13; Phase = 1.7 }, @{ Radius = 0.05; Freq = 17; Phase = 0.3 } ) # Generate the path traced by epicycles $pathPoints = @() $steps = 360 for ($step = 0; $step -lt $steps; $step++) { $t = ($step / [double]$steps) * 2 * [math]::PI $x = 0.0 $y = 0.0 foreach ($epi in $epicycles) { $angle = $t * $epi.Freq + $epi.Phase $x += $epi.Radius * [math]::Cos($angle) $y += $epi.Radius * [math]::Sin($angle) } $pathPoints += @{ X = $x; Y = $y; T = $t } } # Also calculate current epicycle positions for visualization $currentT = (([DateTime]::Now.Ticks / 10000000.0) % 10) / 10.0 * 2 * [math]::PI $circles = @() $cx = 0.0 $cy = 0.0 foreach ($epi in $epicycles) { $angle = $currentT * $epi.Freq + $epi.Phase $nextX = $cx + $epi.Radius * [math]::Cos($angle) $nextY = $cy + $epi.Radius * [math]::Sin($angle) $circles += @{ CenterX = $cx CenterY = $cy Radius = $epi.Radius EndX = $nextX EndY = $nextY } $cx = $nextX $cy = $nextY } # Create grid $grid = @{} # Draw the traced path foreach ($pt in $pathPoints) { $gridX = [int](($pt.X + 1.2) / 2.4 * ($width - 1)) $gridY = [int](($pt.Y + 0.8) / 1.6 * ($height - 1)) if ($gridX -ge 0 -and $gridX -lt $width -and $gridY -ge 0 -and $gridY -lt $height) { $key = "$gridX,$gridY" $grid[$key] = [pscustomobject]@{ Type = 'Path' T = $pt.T / (2 * [math]::PI) } } } # Draw current epicycle circles foreach ($circ in $circles) { # Draw circle outline for ($angle = 0; $angle -lt 360; $angle += 8) { $rad = $angle * [math]::PI / 180.0 $px = $circ.CenterX + $circ.Radius * [math]::Cos($rad) $py = $circ.CenterY + $circ.Radius * [math]::Sin($rad) $gridX = [int](($px + 1.2) / 2.4 * ($width - 1)) $gridY = [int](($py + 0.8) / 1.6 * ($height - 1)) if ($gridX -ge 0 -and $gridX -lt $width -and $gridY -ge 0 -and $gridY -lt $height) { $key = "$gridX,$gridY" if (-not $grid.ContainsKey($key)) { $grid[$key] = [pscustomobject]@{ Type = 'Circle' } } } } # Draw arm from center to end $steps = 20 for ($i = 0; $i -le $steps; $i++) { $t = $i / [double]$steps $px = $circ.CenterX + ($circ.EndX - $circ.CenterX) * $t $py = $circ.CenterY + ($circ.EndY - $circ.CenterY) * $t $gridX = [int](($px + 1.2) / 2.4 * ($width - 1)) $gridY = [int](($py + 0.8) / 1.6 * ($height - 1)) if ($gridX -ge 0 -and $gridX -lt $width -and $gridY -ge 0 -and $gridY -lt $height) { $key = "$gridX,$gridY" if (-not $grid.ContainsKey($key)) { $grid[$key] = [pscustomobject]@{ Type = 'Arm' } } } } } # Render for ($row = $height - 1; $row -ge 0; $row--) { $sb = [System.Text.StringBuilder]::new() for ($col = 0; $col -lt $width; $col++) { $key = "$col,$row" if ($grid.ContainsKey($key)) { $cell = $grid[$key] if ($cell.Type -eq 'Path') { $hue = ($cell.T * 0.8 + 0.1) % 1 $saturation = 0.85 $value = 0.9 $symbol = '●' } elseif ($cell.Type -eq 'Circle') { $hue = 0.55 $saturation = 0.6 $value = 0.7 $symbol = '○' } else { # Arm $hue = 0.15 $saturation = 0.7 $value = 0.6 $symbol = '─' } $rgb = Convert-HsvToRgb -Hue $hue -Saturation $saturation -Value $value $null = $sb.Append("$esc[38;2;$($rgb[0]);$($rgb[1]);$($rgb[2])m$symbol") } else { $null = $sb.Append("$esc[38;2;10;10;15m ") } } Write-Host ($sb.ToString() + $reset) } Write-Host $reset |