Last active
February 19, 2022 09:09
-
-
Save PM2Ring/1db5c92cb4fd3cf0af3015736e1accab to your computer and use it in GitHub Desktop.
Bouncing around a triangular billiards table. SVG output.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" 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) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Live version, running on the SageMathCell server.