billiards

June 12, 2026 · View on GitHub

codecov CodeFactor Code Smells Tests Open in Gitpod GitHub

Demo and Screenshot

This is an open-source project bringing unsophisticated billiards physics written in typescript to the browser.

Online Demo

Demos run in all major desktop and mobile browsers and uses WebGL

Features

  • Backspin, sidespin and cushion bounces well modeled.
  • Presentation using WebGL in any modern browser on mobile, linux, mac or windows.
  • Record and playback breaks.
  • Two player online mode with nchan nginx server.
  • Nine ball, snooker and three cushion billiards rules.
  • Deploys to github pages, vercel.com and render.com with github actions.
  • Runs on and was developed mostly on a potato e.g. Raspberry pi 4.

Reference material

Key equations

Based on Han 2005 paper

surface velocity

va=v+(up×Rω)\vec{v}_a = \vec{v} + (\vec{up} \times R\vec{\omega})

sliding motion

v˙=μgvava\dot{v} = -\mu g \frac{\vec{v}_a}{|\vec{v}_a|}

ω˙=52μgRvava\dot{\omega} = -\frac{5}{2}\frac{\mu g}{R} \frac{\vec{v}_a}{|\vec{v}_a|}

ω˙z=52MzmR2sgn(ωz)\dot{\omega}_z = -\frac{5}{2}\frac{M_z}{mR^2} \text{sgn}(\omega_z)

rolling motion

v˙=57MxymRup×ωω\dot{v} = -\frac{5}{7}\frac{M_{xy}}{mR} \frac{\vec{up} \times \vec{\omega}}{|\vec{\omega}|}

ω˙=57MxymR2ωω\dot{\omega} = -\frac{5}{7}\frac{M_{xy}}{mR^2} \frac{\vec{\omega}}{|\vec{\omega}|}

where

Mxy=752RμmgM_{xy} = \frac{7}{5\sqrt{2}} R \mu m g , Mz=23μmgρM_z = \frac{2}{3} \mu m g \rho

collisions

Based on paper by Alciatore incorporating throw effect due to the small amount of friction between balls. Figures to prove consistency between the code and paper here.

For ball aa:

vava+Jnormalmn^+Jtangentialmt^\vec{v}_a \leftarrow \vec{v}_a + \frac{J_{\text{normal}}}{m}\hat{n} + \frac{J_{\text{tangential}}}{m}\hat{t}

ωaωa+1I(ra×Jtangential)\vec{\omega}_a \leftarrow \vec{\omega}_a + \frac{1}{I} (\vec{r}_a \times \vec{J}_{\text{tangential}})

For ball bb:

vbvbJnormalmn^Jtangentialmt^\vec{v}_b \leftarrow \vec{v}_b - \frac{J_{\text{normal}}}{m}\hat{n} - \frac{J_{\text{tangential}}}{m}\hat{t}

ωbωb+1I(rb×Jtangential)\vec{\omega}_b \leftarrow \vec{\omega}_b + \frac{1}{I} (\vec{r}_b \times \vec{J}_{\text{tangential}})

Where:

The relative velocity at the point of contact is computed as:

vrel=(vavb)+ra×ωarb×ωb\vec{v}_{\text{rel}} = (\vec{v}_a - \vec{v}_b) + \vec{r}_a \times \vec{\omega}_a - \vec{r}_b \times \vec{\omega}_b

vslip=vrel(vreln^)n^\vec{v}_{\text{slip}} = \vec{v}_{\text{rel}} - (\vec{v}_{\text{rel}} \cdot \hat{n}) \hat{n}

ra=Rn^\vec{r}_a = -R \cdot \hat{n} and rb=Rn^\vec{r}_b = R \cdot \hat{n}

Jnormal=(1+e)vrel,normal(2/m)J_{\text{normal}} = \frac{-(1 + e)v_{\text{rel,normal}}}{(2/m)}

