
One good way of making an animation is by simulating a physical system and illustrating the variables in that system using a dynamically changing graphical display.

The classic such physical system is a simple gravity pendulum.



Task

Create a simple physical model of a pendulum and animate it.




# Pendulum simulation.
 
import math
import times
 
import opengl
import opengl/glut
 
var
  # Simulation variables.
  lg: float         # Pendulum length.
  g: float          # Gravity (should be positive).
  currTime: Time    # Current time.
  theta0: float     # Initial angle.
  theta: float      # Current angle.
  omega: float      # Angular velocity = derivative of theta.
  accel: float      # Angular acceleration = derivative of omega.
  e: float          # Total energy.
 
#---------------------------------------------------------------------------------------------------
 
proc initSimulation(length, gravitation, start: float) =
  ## Initialize the simulation.
 
  lg = length
  g = gravitation
  currTime = getTime()
  theta0 = start                    # Initial angle for which omega = 0.
  theta = start
  omega = 0
  accel = -g / lg * sin(theta0)
  e = g * lg * (1 - cos(theta0))    # Total energy = potential energy when starting.
 
#---------------------------------------------------------------------------------------------------
 
proc elapsed(): float =
  ## Return the elapsed time since previous call, expressed in seconds.
 
  let nextTime = getTime()
  result = (nextTime - currTime).inMicroseconds.float / 1e6
  currTime = nextTime
 
#---------------------------------------------------------------------------------------------------
 
proc resize(w, h: GLsizei) =
  ## Resize the window.
 
  glViewport(0, 0, w, h)
  glMatrixMode(GL_PROJECTION)
  glLoadIdentity()
 
  glMatrixMode(GL_MODELVIEW)
  glLoadIdentity()
  glOrtho(0, GLdouble(w), GLdouble(h), 0, -1, 1)
 
#---------------------------------------------------------------------------------------------------
 
proc render() {.cdecl.} =
  ## Render the window.
 
  # Compute the position of the mass.
  var x = 320 + 300 * sin(theta)
  var y = 300 * cos(theta)
 
  resize(640, 320)
  glClear(GL_COLOR_BUFFER_BIT)
 
  # Draw the line from pivot to mass.
  glBegin(GL_LINES)
  glVertex2d(320, 0)
  glVertex2d(x, y)
  glEnd()
  glFlush()
 
  # Update theta and omega.
  let dt = elapsed()
  theta += (omega + dt * accel / 2) * dt
  omega += accel * dt
 
  # If, due to computation errors, potential energy is greater than total energy,
  # reset theta to ±theta0 and omega to 0.
  if lg * g * (1 - cos(theta)) >= e:
    theta = sgn(theta).toFloat * theta0
    omega = 0
 
  accel = -g / lg * sin(theta)
 
#---------------------------------------------------------------------------------------------------
 
proc initGfx(argc: ptr cint; argv: pointer) =
  ## Initialize OpenGL rendering.
 
  glutInit(argc, argv)
  glutInitDisplayMode(GLUT_RGB)
  glutInitWindowSize(640, 320)
  glutIdleFunc(render)
  discard glutCreateWindow("Pendulum")
  glutDisplayFunc(render)
  loadExtensions()
 
#———————————————————————————————————————————————————————————————————————————————————————————————————
 
initSimulation(length = 5, gravitation = 9.81, start = PI / 3)
 
var argc: cint = 0
initGfx(addr(argc), nil)
glutMainLoop()