Animate a pendulum

Animate a pendulum
You are encouraged to solve this task according to the task description, using any language you may know.

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.

Ada

This does not use a GUI, it simply animates the pendulum and prints out the positions. If you want, you can replace the output method with graphical update methods.

X and Y are relative positions of the pendulum to the anchor.

pendulums.ads:

generic
type Float_Type is digits <>;
Gravitation : Float_Type;
package Pendulums is
type Pendulum is private;
function New_Pendulum (Length : Float_Type;
Theta0 : Float_Type) return Pendulum;
function Get_X (From : Pendulum) return Float_Type;
function Get_Y (From : Pendulum) return Float_Type;
procedure Update_Pendulum (Item : in out Pendulum; Time : in Duration);
private
type Pendulum is record
Length   : Float_Type;
Theta    : Float_Type;
X        : Float_Type;
Y        : Float_Type;
Velocity : Float_Type;
end record;
end Pendulums;


pendulums.adb:

with Ada.Numerics.Generic_Elementary_Functions;
package body Pendulums is
package Math is new Ada.Numerics.Generic_Elementary_Functions (Float_Type);

function New_Pendulum (Length : Float_Type;
Theta0 : Float_Type) return Pendulum is
Result : Pendulum;
begin
Result.Length   := Length;
Result.Theta    := Theta0 / 180.0 * Ada.Numerics.Pi;
Result.X        := Math.Sin (Theta0) * Length;
Result.Y        := Math.Cos (Theta0) * Length;
Result.Velocity := 0.0;
return Result;
end New_Pendulum;

function Get_X (From : Pendulum) return Float_Type is
begin
return From.X;
end Get_X;

function Get_Y (From : Pendulum) return Float_Type is
begin
return From.Y;
end Get_Y;

procedure Update_Pendulum (Item : in out Pendulum; Time : in Duration) is
Acceleration : constant Float_Type := Gravitation / Item.Length *
Math.Sin (Item.Theta);
begin
Item.X        := Math.Sin (Item.Theta) * Item.Length;
Item.Y        := Math.Cos (Item.Theta) * Item.Length;
Item.Velocity := Item.Velocity +
Acceleration  * Float_Type (Time);
Item.Theta    := Item.Theta +
Item.Velocity * Float_Type (Time);
end Update_Pendulum;
end Pendulums;


example main.adb:

with Ada.Text_IO;
with Ada.Calendar;
with Pendulums;

procedure Main is
package Float_Pendulum is new Pendulums (Float, -9.81);
use Float_Pendulum;
use type Ada.Calendar.Time;

