Table Tennis
Introduction
Note
The project file for this example may be viewed/run in PFC.[1] The data files used are shown at the end of this example.
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.dat (3D)” 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) = domain.min.x*0.95
xTable(1,2) = domain.max.x*0.95
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. Walls are grouped and named (the first of the four is shown below) 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]
Finally, the net is created as a new polygonal wall.
; 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]
The last step in preparing the game is the creation of the ball. Its initial velocity is set on a per-component basis.
;define the initial velocity of the ball
[XballInitialVelocity = -3.0]
[ZballInitialVelocity = 2.0]
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.
;creation of the ball
ball create id 1 rad [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 ...
property kn 1e6 ks 1e6 fric 0.009
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.
;set fish callbacks to check if the ball touches the net
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))) == "net"
io.out("Net!!")
bp = ball.find(1)
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.
; compute the new position of the racket and move it!
fish define moverackets
bp = ball.find(1)
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
;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))) == "table"
moverackets
endif
end
Everything is ready … have fun!
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.
Data Files
CreateTable.dat (3D)
; excerpt-lmiw-start
fish define dim_table
xTable=array.create(1,2)
yTable=array.create(1,2)
xTable(1,1) = domain.min.x*0.95
xTable(1,2) = domain.max.x*0.95
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]
; excerpt-lmiw-end
; excerpt-hvqy-start
; 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]
; excerpt-hvqy-end
wall generate group 'table' cylinder ...
axis 0 0 -1 ...
base [xTable(1,2)] [yTable(1,2)] [zTable] ...
height [Ly*0.5] ...
one-wall ...
radius [Lx*0.02]
wall generate group 'table' cylinder ...
axis 0 0 -1 ...
base [xTable(1,1)] [yTable(1,1)] [zTable] ...
height [Ly*0.5] ...
one-wall ...
radius [Lx*0.02]
wall generate group 'table' cylinder ...
axis 0 0 -1 ...
base [xTable(1,1)] [yTable(1,2)] [zTable] ...
height [Ly*0.5] ...
one-wall ...
radius [Lx*0.02]
; excerpt-edxd-start
; 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]
; excerpt-edxd-end
; excerpt-stlb-start
; 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]
; excerpt-stlb-end
model save 'table'
TennisTable.dat (3D)
; fname: TableTennis.dat (3D)
; The example shows how to create a table tennis game!
;
; ====================================================
model new
model large-strain on
; excerpt-wywo-start
;define the initial velocity of the ball
[XballInitialVelocity = -3.0]
[ZballInitialVelocity = 2.0]
; excerpt-wywo-end
; definition of extent and condition
model domain extent -1 1
model domain condition destroy
;creation of the table
program call 'CreateTable'
; excerpt-wywp-start
;creation of the ball
ball create id 1 rad [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]
; excerpt-wywp-end
; excerpt-bogg-start
;linear model for ball-facet contacts
contact cmat default type ball-facet model linear ...
property kn 1e6 ks 1e6 fric 0.009
; excerpt-bogg-end
model gravity 0 0 -9.81
; excerpt-rpge-start
; 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))) == "net"
io.out("Net!!")
bp = ball.find(1)
ball.vel(bp,1)=0
ball.vel(bp,3)=0
endif
end
; excerpt-rpge-end
; excerpt-ptva-start
; compute the new position of the racket and move it!
fish define moverackets
bp = ball.find(1)
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
;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))) == "table"
moverackets
endif
end
; excerpt-ptva-end
; excerpt-rymc-start
;set fish callbacks to check if the ball touches the net
fish callback add checknet event contact_activated
;set fish callback to prepare the racket position
fish callback add tablecontact event contact_activated
; excerpt-rymc-end
model solve time 1.0
model save 'game'
;==============================================================
;eof: TableTennis.dat (3D)
Endnotes
[1] | To view this project in PFC, use the program menu.
⮡ PFC |
Was this helpful? ... | PFC © 2021, Itasca | Updated: Feb 25, 2024 |