A Polyspiral is a spiral made of multiple line segments, whereby each segment is larger (or smaller) than the previous one by a given amount. Each segment also changes direction at a given angle.



Task
Animate a series of polyspirals, by drawing a complete spiral then incrementing the angle, and (after clearing the background) drawing the next, and so on. Every spiral will be a frame of the animation. The animation may stop as it goes full circle or continue indefinitely. The given input values may be varied.

If animation is not practical in your programming environment, you may show a single frame instead.



Pseudo code
    set incr to 0.0

    // animation loop
    WHILE true 

        incr = (incr + 0.05) MOD 360
        x = width / 2
        y = height / 2
        length = 5
        angle = incr

        // spiral loop
        FOR 1 TO 150
            drawline
            change direction by angle
            length = length + 3
            angle = (angle + incr) MOD 360
        ENDFOR
    



# Pendulum simulation.
 
import math, random
 
import gintro/[gobject, gdk, gtk, gio, glib, cairo]
 
const
  Width = 500
  Height = 500
  DrawIters = 72
  Red = [float 1, 0, 0]
  Green = [float 0, 1, 0]
  Blue = [float 0, 0, 1]
  Black = [float 0, 0, 0]
  White = [float 255, 255, 255]
  Gold = [float 255, 215, 0]
  Colors = [Blue, Red, Green, White, Gold]
  Angles = [75, 100, 135, 160]
 
type
 
  Vec2 = tuple[x, y: float]
  Point = Vec2
 
  # Description of the simulation.
  Simulation = ref object
    area: DrawingArea
    xmax, ymax: float
    center: Point
    itercount: int
 
#---------------------------------------------------------------------------------------------------
 
proc newSimulation(area: DrawingArea; width, height: int): Simulation {.noInit.} =
  ## Allocate and initialize the simulation object.
 
  new(result)
  result.area = area
  result.xmax = float(width - 1)
  result.ymax = float(height - 1)
  result.center = (result.xmax * 0.5, result.ymax * 0.5)
 
#---------------------------------------------------------------------------------------------------
 
func δ(r, θ: float): Vec2 = (r * cos(degToRad(θ)), r * sin(degToRad(θ)))
 
#---------------------------------------------------------------------------------------------------
 
func nextPoint(p: Point; r, θ: float): Point =
  let dp = δ(r, θ)
  result = (p.x + dp.x, p.y + dp.y)
 
#---------------------------------------------------------------------------------------------------
 
proc draw(sim: Simulation; context: cairo.Context) =
  ## Draw the spiral.
 
  context.setSource(Black)
  context.rectangle(0, 0, sim.xmax, sim.ymax)
  context.fill()
 
  let colorIndex = sim.itercount mod Colors.len
  let color = Colors[colorIndex]
 
  var p1 = sim.center
  let δθ = Angles[sim.itercount mod Angles.len].toFloat
  var θ = δθ * rand(1.0) * 3
  var r = 5.0
  let δr = 3.0
 
  for _ in 1..DrawIters:
    let p2 = p1.nextPoint(r, θ)
    context.moveTo(p1.x, p1.y)
    context.setSource(color)
    context.lineTo(p2.x, p2.y)
    context.setLineWidth(2)
    context.stroke()
    θ += δθ
    r += δr
    p1 = p2
 
#---------------------------------------------------------------------------------------------------
 
proc update(sim: Simulation): gboolean =
  ## Update the simulation state.
 
  result = gboolean(1)
  sim.draw(sim.area.window.cairoCreate())
  inc sim.itercount
 
#---------------------------------------------------------------------------------------------------
 
proc activate(app: Application) =
  ## Activate the application.
 
  let window = app.newApplicationWindow()
  window.setSizeRequest(Width, Height)
  window.setTitle("Polyspiral")
 
  let area = newDrawingArea()
  window.add(area)
 
  let sim = newSimulation(area, Width, Height)
 
  timeoutAdd(500, update, sim)
 
  window.showAll()
 
#———————————————————————————————————————————————————————————————————————————————————————————————————
 
let app = newApplication(Application, "Rosetta.polyspiral")
discard app.connect("activate", activate)
discard app.run()