My_Pendulum : Pendulum := New_Pendulum (10.0, 30.0);
Now, Before : Ada.Calendar.Time;
begin
Before := Ada.Calendar.Clock;
loop
Delay 0.1;
Now := Ada.Calendar.Clock;
Update_Pendulum (My_Pendulum, Now - Before);
Before := Now;
-- output positions relative to origin
-- replace with graphical output if wanted
Ada.Text_IO.Put_Line (" X: " & Float'Image (Get_X (My_Pendulum)) &
" Y: " & Float'Image (Get_Y (My_Pendulum)));
end loop;
end Main;

Output:
 X:  5.00000E+00 Y:  8.66025E+00
X:  4.95729E+00 Y:  8.68477E+00
X:  4.87194E+00 Y:  8.73294E+00
X:  4.74396E+00 Y:  8.80312E+00
X:  4.57352E+00 Y:  8.89286E+00
X:  4.36058E+00 Y:  8.99919E+00
X:  4.10657E+00 Y:  9.11790E+00
X:  3.81188E+00 Y:  9.24498E+00
X:  3.47819E+00 Y:  9.37562E+00
X:  3.10714E+00 Y:  9.50504E+00
X:  2.70211E+00 Y:  9.62801E+00
X:  2.26635E+00 Y:  9.73980E+00
X:  1.80411E+00 Y:  9.83591E+00
X:  1.32020E+00 Y:  9.91247E+00
X:  8.20224E-01 Y:  9.96630E+00
X:  3.10107E-01 Y:  9.99519E+00
X: -2.03865E-01 Y:  9.99792E+00
X: -7.15348E-01 Y:  9.97438E+00
X: -1.21816E+00 Y:  9.92553E+00
X: -1.70581E+00 Y:  9.85344E+00
X: -2.17295E+00 Y:  9.76106E+00
X: -2.61452E+00 Y:  9.65216E+00
X: -3.02618E+00 Y:  9.53112E+00
X: -3.40427E+00 Y:  9.40271E+00
X: -3.74591E+00 Y:  9.27190E+00
X: -4.04873E+00 Y:  9.14373E+00
X: -4.31141E+00 Y:  9.02285E+00
X: -4.53271E+00 Y:  8.91373E+00
X: -4.71186E+00 Y:  8.82034E+00
X: -4.84868E+00 Y:  8.74587E+00
X: -4.94297E+00 Y:  8.69293E+00
X: -4.99459E+00 Y:  8.66337E+00
X: -5.00352E+00 Y:  8.65822E+00
...

Amazing Hopper

Translation of: FreeBASIC
#include <flow.h>
#include <flow-term.h>

DEF-MAIN(argv,argc)

SET( Pen, 0 )
LET( Pen := STR-TO-UTF8(CHAR(219)) )

CLR-SCR
HIDE-CURSOR

GOSUB( Animate a Pendulum )

SHOW-CURSOR
END

RUTINES

DEF-FUN( Animate a Pendulum )
MSET( accel, speed, bx, by )
SET( theta, M_PI_2 )  // pi/2 constant --> flow.h
SET( g, 9.81 )
SET( l, 1 )
SET( px, 65 )
SET( py, 7 )

LOOP( Animate All )
LET( bx := ADD( px, MUL( MUL( l, 23 ), SIN(theta) ) ) )
LET( by := SUB( py, MUL( MUL( l, 23 ), COS(theta) ) ) )

CLR-SCR
{px,py,bx,by} GOSUB( LINE )
{bx, by, 3} GOSUB( CIRCLE )

LET( accel := MUL(g, SIN(theta) DIV-INTO(l) DIV-INTO(4) ) )
LET( speed := ADD( speed, DIV(accel, 100) ) )
LET( theta := ADD( theta, speed ) )
LOCATE (1, 62) PRNL("PENDULUM")
LOCATE (2, 55) PRNL("Press any key to quit")
SLEEP( 0.1 )
BACK-IF ( NOT( KEY-PRESSED? ), Animate All )

RET

/* DDA Algorithm */
DEF-FUN(LINE, x1, y1, x2, y2)
MSET( x, y, dx, dy, paso, i, gm )

STOR( SUB(x2, x1) SUB(y2, y1), dx, dy )
LET( paso := IF( GE?( ABS(dx) » (DX), ABS(dy)»(DY) ), DX, DY ) )

// increment:
STOR( DIV(dx, paso) DIV(dy, paso), dx, dy )

// print line:
SET( i, 0 )
WHILE( LE?(i, paso), ++i )
LOCATE( y1, x1 ), PRNL( Pen )
STOR( ADD( x1, dx) ADD( y1, dy ), x1, y1 )
WEND
RET

DEF-FUN( Plot Points, xc, yc ,x1 ,y1 )
LOCATE( ADD(xc,x1), ADD( yc, y1) ), PRN( Pen )
LOCATE( SUB(xc,x1), ADD( yc, y1) ), PRN( Pen )
LOCATE( ADD(xc,x1), SUB( yc, y1) ), PRN( Pen )
LOCATE( SUB(xc,x1), SUB( yc, y1) ), PRN( Pen )
LOCATE( ADD(xc,y1), ADD( yc, x1) ), PRN( Pen )
LOCATE( SUB(xc,y1), ADD( yc, x1) ), PRN( Pen )
LOCATE( ADD(xc,y1), SUB( yc, x1) ), PRN( Pen )
LOCATE( SUB(xc,y1), SUB( yc, x1) ), PRNL( Pen )
RET

DEF-FUN( CIRCLE, xc, yc, ratio )
MSET( x, p )

SET( y, ratio )
LOCATE( yc,xc ), PRNL("O")
{yc, xc, y, x} GOSUB( Plot Points )
LET( p := SUB( 1, ratio ) )
LOOP( Print Circle )
++x
COND( LT?( p, 0 ) )
LET( p := ADD( p, MUL(2,x) ) PLUS(1) )
ELS
--y
LET( p := ADD( p, MUL(2, SUB(x,y))) PLUS(1) )
CEND
{yc, xc, y, x} GOSUB( Plot Points )
BACK-IF-LT( x, y, Print Circle )
RET

Output:

PENDULUM
Press any key to quit

██
██
██
██
█
██
██
██
██  ███
██   █
███   █
█  O  █
█     █
█   █
███


FALSE MODE GRAPHICS.
You can simulate a pseudo graphical mode in an Ubuntu Linux terminal by adding the following lines:

   SYS("gsettings set org.gnome.Terminal.Legacy.Profile:/org/gnome/terminal/legacy/profiles:/:.../ font 'Ubuntu Mono 1'")

CLR-SCR
HIDE-CURSOR

GOSUB( Animate a Pendulum )

SYS("gsettings set org.gnome.Terminal.Legacy.Profile:/org/gnome/terminal/legacy/profiles:/:.../ font 'Ubuntu Mono 12'")
SHOW-CURSOR
And substituting the holding coordinates of the pendulum:

// in "Animate a Pendulum"

SET( px,  640 )//65 )
SET( py,  30 ) //7 )

// long of the line:

LET( bx := ADD( px, MUL( MUL( l, 180 ), SIN(theta) ) ) )
LET( by := SUB( py, MUL( MUL( l, 180 ), COS(theta) ) ) )

// and circle ratio:
{bx, by, 10} GOSUB( CIRCLE )

AutoHotkey

This version doesn't use an complex physics calculation - I found a faster way.

Library: GDIP
SetBatchlines,-1
;settings
SizeGUI:={w:650,h:400} ;Guisize
pendulum:={length:300,maxangle:90,speed:2,size:30,center:{x:Sizegui.w//2,y:10}} ;pendulum length, size, center, speed and maxangle

pendulum.maxangle:=pendulum.maxangle*0.01745329252
p_Token:=Gdip_Startup()
Gui,+LastFound
Gui,show,% "w" SizeGUI.w  " h" SizeGUI.h
hwnd:=WinActive()
hdc:=GetDC(hwnd)
start:=A_TickCount/1000
G:=Gdip_GraphicsFromHDC(hdc)
pBitmap:=Gdip_CreateBitmap(650, 450)
G2:=Gdip_GraphicsFromImage(pBitmap)
Gdip_SetSmoothingMode(G2, 4)
pBrush := Gdip_BrushCreateSolid(0xff0000FF)
pBrush2 := Gdip_BrushCreateSolid(0xFF777700)
pPen:=Gdip_CreatePenFromBrush(pBrush2, 10)
SetTimer,Update,10

Update:
Gdip_GraphicsClear(G2,0xFFFFFFFF)
time:=start-(A_TickCount/1000*pendulum.speed)
angle:=sin(time)*pendulum.maxangle
x2:=sin(angle)*pendulum.length+pendulum.center.x
y2:=cos(angle)*pendulum.length+pendulum.center.y
Gdip_DrawLine(G2,pPen,pendulum.center.x,pendulum.center.y,x2,y2)
GDIP_DrawCircle(G2,pBrush,pendulum.center.x,pendulum.center.y,15)
GDIP_DrawCircle(G2,pBrush2,x2,y2,pendulum.size)
Gdip_DrawImage(G, pBitmap)
return

GDIP_DrawCircle(g,b,x,y,r){
Gdip_FillEllipse(g, b, x-r//2,y-r//2 , r, r)
}

GuiClose:
ExitApp


BASIC

Applesoft BASIC

Translation of: Commodore BASIC

Two shapes are used to draw and undraw the pendulum. Undrawing and drawing is done on the page that is not being displayed to make the animation flicker free. Animation code is compacted and hoisted to the beginning of the program. Variables are defined for all non-zero values.

 0  ON  NOT T GOTO 9: FOR Q = 0 TO T STEP 0:BX = PX + L * S *  SIN (F):BY = PY - L * S *  COS (F): HCOLOR= 0: FOR I = 0 TO N(P): DRAW T + (I = N(P)) AT X(P,I),Y(P,I): NEXT I:N(P) = 0: HCOLOR= C
1  FOR X = PX TO BX STEP (BX - PX) / Z:Y = PY + (X - PX) * (BY - PY) / (BX - PX): DRAW T AT X,Y:X(P,N(P)) = X:Y(P,N(P)) = Y:N(P) = N(P) + 1: NEXT X
2  HCOLOR= T: DRAW B AT BX,BY:X(P,N(P)) = BX:Y(P,N(P)) = BY:A =  PEEK (R + P):P =  NOT P: POKE U,W + W * P:A = G *  SIN (F) / L / H:V = V + A / Z:F = F + V: NEXT Q
9  DIM N(1),X(1,11),Y(1,11): FOR P = 32 TO 64 STEP 32: POKE 230,P: HCOLOR= 0: HPLOT 0,0: CALL 62454: NEXT :R = 49236:P = ( PEEK (R) +  PEEK (49234) +  PEEK (49239) +  PEEK (49232)) * 0 + 1
10 S$= CHR$ (2) +  CHR$(0) + CHR$ (6) +  CHR$(0) + CHR$ (8) +  CHR$(0) + "-" + CHR$ (0) + ".%'?>..%" +  CHR$(0): PRINT MID$ ( STR$( FRE (0)) + S$,1,0);: POKE 236, PEEK (131): POKE 237, PEEK (132)
15 S =  PEEK (236) +  PEEK (237) * 256: POKE 232, PEEK (S + 1): POKE 233, PEEK (S + 2): SCALE= 1: ROT= 0
20 T = 1
25 F = 3.1415926535 / 2: REM THETA
30 G = 9.81
35 L = 0.5
40 V = 0: REM SPEED
45 PX = 140
50 PY = 80
55 S = 20
60 Z = 10
65 C = 3
70 B = 2
75 U = 230
80 W = 32
85 H = 50
90  GOTO

BBC BASIC

      MODE 8
*FLOAT 64
VDU 23,23,4;0;0;0; : REM Set line thickness

theta = RAD(40) : REM initial displacement
g = 9.81 : REM acceleration due to gravity
l = 0.50 : REM length of pendulum in metres

REPEAT
PROCpendulum(theta, l)
WAIT 1
PROCpendulum(theta, l)
accel = - g * SIN(theta) / l / 100
speed += accel / 100
theta += speed
UNTIL FALSE
END

DEF PROCpendulum(a, l)
LOCAL pivotX, pivotY, bobX, bobY
pivotX = 640
pivotY = 800
bobX = pivotX + l * 1000 * SIN(a)
bobY = pivotY - l * 1000 * COS(a)
GCOL 3,6
LINE pivotX, pivotY, bobX, bobY
GCOL 3,11
CIRCLE FILL bobX + 24 * SIN(a), bobY - 24 * COS(a), 24
ENDPROC


Commodore BASIC

10 GOSUB 1000
20 THETA = π/2
30 G = 9.81
40 L = 0.5
50 SPEED = 0
60 PX = 20
70 PY = 1
80 BX = PX+L*20*SIN(THETA)
90 BY = PY-L*20*COS(THETA)
100 PRINT CHR$(147); 110 FOR X=PX TO BX STEP (BX-PX)/10 120 Y=PY+(X-PX)*(BY-PY)/(BX-PX) 130 PRINT CHR$(19);LEFT$(X$,X);LEFT$(Y$,Y);"."
140 NEXT
150 PRINT CHR$(19);LEFT$(X$,BX);LEFT$(Y$,BY);CHR$(113)
160 ACCEL=G*SIN(THETA)/L/50
170 SPEED=SPEED+ACCEL/10
180 THETA=THETA+SPEED
190 GOTO 80
980 REM ** SETUP STRINGS TO BE USED **
990 REM ** FOR CURSOR POSITIONING   **
1000 FOR I=0 TO 39: X$= X$+CHR$(29): NEXT 1010 FOR I=0 TO 24: Y$ = Y$+CHR$(17): NEXT
1020 RETURN

FreeBASIC

Const PI = 3.141592920
Dim As Double theta, g, l, accel, speed, px, py, bx, by
theta = PI/2
g = 9.81
l = 1
speed = 0
px = 320
py = 10
Screen 17 '640x400 graphic
Do
bx=px+l*300*Sin(theta)
by=py-l*300*Cos(theta)
Cls
Line (px,py)-(bx,by)
Circle (bx,by),5,,,,,F
accel=g*Sin(theta)/l/100
speed=speed+accel/100
theta=theta+speed
Draw String (0,370), "Pendulum"
Draw String (0,385), "Press any key to quit"
Sleep 10
Loop Until Inkey()<>""

IS-BASIC

100 PROGRAM "Pendulum.bas"
110 LET THETA=RAD(50):LET G=9.81:LET L=.5
120 CALL INIC
130 CALL DRAWING
140 CALL ANIMATE
150 CALL RESET
160 END
170 DEF INIC
180   CLOSE #102
190   OPTION ANGLE RADIANS
200   SET STATUS OFF:SET INTERRUPT STOP OFF:SET BORDER 56
210   SET VIDEO MODE 1:SET VIDEO COLOR 1:SET VIDEO X 14:SET VIDEO Y 8
220   FOR I=1 TO 24
230     OPEN #I:"video:"
240     SET #I:PALETTE 56,0,255,YELLOW
250   NEXT
260 END DEF
270 DEF DRAWING
280   LET SPD=0
290   FOR I=1 TO 24
300     DISPLAY #I:AT 3 FROM 1 TO 8
310     SET #I:INK 2
320     PLOT #I:224,280,ELLIPSE 10,10
330     PLOT #I:0,280;214,280,234,280;446,280
340     SET #I:INK 1
350     CALL PENDULUM(THETA,L,I)
360     LET ACC=-G*SIN(THETA)/L/100
370     LET SPD=SPD+ACC/10.5
380     LET THETA=THETA+SPD
390   NEXT
400 END DEF
410 DEF PENDULUM(A,L,CH)
420   LET PX=224:LET PY=280
430   LET BX=PX+L*460*SIN(A)
440   LET BY=PY-L*460*COS(A)
450   PLOT #CH:PX,PY;BX,BY
460   PLOT #CH:BX+24*SIN(A),BY-24*COS(A),ELLIPSE 20,20,
470   SET #CH:INK 3:PLOT #CH:PAINT
480 END DEF
490 DEF ANIMATE
500   DO
510     FOR I=1 TO 24
520       DISPLAY #I:AT 3 FROM 1 TO 8
530     NEXT
540     FOR I=23 TO 2 STEP-1
550       DISPLAY #I:AT 3 FROM 1 TO 8
560     NEXT
570   LOOP UNTIL INKEY$=CHR$(27)
580 END DEF
590 DEF RESET
600   TEXT 40:SET STATUS ON:SET INTERRUPT STOP ON:SET BORDER 0
610   FOR I=24 TO 1 STEP-1
620     CLOSE #I
630   NEXT
640 END DEF

C

Library: GLUT
#include <stdlib.h>
#include <math.h>
#include <GL/glut.h>
#include <GL/gl.h>
#include <sys/time.h>

#define length 5
#define g 9.8
double alpha, accl, omega = 0, E;
struct timeval tv;

double elappsed() {
struct timeval now;
gettimeofday(&now, 0);
int ret = (now.tv_sec - tv.tv_sec) * 1000000
+ now.tv_usec - tv.tv_usec;
tv = now;
return ret / 1.e6;
}

void resize(int w, int h)
{
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glOrtho(0, w, h, 0, -1, 1);
}

void render()
{
double x = 320 + 300 * sin(alpha), y = 300 * cos(alpha);
resize(640, 320);
glClear(GL_COLOR_BUFFER_BIT);

glBegin(GL_LINES);
glVertex2d(320, 0);
glVertex2d(x, y);
glEnd();
glFlush();

double us = elappsed();
alpha += (omega + us * accl / 2) * us;
omega += accl * us;

/* don't let precision error go out of hand */
if (length * g * (1 - cos(alpha)) >= E) {
alpha = (alpha < 0 ? -1 : 1) * acos(1 - E / length / g);
omega = 0;
}
accl = -g / length * sin(alpha);
}

void init_gfx(int *c, char **v)
{
glutInit(c, v);
glutInitDisplayMode(GLUT_RGB);
glutInitWindowSize(640, 320);
glutIdleFunc(render);
glutCreateWindow("Pendulum");
}

int main(int c, char **v)
{
alpha = 4 * atan2(1, 1) / 2.1;
E = length * g * (1 - cos(alpha));

accl = -g / length * sin(alpha);
omega = 0;

gettimeofday(&tv, 0);
init_gfx(&c, v);
glutMainLoop();
return 0;
}


C#

Library: Windows Forms
using System;
using System.Drawing;
using System.Windows.Forms;

class CSharpPendulum
{
Form _form;
Timer _timer;

double _angle = Math.PI / 2,
_angleAccel,
_angleVelocity = 0,
_dt = 0.1;

int _length = 50;

[STAThread]
static void Main()
{
var p = new CSharpPendulum();
}

public CSharpPendulum()
{
_form = new Form() { Text = "Pendulum", Width = 200, Height = 200 };
_timer = new Timer() { Interval = 30 };

_timer.Tick += delegate(object sender, EventArgs e)
{
int anchorX = (_form.Width / 2) - 12,
anchorY = _form.Height / 4,
ballX = anchorX + (int)(Math.Sin(_angle) * _length),
ballY = anchorY + (int)(Math.Cos(_angle) * _length);

_angleAccel = -9.81 / _length * Math.Sin(_angle);
_angleVelocity += _angleAccel * _dt;
_angle += _angleVelocity * _dt;

Bitmap dblBuffer = new Bitmap(_form.Width, _form.Height);
Graphics g = Graphics.FromImage(dblBuffer);
Graphics f = Graphics.FromHwnd(_form.Handle);

g.DrawLine(Pens.Black, new Point(anchorX, anchorY), new Point(ballX, ballY));
g.FillEllipse(Brushes.Black, anchorX - 3, anchorY - 4, 7, 7);
g.FillEllipse(Brushes.DarkGoldenrod, ballX - 7, ballY - 7, 14, 14);

f.Clear(Color.White);
f.DrawImage(dblBuffer, new Point(0, 0));
};

_timer.Start();
Application.Run(_form);
}
}


C++

Library: wxWidgets

File wxPendulumDlg.hpp

#ifndef __wxPendulumDlg_h__
#define __wxPendulumDlg_h__

// ---------------------
/// @author Martin Ettl
/// @date   2013-02-03
// ---------------------

#ifdef __BORLANDC__
#pragma hdrstop
#endif

#ifndef WX_PRECOMP
#include <wx/wx.h>
#include <wx/dialog.h>
#else
#include <wx/wxprec.h>
#endif
#include <wx/timer.h>
#include <wx/dcbuffer.h>
#include <cmath>

class wxPendulumDlgApp : public wxApp
{
public:
bool OnInit();
int OnExit();
};

class wxPendulumDlg : public wxDialog
{
public:

wxPendulumDlg(wxWindow *parent, wxWindowID id = 1, const wxString &title = wxT("wxPendulum"),
const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize,
long style = wxSUNKEN_BORDER | wxCAPTION | wxRESIZE_BORDER | wxSYSTEM_MENU | wxDIALOG_NO_PARENT | wxMINIMIZE_BOX | wxMAXIMIZE_BOX | wxCLOSE_BOX);

virtual ~wxPendulumDlg();

// Event handler
void wxPendulumDlgPaint(wxPaintEvent& event);
void wxPendulumDlgSize(wxSizeEvent& event);
void OnTimer(wxTimerEvent& event);

private:

// a pointer to a timer object
wxTimer *m_timer;

unsigned int m_uiLength;
double  	 m_Angle;
double       m_AngleVelocity;

enum wxIDs
{
ID_WXTIMER1 = 1001,
ID_DUMMY_VALUE_
};

void OnClose(wxCloseEvent& event);
void CreateGUIControls();

DECLARE_EVENT_TABLE()
};

#endif // __wxPendulumDlg_h__


File wxPendulumDlg.cpp

// ---------------------
/// @author Martin Ettl
/// @date   2013-02-03
// ---------------------

#include "wxPendulumDlg.hpp"
#include <wx/pen.h>

IMPLEMENT_APP(wxPendulumDlgApp)

bool wxPendulumDlgApp::OnInit()
{
wxPendulumDlg* dialog = new wxPendulumDlg(NULL);
SetTopWindow(dialog);
dialog->Show(true);
return true;
}

int wxPendulumDlgApp::OnExit()
{
return 0;
}

BEGIN_EVENT_TABLE(wxPendulumDlg, wxDialog)
EVT_CLOSE(wxPendulumDlg::OnClose)
EVT_SIZE(wxPendulumDlg::wxPendulumDlgSize)
EVT_PAINT(wxPendulumDlg::wxPendulumDlgPaint)
EVT_TIMER(ID_WXTIMER1, wxPendulumDlg::OnTimer)
END_EVENT_TABLE()

wxPendulumDlg::wxPendulumDlg(wxWindow *parent, wxWindowID id, const wxString &title, const wxPoint &position, const wxSize& size, long style)
: wxDialog(parent, id, title, position, size, style)
{
CreateGUIControls();
}

wxPendulumDlg::~wxPendulumDlg()
{
}

void wxPendulumDlg::CreateGUIControls()
{
SetIcon(wxNullIcon);
SetSize(8, 8, 509, 412);
Center();

m_uiLength = 200;
m_Angle    = M_PI/2.;
m_AngleVelocity = 0;

m_timer = new wxTimer();
m_timer->SetOwner(this, ID_WXTIMER1);
m_timer->Start(20);
}

void wxPendulumDlg::OnClose(wxCloseEvent& WXUNUSED(event))
{
Destroy();
}

void wxPendulumDlg::wxPendulumDlgPaint(wxPaintEvent& WXUNUSED(event))
{
SetBackgroundStyle(wxBG_STYLE_CUSTOM);
wxBufferedPaintDC dc(this);

// Get window dimensions
wxSize sz = GetClientSize();
// determine the center of the canvas
const wxPoint center(wxPoint(sz.x / 2, sz.y / 2));

// create background color
wxColour powderblue = wxColour(176,224,230);

// draw powderblue background
dc.SetPen(powderblue);
dc.SetBrush(powderblue);
dc.DrawRectangle(0, 0, sz.x, sz.y);

// draw lines
wxPen Pen(*wxBLACK_PEN);
Pen.SetWidth(1);
dc.SetPen(Pen);
dc.SetBrush(*wxBLACK_BRUSH);

double angleAccel, dt = 0.15;

angleAccel = (-9.81 / m_uiLength) * sin(m_Angle);
m_AngleVelocity += angleAccel * dt;
m_Angle += m_AngleVelocity * dt;

int anchorX = sz.x / 2, anchorY = sz.y / 4;
int ballX = anchorX + (int)(sin(m_Angle) * m_uiLength);
int ballY = anchorY + (int)(cos(m_Angle) * m_uiLength);
dc.DrawLine(anchorX, anchorY, ballX, ballY);

dc.SetBrush(*wxGREY_BRUSH);
dc.DrawEllipse(anchorX - 3, anchorY - 4, 7, 7);

dc.SetBrush(wxColour(255,255,0)); // yellow
dc.DrawEllipse(ballX - 7, ballY - 7, 20, 20);
}

void wxPendulumDlg::wxPendulumDlgSize(wxSizeEvent& WXUNUSED(event))
{
Refresh();
}

void wxPendulumDlg::OnTimer(wxTimerEvent& WXUNUSED(event))
{
// force refresh
Refresh();
}


This program is tested with wxWidgets version 2.8 and 2.9. The whole project, including makefile for compiling on Linux can be download from github.

Clojure

Clojure solution using an atom and a separate rendering thread

Library: Swing
Library: AWT
(ns pendulum
(:import
(javax.swing JFrame)
(java.awt Canvas Graphics Color)))

(def length 200)
(def width (* 2 (+ 50 length)))
(def height (* 3 (/ length 2)))
(def dt 0.1)
(def g 9.812)
(def k (- (/ g length)))
(def anchor-x (/ width 2))
(def anchor-y (/ height 8))
(def angle (atom (/ (Math/PI) 2)))

(defn draw [#^Canvas canvas angle]
(let [buffer  (.getBufferStrategy canvas)
g       (.getDrawGraphics buffer)
ball-x (+ anchor-x (* (Math/sin angle) length))
ball-y (+ anchor-y (* (Math/cos angle) length))]
(try
(doto g
(.setColor Color/BLACK)
(.fillRect 0 0 width height)
(.setColor Color/RED)
(.drawLine anchor-x anchor-y ball-x ball-y)
(.setColor Color/YELLOW)
(.fillOval (- anchor-x 3) (- anchor-y 4) 7 7)
(.fillOval (- ball-x 7) (- ball-y 7) 14 14))
(finally (.dispose g)))
(if-not (.contentsLost buffer)
(.show buffer)) ))

(defn start-renderer [canvas]
(->>
(fn [] (draw canvas @angle) (recur))
(new Thread)
(.start)))

(defn -main [& args]
(let [frame  (JFrame. "Pendulum")
canvas (Canvas.)]

(doto frame
(.setSize width height)
(.setDefaultCloseOperation JFrame/EXIT_ON_CLOSE)
(.setResizable false)
(.add canvas)
(.setVisible true))

(doto canvas
(.createBufferStrategy 2)
(.setVisible true)
(.requestFocus))

(start-renderer canvas)

(loop [v 0]
(swap! angle #(+ % (* v dt)))
(Thread/sleep 15)
(recur (+ v (* k (Math/sin @angle) dt)))) ))

(-main)


Common Lisp

An approach using closures. Physics code adapted from Ada.

Pressing the spacebar adds a pendulum.

(defvar *frame-rate* 30)
(defvar *damping* 0.99 "Deceleration factor.")

(defun make-pendulum (length theta0 x)
"Returns an anonymous function with enclosed state representing a pendulum."
(let* ((theta (* (/ theta0 180) pi))
(acceleration 0))
(if (< length 40) (setf length 40)) ;;avoid a divide-by-zero
(lambda ()
;;Draws the pendulum, updating its location and speed.
(sdl:draw-line (sdl:point :x x :y 1)
(sdl:point :x (+ (* (sin theta) length) x)
:y (* (cos theta) length)))
(sdl:draw-filled-circle (sdl:point :x (+ (* (sin theta) length) x)
:y (* (cos theta) length))
20
:color sdl:*yellow*
:stroke-color sdl:*white*)
;;The magic constant approximates the speed we want for a given frame-rate.
(incf acceleration (* (sin theta) (* *frame-rate* -0.001)))
(incf theta acceleration)
(setf acceleration (* acceleration *damping*)))))

(defun main (&optional (w 640) (h 480))
(sdl:with-init ()
(sdl:window w h :title-caption "Pendulums"
:fps (make-instance 'sdl:fps-fixed))
(setf (sdl:frame-rate) *frame-rate*)
(let ((pendulums nil))
(sdl:with-events ()
(:quit-event () t)
(:idle ()
(sdl:clear-display sdl:*black*)
(mapcar #'funcall pendulums) ;;Draw all the pendulums

(sdl:update-display))
(:key-down-event (:key key)
(cond ((sdl:key= key :sdl-key-escape)
(sdl:push-quit-event))
((sdl:key= key :sdl-key-space)
(push (make-pendulum (random (- h 100))
(random 90)
(round w 2))
pendulums))))))))


Delphi

Library: Vcl.Forms
Translation of: C#
unit main;

interface

uses
Vcl.Forms, Vcl.Graphics, Vcl.ExtCtrls;

type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
Timer: TTimer;
angle, angleAccel, angleVelocity, dt: double;
len: Integer;
procedure Tick(Sender: TObject);
end;

var
Form1: TForm1;

implementation

!$INCLUDE="PC.LIB" PROCEDURE PENDULUM(A,L) PIVOTX=320 PIVOTY=0 BOBX=PIVOTX+L*500*SIN(a) BOBY=PIVOTY+L*500*COS(a) LINE(PIVOTX,PIVOTY,BOBX,BOBY,6,FALSE) CIRCLE(BOBX+24*SIN(A),BOBY+24*COS(A),27,11) PAUSE(0.01) LINE(PIVOTX,PIVOTY,BOBX,BOBY,0,FALSE) CIRCLE(BOBX+24*SIN(A),BOBY+24*COS(A),27,0) END PROCEDURE BEGIN SCREEN(9) THETA=40*p/180 ! initial displacement G=9.81 ! acceleration due to gravity L=0.5 ! length of pendulum in metres LINE(0,0,639,0,5,FALSE) LOOP PENDULUM(THETA,L) ACCEL=-G*SIN(THETA)/L/100 SPEED=SPEED+ACCEL/100 THETA=THETA+SPEED END LOOP END PROGRAM PC version: Ctrl+Break to stop. Euler Math Toolbox Euler Math Toolbox can determine the exact period of a physical pendulum. The result is then used to animate the pendulum. The following code is ready to be pasted back into Euler notebooks. >g=gearth$; l=1m;
>function f(x,y) := [y[2],-g*sin(y[1])/l]
>function h(a) := ode("f",linspace(0,a,100),[0,2])[1,-1]
>period=solve("h",2)
2.06071780729
>t=linspace(0,period,30); s=ode("f",t,[0,2])[1];
>function anim (t,s) ...
$setplot(-1,1,-1,1);$  markerstyle("o#");
$repeat$  for i=1 to cols(t)-1;
$clg;$    hold on;
$plot([0,sin(s[i])],[1,1-cos(s[i])]);$    mark([0,sin(s[i])],[1,1-cos(s[i])]);
$hold off;$    wait(t[i+1]-t[i]);
$end;$  until testkey();
$end$endfunction
>anim(t,s);
>


Euphoria

DOS32 version

Works with: Euphoria version 3.1.1
include graphics.e
include misc.e

constant dt = 1E-3
constant g = 50

sequence vc
sequence suspension
atom len

procedure draw_pendulum(atom color, atom len, atom alfa)
sequence point
point = (len*{sin(alfa),cos(alfa)} + suspension)
draw_line(color, {suspension, point})
ellipse(color,0,point-{10,10},point+{10,10})
end procedure

function wait()
atom t0
t0 = time()
while time() = t0 do
if get_key() != -1 then
return 1
end if
end while
return 0
end function

procedure animation()
atom alfa, omega, epsilon

if graphics_mode(18) then
end if

vc = video_config()
suspension = {vc[VC_XPIXELS]/2,vc[VC_YPIXELS]/2}
len = vc[VC_YPIXELS]/2-20

alfa = PI/2
omega = 0

while 1 do
draw_pendulum(BRIGHT_WHITE,len,alfa)
if wait() then
exit
end if
draw_pendulum(BLACK,len,alfa)
epsilon = -len*sin(alfa)*g
omega += dt*epsilon
alfa += dt*omega
end while

if graphics_mode(-1) then
end if
end procedure

animation()

F#

A nice application of F#'s support for units of measure.

open System
open System.Drawing
open System.Windows.Forms

// define units of measurement
[<Measure>] type m;  // metres
[<Measure>] type s;  // seconds

// a pendulum is represented as a record of physical quantities
type Pendulum =
{ length   : float<m>
gravity  : float<m/s^2>
velocity : float<m/s>
angle    : float
}

// calculate the next state of a pendulum
let next pendulum deltaT : Pendulum =
let k = -pendulum.gravity / pendulum.length
let acceleration = k * Math.Sin pendulum.angle * 1.0<m>
let newVelocity = pendulum.velocity + acceleration * deltaT
let newAngle = pendulum.angle + newVelocity * deltaT / 1.0<m>
{ pendulum with velocity = newVelocity; angle = newAngle }

// paint a pendulum (using hard-coded screen coordinates)
let paint pendulum (gr: System.Drawing.Graphics) =
let homeX = 160
let homeY = 50
let length = 140.0
// draw plate
gr.DrawLine( new Pen(Brushes.Gray, width=2.0f), 0, homeY, 320, homeY )
// draw pivot
gr.FillEllipse( Brushes.Gray,           homeX-5, homeY-5, 10, 10 )
gr.DrawEllipse( new Pen(Brushes.Black), homeX-5, homeY-5, 10, 10 )
// draw the pendulum itself
let x = homeX + int( length * Math.Sin pendulum.angle )
let y = homeY + int( length * Math.Cos pendulum.angle )
// draw rod
gr.DrawLine( new Pen(Brushes.Black, width=3.0f), homeX, homeY, x, y )
// draw bob
gr.FillEllipse( Brushes.Yellow,         x-15, y-15, 30, 30 )
gr.DrawEllipse( new Pen(Brushes.Black), x-15, y-15, 30, 30 )

// defines an operator "-?" that calculates the time from t2 to t1
// where t2 is optional
let (-?) (t1: DateTime) (t2: DateTime option) : float<s> =
match t2 with
| None   -> 0.0<s> // only one timepoint given -> difference is 0
| Some t -> (t1 - t).TotalSeconds * 1.0<s>

// our main window is double-buffered form that reacts to paint events
type PendulumForm() as self =
inherit Form(Width=325, Height=240, Text="Pendulum")
let mutable pendulum = { length   = 1.0<m>;
gravity  = 9.81<m/s^2>
velocity = 0.0<m/s>
angle    = Math.PI / 2.0
}
let mutable lastPaintedAt = None
let updateFreq = 0.01<s>

do self.DoubleBuffered <- true
self.Paint.Add( fun args ->
let now = DateTime.Now
let deltaT = now -? lastPaintedAt |> min 0.01<s>
lastPaintedAt <- Some now

pendulum <- next pendulum deltaT

let gr = args.Graphics
gr.Clear( Color.LightGray )
paint pendulum gr

// initiate a new paint event after a while (non-blocking)
async { do! Async.Sleep( int( 1000.0 * updateFreq / 1.0<s> ) )
self.Invalidate()
}
|> Async.Start
)

[<STAThread>]
Application.Run( new PendulumForm( Visible=true ) )


Factor

Approximation of the pendulum for small swings : theta = theta0 * cos(omega0 * t)

USING: accessors alarms arrays calendar colors.constants kernel
locals math math.constants math.functions math.rectangles
math.vectors opengl sequences system ui ui.gadgets ui.render ;
IN: pendulum

CONSTANT: g 9.81
CONSTANT: l 20
CONSTANT: theta0 0.5

: current-time ( -- time ) nano-count -9 10^ * ;

: T0 ( -- T0 ) 2 pi l g / sqrt * * ;
: omega0 ( -- omega0 ) 2 pi * T0 / ;
: theta ( -- theta ) current-time omega0 * cos theta0 * ;

: relative-xy ( theta l -- xy )
swap [ sin * ] [ cos * ] 2bi 2array ;
: theta-to-xy ( origin theta l -- xy ) relative-xy v+ ;

TUPLE: pendulum-gadget < gadget alarm ;

: O ( gadget -- origin ) rect-bounds [ drop ] [ first 2 / ] bi* 0 2array ;
: window-l ( gadget -- l ) rect-bounds [ drop ] [ second ] bi* ;
: gadget-xy ( gadget -- xy ) [ O ] [ drop theta ] [ window-l ] tri theta-to-xy ;

M: pendulum-gadget draw-gadget*
COLOR: black gl-color
[ O ] [ gadget-xy ] bi gl-line ;

M:: pendulum-gadget graft* ( gadget -- )
[ gadget relayout-1 ]
20 milliseconds every gadget (>>alarm) ;
M: pendulum-gadget ungraft* alarm>> cancel-alarm ;

: <pendulum-gadget> ( -- gadget )
pendulum-gadget new
{ 500 500 } >>pref-dim ;
: pendulum-main ( -- )
[ <pendulum-gadget> "pendulum" open-window ] with-ui ;
MAIN: pendulum-main


FBSL

#INCLUDE <Include\Windows.inc>

FBSLSETTEXT(ME, "Pendulum")
FBSL.SETTIMER(ME, 1000, 10)
RESIZE(ME, 0, 0, 300, 200)
CENTER(ME)
SHOW(ME)

BEGIN EVENTS
SELECT CASE CBMSG
CASE WM_TIMER
' Request redraw
InvalidateRect(ME, NULL, FALSE)
RETURN 0
CASE WM_PAINT
Swing()
CASE WM_CLOSE
FBSL.KILLTIMER(ME, 1000)
END SELECT
END EVENTS

SUB Swing()
TYPE RECT: %rcLeft, %rcTop, %rcRight, %rcBottom: END TYPE
STATIC rc AS RECT, !!acceleration, !!velocity, !!angle = M_PI_2, %pendulum = 100

GetClientRect(ME, @rc)

' Recalculate
DIM headX = rc.rcRight / 2, headY = rc.rcBottom / 4
DIM tailX = headX + SIN(angle) * pendulum
DIM tailY = headY + COS(angle) * pendulum

acceleration = -9.81 / pendulum * SIN(angle)
INCR(velocity, acceleration * 0.1)(angle, velocity * 0.1)

' Create backbuffer
CreateCompatibleDC(GetDC(ME))
SelectObject(CreateCompatibleDC, CreateCompatibleBitmap(GetDC, rc.rcRight, rc.rcBottom))

' Draw to backbuffer
FILLSTYLE(FILL_SOLID): FILLCOLOR(RGB(200, 200, 0))
LINE(CreateCompatibleDC, 0, 0, rc.rcRight, rc.rcBottom, GetSysColor(COLOR_BTNHILIGHT), TRUE, TRUE)
LINE(CreateCompatibleDC, 0, headY, rc.rcRight, headY, GetSysColor(COLOR_3DSHADOW))
DRAWWIDTH(3)
LINE(CreateCompatibleDC, headX, headY, tailX, tailY, RGB(200, 0, 0))
DRAWWIDTH(1)
CIRCLE(CreateCompatibleDC, headX, headY, 2, GetSysColor, 0, 360, 1, TRUE)
CIRCLE(CreateCompatibleDC, tailX, tailY, 10, GetSysColor, 0, 360, 1, FALSE)

' Blit to window
BitBlt(GetDC, 0, 0, rc.rcRight, rc.rcBottom, CreateCompatibleDC, 0, 0, SRCCOPY)
ReleaseDC(ME, GetDC)

' Delete backbuffer
DeleteObject(SelectObject(CreateCompatibleDC, SelectObject))
DeleteDC(CreateCompatibleDC)
END SUB


Screenshot:




Fortran

Uses system commands (gfortran) to clear the screen. An initial starting angle is allowed between 90 (to the right) and -90 degrees (to the left). It checks for incorrect inputs.

!Implemented by Anant Dixit (October, 2014)
program animated_pendulum
implicit none
double precision, parameter :: pi = 4.0D0*atan(1.0D0), l = 1.0D-1, dt = 1.0D-2, g = 9.8D0
integer :: io
double precision :: s_ang, c_ang, p_ang, n_ang

write(*,*) 'Enter starting angle (in degrees):'
do
read(*,*,iostat=io) s_ang
if(io.ne.0 .or. s_ang.lt.-90.0D0 .or. s_ang.gt.90.0D0) then
write(*,*) 'Please enter an angle between 90 and -90 degrees:'
else
exit
end if
end do
call execute_command_line('cls')

c_ang = s_ang*pi/180.0D0
p_ang = c_ang

call display(c_ang)
do
call next_time_step(c_ang,p_ang,g,l,dt,n_ang)
if(abs(c_ang-p_ang).ge.0.05D0) then
call execute_command_line('cls')
call display(c_ang)
end if
end do
end program

subroutine next_time_step(c_ang,p_ang,g,l,dt,n_ang)
double precision :: c_ang, p_ang, g, l, dt, n_ang
n_ang = (-g*sin(c_ang)/l)*2.0D0*dt**2 + 2.0D0*c_ang - p_ang
p_ang = c_ang
c_ang = n_ang
end subroutine

subroutine display(c_ang)
double precision :: c_ang
character (len=*), parameter :: cfmt = '(A1)'
double precision :: rx, ry
integer :: x, y, i, j
rx = 45.0D0*sin(c_ang)
ry = 22.5D0*cos(c_ang)
x = int(rx)+51
y = int(ry)+2
do i = 1,32
do j = 1,100
if(i.eq.y .and. j.eq.x) then
write(*,cfmt,advance='no') 'O'
else if(i.eq.y .and. (j.eq.(x-1).or.j.eq.(x+1))) then
write(*,cfmt,advance='no') 'G'
else if(j.eq.x .and. (i.eq.(y-1).or.i.eq.(y+1))) then
write(*,cfmt,advance='no') 'G'
else if(i.eq.y .and. (j.eq.(x-2).or.j.eq.(x+2))) then
write(*,cfmt,advance='no') '#'
else if(j.eq.x .and. (i.eq.(y-2).or.i.eq.(y+2))) then
write(*,cfmt,advance='no') 'G'
else if((i.eq.(y+1).and.j.eq.(x+1)) .or. (i.eq.(y-1).and.j.eq.(x-1))) then
write(*,cfmt,advance='no') '#'
else if((i.eq.(y+1).and.j.eq.(x-1)) .or. (i.eq.(y-1).and.j.eq.(x+1))) then
write(*,cfmt,advance='no') '#'
else if(j.eq.50) then
write(*,cfmt,advance='no') '|'
else if(i.eq.2) then
write(*,cfmt,advance='no') '-'
else
write(*,cfmt,advance='no') ' '
end if
end do
write(*,*)
end do
end subroutine


A small preview (truncated to a few steps of the pendulum changing direction). Initial angle provided = 80 degrees.

                                                 |
-------------------------------------------------|--------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|                G
|               #G#
|              #GOG#
|               #G#
|                G
|
|
|
|
|
|
|
|

|
-------------------------------------------------|--------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|                       G
|                      #G#
|                     #GOG#
|                      #G#
|                       G
|
|
|
|
|
|
|
|
|

|
-------------------------------------------------|--------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|                            G
|                           #G#
|                          #GOG#
|                           #G#
|                            G
|
|
|
|
|
|
|
|
|
|
|

|
-------------------------------------------------|--------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|                                 G
|                                #G#
|                               #GOG#
|                                #G#
|                                 G
|
|
|
|
|
|
|
|
|
|
|
|
|
|

|
-------------------------------------------------|--------------------------------------------------
|
|
|
|
|
|
|
|
|
|                                     G
|                                    #G#
|                                   #GOG#
|                                    #G#
|                                     G
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|

|
-------------------------------------------------|--------------------------------------------------
|
|
|
|
|
|
|
|                                       G
|                                      #G#
|                                     #GOG#
|                                      #G#
|                                       G
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|

|
-------------------------------------------------|--------------------------------------------------
|
|
|
|
|
|                                         G
|                                        #G#
|                                       #GOG#
|                                        #G#
|                                         G
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|

|
-------------------------------------------------|--------------------------------------------------
|
|
|
|                                          G
|                                         #G#
|                                        #GOG#
|                                         #G#
|                                          G
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|



Groovy

Straight translation of Java solution groovified by removing explicit definitions and converting casts to Groovy as style where needed.

import java.awt.*;
import javax.swing.*;

class Pendulum extends JPanel implements Runnable {

private angle = Math.PI / 2;
private length;

Pendulum(length) {
this.length = length;
setDoubleBuffered(true);
}

@Override
void paint(Graphics g) {
g.setColor(Color.WHITE);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(Color.BLACK);
int anchorX = getWidth() / 2, anchorY = getHeight() / 4;
def ballX = anchorX + (Math.sin(angle) * length) as int;
def ballY = anchorY + (Math.cos(angle) * length) as int;
g.drawLine(anchorX, anchorY, ballX, ballY);
g.fillOval(anchorX - 3, anchorY - 4, 7, 7);
g.fillOval(ballX - 7, ballY - 7, 14, 14);
}

void run() {
def angleAccel, angleVelocity = 0, dt = 0.1;
while (true) {
angleAccel = -9.81 / length * Math.sin(angle);
angleVelocity += angleAccel * dt;
angle += angleVelocity * dt;
repaint();
try { Thread.sleep(15); } catch (InterruptedException ex) {}
}
}

@Override
Dimension getPreferredSize() {
return new Dimension(2 * length + 50, (length / 2 * 3) as int);
}

static void main(String[] args) {
def f = new JFrame("Pendulum");
def p = new Pendulum(200);
f.add(p);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.pack();
f.setVisible(true);
new Thread(p).start();
}
}


FutureBasic

void local fn BuildWindow
window 1, @"Animated Pendulum in FutureBasic", ( 0, 0, 640, 400 )
WindowSetBackgroundColor( 1, fn ColorBlack )
WindowSetMinSize( 1, fn CGSizeMake( 640, 400 ) )
WindowSetMaxSize( 1, fn CGSizeMake( 640, 400 ) )
end fn

local fn AnimatedPendulum
block double   theta, gravity, length, accel, speed, weight, tempo, px, py, bx, by
block ColorRef color = fn ColorWithRGB( 0.164, 0.793, 0.075, 1.0 )
theta   = pi/2.0 // Denominator of 2.0 = 180-degree swing, < 2.0 narrows inscribed arc, > 2.0 widens it.
gravity = 9.90   // Adjusts effect of gravity on swing. Smaller values slow arc swing.
length  = 0.95   // Tweak for length of pendulum arm
speed   = 0      // Zero this or you get a propellor!
px      = 320    // Pivot horizontal center x point (half window width)
py      = 30     // Pivot y center y point from top
weight  = 42     // Diameter of pendulum weight
tempo   = 75     // Smaller value increases pendulum tempo, larger value slows it.

timerbegin, 0.02, YES
bx = px + length * 300 * sin(theta) // Pendulum bottom x point
by = py - length * 300 * cos(theta) // Pendulum bottom y point
cls
pen 6.0, color
line px, py to bx, by
oval fill bx -weight/2, by -weight/2, weight, weight, color // Traveling weight
pen 4.0
oval fill 313, 20, 16, 16, fn ColorGray // Top center point
accel  = gravity * sin(theta) / length / tempo
speed += accel / tempo
theta += speed
timerEnd
end fn

void local fn DoDialog( ev as long, tag as long, wnd as long )
select ( ev )
case _windowWillClose : end
end select
end fn

on dialog fn DoDialog

fn BuildWindow
fn AnimatedPendulum

HandleEvents

Go

Using

Library: GXUI

from Github

package main

import (
"github.com/google/gxui"
"github.com/google/gxui/drivers/gl"
"github.com/google/gxui/math"
"github.com/google/gxui/themes/dark"
omath "math"
"time"
)

//Two pendulums animated
//Top: Mathematical pendulum with small-angle approxmiation (not appropiate with PHI_ZERO=pi/2)
//Bottom: Simulated with differential equation phi'' = g/l * sin(phi)

const (
ANIMATION_WIDTH  int     = 480
ANIMATION_HEIGHT int     = 320
BALL_RADIUS      float32 = 25.0
METER_PER_PIXEL  float64 = 1.0 / 20.0
PHI_ZERO         float64 = omath.Pi * 0.5
)

var (
l    float64 = float64(ANIMATION_HEIGHT) * 0.5
freq float64 = omath.Sqrt(9.81 / (l * METER_PER_PIXEL))
)

type Pendulum interface {
GetPhi() float64
}

type mathematicalPendulum struct {
start time.Time
}

func (p *mathematicalPendulum) GetPhi() float64 {
if (p.start == time.Time{}) {
p.start = time.Now()
}
t := float64(time.Since(p.start).Nanoseconds()) / omath.Pow10(9)
return PHI_ZERO * omath.Cos(t*freq)
}

type numericalPendulum struct {
currentPhi float64
angAcc     float64
angVel     float64
lastTime   time.Time
}

func (p *numericalPendulum) GetPhi() float64 {
dt := 0.0
if (p.lastTime != time.Time{}) {
dt = float64(time.Since(p.lastTime).Nanoseconds()) / omath.Pow10(9)
}
p.lastTime = time.Now()

p.angAcc = -9.81 / (float64(l) * METER_PER_PIXEL) * omath.Sin(p.currentPhi)
p.angVel += p.angAcc * dt
p.currentPhi += p.angVel * dt

return p.currentPhi
}

func draw(p Pendulum, canvas gxui.Canvas, x, y int) {
attachment := math.Point{X: ANIMATION_WIDTH/2 + x, Y: y}

phi := p.GetPhi()
ball := math.Point{X: x + ANIMATION_WIDTH/2 + math.Round(float32(l*omath.Sin(phi))), Y: y + math.Round(float32(l*omath.Cos(phi)))}

line := gxui.Polygon{gxui.PolygonVertex{attachment, 0}, gxui.PolygonVertex{ball, 0}}

canvas.DrawLines(line, gxui.DefaultPen)

m := math.Point{int(BALL_RADIUS), int(BALL_RADIUS)}
rect := math.Rect{ball.Sub(m), ball.Add(m)}
canvas.DrawRoundedRect(rect, BALL_RADIUS, BALL_RADIUS, BALL_RADIUS, BALL_RADIUS, gxui.TransparentPen, gxui.CreateBrush(gxui.Yellow))
}

func appMain(driver gxui.Driver) {
theme := dark.CreateTheme(driver)

window := theme.CreateWindow(ANIMATION_WIDTH, 2*ANIMATION_HEIGHT, "Pendulum")
window.SetBackgroundBrush(gxui.CreateBrush(gxui.Gray50))

image := theme.CreateImage()

ticker := time.NewTicker(time.Millisecond * 15)
pendulum := &mathematicalPendulum{}
pendulum2 := &numericalPendulum{PHI_ZERO, 0.0, 0.0, time.Time{}}

go func() {
for _ = range ticker.C {
canvas := driver.CreateCanvas(math.Size{ANIMATION_WIDTH, 2 * ANIMATION_HEIGHT})
canvas.Clear(gxui.White)

draw(pendulum, canvas, 0, 0)
draw(pendulum2, canvas, 0, ANIMATION_HEIGHT)

canvas.Complete()
driver.Call(func() {
image.SetCanvas(canvas)
})
}
}()

window.AddChild(image)

window.OnClose(ticker.Stop)
window.OnClose(driver.Terminate)
}

func main() {
gl.StartDriver(appMain)
}


Haskell

Library: HGL
import Graphics.HGL.Draw.Monad (Graphic, )
import Graphics.HGL.Draw.Picture
import Graphics.HGL.Utils
import Graphics.HGL.Window
import Graphics.HGL.Run

import Control.Exception (bracket, )
import Control.Arrow

toInt = fromIntegral.round

exit

Zig

Library: Raylib
Works with: Zig version 0.11.0dev
Works with: Raylib version 4.6dev
Translation of: Nim
const math = @import("std").math;
const c = @cImport({
@cInclude("raylib.h");
});

pub fn main() void {
c.SetConfigFlags(c.FLAG_VSYNC_HINT);
c.InitWindow(640, 320, "Pendulum");
defer c.CloseWindow();

// Simulation constants.
const g = 9.81; // Gravity (should be positive).
const length = 5.0; // Pendulum length.
const theta0 = math.pi / 3.0; // Initial angle for which omega = 0.

const e = g * length * (1 - @cos(theta0)); // Total energy = potential energy when starting.

// Simulation variables.
var theta: f32 = theta0; // Current angle.
var omega: f32 = 0; // Angular velocity = derivative of theta.
var accel: f32 = -g / length * @sin(theta0); // Angular acceleration = derivative of omega.

c.SetTargetFPS(60);

while (!c.WindowShouldClose()) // Detect window close button or ESC key
{
const half_width = @as(f32, @floatFromInt(c.GetScreenWidth())) / 2;
const pivot = c.Vector2{ .x = half_width, .y = 0 };

// Compute the position of the mass.
const mass = c.Vector2{
.x = 300 * @sin(theta) + pivot.x,
.y = 300 * @cos(theta),
};

{
c.BeginDrawing();
defer c.EndDrawing();

c.ClearBackground(c.RAYWHITE);

c.DrawLineV(pivot, mass, c.GRAY);
c.DrawCircleV(mass, 20, c.GRAY);
}

// Update theta and omega.
const dt = c.GetFrameTime();
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 (length * g * (1 - @cos(theta)) >= e) {
theta = math.sign(theta) * theta0;
omega = 0;
}
accel = -g / length * @sin(theta);
}
}


ZX Spectrum Basic

Translation of: ERRE

In a real Spectrum it is too slow. Use the BasinC emulator/editor at maximum speed for realistic animation.

10 OVER 1: CLS
20 LET theta=1
30 LET g=9.81
40 LET l=0.5
50 LET speed=0
100 LET pivotx=120
110 LET pivoty=140
120 LET bobx=pivotx+l*100*SIN (theta)
130 LET boby=pivoty+l*100*COS (theta)
140 GO SUB 1000: PAUSE 1: GO SUB 1000
190 LET accel=g*SIN (theta)/l/100
200 LET speed=speed+accel/100
210 LET theta=theta+speed
220 GO TO 100
1000 PLOT pivotx,pivoty: DRAW bobx-pivotx,boby-pivoty
1010 CIRCLE bobx,boby,3
1020 RETURN