Click here to Skip to main content
15,867,972 members
Articles / Desktop Programming / WPF

A Pendulum and its Corresponding Oscillations Shown in WPF

Rate me:
Please Sign up or sign in to vote.
4.88/5 (7 votes)
4 Nov 2010CPOL4 min read 49.3K   1.4K   14   12
An article that illustrates how to simulate a pendulum.

Preface

This focus of this article is to illustrate how to build a WPF application that simulates a Physics phenomena, namely a pendulum. To do this, we will have to build a C# library to reference as a DLL in our project. The code presented is meant to illustrate a mathematical technique to enable this pendulum simulation. So, if you can bear with a brief explanation about Physics, we will proceed and build the application. It is, howver, of paramount importance here to make it clear that the code examples of this article are largely based on on the teachings of the WPF Graphics book, as written by Jack Xu. This particular Windows technology author has a plethora of books Jack Xu demonstrated the use of a class library that sucessfully uses C# code to create a fourth-order Calculus library based on the very complicated integral called the Runge Khutta. Stated loosely, the subject of calculus, however extensive, is divided into two parts: the integral and the derivative differential. Combining that with the use of sin and cosine functions that loop when linked to the draw functions enable these examples (amongst other things. The interested student of these topics should try to grasp that integrals and derivatives in Calculus are inverse operations. But for instance, the sin and cosine functions contained the .NET Framework BCL can be used with several of the graphics functions to draw lines using the for loop control structure.

The student of Physics will have inevitably tackled differential equations, solving both partial and ordinary equations. Many Physics phenomena can be described in terms of ordinary differential equations (ODEs). For instance, if a projectile is flying through the air, it will be subject to the force of aerodynamic drag, which is a function of the object's velocity. The sounds of certain musical instruments are a function of the tension of the string. Also consider a spring-mass system. In this system, there are two forces acting on the mass: elastic recovery force, which is proportional to the displacement of the mass, and the damping force, which is proportional to its velocity. The equations of motion describing this system are also a set of ordinary differential equations, which can't be directly solved either. There are, however, a number of techniques that can be used to solve ODEs when an analytically closed form of solution is impossible. One technique is called the Runge-Kutta method. Now, without going into a long, drawn-out explanation of how this technique works, we can look at some C# code that first defines a delegate function that takes a double array x and a double time variable t as its input parameters.

C#
using System;
using System.Windows;
namespace Swing
{
public class ODESolver
{
  public delegate double Function(
  double[] x, double t);
  public static double[] RungeKutta4(
  Function[] f, double[] x0, double t0, double dt)
  {
   int n = x0.Length;
   double[] k1 = new double[n];
   double[] k2 = new double[n];
   double[] k3 = new double[n];
   double[] k4 = new double[n];
   double t = t0;
   double[] x1 = new double[n];
   double[] x = x0;
   for (int i = 0; i < n; i++)
   k1[i] = dt * f[i](x, t);
   for (int i = 0; i < n; i++)
   x1[i] = x[i] + k1[i] / 2;
   for (int i = 0; i < n; i++)
   k2[i] = dt * f[i](x1, t + dt / 2);
   for (int i = 0; i < n; i++)
   x1[i] = x[i] + k2[i] / 2;
   for (int i = 0; i < n; i++)
   k3[i] = dt * f[i](x1, t + dt / 2);
   for (int i = 0; i < n; i++)
   x1[i] = x[i] + k3[i];

   for (int i = 0; i < n; i++)
   k4[i] = dt * f[i](x1, t + dt);
   for (int i = 0; i < n; i++)
   x[i] +=
   (k1[i] + 2 * k2[i] + 2 * k3[i] + k4[i]) / 6;
    return x;
   }
  }
}

