1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// demo parameters
6// maybe overridden in test file
7var numBalls = parseInt(getArgValue('ball_count'));
8if (numBalls == 0 || isNaN(numBalls)) {
9    numBalls = 500;
10}
11var ballDiameter = 30;
12var gravity = 0.5; //screen heights per second^2
13var maxInitVelocity = 0.2;
14var maxAngularVelocity = 5; // rad per second
15var elasticity = 0.7;
16var joltInterval = 1.5;
17
18// globals
19var balls = [];
20var canvasWidth;
21var canvasHeight;
22var borderX;
23var borderY;
24var timeOfLastJolt = 0;
25
26function include(filename) {
27  var head = document.getElementsByTagName('head')[0];
28  var script = document.createElement('script');
29  script.src = filename;
30  script.type = 'text/javascript';
31  head.appendChild(script)
32}
33
34include("bouncing_balls_draw_ball_as_" + getArgValue('ball') + ".js");
35include("bouncing_balls_draw_back_as_" + getArgValue('back') + ".js");
36
37window.requestAnimFrame = (function(){
38  return window.requestAnimationFrame    ||
39      window.webkitRequestAnimationFrame ||
40      window.mozRequestAnimationFrame    ||
41      window.oRequestAnimationFrame      ||
42      window.msRequestAnimationFrame     ||
43      function( callback ){
44        window.setTimeout(callback, 1000 / 60);
45      };
46})();
47
48window.onload = init;
49
50function init(){
51  handleResize();
52  for (var i = 0; i < numBalls; i++) {
53    balls.push(new Ball());
54  }
55  window.addEventListener("resize", handleResize, false);
56  drawBallInit(ballDiameter); // externally defined
57  window.requestAnimFrame(updateFrame);
58}
59
60function handleResize() {
61  canvasWidth = window.innerWidth;
62  canvasHeight = window.innerHeight;
63  canvas.setAttribute('width', canvasWidth);
64  canvas.setAttribute('height', canvasHeight);
65  borderX = ballDiameter/canvasWidth;
66  borderY = ballDiameter/canvasHeight;
67  prepareBackground(); // externally defined
68}
69
70function updateFrame() {
71  var now = new Date().getTime() / 1000;
72  var jolt = false;
73  if (now - timeOfLastJolt > joltInterval) {
74    jolt = true;
75    timeOfLastJolt = now;
76  }
77  drawBackground(); // externally defined
78  for (var i = 0; i < numBalls; i++) {
79    balls[i].step(jolt);
80  }
81  window.requestAnimFrame(updateFrame);
82}
83
84function Ball(){
85  var x = borderX + Math.random()*(1-2*borderX);
86  var y = borderY + Math.random()*(1-2*borderY);
87  var angle = Math.PI * 2 * Math.random();
88  var velocityY = Math.random()*maxInitVelocity*2 - maxInitVelocity;
89  var velocityX = Math.random()*maxInitVelocity*2 - maxInitVelocity;
90  var angularVelocity = Math.random()*maxAngularVelocity*2 -
91      maxAngularVelocity;
92  var previousFrameTime = new Date().getTime();
93  var previousBounceTime = 0;
94  var alive = true;
95  function step(jolt) {
96    var curTime = new Date().getTime();
97    var timeStep = (curTime - previousFrameTime) / 1000;
98    previousFrameTime = curTime;
99
100    // handle balls that are no longer bouncing
101    if (!alive) {
102      if (jolt) {
103        // If a jolt is applied, bump the rollong balls enough for them to
104        // reach between 0.75x and 1x the height of the window
105        velocityY = -Math.sqrt(2 * gravity * (1-2 * borderY) * (0.75 + 0.25 *
106            Math.random()))
107        velocityX = Math.random()*maxInitVelocity*2 - maxInitVelocity;
108        angularVelocity = Math.random()*maxAngularVelocity*2 -
109            maxAngularVelocity;
110        alive = true;
111      } else {
112        // rolling on the ground
113        angularVelocity = 2*velocityX*canvasWidth/ballDiameter;
114      }
115    }
116
117    // Compute angular motion
118    angle += timeStep*angularVelocity;
119    // Compute horizontal motion
120    var remainingTime = timeStep;
121    var deltaX = velocityX*remainingTime;
122    while ((x+deltaX) < borderX || (x+deltaX) > (1-borderX)){
123      if ((x+deltaX) < borderX) {
124        // left side bounce
125        remainingTime -= (borderX - x)/velocityX;
126        x = borderX;
127      } else {
128        // right side bounce
129        remainingTime -= ((1-borderX) - x)/velocityX;
130        x = 1 - borderX;
131      }
132      velocityX = -elasticity*velocityX;
133      deltaX = velocityX*remainingTime;
134    }
135    x += deltaX;
136
137    // Compute vertical motion
138    remainingTime = timeStep;
139    var deltaY = alive ? velocityY*timeStep+gravity*timeStep*timeStep/2 : 0;
140    //Handle floor bounces
141    //To make sure the floor is air tight, we must be able to process multiple
142    //bounces per time step to avoid the "tunnel effect".
143    while ((y + deltaY) > (1 - borderY) && alive) {
144      // time to hit floor
145      var c = y-(1-borderY);
146      var b = velocityY;
147      var a = gravity/2;
148      // The greater root is always the right one
149      var subStep = (-b + Math.sqrt(b*b-4*a*c))/(2*a);
150      //velocity after floor hit
151      velocityY = -elasticity*(velocityY + gravity*subStep);
152      remainingTime -= subStep;
153      var bounceTime = curTime - remainingTime;
154      if (bounceTime - previousBounceTime < 0.005){
155        // The number of iterations may not be finite within a timestep
156        // with elasticity < 1. This is due to power series convergence.
157        // To gard against hanging, we treat the ball as rolling on the ground
158        // once time between bounces is less than 5ms
159        alive = false;
160        deltaY = 0;
161      } else {
162        deltaY = velocityY*remainingTime+gravity*remainingTime*remainingTime/2;
163      }
164      previousBounceTime = bounceTime;
165      y = (1 - borderY);
166    }
167    y += deltaY;
168    velocityY += gravity*remainingTime;
169
170    drawBall(x * canvasWidth, y * canvasHeight, angle); // externally defined
171  }
172
173  return {
174    step: step
175  }
176}
177