"""
Based on https://enki.ws/ganja.js/examples/coffeeshop.html#pga3d_points_and_lines.
"""
# %%
from typing import cast
from kingdon import Algebra
import math
from timeit import default_timer
from kingdon.graph import walker, encode, GraphWidget
import solara.lab
import solara
import asyncio
alg = Algebra(3, 0, 1)
# %%
point = lambda x, y, z: alg.vector(e0=1, e1=x, e2=y, e3=z).dual()
plane = lambda a, b, c, d: alg.vector(e1=a, e2=b, e3=c, e0=d)
def dist_pp(P1, P2) -> float: # point to point
return (P1.normalized() & P2.normalized()).norm().e
def dist_pP(P, p) -> float: # point to plane
return (P.normalized() & p.normalized()).norm().e
def dist_ll(l1, l2) -> float: # line to line
return (l1.normalized() * l2.normalized()).dual().norm().e
def angle_pp(p1, p2): # Angle between planes
return math.acos((p1.normalized() | p2.normalized()).e) * 180 / math.pi
def angle_ll(l1, l2): # Angle between lines
return math.acos((l1.normalized() | l2.normalized()).e) * 180 / math.pi
# Create 5 points.
A = point(0, 0.8, 0)
B = point(0.8, -1, -0.8)
C = point(-0.8, -1, -0.8)
D = point(0.8, -1, 0.8)
E = point(-0.8, -1, 0.8)
# Our ground plane
a = B & C & D
# %%
def graph_func(time):
A = point(0, math.sin(time * 4), 0)
subjects = [
0xD0FFE1,
[A, B, C], # graph on face
0x00AA88,
[A, B],
[A, C],
[A, D],
[B, C],
[B, D],
[C, E],
[A, E],
[E, D], # graph all edges
0x444444,
A,
"A",
B,
"B",
C,
"C",
D,
"D",
E,
"E", # graph all vertices
0xFF8844,
[A, E],
f"{dist_pp(A, E):.2f}", # distance A to E
0x224488,
[A, B + E],
f"{dist_pP(A, a):.2f}", # distance A to a
0x44AA44,
C + E,
f"{dist_ll(C & E, D & B):.2f}", # distance CE to DB
0x44AAFF,
[A + D + E, B + C + 5 * D + 5 * E, D + E],
f"{angle_pp(A & E & D, a):.2f}" + "°", # angle planes.
0x884488,
[A, 2 * A + D, 2 * A + B],
f"{angle_ll(A & D, B & A):.2f}" + "°",
]
camera = math.cos(time) + math.sin(time) * alg.blades.e13
return camera, subjects, A
@solara.component
def Page():
result = solara.use_reactive(cast(tuple, None))
base_options = dict(animate=False, grid=True, lineWidth=3)
async def worker():
while True:
await asyncio.sleep(0.01)
time = default_timer() / 5
result.set(graph_func(time))
task = solara.lab.use_task(worker, dependencies=[])
if task.pending and result.value is None:
solara.Text("Loading...")
else:
camera, raw_subjects, point_A = result.value
subjects = walker(encode(raw_subjects, root=True))
with solara.Card(style={"width": "750px", "height": "100%"}):
with solara.Column():
solara.Text(f"Camera: {str(camera)}")
solara.Text(f"A: {str(point_A.dual())}")
options = dict(base_options, camera=camera)
GraphWidget.element(
algebra=alg,
subjects=subjects,
options=options,
)