Jtangential=min(μJnormalvrel,17)(vrel,tangential)J_{\text{tangential}} = \min\left( \frac{\mu J_{\text{normal}}}{v_{\text{rel}}}, \frac{1}{7} \right)(-v_{\text{rel,tangential}})

n^\hat{n}: normal unit vector along the line of centers.

t^\hat{t}: tangential unit vector perpendicular to n^\hat{n}.

cushion bounce

This is based on a paper by Mathavan. Many of the figures from the paper are recreated to confirm correctness.

Slip velocity at cushion contact point I

x˙I=vx˙+ωy˙Rsinθωz˙Rcosθy˙I=vy˙sinθ+ωx˙Rẋ_I = \dot{v_x} + \dot{\omega_y} R \sin \theta - \dot{\omega_z} R \cos \theta \qquad ẏ'_I = -\dot{v_y} \sin \theta + \dot{\omega_x} R ϕ=arctan(y˙Ix˙I)s=(x˙I)2+(y˙I)2\phi = \arctan\left(\frac{ẏ'_I}{ẋ_I}\right) \qquad s = \sqrt{(ẋ_I)^2 + (ẏ'_I)^2}

Slip velocity at table contact point C

x˙C=vx˙ωy˙Ry˙C=vy˙+ωx˙Rẋ_C = \dot{v_x} - \dot{\omega_y} R \qquad ẏ_C = \dot{v_y} + \dot{\omega_x} R ϕ=arctan(y˙Cx˙C)s=(x˙C)2+(y˙C)2\phi' = \arctan\left(\frac{ẏ_C}{ẋ_C}\right) \qquad s' = \sqrt{(ẋ_C)^2 + (ẏ_C)^2}

Numerical solutions for the centroid velocity of the ball during compression and resititution phases.

(vx˙)n+1(vx˙)n=1M[μwcos(ϕ)+μscos(ϕ)(sinθ+μwsin(ϕ)cosθ)]ΔPI(\dot{v_x})_{n+1} - (\dot{v_x})_n = - \frac{1}{M} \left[\mu_w \cos(\phi) + \mu_s \cos(\phi') \cdot (\sin \theta + \mu_w \sin(\phi) \cos \theta)\right] \Delta P_I (vy˙)n+1(vy˙)n=1M[cosθμwsinθsinϕ+μssinϕ(sinθ+μwsinϕcosθ)]ΔPI(\dot{v_y})_{n+1} - (\dot{v_y})_n = - \frac{1}{M} \left[ \cos \theta - \mu_w \sin \theta \sin \phi + \mu_s \sin \phi' \cdot \left( \sin \theta + \mu_w \sin \phi \cos \theta \right) \right] \Delta P_I

Numerical solutions for angular velocity of ball

(ωx˙)n+1(ωx˙)n=52MR[μwsin(ϕ)+μssin(ϕ)×(sin(θ)+μwsin(ϕ)cos(θ))]ΔPI(\dot{\omega_x})_{n+1}−(\dot{\omega_x})_n = -\frac{5}{2MR}[\mu_w \sin(\phi) + \mu_s \sin(\phi') \times (\sin(\theta) + \mu_w \sin(\phi)\cos(\theta))]\Delta P_I (ωy˙)n+1(ωy˙)n=52MR[μwcos(ϕ)sin(θ)μscos(ϕ)×(sin(θ)+μwsin(ϕ)cos(θ))]ΔPI(\dot{\omega_y})_{n+1}−(\dot{\omega_y})_n = -\frac{5}{2MR}[\mu_w \cos(\phi)\sin(\theta) - \mu_s \cos(\phi') \times (\sin(\theta) + \mu_w \sin(\phi)\cos(\theta))]\Delta P_I (ωz˙)n+1(ωz˙)n=52MR(μwcos(ϕ)cos(θ))ΔPI(\dot{\omega_z})_{n+1}−(\dot{\omega_z})_n = \frac{5}{2MR}(\mu_w \cos(\phi)\cos(\theta))\Delta P_I

θ\theta is a constant of the angle of cushion contact above ball centre with sin(θ)=2/5\sin(\theta) = 2/5. μs\mu_s is the coefficient of sliding friction between the ball and table surface. μw\mu_w is the coefficient of sliding friction between the ball and the cushion.

Work done by the normal force at contact point II along the ZZ'-axis which is aligned from the ball centre to I

WZI(PI(n+1))=WZI(PI(n))+ΔPI2(zI(PI(n+1))+zI(PI(n)))W_{Z'}^I(P_I^{(n+1)}) = W_{Z'}^I(P_I^{(n)}) + \frac{\Delta P_I}{2} \left( z'_I(P_I^{(n+1)}) + z'_I(P_I^{(n)}) \right)

The ball is assumed to be bouncing in the +y cushion. Compression phase iterates until

v˙y0\dot{v}_y \le 0

For the restitution phase the iteration continues until the work done is

WZIee2WcompressionW_{Z'}^I \ge e_e^2 W_{\text{compression}}

Some of the Mathavan equations not supplied by the paper were inferred to bridge gaps for a complete numerical solution.

Stronge compliant cushion model

Based on the book Impact Mechanics by Stronge. This analytical model accounts for the compliant nature of cushion deformation and resolves the collision across three slip regimes.

Contact velocity at the cushion contact point:

Vc=v(ω×Rn^)\vec{V}_c = \vec{v} - (\vec{\omega} \times R \hat{n})

Regime classification is determined by the ratio vratio=vt0/vn0v_{\text{ratio}} = v_{t0} / v_{n0} and thresholds involving the friction coefficient μ\mu, restitution ene_n, and mass-matrix coefficients β\beta:

RegimeCondition
Gross slipvratio>μ((1+en)βratioη2en)v_{\text{ratio}} > \mu \left( (1 + e_n) \beta_{\text{ratio}} - \frac{\eta^2}{e_n} \right)
Initial stickvratio<μη2v_{\text{ratio}} < \mu \eta^2
Slip-stick-slipneither of the above

where η2\eta^2 is derived from the frequency ratio ωt/ωn\omega_t/\omega_n.

Velocity reconstruction from scalar solver results vnfv_{nf} and vtfv_{tf}:

Δvn=vnfvn0βn,Δvt=vtfvt0βt\Delta v_n = \frac{v_{nf} - v_{n0}}{\beta_n} , \quad \Delta v_t = \frac{v_{tf} - v_{t0}}{\beta_t}

Final updates:

vv+Δvnn^+Δvtt^\vec{v} \leftarrow \vec{v} + \Delta v_n \hat{n} + \Delta v_t \hat{t} ωω+mRI(n^×Δvtt^)\vec{\omega} \leftarrow \vec{\omega} + \frac{mR}{I} (-\hat{n} \times \Delta v_t \hat{t})

Useful commands

Install

nvm use v24.11.0
corepack enable
yarn set version 4.9.1
yarn install
yarn build
yarn gltfpack

This generates artefacts in /dist for prod deployment (e.g. on github static pages)

Run

yarn serve

Then open http://localhost:8080/ in your browser to play

Test

yarn test
yarn coverage

Maintain

yarn deps
yarn upgrade -L
yarn prettify

Two player

yarn serve

then open http://localhost:8080/multi.html to see options, message server is public nchan.

Controls

Use mouse, touch screen or keyboard:

Aim

Control Fine aim

Topspin and backspin

Shift Side spin

Space Hit - hold for more power

A Toggle aim helper

M Masse angle

O Camera view

F Full screen

C Chat

H Help

+ - Camera height

Mouse

scroll Shot power

dbl click Hit

Progress snapshots

July 2018

2018

July 2019

2019

March 2021

2021

August 2023 (mobile)

topaim
20232023

2026

Star History

Star History Chart

Licence

This project is open source and licensed under the GNU General Public License - see the LICENSE file for details. Contributions welcome.