/* Model of a billiards/pool table */ #define radius 3 #define xMax 150 #define yMax 300 #define pocket 7 #define fric 1 /* this class defines the dynamics of a billiards ball on a table with friction. A ball continues rolling in the same direction until it comes to a halt, or hits another ball or an edge. The ball disappears if it falls into a pocket, as indicated by do ... until Pocketed. The signal variables ChangeX and ChangeY are set when either the x or y velocities are changed due to a collision. Note that the position is continuous, so cannot be changed. */ Ball = (initpx, initpy, initvx, initvy)[px, py, vx, vy, ChangeX, ChangeY, Pocketed] { px = initpx, py = initpy, vx = initvx, vy = initvy, new direction in { realvar(direction), direction = vx^2/(vx^2 + vy^2), do always { cont(px), cont(py), lcont(vx), lcont(vy), if (ChangeX || ChangeY) then direction = vx^2/(vx^2 + vy^2), unless (ChangeX || ChangeY) then direction' = 0, px' = vx, py' = vy, unless ChangeX then { cont(vx), if (vx < 0) then vx' = fric * direction^0.5, if (vx > 0) then vx' = -fric * direction^0.5, if (vx = 0) then vx' = 0 }, unless ChangeY then { cont(vy), if (vy < 0) then vy' = fric * (1 - direction)^0.5, if (vy > 0) then vy' = -fric * (1 - direction)^0.5, if (vy = 0) then vy' = 0 } } until Pocketed } }, /* this procedure determines if any ball has hit the edges of the table. If it does, it signals that its speed must change, and resets it. Note that since it does not know about pockets, it could allow both edges to be hit simulataneously. */ Edges = (){ always forall Ball(X) do { if (X.px = radius || X.px = xMax - radius) then { X.ChangeX, X.vx = -prev(X.vx) }, if (X.py = radius || X.py = yMax - radius) then { X.ChangeY, X.vy = -prev(X.vy) } } }, /* procedure for determining if any 2 balls collide. Currently multiple ball collisions are not modeled. if 2 balls are determined to have collided, we use the conservation of momentum and energy laws, along with the fact that transfer of momentum occurs along the line joining the centers of the balls (assuming frictionless balls) to determine the resulting velocities. Ideally we would like to just state the conservation of energy, but Hybrid cc's constraint propagation cannot handle this. */ Collisions = (){ always forall Ball(A) do forall Ball(B) do if (A < B) then //not the same ball if ((B.px - A.px)^2 + (B.py - A.py)^2 = 4*radius^2) then { if (A.px = B.px) then { A.ChangeY, B.ChangeY, A.vy = prev(B.vy), B.vy = prev(A.vy) }, if (A.px < B.px) then { A.ChangeX, B.ChangeX, A.ChangeY, B.ChangeY, new c in new ix in { c = (A.py - B.py)/(A.px - B.px), ix = (prev(B.vx - A.vx) + c*prev(B.vy - A.vy))/(1+c^2), B.vx = prev(B.vx) - ix, B.vy = prev(B.vy) - c*ix, A.vx + B.vx = prev(A.vx + B.vx), // X-momentum A.vy + B.vy = prev(A.vy + B.vy) // Y-momentum } } } }, /* procedure for determining if any ball has fallen into a pocket */ Pockets = (){ always forall Ball(X) do if (prev(X.px)^2 + prev(X.py)^2 <= pocket^2 || prev(X.px)^2 + (prev(X.py) - yMax/2)^2 <= pocket^2 || prev(X.px)^2 + (prev(X.py) - yMax)^2 <= pocket^2 || (prev(X.px) - xMax)^2 + prev(X.py)^2 <= pocket^2 || (prev(X.px) - xMax)^2 + (prev(X.py) - yMax/2)^2 <= pocket^2 || (prev(X.px) - xMax)^2 + (prev(X.py) - yMax)^2 <= pocket^2) then X.Pocketed }, /* the basic setup. More balls can be added by adding extra lines here */ Ball(B1, 10, 10, 25, 25), Ball(B2, 20, 11, -35, 55), Ball(B3, 80, 51, -15, 49), Ball(B4, 120, 51, -15, 49), Edges(), Collisions(), Pockets()