micropython-samples/quaternion/graph3d.py

191 wiersze
6.0 KiB
Python

# graph3d.py 3D graphics demo of quaternion use
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2020 Peter Hinch
import gc
from math import pi
from quat import Rotator, Point
from setup3d import fill, line, show, DIMENSION
class Line:
def __init__(self, p0, p1, color):
#assert p0.isvec() and p1.isvec()
self.start = p0
self.end = p1
self.color = color
def show(self, ssd):
_, xs, ys, zs = self.start
_, xe, ye, ze = self.end
# Handle perspective and scale to display
w = DIMENSION # Viewing area is square
h = w
xs = round((1 + xs/zs) * w)
ys = round((1 - ys/zs) * h)
xe = round((1 + xe/ze) * w)
ye = round((1 - ye/ze) * h)
line(xs,ys, xe, ye, self.color)
def __add__(self, to): # to is a Point or 3-tuple
return Line(self.start + to, self.end + to, self.color)
def __sub__(self, v): # to is a Point or 3-tuple
return Line(self.start - v, self.end - v, self.color)
def __mul__(self, by): # by is a 3-tuple
return Line(self.start * by, self.end * by, self.color)
def __matmul__(self, rot): # rot is a rotation quaternion
#assert rot.isrot()
return Line(self.start @ rot, self.end @ rot, self.color)
def camera(self, rot, distance): # rot is a rotation quaternion, distance is scalar
#assert rot.isrot()
gc.collect()
ps = self.start @ rot
ps = Point(ps.x * distance, ps.y * distance, distance - ps.z)
pe = self.end @ rot
pe = Point(pe.x * distance, pe.y * distance, distance - pe.z)
return Line(ps, pe, self.color)
def __str__(self):
return 'start {} end {}'.format(self.start, self.end)
class Shape:
def __init__(self, lines):
self.lines = lines
def __add__(self, to):
return Shape([l + to for l in self.lines])
def __sub__(self, v):
return Shape([l - v for l in self.lines])
def __mul__(self, by):
return Shape([l * by for l in self.lines])
def __matmul__(self, rot):
l = []
for line in self.lines:
l.append(line @ rot)
return Shape(l)
def camera(self, rot, distance):
l = []
for line in self.lines:
l.append(line.camera(rot, distance))
return Shape(l)
def show(self, ssd):
for line in self.lines:
line.show(ssd)
def __str__(self):
r = ''
for line in self.lines:
r = ''.join((r, '{}\n'.format(line)))
return r
class Axes(Shape):
def __init__(self, color):
l = (Line(Point(-1.0, 0, 0), Point(1.0, 0, 0), color),
Line(Point(0, -1.0, 0), Point(0, 1.0, 0), color),
Line(Point(0, 0, -1.0), Point(0, 0, 1.0), color))
super().__init__(l)
class Square(Shape): # Unit square in XY plane
def __init__(self, color): # Corner located at origin
l = (Line(Point(0, 0, 0), Point(1, 0, 0), color),
Line(Point(1, 0, 0), Point(1, 1, 0), color),
Line(Point(1, 1, 0), Point(0, 1, 0), color),
Line(Point(0, 1, 0), Point(0, 0, 0), color))
super().__init__(l)
class Cube(Shape):
def __init__(self, color, front=None, sides=None): # Corner located at origin
front = color if front is None else front
sides = color if sides is None else sides
l = (Line(Point(0, 0, 0), Point(1, 0, 0), color),
Line(Point(1, 0, 0), Point(1, 1, 0), color),
Line(Point(1, 1, 0), Point(0, 1, 0), color),
Line(Point(0, 1, 0), Point(0, 0, 0), color),
Line(Point(0, 0, 1), Point(1, 0, 1), front),
Line(Point(1, 0, 1), Point(1, 1, 1), front),
Line(Point(1, 1, 1), Point(0, 1, 1), front),
Line(Point(0, 1, 1), Point(0, 0, 1), front),
Line(Point(0, 0, 0), Point(0, 0, 1), sides),
Line(Point(1, 0, 0), Point(1, 0, 1), sides),
Line(Point(1, 1, 0), Point(1, 1, 1), sides),
Line(Point(0, 1, 0), Point(0, 1, 1), sides),
)
super().__init__(l)
class Cone(Shape):
def __init__(self, color, segments=12):
rot = Rotator(2*pi/segments, 0, 1, 0)
p0 = Point(1, 1, 0)
p1 = p0.copy()
orig = Point(0, 0, 0)
lines = []
for _ in range(segments + 1):
p1 @= rot
lines.append(Line(p0, p1, color))
lines.append(Line(orig, p0, color))
p0 @= rot
super().__init__(lines)
class Circle(Shape): # Unit circle in XY plane centred on origin
def __init__(self, color, segments=12):
rot = Rotator(2*pi/segments, 0, 1, 0)
p0 = Point(1, 0, 0)
p1 = p0.copy()
lines = []
for _ in range(segments + 1):
p1 @= rot
lines.append(Line(p0, p1, color))
p0 @= rot
super().__init__(lines)
class Sphere(Shape): # Unit sphere in XY plane centred on origin
def __init__(self, color, segments=12):
lines = []
s = Circle(color)
xrot = Rotator(2 * pi / segments, 1, 0, 0)
for _ in range(segments / 2 + 1):
gc.collect()
lines.extend(s.lines[:])
s @= xrot
super().__init__(lines)
# Composition rather than inheritance as MP can't inherit builtin types.
class DisplayDict:
def __init__(self, ssd, angle, distance):
self.ssd = ssd
self.distance = distance # scalar
# Rotation quaternion for camera view
self.crot = Rotator(angle, 1, 1, 0)
self.d = {}
def __setitem__(self, key, value):
if not isinstance(value, Shape):
raise ValueError('DisplayDict entries must be Shapes')
self.d[key] = value
def __getitem__(self, key):
return self.d[key]
def __delitem__(self, key):
del self.d[key]
def show(self):
ssd = self.ssd
fill(0)
crot = self.crot
dz = self.distance
for shape in self.d.values():
s = shape.camera(crot, dz)
s.show(ssd)
show()