## Billiard simulation–part 6: balls rotation

June 2, 2013 at 11:09 AM

The simulation is two-dimensional: the table and balls are seen from straight above. Drawing the balls as simple circles produces a very un-realistic effect though; to improve things it is necessary to render in some way the rotation of the balls. The easiest way to do this is to add some feature to a ball surface – like a white circle – and change its position and size as the ball rotates.

A point on the surface of a ball is identified by two angles $$(\varphi, \vartheta)$$:

As the balls moves, it rotates by an angle $$\delta = \Delta s / r$$, where $$r$$ is the ball radius and $$\Delta s = \sqrt{\Delta x^2 + \Delta y^2}$$ is the distance travelled. The direction of the movement is the direction of the ball velocity, so this is the situation as seen ‘from above’:

where $$\alpha = \arctan(w/v)$$. Let’s rotate the $$x, y$$ axes by the angle $$\alpha$$, so that the movement is along the new $$\bar x$$ axis: to do it simply subtract $$\alpha$$ from $$\varphi$$. Using these new coordinates the situation as seen ‘from the side’ is:

(the $$\bar y$$ axis goes away from you ‘through’ the screen). The initial coordinate of the point are:

$$\bar x = r \sin \vartheta cos (\varphi-\alpha)$$

$$\bar y = r \sin \vartheta sin (\varphi-\alpha)$$

$$z = r \cos \vartheta$$

after the ball moves the $$\bar y$$ coordinate is the same, whereas $$\bar x$$ and $$z$$ are rotated by $$\delta$$ clockwise:

$$\bar{x}’ = \bar x \cos\delta + z \sin\delta = r \sin \vartheta cos (\varphi-\alpha) \cos\delta + r \cos \vartheta \sin\delta$$

$$\bar y’ = r \sin \vartheta sin (\varphi-\alpha)$$

$$z’ = –\bar x \sin\delta + z \cos\delta = –r \sin \vartheta cos (\varphi-\alpha) \sin\delta + r \cos \vartheta \cos\delta$$

The angles after the ball moves are then:

$$\varphi’ = \alpha + \arctan \frac{\bar y’}{\bar x’} = \alpha + \arctan \frac {\sin \vartheta sin (\varphi-\alpha)} {\sin \vartheta cos (\varphi-\alpha) \cos\delta + \cos \vartheta \sin\delta}$$

$$\vartheta’ = \arccos \frac{z’}{r} = \arccos(-\sin \vartheta cos (\varphi-\alpha) \sin\delta + \cos \vartheta \cos\delta)$$

and here is the corresponding code:

  /**
* Updates the ball position and orientation, applying the current velocity for a specified time interval
* @param t the time interval to use. The velocity is considered constant, so the time interval must be
* small compared to the rate of change of the velocity
*/
updatePosition(t: number) {
var dx = this.v * t;
var dy = this.w * t
// Update the ball position
this.x += dx;
this.y += dy;
var ds2 = dx * dx + dy * dy;
if (ds2 > 0) {
// Update the ball orientation
var delta = Math.sqrt(ds2) / this.radius;
var sinDelta = Math.sin(delta);
var cosDelta = Math.cos(delta);
var alpha = Math.atan2(this.w, this.v);
var sinTheta = Math.sin(this.theta);
var cosTheta = Math.cos(this.theta);
var phiAlpha = this.phi - alpha;
var sinPhiAlpha = Math.sin(phiAlpha);
var cosPhiAlpha = Math.cos(phiAlpha);
this.phi = alpha + Math.atan2(sinTheta * sinPhiAlpha, sinTheta * cosPhiAlpha * cosDelta + cosTheta * sinDelta);
this.theta = Math.acos(-sinTheta * cosPhiAlpha * sinDelta + cosTheta * cosDelta);
}
} // updatePosition

A circle drawn on the surface of the ball that is centered at the point $$(\varphi, \vartheta)$$ looks like a circle when $$\vartheta=0$$, and then get progressively ‘squashed’ as $$\vartheta$$ increases, until is disappears when $$\vartheta>\pi/2$$:

The circle is actually a spherical one, that should be projected on a horizontal plane to compute the exact shape to be drawn; approximately though the circle becomes an ellipse as $$\vartheta$$ increases – with the axis in the direction from its center to the center of the ball decreasing with $$\cos \vartheta$$. Using this approximation the code to draw the ball is this:

  /**
* draw: draws the ball
* @param ctx the canvas rendering context to use to draw the ball
*/
draw(ctx: CanvasRenderingContext2D) {
ctx.save();
// Move the coordinates to the center of the ball - simplifies everything else
ctx.translate(this.x, this.y);
// Gradient from the ball color to black, used to shade the ball color to give an illusion of depth
// Gradient from white to black, used to shade the white circle to give an illusion of depth
// Draw the ball: a circle filled with the ball color shading to black
ctx.beginPath();
ctx.fill();
if (this.theta < Math.PI / 2) {
// Draw the white circle if it is visible
var d = this.radius * Math.sin(this.theta);
var cosTheta = Math.cos(this.theta);
var s = this.circleRadius * cosTheta;
if (d - s < this.radius) {
var cosPhi = Math.cos(this.phi);
var sinPhi = Math.sin(this.phi);
// Clip to the ball's circle - do not want to draw parts of the white circle that fall outside the ball borders
ctx.clip();
// Move the coordinates to the center of the white circle
ctx.translate(d * cosPhi, d * sinPhi);
// Compress the coordinates by cosTheta in the direction between the center of the white circle and the center of the ball
ctx.rotate(this.phi);
ctx.scale(cosTheta, 1);
// Draw the white circle
ctx.beginPath();
ctx.arc(0, 0, this.circleRadius, 0, 2 * Math.PI);
ctx.fill();
}
}
ctx.restore();
} // draw

Note how the code uses gradients to fill the ball and the white circle to give a more ‘tridimensional’ look.

Aside: these first experiences of using the HTML canvas are very positive: the API is complete, easy to use and works well in multiple ‘modern’ browsers

Posted in: Programming