logo
BilliardTable

physics · cue-sports

BilliardTable

Talks toScore
2
Unlock

Install with the CLI:

bjs download BilliardTable

BilliardTable

A pool table that runs on a fixed step. Balls slide and then roll, hit each other without passing through, bank off cushions and drop into pockets.

What it does

Put BilliardTable on the entity whose MeshPrimitive is the bed. The bed's position is the middle of the table. The system then runs every BilliardBall in the world.

It spends each frame in fixed steps of 1/120 s and carries the rest to the next frame, the same scheme as GravityBody. Every step does four things in the same order.

  1. Move. Friction acts where the ball touches the cloth. It pushes against the slip of that point (its velocity plus the spin's swing), not against the ball's velocity. The one push slows the ball and spins it up, so a ball hit at the centre slides first. When the slip would reach zero inside a step, the ball snaps to rolling at 5/7 of its speed. A rolling ball slows at roll times g, twenty times gentler.
  2. Collide. Before moving the balls along, the step finds the first moment two of them are exactly touching (the time of impact). It moves everything to that moment, pushes the pair apart along the line of their centres, and spends the rest of the step. The push is j = (1 + e) · m/2 · closing speed, and it is only given while the pair is closing. Momentum is kept exactly. The push goes through both centres, so it changes no spin.
  3. Cushions. The ball's centre can only reach the bed shrunk by one radius, so each cushion is a straight line. A ball meets it at its own time of impact. The speed going into the rail comes back times cushionRestitution; the speed along the rail is kept. The rail meets the ball 2/5 of a radius above its centre, so a rolling ball comes off rolling.
  4. Pockets. After the step, each ball is checked against every pocket: an entity with CaptureZone that takes it. An object ball stops in the pocket, and CaptureZone takes it a moment later. The cue ball (number 0) goes back on the head spot instead, and the table says it scratched.

Because every effect happens inside the step, a break comes out the same at 30 fps and at 144 fps.

Use it in a scene

A scene is just data — a list of entities, each with its components. At startup the SceneLoader turns this JSON into a live world; edit the file and reload to rebuild it.

Fastest path

bjs download scene BilliardTable

Pulls this exact scene into your project and runs it — no copy-paste. Add --all to grab the components and assets it needs too.

Or build it from scratch with the bjs CLI:

  1. 1Scaffold a project
    npm create @babylonjsmarket/arcade@latest my-game

    World, renderer, and dev server — ready to run.

  2. 2Install dependencies
    cd my-game && npm install
  3. 3Add BilliardTable
    bjs download BilliardTable

    Copies its source into src/ so the scene resolves.

    First time? Run bjs login once.

  4. 4Paste the scene into src/scenes/arcade-room.ts and run
    npm run dev

    SceneLoader builds the world from the JSON; reload to rebuild.

JSON
{
  "Table": {
    "components": {
      "MeshPrimitive": {
        "primitive": "box",
        "width": 2.54,
        "height": 0.04,
        "depth": 1.27,
        "position": [0, -0.02, 0],
        "material": { "diffuseColor": [0.04, 0.42, 0.24] }
      },
      "BilliardTable": { "slide": 0.2, "roll": 0.01, "ballRestitution": 0.95, "cushionRestitution": 0.75 }
    }
  },
  "Pocket_TopLeft": {
    "components": {
      "MeshPrimitive": { "primitive": "cylinder", "height": 0.036, "diameter": 0.13, "position": [-1.27, 0.028575, -0.635] },
      "CaptureZone": { "radius": 0.065, "acceptTag": "ball", "points": 1 }
    }
  }
}

The bed's top is at y = 0 here, so balls sit at y = 0.028575 and so do the pockets. Tag every ball ball so the pockets take it.

Props

  • slide (number, default 0.2). Sliding friction between ball and cloth.
  • roll (number, default 0.01). Rolling resistance.
  • gravity (number, default 9.81). m/s².
  • ballRestitution (number, default 0.95). How much of their closing speed two balls part at.
  • cushionRestitution (number, default 0.75). How much of its speed into a rail a ball comes back with. 0.75 keeps 56% of that energy.
  • cushionHeight (number, default 0.4). Where the rail meets the ball, as a fraction of the radius above its centre.
  • cushions (boolean, default true). Off, there are no rails and balls roll off the bed.
  • bed ([length, width], default [2.54, 1.27]). Metres. Length runs along x.
  • owner (string, default "player"). Who a potted ball scores for.

Events

Emits:

  • billiards.ball.collided, { aId, bId, speed, x, z }. speed is the closing speed.
  • billiards.ball.cushioned, { entityId, rail, speed, x, z }. rail is left, right, top or bottom in a top view.
  • billiards.ball.potted, { entityId, number, pocketId, x, z }.
  • score.add, { ownerEntity, points }, for each pot, with the pocket's CaptureZone points.
  • billiards.cue.scratched, { entityId, pocketId }.
  • billiards.table.settled, { moving: 0 }, when the last ball stops.

Listens to nothing.

Dependencies

Notes

  • The core's predictShot runs the same step forward with only the cue ball moving. BilliardCue uses it for the ghost ball, so the ghost sits where the real contact will be.
  • Contacts slower than 5 mm/s are not announced; a settling rack would flood the bus.
  • The head spot and foot spot sit a quarter of the bed in from each end. The cue ball starts on the head spot; the rack's apex sits on the foot spot.
  • A scratched cue ball leaves play and comes straight back, as a pooled ball does, so its trail restarts on the spot.

More like this

BilliardBall
CaptureZone
MeshPrimitive
Score
BilliardCue
BilliardRack
BlackHole
BoatMode
BoatWater
Bumper
ConicPath
ContactDamage
ContactImpulse
GravityBody
GravityLens
GravityMass
MassDriver
MouseHole
Obstacle
Physics
Spinner
SurfacePatch
TrajectoryPreview
Velocity6DOF
WhiteHole
Wormhole

Was this page helpful?

We read every note — tell us what's working and what isn't.

↑↓ NavigateEnter SelectEsc CloseCtrl+K Open Search