Easy Java Simulations step-by-step series of examples Multiple collisions Description This simulation models the collision of planar disks which move inside a box with uniformly accelerated motion (when gravity is turned on) and which collide with the walls of the box and among themselves. Collisions can be elastic or inelastic and may take place simultaneously. We will need to define a rather sophisticated event to take care of all possible simultaneous collisions that may take place. Model Variables For clarity, we create three pages of variables: n indicates the number of disks in the box. The arrays x, y, vx, and vy contain the disks positions and velocities. The arrays mass and diameter contain the physical characteristics of the disks (which can be different from disk to disk). Throwing a ball (parabolic throw) page 1 of 9 Easy Java Simulations step-by-step series of examples The minimum and maximum x and y variables indicate the box boundaries. t is the time. g is the acceleration due to gravity. k is the restitution coefficient for all collisions (1 when the collisions are elastic). energy is a variable that we will use to compute the total mechanical energy of the system. Keeping the energy constant (for elastic collisions) is a good test for our collision algorithm. The gravityOn boolean variable indicates whether gravity is acting on the particles. This final page of variables contains variables that we will use in the events. Initialization The code for the initialization sets random initial positions and velocities for our disks. However, it is important that this configuration is legal. This means that no two disks can overlap each other. If this were the case, the event algorithm would fail and we would get a nasty error message at start-up. The code below tries up to 50 times to get such a legal initial configuration: t = 0; // reset the time _setDelay(1); // run very fast (a delay of 1 millisecond only) // Search for a random legal state for (int attempts=0; attempts<50; attempts++) { // Try 50 times Throwing a ball (parabolic throw) page 2 of 9 Easy Java Simulations step-by-step series of examples for (int i=0; i<n; i++) { x[i] = xmin+0.5*diameter[i] + (xmax-xmin-diameter[i])*Math.random(); vx[i] = (xmax-xmin)*(Math.random()-0.5); y[i] = ymin+0.5*diameter[i] + (ymax-ymin-diameter[i])*Math.random(); vy[i] = (ymax-ymin)*(Math.random()-0.5); } // Check for illegal states boolean legalState = true; for (int i=0; i<n; i++) { if (!legalState) break; for (int j=i+1; j<n; j++) { double d1 = x[j]-x[i]; double d2 = y[j]-y[i]; double result = d1*d1+d2*d2(diameter[i]+diameter[j])*(diameter[i]+diameter[j])/4; if (result<0) { legalState = false; break; } } } if (legalState) break; // exit for attempts } Evolution The evolution uses first a page of ODEs. Notice that the vertical acceleration is given by a method acc that we will need to define in the Custom panel. Also, the ODE defines two pages of events. These pages are good examples of how to create events for systems of particles. Throwing a ball (parabolic throw) page 3 of 9 Easy Java Simulations step-by-step series of examples The code for the Zero Condition of the first page of events is given by: double min = TOLERANCE; for (int i=0; i<n; i++) { double radius = diameter[i]/2; double d = y[i]-ymin-radius; if (vy[i]<0 && d<min) { horizontalRebound = false; ballRebounded = i; min=d; } d = ymax-radius-y[i]; if (vy[i]>0 && d<min) { horizontalRebound = false; ballRebounded = i; min=d; } d = x[i]-xmin-radius; if (vx[i]<0 && d<min) { horizontalRebound = true; ballRebounded = i; min=d; } d = xmax-radius-x[i]; if (vx[i]>0 && d<min) { horizontalRebound = true; ballRebounded = i; min=d; } } return min; Notice how the code uses the global variables horizontalRebound and ballRebounded to keep the type of rebound and the index of the disk which rebounded against a wall. Throwing a ball (parabolic throw) page 4 of 9 Easy Java Simulations step-by-step series of examples These variables are then used by the Action code of the event to correctly invert the horizontal or vertical velocity of the disk which rebounded. The fact that these global variables don’t change from the detection of the event to the invocation of the action code is an important feature of Ejs’ event handler. The line: if (Math.abs(vy[ballRebounded])<TOLERANCE) vy[ballRebounded] = 0.0; in the Action code of this event is required, together with a clever definition of the acc custom method below, to avoid the terrible “Zeno effect” that would happen if the restitution coefficient was smaller than one and, eventually, disks rebounded with less and less energy against the lower wall. The Zeno effect could otherwise freeze (hang) our simulation. Similarly, the code for the Zero Condition of the second event (which deals with the collision among disks) is given by: double min = TOLERANCE; for (int i=0; i<n; i++) { for (int j=i+1; j<n; j++) { double deltax = x[j]-x[i], deltay = y[j]-y[i]; double allowed = (diameter[i]+diameter[j])/2; double distance = deltax*deltax+deltay*deltay-allowed*allowed; if (distance<min) { double deltaVx = vx[j]-vx[i], deltaVy = vy[j]-vy[i]; if (deltax*deltaVx+deltay*deltaVy<0) { collision1 = i; collision2 = j; min = distance; } } } } return min; Throwing a ball (parabolic throw) page 5 of 9 Easy Java Simulations step-by-step series of examples And the corresponding Action code, which computes the velocities after the collision takes place, is given by: double deltax = x[collision2]-x[collision1]; double deltay = y[collision2]-y[collision1]; double distance = Math.sqrt(deltax*deltax+deltay*deltay); double rx=deltax/distance, ry=deltay/distance; // Unit vector joining centers double sx=-ry, sy=rx; // Vector ortogonal to the previous one double vr1=(vx[collision1]*rx+vy[collision1]*ry), vs1=(vx[collision1]*sx+vy[collision1]*sy); // Projections for disk 1 double vr2=(vx[collision2]*rx+vy[collision2]*ry), vs2=(vx[collision2]*sx+vy[collision2]*sy); // Projections for disk 2 double vr1d=( 2*mass[collision2]*vr2 + (mass[collision1]mass[collision2])*vr1 )/(mass[collision1]+mass[collision2]); // New velocity for disk 1 double vr2d=( 2*mass[collision1]*vr1 + (mass[collision2]mass[collision1])*vr2 )/(mass[collision1]+mass[collision2]); // New velocity for disk 2 // Undo the projections vx[collision1]=vr1d*rx+vs1*sx; vy[collision1]=vr1d*ry+vs1*sy; vx[collision2]=vr2d*rx+vs2*sx; vy[collision2]=vr2d*ry+vs2*sy; No Zeno-effect can take place fro this second event (at least under normal circumstances) in our problem. Constraints A single page of constraints computes the total mechanical energy of the system: Throwing a ball (parabolic throw) page 6 of 9 Easy Java Simulations step-by-step series of examples Custom code A page of custom code is used to define the acc method. As mentioned above, the line: if (h[i]-ymin-0.5*diameter[i]<TOLERANCE && vh[i]==0) return 0; // on the floor is needed to avoid the Zeno effect. View The view starts with the compound element based on a drawing panel, with the default particle replaced by a ParticleSet. The rest of the interface is rather standard. The main properties that need to be modified are shown in the colored fields of the property panels below: Throwing a ball (parabolic throw) page 7 of 9 Easy Java Simulations step-by-step series of examples Note: The _resetSolvers predefined method is required only by advanced ODE solvers to correctly reset their internal states when the user interacts with the interface to change the state of the ODE. Although the solver method selected for the example does not need this (thus calling the method has no real effect), we call this method on the On Drag property of the particle set in prevision the reader decides to select a different solver in the future. Throwing a ball (parabolic throw) page 8 of 9 Easy Java Simulations step-by-step series of examples Running the simulation A sample execution with a value of 0.6 for the restitution coefficient and gravity turned on leads eventually to the following state: Author Francisco Esquembre Universidad de Murcia, Spain July 2007 Throwing a ball (parabolic throw) page 9 of 9