This file can be compiled on the command line using the /t:library switch or as a class file in a C# Class Library project using Visual Studio. We are going to reference this DLL when we build our WPF app. We want to simulate the motion of a pendulum. When a pendulum is displaced from its resting equilibrium position, it is subject to a restoring force due to gravity that will accelerate it back toward the equilibrium position. When released, the restoring force combined with the pendulum's mass causes it to oscillate about the equilibrium position, swinging back and forth. The time for one complete cycle, a left swing and a right swing, is called the period. A pendulum swings with a specific period which depends (mainly) on its length. This means that we are going to use it to simulate this model.

Examine the image below. A string with a mass hanging on one end is displayed in the bottom-left pane. The bottom-right pane shows how the swing angle changes with time. In addition, there are several TextBox fields that allow you to input the mass, string length, damping coefficient, initial angle, and initial angle velocity. A Start button begins the pendulum simulator, a Stop button stops the simulation, and a Reset button stops the simulation and returns the pendulum to its initial position. So let's fire up either Expression Blend or Visual Studio, start a new project called Swing, add a reference to ODESolver.dll, add a new WPF window, or rename the MainWindow to Pendulum, and take a look at the XAML:

Physics/Capture.JPG

XML
<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="Swing.Pendulum"
    x:Name="Window"
    Title="Swing
    Out"
    Width="640"
    Height="480" Background="MediumPurple">
<Window.Resources>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Width" Value="50"/>
<Setter Property="Height" Value="20"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="TextAlignment" Value="Center"/>
<Setter Property="Margin" Value="2"/>
</Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Margin" Value="5,2,2,5"/>
<Setter Property="Width" Value="70"/>
<Setter Property="TextAlignment" Value="Right"/>
</Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Margin" Value="2"/>
<Setter Property="Width" Value="75"/>
<Setter Property="Height" Value="25"/>
</Style>
</Window.Resources>
<Window.Foreground>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="Black" Offset="0"/>
<GradientStop Color="Black" Offset="1"/>
</LinearGradientBrush>
</Window.Foreground>
<StackPanel Margin="10">
<StackPanel Orientation="Horizontal">
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock FontSize="14.667" FontFamily="Times New Roman"
   FontWeight="Bold">Mass:</TextBlock>
<TextBox Name="tbMass" Text="1"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock FontFamily="Times New Roman" 
  FontWeight="Bold" FontSize="14.667">Length:</TextBlock>
<TextBox Name="tbLength" Text="1"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock FontFamily="Times New Roman" FontWeight="Bold"
  FontSize="14.667">Damping:</TextBlock>
<TextBox Name="tbDamping" Text="0.1"/>
</StackPanel>
</StackPanel>
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock FontFamily="Times New Roman" FontWeight="Bold"
   FontSize="14.667">Theta0:</TextBlock>
<TextBox Name="tbTheta0" Text="45"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock FontFamily="Times New Roman" FontWeight="Bold"
  FontSize="14.667">Alpha0:</TextBlock>
<TextBox Name="tbAlpha0" Text="0"/>
</StackPanel>
</StackPanel>
<StackPanel Margin="70,0,0,10">
<Button Click="btnStart_Click" Content="Start"/>
<Button Click="btnStop_Click" Content="Stop"/>
<Button Click="btnReset_Click" Content="Reset"/>
</StackPanel>
<StackPanel Margin="70,40,0,0">
<TextBlock Name="tbDisplay" FontSize="16"
  Foreground="Black" FontFamily="Tahoma"
  FontWeight="Bold">Stopped
</TextBlock>
</StackPanel>
</StackPanel>
<Separator Margin="0,10,0,10"></Separator>
<Viewbox Stretch="Fill">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Canvas Name="canvasLeft" Grid.Column="0"
   Width="280" Height="170">
<Rectangle Fill="DarkGoldenrod" Width="50"
  Height="10" Canvas.Left="115"
  Canvas.Top="10"/>
<Line Name="line1" X1 ="140" Y1="20"
  X2="140" Y2="150" Stroke="Red"/>
