#! /usr/bin/python3
# ============================================================
# 1. test graphics transformations
# 2. test basic graphics objects
# 3. manipulate/draw wireframe (cube)
# 4. manipulate/draw a polygon (trapezoid)
#
# This program demonstrates using a transformation matrix.
#
# A transformation matrix transforms the coordinates of
# points defining a wireframe graphics object. In this
# way you can rotate and translate the wireframe object.
#
# Multiple transformation matrices can be collected into
# one transformation matrix for use. This is more efficient
# than using multiple transforming matrices.
#
# Center (Cartesian) coordinates are used by the program
# and only converted to window coordinates when it is time
# to draw something in the graphics window.
# ============================================================
#
# Perspective
#
# 1. The art of creating the illusion of depth and distance
# on a flat surface by showing objects as they appear to
# the eye.
# 2. The farther away it is the smaller it appears.
#
# ------------------------------------------------------------
#
# Viewer is at +Z infinity (without Perspective).
#
# window coordinates center (Cartesian) coordinates
#
# 0,0 +Y
# +--------- +X |
# | |
# | |
# | +------- +X
# +Y 0,0
#
# ------------------------------------------------------------
#
# Viewer is at +X infinity (without Perspective).
#
# window coordinates center (Cartesian) coordinates
#
# 0,0 +Y
# +--------- +X |
# | |
# | |
# | +Z ------+
# +Y 0,0
#
# ============================================================
import coordinate_conversion as cc
import transformation_matrix as tm
import user_interface as ui
from graphics import *
import numpy as np
import sys
WINHEIGHT = 801 # graphics window height
WINWIDTH = 801 # graphics window width
# ------------------------------------------------------------
# ---- wireframe (cube)
# ----
# ---- notes:
# ---- 1. Cartesian coordinates
# ---- 2. coordinates are of a size so they do not need to be
# ---- scaled to fit in the graphics window
# ---- 3. the pivot point coordinates are the center of a
# ---- graphics object and is used when rotating it's
# ---- wireframe in place (not needed at the origin)
# ------------------------------------------------------------
# ---- wireframe (cube) 8 corner points
pts = [ (-100, 100, 100), (-100, 100,-100),
( 100, 100,-100), ( 100, 100, 100),
(-100,-100, 100), (-100,-100,-100),
( 100,-100,-100), ( 100,-100, 100) ]
# ---- wireframe (cube) pivot point
WIREFRAME_PRIVOT = (0, 0, 0)
# ---- wireframe (cube) 12 lines
WIREFRAME_LNS = [
(pts[0],pts[4],'black'), (pts[1],pts[5],'black'),
(pts[2],pts[6],'black'), (pts[3],pts[7],'black'),
(pts[0],pts[1],'red'), (pts[1],pts[2],'red'),
(pts[2],pts[3],'red'), (pts[3],pts[0],'red'),
(pts[4],pts[5],'blue'), (pts[5],pts[6],'blue'),
(pts[6],pts[7],'blue'), (pts[7],pts[4],'blue'),
]
# ------------------------------------------------------------
# ---- polygon (Trapezoid)
# ----
# ---- notes:
# ---- 1. Cartesian coordinates
# ---- 2. coordinates are of a size so they do not need to be
# ---- scaled to fit in the graphics window
# ---- 3. the pivot point coordinates (0,0,0) are the center
# ---- of a graphics object and is used when rotating it
# ---- in place
# ------------------------------------------------------------
# ---- big polygon
B_POLYGONPOINTS = [
(-100, 100, 0), ( 100, 100, 0),
( 200, -100, 0), (-200, -100, 0)
]
# ---- small polygon
S_POLYGONPOINTS = [
( 10, -10, 0), (-10, -10, 0),
(-20, 10, 0), ( 20, 10, 0)
]
# ------------------------------------------------------------
# ---- XYZ 3D axes
# ----
# ---- notes:
# ---- 1. Cartesian coordinates
# ---- 2. the origin (0,0,0) is the center of the
# ---- graphics window
# ------------------------------------------------------------
AXES = (((-300,0,0),(300,0,0)), # X axis
((0,-300,0),(0,300,0)), # Y axis
((0,0,-300),(0,0,300))) # Z axis
# ------------------------------------------------------------
# ---- class - 3D transformation matrix
# ----
# ---- notes:
# ---- 1. Cartesian coordinates
# ------------------------------------------------------------
class TransformationMatrix3D:
# ---- initialize the class (create matrix)
def __init__(self):
self.mtrx = np.identity(4)
#---- reset matrix to initial condition
def reset(self):
self.mtrx = np.identity(4)
# ---- transform a point's coordinates using the
# ---- transformation matrix
def transform_coords(self,x,y,z):
p = self.mtrx @ [x,y,z,1]
return (p[0],p[1],p[2])
# ---- modify transformation matrix
# ---- change the location (coordinates) of a point
# ---- Note: DX,DY,DZ are the change in a point's
# ---- current X,Y,Z coordinates
def translate_to_location(self,dx,dy,dz):
m = tm.get_translation_matrix_3d(dx,dy,dz)
mm = m @ self.mtrx
self.mtrx = mm
# ---- modify transformation matrix
# ---- move a point to the origin (0,0,0)
# ---- Note: X,Y,Z are a point's current coordinates
def translate_to_origin(self,x,y,z):
m = tm.get_translation_matrix_3d(-x,-y,-z)
mm = m @ self.mtrx
self.mtrx = mm
# ---- modify transformation matrix
# ---- rotate around the X axis
def rotate_around_x_axis(self,degrees):
m = tm.get_x_rotation_matrix_3d(degrees)
mm = m @ self.mtrx
self.mtrx = mm
# ---- modify transformation matrix
# ---- rotate around the Y axis
def rotate_around_y_axis(self,degrees):
m = tm.get_y_rotation_matrix_3d(degrees)
mm = m @ self.mtrx
self.mtrx = mm
# ---- modify transformation matrix
# ---- rotate around the Z axis
def rotate_around_z_axis(self,degrees):
m = tm.get_z_rotation_matrix_3d(degrees)
mm = m @ self.mtrx
self.mtrx = mm
# ---- modify transformation matrix
# ---- scale a point's coordinates in the X,Y,Z direction
def scale_xyz(self,sx,sy,sz):
m = tm.get_scaling_matrix_3d(sx,sy,sz)
mm = m @ self.mtrx
self.mtrx = mm
# ---- return a deepcopy of the current
# ---- transformation matrix
def copy_matrix(self):
return copy.deepcopy(self.mtrx)
# ---- return a shallow copy of the current
# ---- transformation matrix
def get_matrix(self):
return self.mtrx
# ---- display the current transformation matrix
def display_matrix(self):
print(self.mtrx)
# ------------------------------------------------------------
# ---- draw XYZ 3D axes
# ----
# ---- Note:
# ---- 1. axes Cartesian coordinates
# ---- 2. the origin (0,0,0) is assumed to be at the center
# ---- of the graphics window
# ------------------------------------------------------------
def draw_xyz_axes(win, mtrx, axes, width=2,
color='black', draw_z_axis=False):
axis_objs = []
# ---- draw X axis (line)
xaxis = axes[0]
x0 = cc.center_to_win_coords(xaxis[0][0],xaxis[0][1],
win.width,win.height)
x1 = cc.center_to_win_coords(xaxis[1][0],xaxis[1][1],
win.width,win.height)
lobj = Line(Point(x0[0],x0[1]),Point(x1[0],x1[1]))
lobj.setWidth(width)
lobj.setFill(color)
lobj.draw(win)
axis_objs.append(lobj)
# ---- draw Y axis (line)
yaxis = axes[1]
y0 = cc.center_to_win_coords(yaxis[0][0],yaxis[0][1],
win.width,win.height)
y1 = cc.center_to_win_coords(yaxis[1][0],yaxis[1][1],
win.width,win.height)
lobj = Line(Point(y0[0],y0[1]),Point(y1[0],y1[1]))
lobj.setWidth(width)
lobj.setFill(color)
lobj.draw(win)
axis_objs.append(lobj)
# ---- draw Z axis (line)
if draw_z_axis:
zaxis = axes[2]
z0 = cc.center_to_win_coords(zaxis[0][0],zaxis[0][1],
win.width,win.height)
z1 = cc.center_to_win_coords(zaxis[1][0],zaxis[1][1],
win.width,win.height)
lobj = Line(Point(z0[0],z0[1]),Point(z1[0],z1[1]))
lobj.setWidth(width)
lobj.setFill(color)
lobj.draw(win)
axis_objs.append(lobj)
return axis_objs
# ------------------------------------------------------------
# ---- draw graphics window 2D axes
# ----
# ---- Note:
# ---- 1. the origin (0,0,0) is assumed to be at the center
# ---- of the graphics window
# ------------------------------------------------------------
def draw_graphics_window_axes(win, width=1 ,
color='black', labels=True):
wx = win.width # window width
wy = win.height # window height
wcx = round(wx/2.0) # window center X
wcy = round(wy/2.0) # window center Y
axis_objs = []
# ---- X axis
xl = Line(Point(0,wcy),Point(wx-1,wcy))
xl.setWidth(width)
xl.setFill(color)
xl.draw(win)
axis_objs.append(xl)
if labels:
txt = Text(Point(wx-20,wcy),'X')
txt.setFace('courier')
txt.setSize(24)
txt.setTextColor(color)
txt.draw(win)
axis_objs.append(txt)
# ---- Y axis
yl = Line(Point(wcx,0),Point(wcx,wy-1))
yl.setWidth(width)
yl.setFill(color)
yl.draw(win)
axis_objs.append(yl)
if labels:
txt = Text(Point(wcx,20),'y')
txt.setFace('courier')
txt.setSize(24)
txt.setTextColor(color)
txt.draw(win)
axis_objs.append(txt)
return axis_objs
#-------------------------------------------------------------
# ---- create a line graphics object
# ---- convert center (Cartesian) 3D coordinates to
# ---- 2D graphics window coordinates. the viewer
# ---- is at +Z infinity. no perspective adjustment.
# ------------------------------------------------------------
def create_line_graphics_object(win, mtrx,
x0,y0,z0,x1,y1,z1, width=2,
color='black', draw_obj=True):
# ---- convert Cartesian coords to window coords
xy0 = cc.center_to_win_coords(x0,y0,
win.width,win.height)
xy1 = cc.center_to_win_coords(x1,y1,
win.width,win.height)
# ---- line graphics object
lobj = Line(Point(xy0[0],xy0[1]), Point(xy1[0],xy1[1]))
lobj.setWidth(width)
lobj.setFill(color)
if draw_obj: lobj.draw(win)
return lobj
# ------------------------------------------------------------
# ---- create a circle graphics object
# ---- convert center (Cartesian) 3D coordinates to
# ---- 2D graphics window coordinates.the viewer
# ---- is at +Z infinity. no perspective adjustment.
# ------------------------------------------------------------
def create_circle_graphics_object(win, mtrx,
x,y,z, width=2, radius=4,
color='white', draw_obj=True):
# ---- convert Cartesian coords to window coords
xy = cc.center_to_win_coords(x,y,
win.width,win.height)
# ---- circle graphics object
cobj = Circle(Point(xy[0],xy[1]),radius)
cobj.setOutline('black')
cobj.setWidth(width)
cobj.setFill(color)
if draw_obj: cobj.draw(win)
return cobj
# ------------------------------------------------------------
# ---- create a rectangle graphics object
# ---- convert center (Cartesian) 3D coordinates to
# ---- 2D graphics window coordinates.the viewer
# ---- is at +Z infinity. no perspective adjustment.
# ------------------------------------------------------------
def create_rectangle_graphics_object(win, mtrx,
x0,y0,z0,x1,y1,z1, width=2,
color='white', draw_obj=True):
# ---- convert Cartesian coords to window coords
xy0 = cc.center_to_win_coords(x0,y0,
win.width,win.height)
xy1 = cc.center_to_win_coords(x1,y1,
win.width,win.height)
# ---- rectangle graphics object
robj = Rectangle(Point(xy0[0],xy0[1]),
Point(xy1[0],xy1[1]))
robj.setOutline('black')
robj.setWidth(width)
robj.setFill(color)
if draw_obj: robj.draw(win)
return robj
# ------------------------------------------------------------
# ---- draw wireframe lines
# ------------------------------------------------------------
def draw_wireframe(win, mtrx, wireframe_lines):
line_objs = []
for line in wireframe_lines:
x0,y0,z0 = mtrx.transform_coords(line[0][0], # X
line[0][1], # Y
line[0][2]) # Z
x1,y1,z1 = mtrx.transform_coords(line[1][0], # X
line[1][1], # y
line[1][2]) # Z
lobj = create_line_graphics_object(win, mtrx,
x0,y0,z0,x1,y1,z1,
color=line[2])
line_objs.append(lobj)
return line_objs
# ------------------------------------------------------------
# ---- create a polygon graphics object
# ---- convert center (Cartesian) 3D coordinates to
# ---- 2D graphics window coordinates.the viewer
# ---- is at +Z infinity. no perspective adjustment.
# ------------------------------------------------------------
def draw_polygon(win, mtrx, polygon_points,
width=2, color='green', draw_obj=True):
# ---- transform Cartesian coordinates
new_polygon_points = []
for pt in polygon_points:
x,y,z = mtrx.transform_coords(pt[0],pt[1],pt[2])
# ---- convert transformed coords to window coords
wx,wy = cc.center_to_win_coords(x,y,
win.width,win.height)
# ---- save list of points
new_polygon_points.append(Point(wx,wy))
# ---- create polygon graphics object
pobj = Polygon(*new_polygon_points)
pobj.setOutline('black')
pobj.setWidth(width)
pobj.setFill(color)
if draw_obj: pobj.draw(win)
return [pobj] # return polygon object
# ------------------------------------------------------------
# ---- delete graphics objects from graphics window
# ------------------------------------------------------------
def clear_graphics_window(objs):
for o in objs:
o.undraw()
objs = []
# ------------------------------------------------------------
# ---- test basic transformations
# ------------------------------------------------------------
def test_basic_transformations():
print()
print('test basic transformations')
print()
# ---- create coordinate transformation matrix
mtrx = TransformationMatrix3D()
# ---- test #1 -------------------------------------------
msg = '''
test #1
point at y=100, rotate it counterclockwise around
the Z axis to x=100
results should end up on the +X axis'''
mtrx.reset()
mtrx.rotate_around_z_axis(-90.0)
print(msg)
p = mtrx.transform_coords(0.0,100.0,0.0)
print(f' results is x={p[0]},y={p[1]},z={p[2]}')
# ---- test #2 -------------------------------------------
msg = '''
test #2
point at y=100, rotate it counterclockwise around
the Z axis to x=-100
results should end up on the -X axis'''
mtrx.reset()
mtrx.rotate_around_z_axis(90.0)
print(msg)
p = mtrx.transform_coords(0.0,100.0,0.0)
print(f' results is x={p[0]},y={p[1]},z={p[2]}')
# ---- test #3 -------------------------------------------
msg = '''
test #3
point at x=100, rotate it clockwise around
the Y axis to z=100
results should end up on the Z axis'''
mtrx.reset()
mtrx.rotate_around_y_axis(-90.0)
print(msg)
p = mtrx.transform_coords(100.0,0.0,0.0)
print(f' results is x={p[0]},y={p[1]},z={p[2]}')
# ---- test #4 -------------------------------------------
msg = '''
test #4
point at x=100, rotate it counterclockwise around
the Z axis to 45 degrees
results should end up on the Z axis'''
mtrx.reset()
mtrx.rotate_around_z_axis(-45.0)
print(msg)
p = mtrx.transform_coords(0.0,100.0,0.0)
print(f' results is x={p[0]},y={p[1]},z={p[2]}')
# ---- test #5 -------------------------------------------
msg = '''
test #5
point at y=100, rotate it counterclockwise around
the Z axis to 45 degrees, then rotate it
counterclockwise around the Y axis 90 degrees
results should end up on the +Z+Y plane'''
mtrx.reset()
mtrx.rotate_around_z_axis(-45.0)
mtrx.rotate_around_y_axis(-90.0)
print(msg)
p = mtrx.transform_coords(0.0,100.0,0.0)
print(f' results is x={p[0]},y={p[1]},z={p[2]}')
# ---- test #6 -------------------------------------------
msg = '''
test #6
point at y=100, rotate it counterclockwise around
the Y axis 90 degrees, then rotate it
counterclockwise around the Z axis to 45 degrees,
results should end up on the +X+Y plane'''
mtrx.reset()
mtrx.rotate_around_y_axis(-90.0)
mtrx.rotate_around_z_axis(-45.0)
print(msg)
p = mtrx.transform_coords(0.0,100.0,0.0)
print(f' results is x={p[0]},y={p[1]},z={p[2]}')
# ---- test #7 -------------------------------------------
msg = '''
test #7
starting point at (0,100,0),
change its location to dx=-50,dy=100,dz=75'''
mtrx.reset()
mtrx.translate_to_location(-50.0,100.0,75.0)
print(msg)
p = mtrx.transform_coords(0.0,100.0,-25.0)
print(f' results is x={p[0]},y={p[1]},z={p[2]}')
# ------------------------------------------------------------
# ---- test basic graphics objects (line, circle, rectangle)
# ------------------------------------------------------------
def test_basic_graphics_objects():
print()
print('test basic graphics objects')
print()
# ---- create graphics window
win = GraphWin('Test Basic Graphics Objects',
WINWIDTH, WINHEIGHT)
win.setBackground('white')
# ---- create X,Y,Z coordinate transformation matrix
mtrx = TransformationMatrix3D()
# ---- draw X,Y,Z coordinate axes
ax_objs = []
ax_objs = draw_xyz_axes(win,mtrx,AXES)
# ---- create/draw graphics objects
gr_objs = []
x = create_line_graphics_object(win, mtrx,
-200.0,200.0,0.0,-100.0,100.0,0.0)
gr_objs.append(x)
x = create_circle_graphics_object(win, mtrx,
100.0,100.0,0.0,color='red')
gr_objs.append(x)
x = create_rectangle_graphics_object(win, mtrx,
100.0,-100.0,0.0,200.0,-200.0,0.0,color='green')
gr_objs.append(x)
# ---- end of test
ui.pause()
clear_graphics_window(ax_objs)
clear_graphics_window(gr_objs)
ui.pause()
win.close()
# ------------------------------------------------------------
# ---- menu interface
# ------------------------------------------------------------
def menu_interface():
menu = '''
------------------------------------------------
---------- test Transformation Matrix ----------
Negative rotation angles rotate objs clockwise.
Positive angles rotate objs counterclockwise.
DX, DY, DZ are changes in an obj's current
location, not an absolute location.
------------------------------------------------
q = quit
i = reset to initial conditions
d = draw wireframe
m = display transformation matrix
c = clear the graphics window (undraw)
--------- Build Transformation Matrix ----------
rx angle = rotate around X axis (deg)
ry angle = rotate around Y axis (deg)
rz angle = rotate around Z axis (deg)
t dx dy dz = move graphics object
s sx sy sz = scale graphics object'''
# ---- menu support function - oops!
def _oops(s):
print()
print(f'OOPS! unknown/bad command ({s})')
ui.pause()
# ---- graphics object lists
ax_objs = [] # axis graphics objects
gr_objs = [] # other graphics objects
# ---- create graphics window
win = GraphWin('wireframe Transformation Test',
WINWIDTH, WINHEIGHT)
win.setBackground('white')
# ---- create X,Y,Z coordinate transformation matrix
mtrx = TransformationMatrix3D()
# ---- draw X,Y,Z coordinate axes
##ax_objs = draw_xyz_axes(win,mtrx,AXES)
ax_objs = draw_graphics_window_axes(win)
# ---- display the menu
# ---- ask the user to make a selection
# ---- process the selection
while True:
##ui.clear_screen()
print(menu)
# ---- ask the user to make a selection
print()
s = ui.get_user_input('Enter command: ')
if not s: break # empty string?
# ---- lowercase the user's input
ss = s.lower()
# ---- quit
if ss[0] == 'q': break
# ---- display transformation matrix
if ss[0] == 'm':
print('-'*44)
mtrx.display_matrix()
print('-'*44)
ui.pause()
continue
# ---- create/draw wireframe
if ss[0] == 'd':
objs = draw_wireframe(win,mtrx,WIREFRAME_LNS)
gr_objs += objs
continue
# ---- special test case for a polygon
if ss == 'poly': # large polygon
objs = draw_polygon(win,mtrx,_BPOLYGONPOINTS)
gr_objs += objs
continue
if ss == 'spoly': # small polygon
objs = draw_polygon(win,mtrx,
S_POLYGONPOINTS,color='yellow')
gr_objs += objs
continue
# ---- reset matrix to initial state
if ss[0] == 'i':
mtrx.reset()
continue
# ---- clear graphics window
if ss == 'c':
##clear_graphics_window(ax_objs)
clear_graphics_window(gr_objs)
continue
# ---- add rotation to transformation matrix
if ss[0] == 'r':
x = ss.replace(',', ' ').split()
if len(x) != 2:
_oops(s)
continue
tf,deg = ui.is_float(x[1])
if not tf:
_oops(s)
continue
if x[0][1] == 'x':
print('rotate around X axis')
mtrx.rotate_around_x_axis(deg)
continue
if x[0][1] == 'y':
print('rotate around Y axis')
mtrx.rotate_around_y_axis(deg)
continue
if x[0][1] == 'z':
print('rotate around Z axis')
mtrx.rotate_around_z_axis(deg)
continue
# ---- add X,Y,Z movement to transformation matrix
if ss[0] == 't':
x = ss.replace(',', ' ').split()
if len(x) != 4:
_oops(s)
continue
tf,dx = ui.is_float(x[1]) # DX
if not tf:
_oops(s)
continue
tf,dy = ui.is_float(x[2]) # DY
if not tf:
_oops(s)
continue
tf,dz = ui.is_float(x[3]) # DZ
if not tf:
_oops(s)
continue
mtrx.translate_to_location(dx,dy,dz)
continue
# ---- add scaling to transformation matrix
if ss[0] == 's':
print()
print('Add your scaling code here. This')
print('is an exercise for the reader.')
ui.pause()
continue
# ---- OOPS!
_oops(s)
# ---- close graphics window
win.close()
# ------------------------------------------------------------
# ---- main
# ------------------------------------------------------------
if __name__ == '__main__':
# ---- test basic transformations
##test_basic_transformations()
# ---- test basic graphics objects
##test_basic_graphics_objects()
# ---- use the menu interface to manipulate/draw
# ---- graphics objects
menu_interface()
print()
For other wireframe objects click
HERE