kopia lustrzana https://github.com/peterhinch/micropython-samples
191 wiersze
6.0 KiB
Python
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()
|