animated wave

animated wave

Methods
N
R
S
Class Public methods
new(frame)
# File ext/tk/sample/demos-en/pendulum.rb, line 53
  def initialize(frame)
    # Create some structural widgets
    @pane = TkPanedWindow.new(frame, :orient=>:horizontal).pack(:fill=>:both, :expand=>true)
#    @pane.add(@lf1 = TkLabelFrame.new(@pane, :text=>'Pendulum Simulation'))
#    @pane.add(@lf2 = TkLabelFrame.new(@pane, :text=>'Phase Space'))
    @lf1 = TkLabelFrame.new(@pane, :text=>'Pendulum Simulation')
    @lf2 = TkLabelFrame.new(@pane, :text=>'Phase Space')

    # Create the canvas containing the graphical representation of the
    # simulated system.
    @c = TkCanvas.new(@lf1, :width=>320, :height=>200, :background=>'white',
                      :borderwidth=>2, :relief=>:sunken)
    TkcText.new(@c, 5, 5, :anchor=>:nw,
                :text=>'Click to Adjust Bob Start Position')
    # Coordinates of these items don't matter; they will be set properly below
    @plate = TkcLine.new(@c, 0, 25, 320, 25, :width=>2, :fill=>'grey50')
    @rod = TkcLine.new(@c, 1, 1, 1, 1, :width=>3, :fill=>'black')
    @bob = TkcOval.new(@c, 1, 1, 2, 2,
                       :width=>3, :fill=>'yellow', :outline=>'black')
    TkcOval.new(@c, 155, 20, 165, 30, :fill=>'grey50', :outline=>'')

    # pack
    @c.pack(:fill=>:both, :expand=>true)

    # Create the canvas containing the phase space graph; this consists of
    # a line that gets gradually paler as it ages, which is an extremely
    # effective visual trick.
    @k = TkCanvas.new(@lf2, :width=>320, :height=>200, :background=>'white',
                      :borderwidth=>2, :relief=>:sunken)
    @y_axis = TkcLine.new(@k, 160, 200, 160, 0, :fill=>'grey75', :arrow=>:last)
    @x_axis = TkcLine.new(@k, 0, 100, 320, 100, :fill=>'grey75', :arrow=>:last)

    @graph = {}
    90.step(0, -10){|i|
      # Coordinates of these items don't matter;
      # they will be set properly below
      @graph[i] = TkcLine.new(@k, 0, 0, 1, 1, :smooth=>true, :fill=>"grey#{i}")
    }

    # labels
    @label_theta = TkcText.new(@k, 0, 0, :anchor=>:ne,
                               :text=>'q', :font=>'Symbol 8')
    @label_dtheta = TkcText.new(@k, 0, 0, :anchor=>:ne,
                               :text=>'dq', :font=>'Symbol 8')

    # pack
    @k.pack(:fill=>:both, :expand=>true)

    # Initialize some variables
    @points = []
    @theta = 45.0
    @dTheta = 0.0
    @length = 150

    # animation loop
    @timer = TkTimer.new(15){ repeat }

    # binding
    @c.bindtags_unshift(btag = TkBindTag.new)
    btag.bind('Destroy'){ @timer.stop }
    btag.bind('1', proc{|x, y| @timer.stop; showPendulum(x.to_i, y.to_i)},
              '%x %y')
    btag.bind('B1-Motion', proc{|x, y| showPendulum(x.to_i, y.to_i)}, '%x %y')
    btag.bind('ButtonRelease-1',
              proc{|x, y| showPendulum(x.to_i, y.to_i); @timer.start },
              '%x %y')

    btag.bind('Configure', proc{|w| @plate.coords(0, 25, w.to_i, 25)}, '%w')

    @k.bind('Configure', proc{|h, w|
              h = h.to_i
              w = w.to_i
              @psh = h/2;
              @psw = w/2
              @x_axis.coords(2, @psh, w-2, @psh)
              @y_axis.coords(@psw, h-2, @psw, 2)
              @label_theta.coords(@psw-4, 6)
              @label_dtheta.coords(w-6, @psh+4)
            }, '%h %w')

    # add
    Tk.update
    @pane.add(@lf1)
    @pane.add(@lf2)

    # init display
    showPendulum

    # animation start
    @timer.start(500)
  end
