Skip to content

Instantly share code, notes, and snippets.

@PM2Ring
Last active February 19, 2022 09:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save PM2Ring/1db5c92cb4fd3cf0af3015736e1accab to your computer and use it in GitHub Desktop.
Save PM2Ring/1db5c92cb4fd3cf0af3015736e1accab to your computer and use it in GitHub Desktop.
Bouncing around a triangular billiards table. SVG output.
""" Triangular billiards
With SVG output
Written by PM 2Ring 2022.02.02
"""
def make_svg(bbox, tri, path):
def f(x): return f'{x:0.4f}'.rstrip('0.')
def dot(x, y, color):
return f'<circle cx="{f(x)}" cy="{f(y)}" r="0.07" fill="{color}"/>\n'
mar = 0.1
viewbox = (
bbox[0] - mar, bbox[1] - mar,
bbox[2] - bbox[0] + 2*mar, bbox[3] - bbox[1] + 2*mar
)
vbox = ' '.join([f(u) for u in viewbox])
maxy = bbox[1] + bbox[3]
out = f'''\
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="{vbox}">
<g transform="scale(1, -1), translate(0, {f(-maxy)})" style="fill:none">
'''
#out += f'<rect x="{f(viewbox[0])}" y="{f(viewbox[1])}" width="100%" height="100%" fill="#777"/>\n'
out += ''.join([dot(*u, "blue") for u in tri])
colors = "#0ff", "#088", "#808", "#f0f"
pts = [path[u] for u in (0, 1, -2, -1)]
out += ''.join([dot(*u, c) for u, c in zip(pts, colors)])
s = ' '.join([f'{f(x)},{f(y)}' for x, y in tri])
out += f'<polygon style="stroke:blue; stroke-width:0.02" points="{s}"/>\n'
s = '\n'.join([f'{f(x)},{f(y)}' for x, y in path])
out += f'<polyline style="stroke:red; stroke-width:0.01" points="\n{s}\n"/>\n'
return out + '</g></svg>'
def delta(a, b):
ax, ay = a
bx, by = b
return ax - bx, ay - by
def slope_diff(ax, ay, bx, by):
# tangent difference
x, y = ax * bx + ay * by, ay * bx - ax * by
# normalize
r = sqrt(x*x + y*y)
return x / r, y / r
class Line:
def __init__(self, point, vector):
self.x, self.y = point
self.dx, self.dy = vector
def __repr__(self):
return f"Line(({self.x}, {self.y}), ({self.dx}, {self.dy}))"
def point(self, t):
return self.x + t * self.dx, self.y + t * self.dy
def intersect(self, other):
d = self.dx * other.dy - self.dy * other.dx
if d == 0:
# Parallel
return None
x = self.x - other.x
y = self.y - other.y
t = (other.dx * y - other.dy * x) / d
# other_t = (self.dx * y - self.dy * x) / d
return t
class Edge(Line):
def __init__(self, point0, point1):
dx, dy = vector = delta(point1, point0)
super().__init__(point0, vector)
self.dx2, self.dy2 = slope_diff(dx, dy, dx, -dy)
# Find slope of new line when other reflects off self
def reflect(self, other):
return slope_diff(self.dx2, self.dy2, other.dx, other.dy)
def bounce(edges, e0, t0, e1, t1, num):
eps = 5e-15
tlo, thi = eps, 1 - eps
p = edges[e0].point(t0)
current_edge = edges[e1]
q = current_edge.point(t1)
line = Line(p, delta(q, p))
path = [p, q]
for i in range(num - 1):
line = Line(q, current_edge.reflect(line))
# Test for intersections of line
for e in edges:
if e == current_edge:
continue
t = e.intersect(line)
if t is None:
continue
# Is it between the edge vertices?
if 0.0 <= t <= 1.0:
break
current_edge = e
q = e.point(t)
path.append(q)
# Is it too close to a vertex?
if t < tlo or t > thi:
break
return path
@interact
def test(ax=0, ay=1, bx=8, by=1, cx=1, cy=12,
e0=2, t0=0.7, e1=1, t1=0.35, num=10, auto_update=False):
tri = [(ax, ay), (bx, by), (cx, cy)]
edges = [Edge(*t) for t in zip(tri, tri[1:] + tri[:1])]
bbox = (
min(ax, bx, cx), min(ay, by, cy),
max(ax, bx, cx), max(ay, by, cy)
)
path = bounce(edges, e0, t0, e1, t1, num)
svg = make_svg(bbox, tri, path)
show(html(svg))
#print(svg)
#with open("bounce.svg", "w") as f: print(svg, file=f)
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
@PM2Ring
Copy link
Author

PM2Ring commented Feb 3, 2022

Live version, running on the SageMathCell server.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment