155 lines
4.6 KiB
Python
155 lines
4.6 KiB
Python
|
"""
|
||
|
This little library provides functions to add corner pin functionality
|
||
|
into python in general, and pyglet specifically.
|
||
|
|
||
|
Long time ago I used a set of javascript functions from Cihad Turhan (see
|
||
|
https://stackoverflow.com/a/30646172), which I now ported to python.
|
||
|
These computations might be faster with numpy, but for me these run only
|
||
|
incidently so won't really hurt performance.
|
||
|
"""
|
||
|
|
||
|
import pyglet
|
||
|
from typing import Optional
|
||
|
import logging
|
||
|
|
||
|
def adj(m): # Compute the adjugate of m
|
||
|
return [
|
||
|
m[4]*m[8]-m[5]*m[7], m[2]*m[7]-m[1]*m[8], m[1]*m[5]-m[2]*m[4],
|
||
|
m[5]*m[6]-m[3]*m[8], m[0]*m[8]-m[2]*m[6], m[2]*m[3]-m[0]*m[5],
|
||
|
m[3]*m[7]-m[4]*m[6], m[1]*m[6]-m[0]*m[7], m[0]*m[4]-m[1]*m[3]
|
||
|
]
|
||
|
|
||
|
def multmm(a, b) -> list[float]: # multiply two matrices
|
||
|
c = [0.]*9
|
||
|
for i in range(3):
|
||
|
for j in range(3):
|
||
|
cij = 0
|
||
|
for k in range(3):
|
||
|
cij += a[3*i + k]*b[3*k + j]
|
||
|
c[3 * i + j] = cij
|
||
|
return c
|
||
|
|
||
|
def multmv(m, v): # multiply matrix and vector
|
||
|
return [
|
||
|
m[0]*v[0] + m[1]*v[1] + m[2]*v[2],
|
||
|
m[3]*v[0] + m[4]*v[1] + m[5]*v[2],
|
||
|
m[6]*v[0] + m[7]*v[1] + m[8]*v[2]
|
||
|
]
|
||
|
|
||
|
# def pdbg(m, v) :
|
||
|
# r = multmv(m, v)
|
||
|
# return r + " (" + r[0]/r[2] + ", " + r[1]/r[2] + ")"
|
||
|
|
||
|
def basisToPoints(x1, y1, x2, y2, x3, y3, x4, y4) :
|
||
|
m = [
|
||
|
x1, x2, x3,
|
||
|
y1, y2, y3,
|
||
|
1, 1, 1
|
||
|
]
|
||
|
v = multmv(adj(m), [x4, y4, 1])
|
||
|
return multmm(m, [
|
||
|
v[0], 0, 0,
|
||
|
0, v[1], 0,
|
||
|
0, 0, v[2]
|
||
|
])
|
||
|
|
||
|
def general2DProjection(
|
||
|
x1s, y1s, x1d, y1d,
|
||
|
x2s, y2s, x2d, y2d,
|
||
|
x3s, y3s, x3d, y3d,
|
||
|
x4s, y4s, x4d, y4d
|
||
|
) :
|
||
|
s = basisToPoints(x1s, y1s, x2s, y2s, x3s, y3s, x4s, y4s)
|
||
|
d = basisToPoints(x1d, y1d, x2d, y2d, x3d, y3d, x4d, y4d)
|
||
|
return multmm(d, adj(s))
|
||
|
|
||
|
def transform2d(w, h, x1, y1, x2, y2, x3, y3, x4, y4):
|
||
|
t = general2DProjection(0, 0, x1, y1, w, 0, x2, y2, 0, h, x3, y3, w, h, x4, y4)
|
||
|
for i in range(9):
|
||
|
t[i] = t[i] / t[8]
|
||
|
|
||
|
t = [t[0], t[3], 0, t[6],
|
||
|
t[1], t[4], 0, t[7],
|
||
|
0 , 0 , 1, 0 ,
|
||
|
t[2], t[5], 0, t[8]]
|
||
|
return t
|
||
|
|
||
|
# Below are all pyglet specific functions
|
||
|
|
||
|
class PygletCornerPin(pyglet.event.EventDispatcher):
|
||
|
def __init__(self, window: pyglet.window.Window, corners: Optional[list[list[float]]] = None) -> None:
|
||
|
"""
|
||
|
Creates CornerPin utility for a given Pyglet window. If not given, corners default to the
|
||
|
bottom left, bottom right, top left, top right corners of the window (i.e. no transform)
|
||
|
|
||
|
Do not forget to register event handlers by running `window.register_handlers(pins)`.
|
||
|
"""
|
||
|
self.window = window
|
||
|
self.corners = corners if corners else [
|
||
|
[0, 0],
|
||
|
[self.window.width, 0],
|
||
|
[0, self.window.height],
|
||
|
[self.window.width, self.window.height],
|
||
|
]
|
||
|
self.batch = pyglet.graphics.Batch()
|
||
|
self.dragging: Optional[int] = None
|
||
|
self.handles = [
|
||
|
pyglet.shapes.Arc(c[0],c[1],20, thickness=2, color=(0,0,255,255), batch=self.batch) for c in self.corners
|
||
|
]
|
||
|
|
||
|
def update_handles(self):
|
||
|
for i, c in enumerate(self.corners):
|
||
|
self.handles[i].x = c[0]
|
||
|
self.handles[i].y = c[1]
|
||
|
|
||
|
def draw(self):
|
||
|
"""
|
||
|
Draw the handles in the on_draw loop
|
||
|
"""
|
||
|
self.batch.draw()
|
||
|
|
||
|
def transform2d_matrix(self, x1, y1, x2, y2, x3, y3, x4, y4):
|
||
|
"""
|
||
|
Calculate the transform matrix
|
||
|
"""
|
||
|
t = transform2d(
|
||
|
self.window.width,
|
||
|
self.window.height,
|
||
|
x1, y1, x2, y2, x3, y3, x4, y4 )
|
||
|
return pyglet.math.Mat4(t)
|
||
|
|
||
|
# event handlers
|
||
|
|
||
|
def on_mouse_press(self, x, y, button, modifiers):
|
||
|
r = 20
|
||
|
currentcorner = None
|
||
|
dist = r * r
|
||
|
for i in range(4):
|
||
|
dx = x - self.corners[i][0]
|
||
|
dy = y - self.corners[i][1]
|
||
|
if dist > dx**2 + dy ** 2:
|
||
|
dist = dx**2 + dy ** 2
|
||
|
currentcorner = i
|
||
|
|
||
|
self.dragging = currentcorner
|
||
|
return pyglet.event.EVENT_HANDLED if self.dragging else pyglet.event.EVENT_UNHANDLED
|
||
|
|
||
|
def on_mouse_release(self, x, y, button, modifiers):
|
||
|
if self.dragging is None:
|
||
|
return pyglet.event.EVENT_UNHANDLED
|
||
|
self.dragging = None
|
||
|
logging.debug(f"Corner pins set to {self.corners}")
|
||
|
return pyglet.event.EVENT_HANDLED
|
||
|
|
||
|
def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers):
|
||
|
if self.dragging is None:
|
||
|
return pyglet.event.EVENT_UNHANDLED
|
||
|
|
||
|
self.corners[self.dragging][0] = x
|
||
|
self.corners[self.dragging][1] = y
|
||
|
self.update_view()
|
||
|
|
||
|
|
||
|
def update_view(self):
|
||
|
self.window.view = self.transform2d_matrix(*[x for c in self.corners for x in c])
|