<Path Fill="Blue">
<Path.Data>
<EllipseGeometry x:Name="ball" RadiusX="10"
  RadiusY="10" Center="140,150"/>
</Path.Data>
</Path>
</Canvas>
<Canvas Name="canvasRight" Grid.Column="1"
  ClipToBounds="True" Width="280"
  Height="170">
<Line X1="10" Y1="0" X2="10" Y2="170"
  Stroke="Gray" StrokeThickness="1"/>
<Line X1="10" Y1="85"
   X2="280" Y2="85"
  Stroke="Gray" StrokeThickness="1"/>
<TextBlock TextAlignment="Left"
 Canvas.Left="10" FontFamily="Times New Roman" 
 FontWeight="Bold" FontSize="14.667">theta
</TextBlock>
<TextBlock TextAlignment="Left" Canvas.Left="248.51"
  Canvas.Top="89.5" FontFamily="Times New Roman" 
  FontWeight="Bold" FontSize="14.667"
  Margin="0">time
</TextBlock>
</Canvas>
</Grid>
</Viewbox>
</StackPanel>
</Window>

When the Start button is pressed, the input values for the mass, string length, damping coefficient, and initial position and velocity are obtained from the values inside their corresponding TextBox fields. At the same time, the event handler StartAnimation is attached to the static CompositionTarget.Rendering event. Here is the code-behind file:

C#
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
namespace Swing
{
public partial class Pendulum : Window
{
    private double PendulumMass = 1;
    private double PendulumLength = 1;
    private double DampingCoefficient = 0.5;
    private double Theta0 = 45;
    private double Alpha0 = 0;
    double[] xx = new double[2];
    double time = 0;
    double dt = 0.03;
    Polyline pl = new Polyline();
    double xMin = 0;
    Double yMin = -100;
    double xMax = 50;
    double yMax = 100;
    public Pendulum()
    {
        InitializeComponent();
    }
    private void btnStart_Click(object sender, RoutedEventArgs e)
    {
        PendulumMass = Double.Parse(tbMass.Text);
        PendulumLength = Double.Parse(tbLength.Text);
        DampingCoefficient = Double.Parse(tbDamping.Text);
        Theta0 = Double.Parse(tbTheta0.Text);
        Theta0 = Math.PI * Theta0 / 180;
        Alpha0 = Double.Parse(tbAlpha0.Text);
        Alpha0 = Math.PI * Alpha0 / 180;
        tbDisplay.Text = "Starting...";
        if (canvasRight.Children.Count > 4)
        canvasRight.Children.Remove(pl);

        pl = new Polyline();
        pl.Stroke = Brushes.Red;
        canvasRight.Children.Add(pl);
        time = 0;
        xx = new double[2] { Theta0, Alpha0 };
        CompositionTarget.Rendering += StartAnimation;
    }
    private void StartAnimation(object sender, EventArgs e)
    {
        // Invoke ODE solver:
        ODESolver.Function[] f =
        new ODESolver.Function[2] { f1, f2 };
        double[] result = ODESolver.RungeKutta4(
        f, xx, time, dt);
        // Display moving pendulum on screen:
        Point pt = new Point(
        140 + 130 * Math.Sin(result[0]),
        20 + 130 * Math.Cos(result[0]));
        ball.Center = pt;
        line1.X2 = pt.X;
        line1.Y2 = pt.Y;
        // Display theta - time curve on canvasRight:
        if (time < xMax)
        pl.Points.Add(new Point(XNormalize(time) + 10,
        YNormalize(180 * result[0] / Math.PI)));
        // Reset the initial values for next calculation:
        xx = result;
        time += dt;
        if (time > 0 && Math.Abs(result[0]) < 0.01 &&
            Math.Abs(result[1]) < 0.001)
        {
            tbDisplay.Text = "Stopped";
            CompositionTarget.Rendering -= StartAnimation;
        }
    }
    private void btnReset_Click( object sender, RoutedEventArgs e)
    {
        PendulumInitialize();
        tbDisplay.Text = "Stopped";
        if (canvasRight.Children.Count > 4)
            canvasRight.Children.Remove(pl);
        CompositionTarget.Rendering -= StartAnimation;
    }

