BilliardCue
Aim with the pointer, pull back, let go. Where the tip lands decides how the cue ball spins.
What it does
The cue only works while every ball is still. It points from the cue ball (the BilliardBall numbered 0) toward the pointer on the table. Press, drag back away from the ball, and let go. The further you drag, up to pull metres on the table, the harder the shot, up to maxSpeed.
A cue meets the ball for about a millisecond, so the strike is one push. Landing tipOffset radii above or below the centre, it sets the speed and the spin together: speed J/m, spin J·h/I. At 0.4 of the radius the ball rolls from the first instant. At the centre it slides first. Below the centre it is draw; above, follow. The up and down arrows, or the wheel, move the tip a step at a time.
With ghost on, the cue runs the shot forward with the table's own step before you play it. It draws a ghost ball where the cue ball will touch the first ball, a line where that ball will go, and the path the cue ball will take after. A small ball face on the bottom rail shows where the tip will land.
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.
Build it from scratch with the bjs CLI:
- 1Scaffold a project
World, renderer, and dev server — ready to run.
- 2Install dependencies
- 3Add BilliardCue
Copies its source into src/ so the scene resolves.
First time? Run bjs login once.
- 4Paste the scene into src/scenes/arcade-room.ts and run
SceneLoader builds the world from the JSON; reload to rebuild.
It needs a cue ball, a table to read, and a camera that announces itself (AxisViewCamera does).
Props
maxSpeed(number, default10). The cue ball's speed at full pull, m/s.tipOffset(number, default0). Where the tip lands, in radii above (+) or below (-) the centre.maxTip(number, default0.5). How far from the centre the tip may go.tipStep(number, default0.1). One key press or wheel notch.pull(number, default0.5). Metres of drag on the table for full power.minSpeed(number, default0.05). A softer release is no shot.previewSpeed(number, default2). The speed the ghost ball predicts while you are only aiming.ghost(boolean, defaulttrue). Draw the ghost ball and the two lines.followKey(string, default"ArrowUp"). Moves the tip up.drawKey(string, default"ArrowDown"). Moves the tip down.
Events
Emits:
billiards.cue.struck,{ entityId, speed, tipOffset, dirX, dirZ }.
Listens to:
camera.active.changedandarccamera.created, for the camera to cast the pointer from.keyboard.keydown, for the tip keys.
Dependencies
MeshPrimitive,BilliardBall. The cue ball.BilliardTable. Its numbers and itspredictShot.CaptureZone. Pockets, so the ghost knows when the cue ball would drop.PointerDrag. Its ray maths, to find the pointer on the table.
Notes
- Draw needs something to hit. A ball struck low on its own slides forward spinning backward, then rolls on forward at a slower speed. After a full hit the same spin pulls it back.
- From half a metre at 2 m/s, a centre-ball hit has nearly rolled by the time it arrives. Three presses of the down arrow make it a stun shot that stops dead.
- Pointer handlers only remember the pointer. Aiming, power and the strike all happen in the system's own update.





















