# Randomly bouncing balls with constant tails in a spherical constraint

How easy is the following animation made with TikZ, PSTricks, and Asymptote?

Use the following as a starting template to save your time.

\documentclass[border=12pt]{standalone}
\begin{document}
.
\end{document}

-
A challenging problem that I'll look into after the thesis is out of the way. In the meantime, en.wikipedia.org/wiki/Collision_detection might help. – Jubobs Jan 15 '14 at 1:55
+1 for the template – Papiro Jan 15 '14 at 6:49
One down vote detected. Thanks. – kiss my armpit Jan 18 '14 at 11:34

Not perfect, not optimal, not commented, not quick to compile, and not portable. Requires lualatex.

\documentclass[tikz,border=.125cm]{standalone}
\directlua{%

Vector = {}
Vector.__index = Vector

function Vector.new(x, y)
local obj = {x=x, y=y}
setmetatable(obj, Vector)
return obj
end

function Vector.fromPolar(a, r)
end

function Vector:copy()
return Vector.new(self.x, self.y)
end

return Vector.new(p.x+q.x, p.y+q.y)
end

function Vector.__sub(p, q)
return Vector.new(p.x-q.x, p.y-q.y)
end

function Vector.__mul(p, q)
if getmetatable(p) == Vector then
if getmetatable(q) == Vector then
return p.x*q.x + p.y*q.y
else
return Vector.new(p.x*q, p.y*q)
end
else
return Vector.new(p*q.x, p*q.y)
end
end

function Vector.__div(p, q)
return Vector.new(p.x/q, p.y/q)
end

function Vector.__unm(p)
return Vector.new(-p.x, -p.y)
end

function Vector:length()
return math.sqrt(self.x^2 +  self.y^2)
end

function Vector:angle()
return math.atan2(self.y, self.x)
end

function Vector:normalised()
local l
l = self:length()
if l > 0 then
return self / l
else
return Vector.new(0,0)
end
end

function Vector:tostring()
return "(" .. self.x .. ", " .. self.y .. ")"
end

History = {}
History.__index = History

function History.new(l)
local obj = {data={}, length=l, _i=1}
setmetatable(obj, History)
return obj
end

function History:update(v)
self.data[self._i] = v
self._i = self._i + 1
if self._i > self.length then
self._i = 1
end
end

function History:get(i)
local j
j = self._i - i
if j < 1 then
j = j + self.length
end
return self.data[j]
end

function History:tostring()
local s, i
s = self:get(1):tostring()
for i = 2,self.length do
s = s .. " -- " .. self:get(i):tostring()
end
return s
end

Ball = {}
Ball.__index = Ball

function Ball.new(pos, dir, r, h, c)
h = h or 1
c = c or "black"
local obj = {pos=pos, dir=dir, radius=r, history=History.new(h), color=c}
setmetatable(obj, Ball)
local i
for i=1,h do
obj.history:update(pos)
end
return obj
end

function Ball:move()
self.history:update(self.pos)
self.pos = self.pos + self.dir
end

function Ball:collide(b)
local d = self.pos - b.pos
end

function Ball:tostring()
end
Arena = {}
Arena.__index = Arena

function Arena.new(R, n, r, h, col, ncol)
local obj = {radius=R, balls={}, nballs=n}
setmetatable(obj, Arena)
local i, j, c, ball, done
for i = 1,n do
done = false
while not done do
c = col[math.random(1, ncol)]
ball = Ball.new(Vector.fromPolar(math.random(0,359), math.random()*(R-r)),
Vector.fromPolar(math.random(0,359), math.random()), r, h, c)
done = true
if i > 1 then
for j = 1,i-1 do
if ball:collide(obj.balls[j]) then
done = false
break
end
end
end
end
obj.balls[i] = ball
end
return obj
end

function Arena:update(K)
K = K or 1
local i, j, k, b, d, n, v
local v1, v2, vn1, vn2, vt1, vt2
for k = 1,K do
for i = 1,self.nballs do
self.balls[i]:move()
n = -self.balls[i].pos:normalised()
d = self.balls[i].dir
self.balls[i].dir = d - 2*(d*n)*n
self.balls[i]:move()
end
for j = 1,self.nballs do
if not (i == j) then
if self.balls[i]:collide(self.balls[j]) then
d = self.balls[i].pos - self.balls[j].pos
v1 = self.balls[i].dir
v2 = self.balls[j].dir
n = d:normalised()
vn1 = (v1*(-n))*(-n)
vn2 = (v2*n)*n
vt1 = vn1 - v1
vt2 = vn2 - v2
v1 = vt1 + vn2
v2 = vt2 + vn1
self.balls[i].dir = v1
self.balls[j].dir = v2
self.balls[i]:move()
self.balls[j]:move()
end
end
end
end
end
end
}

\def\getlua#1{\directlua{tex.print("" .. #1)}}

\pgfdeclarelayer{background}
\pgfsetlayers{background,main}
\begin{document}
\directlua{%
A = Arena.new(75, 20, 5, 75, {"red", "yellow", "pink", "green", "orange", "purple", "blue"}, 7)
}

\foreach \j in {1,...,100}{

\begin{tikzpicture}[x=1pt,y=1pt]

\foreach \i in {1,...,\getlua{A.nballs}}{
%
\fill [opacity=0.5] \getlua{A.balls[\i]:tostring()};
%
\begin{pgfonlayer}{background}
\draw [very thick, line join=round, line cap=round, \getlua{A.balls[\i].color}]
\getlua{A.balls[\i].history:tostring()};
\end{pgfonlayer}
}

\directlua{A:update(5)}

\end{tikzpicture}
}
\end{document}


-
I can watch this for 10 hours nonstop. :) – Paulo Cereda Jan 17 '14 at 20:49
@CodeMocker You mean you want the balls to move faster? So fast that you can't count the balls? – Mark Wibrow Jan 18 '14 at 7:53
@CodeMocker actually there appears to be some problems with the code as sometimes balls get stuck together or to the edge of the circle. – Mark Wibrow Jan 18 '14 at 10:44
@MarkWibrow - Yes, the collision code in Arena:update doesn't look like standard Physics, and doesn't generate very realistic collisions. This may be the cause of the strange behaviour you observed. e.g. the collision between red and yellow at the 6'oclock position in frame 78 does not conserve momentum. – Andrew Kepert Jan 20 '14 at 7:52
@AndrewKepert possibly, but every frame of the gif corresponds to 5 iterations of the update method. Most of the problems (I believe) with collisions stem from the fact that the balls are updated in a loop, and both balls in a collision are moved without checking if this leads to further collisions (until the next update). Either way it should be straightforward for someone who is more familiar with the "standard Physics" to replace the code as the overloaded operators in Vector class mean the necessary equations can be specified easily. – Mark Wibrow Jan 20 '14 at 8:48