    private void PendulumInitialize()
    {
        tbMass.Text = "1";
        tbLength.Text = "1";
        tbDamping.Text = "0.1";
        tbTheta0.Text = "45";
        tbAlpha0.Text = "0";
        line1.X2 = 140;
        line1.Y2 = 150;
        ball.Center = new Point(140, 150);
    }
    private void btnStop_Click( object sender, RoutedEventArgs e)
    {
        line1.X2 = 140;
        line1.Y2 = 150;
        ball.Center = new Point(140, 150);
        tbDisplay.Text = "Stopped";
        CompositionTarget.Rendering -= StartAnimation;
    }
    private double f1(double[]xx, double t)
    {
        return xx[1];
    }
    private double f2(double[] xx, double t)
    {
        double m = PendulumMass;
        double L = PendulumLength;
        double g = 9.81;
        double b = DampingCoefficient;
        return -g * Math.Sin(xx[0]) / L - b * xx[1] / m;
    }
    private double XNormalize(double x)
    {
        double result = (x - xMin) *
        canvasRight.Width / (xMax - xMin);
        return result;
    }
    private double YNormalize(double y)
    {
        double result = canvasRight.Height - (y - yMin) *
        canvasRight.Height / (yMax - yMin);
        return result;
    }
}
}

Once the new values of angle and velocity are obtained, you update the screen that shows the moving pendulum and the swing angle as a function of time on the screen. Next, you set the current solution as the initial values for the next round simulation. When the swing angle and angle velocity are so small that the pendulum almost doesn't swing, you can stop the animation by detaching the StartAnimation event handler using the statement:

C#
CompositionTarget.Rendering -= StartAnimation;

You can play around with the Pendulum Simulator by changing the values of the mass, damping coefficient, initial string angle, and initial angle velocity, and watch their effects on the motion of the pendulum.

References

Jack Xu Practical WPF Graphics Programming

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Monroe Community
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionExport To Microsoft Office. Pin
Member 1599672325-May-23 8:51
Member 1599672325-May-23 8:51 
GeneralMy vote of 4 Pin
Krzysztof Krefta12-Apr-17 8:04
Krzysztof Krefta12-Apr-17 8:04 
4/5 for using parse instead of tryparse!

Code does not run out of the box at some locations (some countries like poland use , instead of . as decimal separator, its retarded I know but it's still a thing and shall be handled)
QuestionPurpose of line of code Pin
Member 80847368-Apr-15 19:51
Member 80847368-Apr-15 19:51 
GeneralMy vote of 5 Pin
Reza Ahmadi9-Jun-12 7:04
Reza Ahmadi9-Jun-12 7:04 
Questiona question Pin
kaanyilgin28-Apr-12 5:57
kaanyilgin28-Apr-12 5:57 
QuestionWould appreciate an honest answer... Pin
suchit_ust4-Oct-11 6:46
suchit_ust4-Oct-11 6:46 
Questionphysic formular Pin
twintaha14-Apr-11 16:39
twintaha14-Apr-11 16:39 
AnswerRe: physic formular Pin
logicchild15-Apr-11 12:24
professionallogicchild15-Apr-11 12:24 
QuestionQuestion Pin
Jonathan201027-Nov-10 22:05
Jonathan201027-Nov-10 22:05 
AnswerRe: Question Pin
logicchild29-Nov-10 11:46
professionallogicchild29-Nov-10 11:46 
GeneralMy vote of 5 Pin
sam.hill5-Nov-10 7:07
sam.hill5-Nov-10 7:07 
GeneralOscillate Whitespace Pin
AspDotNetDev4-Nov-10 19:15
protectorAspDotNetDev4-Nov-10 19:15 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.