Instance Public methods
recomputeAngle()

This procedure is the “business” part of the simulation that does simple numerical integration of the formula for a simple rotational pendulum.

# File ext/tk/sample/demos-en/pendulum.rb, line 192
def recomputeAngle
  scaling = 3000.0/@length/@length

  # To estimate the integration accurately, we really need to
  # compute the end-point of our time-step.  But to do *that*, we
  # need to estimate the integration accurately!  So we try this
  # technique, which is inaccurate, but better than doing it in a
  # single step.  What we really want is bound up in the
  # differential equation:
  #       ..             - sin theta
  #      theta + theta = -----------
  #                         length
  # But my math skills are not good enough to solve this!

  # first estimate
  firstDDTheta = -Math.sin(@theta * Math::PI/180) * scaling
  midDTheta = @dTheta + firstDDTheta
  midTheta = @theta + (@dTheta + midDTheta)/2
  # second estimate
  midDDTheta = -Math.sin(midTheta * Math::PI/180) * scaling
  midDTheta = @dTheta + (firstDDTheta + midDDTheta)/2
  midTheta = @theta + (@dTheta + midDTheta)/2
  # Now we do a double-estimate approach for getting the final value
  # first estimate
  midDDTheta = -Math.sin(midTheta * Math::PI/180) * scaling
  lastDTheta = midDTheta + midDDTheta
  lastTheta = midTheta + (midDTheta+ lastDTheta)/2
  # second estimate
  lastDDTheta = -Math.sin(lastTheta * Math::PI/180) * scaling
  lastDTheta = midDTheta + (midDDTheta + lastDDTheta)/2
  lastTheta = midTheta + (midDTheta + lastDTheta)/2
  # Now put the values back in our globals
  @dTheta = lastDTheta
  @theta = lastTheta
end
repeat()

This method ties together the simulation engine and the graphical display code that visualizes it.

# File ext/tk/sample/demos-en/pendulum.rb, line 230
def repeat
  # Simulate
  recomputeAngle

  # Update the display
  showPendulum
  showPhase
end
showPendulum(x=nil, y=nil)

This procedure makes the pendulum appear at the correct place on the canvas. If the additional arguments x, y are passed instead of computing the position of the pendulum from the length of the pendulum rod and its angle, the length and angle are computed in reverse from the given location (which is taken to be the centre of the pendulum bob.)

# File ext/tk/sample/demos-en/pendulum.rb, line 150
def showPendulum(x=nil, y=nil)
  if x && y && (x != 160 || y != 25)
    @dTheta = 0.0
    x2 = x - 160
    y2 = y - 25
    @length = Math.hypot(x2, y2)
    @theta = Math.atan2(x2,y2)*180/Math::PI
  else
    angle = @theta*Math::PI/180
    x = 160 + @length*Math.sin(angle)
    y = 25 + @length*Math.cos(angle)
  end

  @rod.coords(160, 25, x, y)
  @bob.coords(x-15, y-15, x+15, y+15)
end
showPhase()

Update the phase-space graph according to the current angle and the rate at which the angle is changing (the first derivative with respect to time.)

# File ext/tk/sample/demos-en/pendulum.rb, line 170
def showPhase
  unless @psw && @psh
    @psw = @k.width/2
    @psh = @k.height/2
  end
  @points << @theta + @psw << -20*@dTheta + @psh
  if @points.length > 100
    @points = @points[-100..-1]
  end
  (0...100).step(10){|i|
    first = - i
    last = 11 - i
    last = -1 if last >= 0
    next if first > last
    lst = @points[first..last]
    @graph[i].coords(lst) if lst && lst.length >= 4
  }
end