Table Tennis

  Tutorial Resources
Data Files Project: Open “TableTennis.p3prj” in PFC3D [1]

Introduction

This example simulates a table tennis game.

The basic use of the wall generate command is shown. The fish callback command is used so that specific operations are performed when the ball hits the table or the net. The ball trajectory is traced during the game using the ball trace command.

Numerical Model

The “CreateTable.p3dat” data file creates the table and the rackets. First, the table dimensions are calculated from the domain size.

fish define dim_table
  xTable=array.create(1,2)
  yTable=array.create(1,2)
  xTable(1,1) = -1
  xTable(1,2) = 1
  Lx = math.abs(xTable(1,1))+math.abs(xTable(1,2))
  Ly = Lx*0.6
  yTable(1,1) = domain.min.y*0.95
  yTable(1,2) = yTable(1,1)+Ly
  zTable = (domain.min.z+domain.max.z)*0.5
end
@dim_table

The table surface is then created using the wall generate command to create a planar polygonal wall (polygon keyword). The legs of the table are also created by introducing four cylindrical walls with the cylinder keyword. Finally, the net is created as a new polygonal wall. Walls are grouped and named to facilitate later operations.

; creation of the table
wall generate group 'table' polygon ...
            @xTable(1,2) @yTable(1,1) @zTable ...
            @xTable(1,2) @yTable(1,2) @zTable ...
            @xTable(1,1) @yTable(1,1) @zTable ...
            @xTable(1,1) @yTable(1,2) @zTable

; insertion of table legs
wall generate group 'table' cylinder ...
    axis 0 0 -1 ...
    base @xTable(1,2) @yTable(1,1) @zTable ...
    height [Ly*0.5] ...
    one-wall ...
    radius [Lx*0.02]

; creation of the net
wall generate group 'net' polygon ...
           [(xTable(1,1)+xTable(1,2))*0.5] @yTable(1,1) @zTable ...
           [(xTable(1,1)+xTable(1,2))*0.5] @yTable(1,2) @zTable ...
           [(xTable(1,1)+xTable(1,2))*0.5] @yTable(1,1) [zTable+Lx*0.075] ...
           [(xTable(1,1)+xTable(1,2))*0.5] @yTable(1,2) [zTable+Lx*0.075]

Then the rackets are created and placed at the table extremities. Rackets are represented by two disk-shaped walls using the disk keyword.

;creation of the rackets
wall generate group 'player1' disk ... ;racket Player1
    dip 90 ...
    dip-direction 90 ...
    position @xTable(1,1) [(yTable(1,1)+yTable(1,2))*0.5] [zTable+Ly*0.3] ...
    rad [Lx*0.05]

wall generate group 'player2' disk ... ;racket Player2
    dip 90 ...
    dip-direction 90 ...
    position @xTable(1,2) [(yTable(1,1)+yTable(1,2))*0.5] [zTable+Ly*0.3] ...
    rad [Lx*0.05]
../../../../../../../_images/pingpong1.png

Figure 1: The table, the net, and rackets.

The last step in preparing the game is the creation of the ball. Its initial velocity is set on a per-component basis. The ball trajectory during the game is traced using the ball trace command. The ball density and initial velocity are set with the ball attribute command.

;define the initial velocity of the ball
[XballInitialVelocity = -3.0]
[ZballInitialVelocity = 2.0]

;creation of the ball
ball create id 1 ...
            radius   [Lx*0.005]                      ...
            position [xTable(1,2)*0.98]              ...
                     [(yTable(1,1)+yTable(1,2))*0.5] ...
                     [zTable+Ly*0.3]
ball trace id 1
ball attribute density 10            ...
    velocity-x @XballInitialVelocity ...
    velocity-z @ZballInitialVelocity

The Contact Model Assignment Table (CMAT) must be defined in each PFC model. Each contact in the model domain is assigned a contact constitutive model in this way. The default slot of the CMAT is defined for ball-facet contacts. A linear model with a Coulomb criterion associated with the contacts is chosen.

;linear model for ball-facet contacts
contact cmat default type ball-facet model linear ...

To complete the preparation of the model, some function is needed to control the movement of the rackets and to stop the game in case of a fault. To do this, two FISH functions are created and registered with callback events with the fish callback command.

fish callback add @checknet event contact_activated
;set fish callback to prepare the racket position
fish callback add @tablecontact event contact_activated

Use the program list cycle-sequence command to list the cycle points during a PFC cycle and the fish list callback-list command to list the available callback events. One can insert FISH functions between the preset cycle points during the cycle sequence.

In this example we choose to manage the ball-net and/or ball-table interactions using callback events. Regarding ball-net contacts using the contact_activated event (see “Linear Model Callback Events”), every time a contact is activated, the function checknet checks whether it is a ball-net contact. If so, the Fault! message is sent.

;check for ball-net contacts
fish define checknet(arr)
    ct=arr(1)
    ;in case of ball-facet contacts, contact.end1 refers to the ball, 
    ;contact.end2 refers to the facet
    if wall.group(wall.facet.wall(contact.end2(ct)),"Default") == "net"
      io.out("Net!!")
      ball.vel(bp,1)=0
      ball.vel(bp,3)=0
    endif
end

Please be aware of the different ways one can use contact_create/delete and/or contact_activated events: a contact may be created whenever the cell extents of two pieces touch or overlap (see the 35.0 - Create/delete Contacts reference page). The contacts are initially inactive upon creation, and contact resolution is required to determine their activity state. Ball-net contacts could be created and remain inactive, and the game must continue in such a case.

Regarding ball-table contacts, every time a contact is deleted, the function tablecontact is called. If the ball is found to have touched the table, the racket of the respective player is moved to a new position that is predicted according to the theory of parabolic projectiles.

;check fi=ot ball-table contacts!
fish define tablecontact(ct)
    con = ct(1)
    ;in case of ball-facet contacts, contact.end1 refers to the ball,
    ;contact.end2 refers to the facet
    if wall.group(wall.facet.wall(contact.end2(con)),"Default") == "table"
      moverackets
    endif
end

;compute the new position of the racket and move it!
fish define moverackets
  xvel = ball.vel.x(bp)
  zvel = ball.vel.z(bp)
  xpos = ball.pos.x(bp)
  zpos = ball.pos.z(bp)
  if xvel < 0
    if xpos > 0   
      ball.vel(bp,1)=0
      ball.vel(bp,3)=0
	  io.out("Fault!!")
    endif
    wp = wall.find(7)
    L = xTable(1,1) - xpos
  else
    if xpos < 0   
      ball.vel(bp,1)=0
      ball.vel(bp,3)=0
	  io.out("Fault!!")
    endif
    wp = wall.find(8)
    L = xTable(1,2) - xpos
  endif
    zM = -(zvel/xvel)*L + (-9.81/2)*((L/xvel)^2)
  if zM > domain.min.z
    if zM < domain.max.z
      wall.pos.z(wp) = zM + zpos
    endif
  endif
end

Everything is ready … have fun!

../../../../../../../_images/pingpong2.png

Figure 2: A table tennis game.

Discussion

This tutorial presents an example of registering FISH functions with callback events to produce custom behavior (see the fish callback command). This demonstrates one of the core features of PFC: the ability to model complex phenomena using FISH.

Endnote

[1]These may be found in PFC3D under the “tutorials/table_tennis” folder in the Examples dialog (Help —> Examples on the menu). If this entry does not appear, please copy the application data to a new directory. (Use the menu commands Tools —> Copy App Data …. See the “Copy Application Data” section for details.)